// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.turnrestrictions.editor; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.MouseInfo; import java.awt.Point; import java.awt.PointerInfo; import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import javax.swing.AbstractAction; import javax.swing.JButton; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.Popup; import javax.swing.PopupFactory; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableColumnModel; import javax.swing.table.TableColumn; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.Relation; import org.openstreetmap.josm.gui.layer.OsmDataLayer; import org.openstreetmap.josm.plugins.turnrestrictions.TurnRestrictionBuilder; import org.openstreetmap.josm.plugins.turnrestrictions.list.TurnRestrictionCellRenderer; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.ImageProvider; /** * TurnRestrictionSelectionPopupPanel is displayed in a {@link Popup} to select whether * the user wants to create a new turn restriction or whether he wants to edit one * of a list of turn restrictions. * */ public class TurnRestrictionSelectionPopupPanel extends JPanel { /** the parent popup */ private Popup parentPopup; /** the button for creating a new turn restriction */ private JButton btnNew; /** the table with the turn restrictions which can be edited */ private JTable tblTurnRestrictions; private OsmDataLayer layer; /** * Replies the collection of turn restrictions the primitives in {@code primitives} * currently participate in. * * @param primitives the collection of primitives. May be null. * @return the collection of "parent" turn restrictions. */ public static Collection<Relation> getTurnRestrictionsParticipatingIn(Collection<OsmPrimitive> primitives) { HashSet<Relation> ret = new HashSet<>(); if (primitives == null) return ret; for (OsmPrimitive p: primitives) { if (p == null) continue; if (p.isDeleted() || !p.isVisible()) continue; for (OsmPrimitive parent: p.getReferrers()) { if (!(parent instanceof Relation)) continue; String type = parent.get("type"); if (type == null || !type.equals("restriction")) continue; if (parent.isDeleted() || !parent.isVisible()) continue; ret.add((Relation) parent); } } return ret; } /** * Registers 1..9 shortcuts for the first 9 turn restrictions to * edit * * @param editCandiates the edit candidates */ protected void registerEditShortcuts(Collection<Relation> editCandiates) { for (int i = 1; i <= Math.min(editCandiates.size(), 9); i++) { int vkey = 0; switch(i) { case 1: vkey = KeyEvent.VK_1; break; case 2: vkey = KeyEvent.VK_2; break; case 3: vkey = KeyEvent.VK_3; break; case 4: vkey = KeyEvent.VK_4; break; case 5: vkey = KeyEvent.VK_5; break; case 6: vkey = KeyEvent.VK_6; break; case 7: vkey = KeyEvent.VK_7; break; case 8: vkey = KeyEvent.VK_8; break; case 9: vkey = KeyEvent.VK_9; break; } registerKeyboardAction(new EditTurnRestrictionAction(i-1), KeyStroke.getKeyStroke(vkey, 0), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); } } /** * Builds the panel with the turn restrictions table * * @param editCandiates the list of edit candiates * @return the panel */ protected JPanel buildTurnRestrictionTablePanel(Collection<Relation> editCandiates) { tblTurnRestrictions = new JTable(new TurnRestrictionTableModel(editCandiates), new TurnRestrictionTableColumnModel()); tblTurnRestrictions.setColumnSelectionAllowed(false); tblTurnRestrictions.getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); TurnRestrictionCellRenderer renderer = new TurnRestrictionCellRenderer(); tblTurnRestrictions.setRowHeight((int) renderer.getPreferredSize().getHeight()); // create a scroll pane, remove the table header JScrollPane pane = new JScrollPane(tblTurnRestrictions); pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); tblTurnRestrictions.setTableHeader(null); pane.setColumnHeaderView(null); // respond to double click and ENTER EditSelectedTurnRestrictionAction action = new EditSelectedTurnRestrictionAction(); tblTurnRestrictions.addMouseListener(action); tblTurnRestrictions.registerKeyboardAction(action, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), WHEN_FOCUSED); tblTurnRestrictions.addFocusListener(new FocusHandler()); JPanel pnl = new JPanel(new BorderLayout()); pnl.add(pane, BorderLayout.CENTER); pnl.setBackground(UIManager.getColor("Table.background")); pane.setBackground(UIManager.getColor("Table.background")); return pnl; } /** * Builds the panel * * @param editCandiates the edit candidates */ protected void build(Collection<Relation> editCandiates) { setLayout(new BorderLayout()); add(btnNew = new JButton(new NewAction()), BorderLayout.NORTH); btnNew.setFocusable(true); btnNew.registerKeyboardAction(btnNew.getAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), WHEN_FOCUSED); registerKeyboardAction(new CloseAction(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); registerKeyboardAction(btnNew.getAction(), KeyStroke.getKeyStroke(KeyEvent.VK_N, 0), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); btnNew.addFocusListener(new FocusHandler()); if (editCandiates != null && !editCandiates.isEmpty()) { add(buildTurnRestrictionTablePanel(editCandiates), BorderLayout.CENTER); registerEditShortcuts(editCandiates); } setBackground(UIManager.getColor("Table.background")); } /** * Creates the panel * * @param layer the reference OSM data layer. Must not be null. * @throws IllegalArgumentException thrown if {@code layer} is null */ public TurnRestrictionSelectionPopupPanel(OsmDataLayer layer) throws IllegalArgumentException { CheckParameterUtil.ensureParameterNotNull(layer, "layer"); this.layer = layer; build(getTurnRestrictionsParticipatingIn(layer.data.getSelected())); } /** * Creates the panel * * @param layer the reference OSM data layer. Must not be null. * @param editCandidates a collection of turn restrictions as edit candidates. May be null. * @throws IllegalArgumentException thrown if {@code layer} is null */ public TurnRestrictionSelectionPopupPanel(OsmDataLayer layer, Collection<Relation> editCandiates) { CheckParameterUtil.ensureParameterNotNull(layer, "layer"); this.layer = layer; build(editCandiates); } /** * Launches a popup with this panel as content */ public void launch() { PointerInfo info = MouseInfo.getPointerInfo(); Point pt = info.getLocation(); parentPopup = PopupFactory.getSharedInstance().getPopup(Main.map.mapView, this, pt.x, pt.y); parentPopup.show(); btnNew.requestFocusInWindow(); } @Override public Dimension getPreferredSize() { int bestheight = (int) btnNew.getPreferredSize().getHeight() + Math.min(2, tblTurnRestrictions.getRowCount()) * tblTurnRestrictions.getRowHeight() + 5; return new Dimension(300, bestheight); } /* --------------------------------------------------------------------------------------- */ /* inner classes */ /* --------------------------------------------------------------------------------------- */ private class NewAction extends AbstractAction { NewAction() { putValue(NAME, tr("Create new turn restriction")); putValue(SHORT_DESCRIPTION, tr("Launch the turn restriction editor to create a new turn restriction")); new ImageProvider("new").getResource().attachImageIcon(this); putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_N, 0)); } @Override public void actionPerformed(ActionEvent e) { Relation tr = new TurnRestrictionBuilder().buildFromSelection(layer); TurnRestrictionEditor editor = new TurnRestrictionEditor(Main.map.mapView, layer, tr); TurnRestrictionEditorManager.getInstance().positionOnScreen(editor); TurnRestrictionEditorManager.getInstance().register(layer, tr, editor); if (parentPopup != null) { parentPopup.hide(); } editor.setVisible(true); } } private abstract class AbstractEditTurnRestrictionAction extends AbstractAction { protected void launchEditor(Relation tr) { TurnRestrictionEditorManager manager = TurnRestrictionEditorManager.getInstance(); TurnRestrictionEditor editor = manager.getEditorForRelation(layer, tr); if (parentPopup != null) { parentPopup.hide(); } if (editor != null) { editor.setVisible(true); editor.toFront(); } else { editor = new TurnRestrictionEditor(Main.map.mapView, layer, tr); manager.positionOnScreen(editor); manager.register(layer, tr, editor); editor.setVisible(true); } } } private class EditTurnRestrictionAction extends AbstractEditTurnRestrictionAction { private int idx; EditTurnRestrictionAction(int idx) { this.idx = idx; } @Override public void actionPerformed(ActionEvent e) { Relation tr = (Relation) tblTurnRestrictions.getModel().getValueAt(idx, 1); launchEditor(tr); } } private class EditSelectedTurnRestrictionAction extends AbstractEditTurnRestrictionAction implements MouseListener { public void editTurnRestrictionAtRow(int row) { if (row < 0) return; Relation tr = (Relation) tblTurnRestrictions.getModel().getValueAt(row, 1); launchEditor(tr); } @Override public void actionPerformed(ActionEvent e) { int row = tblTurnRestrictions.getSelectedRow(); editTurnRestrictionAtRow(row); } @Override public void mouseClicked(MouseEvent e) { if (!(SwingUtilities.isLeftMouseButton(e) && e.getClickCount() >= 2)) return; int row = tblTurnRestrictions.rowAtPoint(e.getPoint()); if (row < 0) return; editTurnRestrictionAtRow(row); } @Override public void mouseEntered(MouseEvent e) {} @Override public void mouseExited(MouseEvent e) {} @Override public void mousePressed(MouseEvent e) {} @Override public void mouseReleased(MouseEvent e) {} } private class CloseAction extends AbstractAction { @Override public void actionPerformed(ActionEvent e) { if (parentPopup != null) { parentPopup.hide(); } } } private static class TurnRestrictionTableModel extends AbstractTableModel { private final ArrayList<Relation> turnrestrictions = new ArrayList<>(); TurnRestrictionTableModel(Collection<Relation> turnrestrictions) { this.turnrestrictions.clear(); if (turnrestrictions != null) { this.turnrestrictions.addAll(turnrestrictions); } fireTableDataChanged(); } @Override public int getRowCount() { return turnrestrictions.size(); } @Override public int getColumnCount() { return 2; } @Override public Object getValueAt(int rowIndex, int columnIndex) { switch(columnIndex) { case 0: if (rowIndex <= 8) { return Integer.toString(rowIndex+1); } else { return ""; } case 1: return turnrestrictions.get(rowIndex); } // should not happen return null; } } private static class TurnRestrictionTableColumnModel extends DefaultTableColumnModel { TurnRestrictionTableColumnModel() { // the idx column TableColumn col = new TableColumn(0); col.setResizable(false); col.setWidth(50); addColumn(col); // the column displaying turn restrictions col = new TableColumn(1); col.setResizable(false); col.setPreferredWidth(400); col.setCellRenderer(new TurnRestrictionCellRenderer()); addColumn(col); } } private class FocusHandler extends FocusAdapter { @Override public void focusLost(FocusEvent e) { // if we loose the focus to a component outside of the popup panel // we hide the popup if (e.getOppositeComponent() == null || !SwingUtilities.isDescendingFrom(e.getOppositeComponent(), TurnRestrictionSelectionPopupPanel.this)) { if (parentPopup != null) { parentPopup.hide(); } } } } }