// 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.FlowLayout; import java.awt.Font; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.io.IOException; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Observable; import java.util.Observer; import java.util.Set; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.TransferHandler; import javax.swing.UIManager; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.OsmPrimitiveType; import org.openstreetmap.josm.data.osm.PrimitiveId; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.DefaultNameFormatter; import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher; import org.openstreetmap.josm.plugins.turnrestrictions.dnd.PrimitiveIdListProvider; import org.openstreetmap.josm.plugins.turnrestrictions.dnd.PrimitiveIdListTransferHandler; import org.openstreetmap.josm.plugins.turnrestrictions.dnd.PrimitiveIdTransferable; import org.openstreetmap.josm.tools.CheckParameterUtil; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Shortcut; /** * This is an editor for one of the two legs of a turn restriction. */ public class TurnRestrictionLegEditor extends JPanel implements Observer, PrimitiveIdListProvider { //private static final Logger logger = Logger.getLogger(TurnRestrictionLegEditor.class.getName()); private JLabel lblOsmObject; private final Set<OsmPrimitive> legs = new HashSet<>(); private TurnRestrictionEditorModel model; private TurnRestrictionLegRole role; private DeleteAction actDelete; private CopyAction actCopy; private PasteAction actPaste; private AcceptAction actAccept; private TransferHandler transferHandler; /** * builds the UI */ protected void build() { setLayout(new BorderLayout()); add(lblOsmObject = new JLabel(), BorderLayout.CENTER); lblOsmObject.setOpaque(true); lblOsmObject.setBorder(null); setBorder( BorderFactory.createCompoundBorder( BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder(1, 1, 1, 1) ) ); JPanel pnlButtons = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0)); pnlButtons.setBorder(null); JButton btn; actDelete = new DeleteAction(); pnlButtons.add(btn = new JButton(actDelete)); btn.setFocusable(false); btn.setText(null); btn.setBorder(BorderFactory.createRaisedBevelBorder()); actAccept = new AcceptAction(); pnlButtons.add(btn = new JButton(actAccept)); btn.setFocusable(false); btn.setText(null); btn.setBorder(BorderFactory.createRaisedBevelBorder()); add(pnlButtons, BorderLayout.EAST); // focus handling FocusHandler fh = new FocusHandler(); lblOsmObject.setFocusable(true); lblOsmObject.addFocusListener(fh); this.addFocusListener(fh); // mouse event handling MouseEventHandler meh = new MouseEventHandler(); lblOsmObject.addMouseListener(meh); addMouseListener(meh); lblOsmObject.addMouseListener(new PopupLauncher()); // enable DEL to remove the object from the turn restriction registerKeyboardAction(actDelete, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); getInputMap().put(Shortcut.getCopyKeyStroke(), TransferHandler.getCopyAction().getValue(Action.NAME));; getInputMap().put(Shortcut.getPasteKeyStroke(), TransferHandler.getPasteAction().getValue(Action.NAME));; getActionMap().put(TransferHandler.getCopyAction().getValue(Action.NAME), TransferHandler.getCopyAction()); getActionMap().put(TransferHandler.getPasteAction().getValue(Action.NAME), TransferHandler.getPasteAction()); lblOsmObject.setTransferHandler(transferHandler = new LegEditorTransferHandler(this)); lblOsmObject.addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent e) { JComponent c = (JComponent) e.getSource(); TransferHandler th = c.getTransferHandler(); th.exportAsDrag(c, e, TransferHandler.COPY); } }); actCopy = new CopyAction(); actPaste = new PasteAction(); } /** * Constructor * * @param model the model. Must not be null. * @param role the leg role of the leg this editor is editing. Must not be null. * @exception IllegalArgumentException thrown if model is null * @exception IllegalArgumentException thrown if role is null */ public TurnRestrictionLegEditor(TurnRestrictionEditorModel model, TurnRestrictionLegRole role) { CheckParameterUtil.ensureParameterNotNull(model, "model"); CheckParameterUtil.ensureParameterNotNull(role, "role"); this.model = model; this.role = role; build(); model.addObserver(this); refresh(); } protected void refresh() { legs.clear(); legs.addAll(model.getTurnRestrictionLeg(role)); if (legs.isEmpty()) { lblOsmObject.setFont(UIManager.getFont("Label.font").deriveFont(Font.ITALIC)); lblOsmObject.setIcon(null); lblOsmObject.setText(tr("please select a way")); lblOsmObject.setToolTipText(null); } else if (legs.size() == 1) { OsmPrimitive leg = legs.iterator().next(); lblOsmObject.setFont(UIManager.getFont("Label.font")); lblOsmObject.setIcon(ImageProvider.get("data", "way")); lblOsmObject.setText(leg.getDisplayName(DefaultNameFormatter.getInstance())); lblOsmObject.setToolTipText(DefaultNameFormatter.getInstance().buildDefaultToolTip(leg)); } else { lblOsmObject.setFont(UIManager.getFont("Label.font").deriveFont(Font.ITALIC)); lblOsmObject.setIcon(null); lblOsmObject.setText(tr("multiple objects with role ''{0}''", this.role.getOsmRole())); lblOsmObject.setToolTipText(null); } renderColors(); actDelete.updateEnabledState(); } /** * Render the foreground and background color */ protected void renderColors() { if (lblOsmObject.hasFocus()) { setBackground(UIManager.getColor("List.selectionBackground")); setForeground(UIManager.getColor("List.selectionForeground")); lblOsmObject.setBackground(UIManager.getColor("List.selectionBackground")); lblOsmObject.setForeground(UIManager.getColor("List.selectionForeground")); } else { lblOsmObject.setBackground(UIManager.getColor("List.background")); lblOsmObject.setForeground(UIManager.getColor("List.foreground")); } } /** * Replies the model for this editor * * @return the model */ public TurnRestrictionEditorModel getModel() { return model; } /** * Replies the role of this editor * * @return the role */ public TurnRestrictionLegRole getRole() { return role; } /* ----------------------------------------------------------------------------- */ /* interface Observer */ /* ----------------------------------------------------------------------------- */ @Override public void update(Observable o, Object arg) { refresh(); } /* ----------------------------------------------------------------------------- */ /* interface PrimitiveIdListProvider */ /* ----------------------------------------------------------------------------- */ @Override public List<PrimitiveId> getSelectedPrimitiveIds() { if (legs.size() == 1) { return Collections.singletonList(legs.iterator().next().getPrimitiveId()); } return Collections.emptyList(); } /* ----------------------------------------------------------------------------- */ /* inner classes */ /* ----------------------------------------------------------------------------- */ /** * Responds to focus change events */ class FocusHandler extends FocusAdapter { @Override public void focusGained(FocusEvent e) { renderColors(); } @Override public void focusLost(FocusEvent e) { renderColors(); } } class MouseEventHandler extends MouseAdapter { @Override public void mouseClicked(MouseEvent e) { lblOsmObject.requestFocusInWindow(); } } /** * Deletes the way from the turn restriction */ class DeleteAction extends AbstractAction { DeleteAction() { putValue(SHORT_DESCRIPTION, tr("Delete from turn restriction")); putValue(NAME, tr("Delete")); new ImageProvider("deletesmall").getResource().attachImageIcon(this); putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)); updateEnabledState(); } @Override public void actionPerformed(ActionEvent e) { model.setTurnRestrictionLeg(role, null); } public void updateEnabledState() { setEnabled(legs.size() > 0); } } /** * Accepts the currently selected way as turn restriction leg. Only enabled, * if there is exactly one way selected */ class AcceptAction extends AbstractAction implements ListSelectionListener { AcceptAction() { putValue(SHORT_DESCRIPTION, tr("Accept the currently selected way")); putValue(NAME, tr("Accept")); new ImageProvider("accept").getResource().attachImageIcon(this); model.getJosmSelectionListModel().getListSelectionModel().addListSelectionListener(this); updateEnabledState(); } @Override public void actionPerformed(ActionEvent e) { List<Way> selWays = OsmPrimitive.getFilteredList(model.getJosmSelectionListModel().getSelected(), Way.class); if (selWays.size() != 1) return; Way w = selWays.get(0); model.setTurnRestrictionLeg(role, w); } public void updateEnabledState() { setEnabled(OsmPrimitive.getFilteredList(model.getJosmSelectionListModel().getSelected(), Way.class).size() == 1); } @Override public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } } /** * The transfer handler for Drag-and-Drop. */ class LegEditorTransferHandler extends PrimitiveIdListTransferHandler { Logger logger = Logger.getLogger(LegEditorTransferHandler.class.getName()); LegEditorTransferHandler(PrimitiveIdListProvider provider) { super(provider); } @SuppressWarnings("unchecked") @Override public boolean importData(JComponent comp, Transferable t) { try { List<PrimitiveId> ids = (List<PrimitiveId>) t.getTransferData(PrimitiveIdTransferable.PRIMITIVE_ID_LIST_FLAVOR); if (ids.size() != 1) { return false; } PrimitiveId id = ids.get(0); if (!id.getType().equals(OsmPrimitiveType.WAY)) return false; model.setTurnRestrictionLeg(role, id); return true; } catch (IOException e) { // ignore return false; } catch (UnsupportedFlavorException e) { // ignore return false; } } @Override protected Transferable createTransferable(JComponent c) { if (legs.size() != 1) return null; return super.createTransferable(c); } } class PopupLauncher extends PopupMenuLauncher { @Override public void launch(MouseEvent evt) { new PopupMenu().show(lblOsmObject, evt.getX(), evt.getY()); } } class PopupMenu extends JPopupMenu { PopupMenu() { actCopy.updateEnabledState(); JMenuItem item = add(actCopy); item.setTransferHandler(transferHandler); actPaste.updateEnabledState(); item = add(actPaste); item.setTransferHandler(transferHandler); addSeparator(); add(actDelete); } } class CopyAction extends AbstractAction { private Action delegate; CopyAction() { putValue(NAME, tr("Copy")); putValue(SHORT_DESCRIPTION, tr("Copy to the clipboard")); new ImageProvider("copy").getResource().attachImageIcon(this); putValue(ACCELERATOR_KEY, Shortcut.getCopyKeyStroke()); delegate = TurnRestrictionLegEditor.this.getActionMap().get("copy"); updateEnabledState(); } @Override public void actionPerformed(ActionEvent e) { delegate.actionPerformed(e); } public void updateEnabledState() { setEnabled(legs.size() == 1); } } class PasteAction extends AbstractAction { private Action delegate; public boolean canPaste() { Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); for (DataFlavor df: clipboard.getAvailableDataFlavors()) { if (df.equals(PrimitiveIdTransferable.PRIMITIVE_ID_LIST_FLAVOR)) return true; } // FIXME: check whether there are selected objects in the JOSM copy/paste buffer return false; } PasteAction() { putValue(NAME, tr("Paste")); putValue(SHORT_DESCRIPTION, tr("Paste from the clipboard")); new ImageProvider("paste").getResource().attachImageIcon(this); putValue(ACCELERATOR_KEY, Shortcut.getPasteKeyStroke()); delegate = TurnRestrictionLegEditor.this.getActionMap().get("paste"); } public void updateEnabledState() { setEnabled(canPaste()); } @Override public void actionPerformed(ActionEvent e) { delegate.actionPerformed(e); } } }