// 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.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.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.io.IOException; import java.util.List; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.DefaultListSelectionModel; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.JPopupMenu; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.TransferHandler; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openstreetmap.josm.data.osm.OsmPrimitive; import org.openstreetmap.josm.data.osm.PrimitiveId; import org.openstreetmap.josm.gui.OsmPrimitivRenderer; 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.ImageProvider; import org.openstreetmap.josm.tools.Shortcut; /** * <p>ViaList is a JList which displays the 'via' members of a turn restriction.</p> * * <p>A ViaList is connected to a {@link TurnRestrictionEditorModel} through its * {@link ViaListModel}.</p> * */ public class ViaList extends JList<OsmPrimitive> { //private static final Logger logger = Logger.getLogger(ViaList.class.getName()); private ViaListModel model; private DeleteAction actDelete; private MoveUpAction actMoveUp; private MoveDownAction actMoveDown; private CopyAction actCopy; private PasteAction actPaste; private TransferHandler transferHandler; /** * Constructor * * @param model the via list model. Must not be null. * @param selectionModel the selection model. Must not be null. * */ public ViaList(ViaListModel model, DefaultListSelectionModel selectionModel) { super(model); this.model = model; setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); setSelectionModel(selectionModel); setCellRenderer(new OsmPrimitivRenderer()); setDragEnabled(true); setTransferHandler(transferHandler = new ViaListTransferHandler(model)); setVisibleRowCount(4); actDelete = new DeleteAction(); selectionModel.addListSelectionListener(actDelete); registerKeyboardAction(actDelete, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); actMoveDown = new MoveDownAction(); selectionModel.addListSelectionListener(actMoveDown); registerKeyboardAction(actMoveDown, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); actMoveUp = new MoveUpAction(); selectionModel.addListSelectionListener(actMoveUp); registerKeyboardAction(actMoveUp, KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.ALT_DOWN_MASK), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); actCopy = new CopyAction(); actPaste = new PasteAction(); getSelectionModel().addListSelectionListener(actCopy); addMouseListener(new ViaListPopupMenuLaucher()); } /** * The transfer handler for Drag-and-Drop. */ class ViaListTransferHandler extends PrimitiveIdListTransferHandler { Logger logger = Logger.getLogger(ViaListTransferHandler.class.getName()); private boolean isViaListInDragOperation = false; private List<Integer> selectedRowsMemento = null; ViaListTransferHandler(PrimitiveIdListProvider provider) { super(provider); } @Override public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { // a drag operation on itself is always allowed if (isViaListInDragOperation) return true; return isSupportedFlavor(transferFlavors); } @SuppressWarnings("unchecked") @Override public boolean importData(JComponent comp, Transferable t) { if (!isSupportedFlavor(t.getTransferDataFlavors())) return false; if (isViaListInDragOperation) { // this is a drag operation on itself int targetRow = getSelectedIndex(); if (targetRow < 0) return true; model.moveVias(selectedRowsMemento, targetRow); } else { // this is a drag operation from another component try { List<PrimitiveId> idsToAdd = (List<PrimitiveId>) t.getTransferData(PrimitiveIdTransferable.PRIMITIVE_ID_LIST_FLAVOR); model.insertVias(idsToAdd); } catch (IOException e) { e.printStackTrace(); } catch (UnsupportedFlavorException e) { e.printStackTrace(); } } return true; } @Override protected void exportDone(JComponent source, Transferable data, int action) { isViaListInDragOperation = false; super.exportDone(source, data, action); } @Override public void exportAsDrag(JComponent comp, InputEvent e, int action) { isViaListInDragOperation = true; selectedRowsMemento = model.getSelectedRows(); super.exportAsDrag(comp, e, action); } } class DeleteAction extends AbstractAction implements ListSelectionListener { DeleteAction() { putValue(NAME, tr("Remove")); new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this); putValue(SHORT_DESCRIPTION, tr("Remove the currently selected vias")); putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)); updateEnabledState(); } @Override public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } public void updateEnabledState() { setEnabled(getSelectedIndex() >= 0); } @Override public void actionPerformed(ActionEvent e) { model.removeSelectedVias(); } } class MoveDownAction extends AbstractAction implements ListSelectionListener { MoveDownAction() { putValue(NAME, tr("Move down")); putValue(SHORT_DESCRIPTION, tr("Move the selected vias down by one position")); putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK)); new ImageProvider("dialogs", "movedown").getResource().attachImageIcon(this); updateEnabledState(); } @Override public void actionPerformed(ActionEvent e) { model.moveDown(); } public void updateEnabledState() { if (getSelectedIndex() < 0) { setEnabled(false); return; } setEnabled(getSelectionModel().getMaxSelectionIndex() < getModel().getSize() -1); } @Override public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } } class MoveUpAction extends AbstractAction implements ListSelectionListener { MoveUpAction() { putValue(NAME, tr("Move up")); putValue(SHORT_DESCRIPTION, tr("Move the selected vias up by one position")); putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.ALT_DOWN_MASK)); new ImageProvider("dialogs", "moveup").getResource().attachImageIcon(this); updateEnabledState(); } @Override public void actionPerformed(ActionEvent e) { model.moveUp(); } public void updateEnabledState() { if (getSelectedIndex() < 0) { setEnabled(false); return; } setEnabled(getSelectionModel().getMinSelectionIndex() > 0); } @Override public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } } class CopyAction extends AbstractAction implements ListSelectionListener { private Action delegate; CopyAction() { putValue(NAME, tr("Copy")); putValue(SHORT_DESCRIPTION, tr("Copy the selected vias to the clipboard")); new ImageProvider("copy").getResource().attachImageIcon(this); putValue(ACCELERATOR_KEY, Shortcut.getCopyKeyStroke()); delegate = ViaList.this.getActionMap().get("copy"); } @Override public void actionPerformed(ActionEvent e) { delegate.actionPerformed(e); } protected void updateEnabledState() { setEnabled(!model.getSelectedVias().isEmpty()); } @Override public void valueChanged(ListSelectionEvent e) { updateEnabledState(); } } 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("Insert ''via'' objects from the clipboard")); new ImageProvider("paste").getResource().attachImageIcon(this); putValue(ACCELERATOR_KEY, Shortcut.getPasteKeyStroke()); delegate = ViaList.this.getActionMap().get("paste"); updateEnabledState(); } public void updateEnabledState() { setEnabled(canPaste()); } @Override public void actionPerformed(ActionEvent e) { delegate.actionPerformed(e); } } class ViaListPopupMenu extends JPopupMenu { ViaListPopupMenu() { JMenuItem item = add(actCopy); item.setTransferHandler(transferHandler); item = add(actPaste); actPaste.updateEnabledState(); item.setTransferHandler(transferHandler); addSeparator(); add(actDelete); addSeparator(); add(actMoveUp); add(actMoveDown); } } class ViaListPopupMenuLaucher extends PopupMenuLauncher { @Override public void launch(MouseEvent evt) { if (getSelectedIndex() < 0) { int idx = locationToIndex(evt.getPoint()); if (idx >= 0) { setSelectedIndex(idx); } } new ViaListPopupMenu().show(ViaList.this, evt.getX(), evt.getY()); } } }