// 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.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
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.PrimitiveId;
import org.openstreetmap.josm.gui.widgets.PopupMenuLauncher;
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;
/**
* RelationMemberTable is the table for editing the raw member list of
* a turn restriction.
*
*/
public class RelationMemberTable extends JTable {
private TurnRestrictionEditorModel model;
private DeleteAction actDelete;
private PasteAction actPaste;
private MoveUpAction actMoveUp;
private MoveDownAction actMoveDown;
private TransferHandler transferHandler;
public RelationMemberTable(TurnRestrictionEditorModel model) {
super(
model.getRelationMemberEditorModel(),
new RelationMemberColumnModel(model.getRelationMemberEditorModel().getColSelectionModel()),
model.getRelationMemberEditorModel().getRowSelectionModel()
);
this.model = model;
setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
setRowSelectionAllowed(true);
setColumnSelectionAllowed(true);
setFillsViewportHeight(true); // make sure we can drag onto an empty table
// register the popup menu launcher
addMouseListener(new TablePopupLauncher());
// transfer handling
setDragEnabled(true);
setTransferHandler(new RelationMemberTransferHandler());
setDropTarget(new RelationMemberTableDropTarget());
// initialize the delete action
//
actDelete = new DeleteAction();
model.getRelationMemberEditorModel().getRowSelectionModel().addListSelectionListener(actDelete);
registerKeyboardAction(actDelete, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
// initialize the paste action (will be used in the popup, the action map already includes
// the standard paste action for transfer handling)
actPaste = new PasteAction();
actMoveUp = new MoveUpAction();
model.getRelationMemberEditorModel().getRowSelectionModel().addListSelectionListener(actMoveUp);
registerKeyboardAction(actMoveUp, actMoveUp.getKeyStroke(), WHEN_FOCUSED);
actMoveDown = new MoveDownAction();
model.getRelationMemberEditorModel().getRowSelectionModel().addListSelectionListener(actMoveDown);
registerKeyboardAction(actMoveDown, actMoveDown.getKeyStroke(), WHEN_FOCUSED);
}
/**
* The action for deleting the selected table cells
*
*/
class DeleteAction extends AbstractAction implements ListSelectionListener {
DeleteAction() {
putValue(NAME, tr("Delete"));
putValue(SHORT_DESCRIPTION, tr("Clear the selected roles or delete the selected members"));
new ImageProvider("deletesmall").getResource().attachImageIcon(this);
putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
updateEnabledState();
}
public void updateEnabledState() {
setEnabled(model.getRelationMemberEditorModel().getRowSelectionModel().getMinSelectionIndex() >= 0);
}
@Override
public void actionPerformed(ActionEvent e) {
model.getRelationMemberEditorModel().deleteSelected();
}
@Override
public void valueChanged(ListSelectionEvent e) {
updateEnabledState();
}
}
/**
* The action for pasting into the relation member table
*
*/
class PasteAction extends AbstractAction {
PasteAction() {
putValue(NAME, tr("Paste"));
putValue(SHORT_DESCRIPTION, tr("Insert new relation members from object in the clipboard"));
new ImageProvider("paste").getResource().attachImageIcon(this);
putValue(ACCELERATOR_KEY, Shortcut.getPasteKeyStroke());
updateEnabledState();
}
public void updateEnabledState() {
DataFlavor[] flavors = Toolkit.getDefaultToolkit().getSystemClipboard().getAvailableDataFlavors();
setEnabled(PrimitiveIdListTransferHandler.isSupportedFlavor(flavors));
}
@Override
@SuppressWarnings("unchecked")
public void actionPerformed(ActionEvent evt) {
// tried to delegate to 'paste' action in the action map of the
// table, but didn't work. Now duplicating the logic of importData(...) in
// the transfer handler.
//
Clipboard cp = Toolkit.getDefaultToolkit().getSystemClipboard();
if (!PrimitiveIdListTransferHandler.isSupportedFlavor(cp.getAvailableDataFlavors())) return;
try {
List<PrimitiveId> ids;
ids = (List<PrimitiveId>) cp.getData(PrimitiveIdTransferable.PRIMITIVE_ID_LIST_FLAVOR);
try {
model.getRelationMemberEditorModel().insertMembers(ids);
} catch (IllegalArgumentException e) {
e.printStackTrace();
// FIXME: provide user feedback
}
} catch (IOException e) {
e.printStackTrace();
} catch (UnsupportedFlavorException e) {
e.printStackTrace();
}
}
}
class MoveDownAction extends AbstractAction implements ListSelectionListener {
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.ALT_DOWN_MASK);
MoveDownAction() {
putValue(NAME, tr("Move down"));
putValue(SHORT_DESCRIPTION, tr("Move the selected relation members down by one position"));
putValue(ACCELERATOR_KEY, keyStroke);
new ImageProvider("dialogs", "movedown").getResource().attachImageIcon(this);
updateEnabledState();
}
@Override
public void actionPerformed(ActionEvent e) {
model.getRelationMemberEditorModel().moveDownSelected();
}
public void updateEnabledState() {
setEnabled(model.getRelationMemberEditorModel().canMoveDown());
}
@Override
public void valueChanged(ListSelectionEvent e) {
updateEnabledState();
}
KeyStroke getKeyStroke() {
return keyStroke;
}
}
class MoveUpAction extends AbstractAction implements ListSelectionListener {
private KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.ALT_DOWN_MASK);
MoveUpAction() {
putValue(NAME, tr("Move up"));
putValue(SHORT_DESCRIPTION, tr("Move the selected relation members up by one position"));
putValue(ACCELERATOR_KEY, keyStroke);
new ImageProvider("dialogs", "moveup").getResource().attachImageIcon(this);
updateEnabledState();
}
@Override
public void actionPerformed(ActionEvent e) {
model.getRelationMemberEditorModel().moveUpSelected();
}
public void updateEnabledState() {
setEnabled(model.getRelationMemberEditorModel().canMoveUp());
}
@Override
public void valueChanged(ListSelectionEvent e) {
updateEnabledState();
}
KeyStroke getKeyStroke() {
return keyStroke;
}
}
class TablePopupLauncher extends PopupMenuLauncher {
@Override
public void launch(MouseEvent evt) {
int row = rowAtPoint(evt.getPoint());
if (getSelectionModel().getMinSelectionIndex() < 0 && row >= 0) {
getSelectionModel().setSelectionInterval(row, row);
getColumnModel().getSelectionModel().setSelectionInterval(0, 1);
}
new PopupMenu().show(RelationMemberTable.this, evt.getX(), evt.getY());
}
}
class PopupMenu extends JPopupMenu {
PopupMenu() {
JMenuItem item = add(actPaste);
item.setTransferHandler(transferHandler);
actPaste.updateEnabledState();
addSeparator();
add(actDelete);
addSeparator();
add(actMoveUp);
add(actMoveDown);
}
}
/**
* The transfer handler for the relation member table.
*
*/
class RelationMemberTransferHandler extends TransferHandler {
@Override
public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
return PrimitiveIdListTransferHandler.isSupportedFlavor(transferFlavors);
}
@SuppressWarnings("unchecked")
@Override
public boolean importData(JComponent comp, Transferable t) {
try {
List<PrimitiveId> ids;
ids = (List<PrimitiveId>) t.getTransferData(PrimitiveIdTransferable.PRIMITIVE_ID_LIST_FLAVOR);
try {
model.getRelationMemberEditorModel().insertMembers(ids);
} catch (IllegalArgumentException e) {
e.printStackTrace();
// FIXME: provide user feedback
return false;
}
return true;
} catch (IOException e) {
e.printStackTrace();
} catch (UnsupportedFlavorException e) {
e.printStackTrace();
}
return false;
}
@Override
public int getSourceActions(JComponent c) {
return COPY_OR_MOVE;
}
}
/**
* A custom drop target for the relation member table. During dragging we need to
* disable colum selection model.
*
*/
class RelationMemberTableDropTarget extends DropTarget {
private boolean dropAccepted = false;
/**
* Replies true if {@code transferFlavors} includes the data flavor {@link PrimitiveIdTransferable#PRIMITIVE_ID_LIST_FLAVOR}.
* @param transferFlavors an array of transferFlavors
*/
protected boolean isSupportedFlavor(DataFlavor[] transferFlavors) {
for (DataFlavor df: transferFlavors) {
if (df.equals(PrimitiveIdTransferable.PRIMITIVE_ID_LIST_FLAVOR)) return true;
}
return false;
}
@Override
public synchronized void dragEnter(DropTargetDragEvent dtde) {
if (isSupportedFlavor(dtde.getCurrentDataFlavors())) {
if ((dtde.getSourceActions() & DnDConstants.ACTION_COPY_OR_MOVE) != 0) {
dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
setColumnSelectionAllowed(false);
dropAccepted = true;
} else {
dtde.rejectDrag();
}
} else {
dtde.rejectDrag();
}
}
@Override
public synchronized void dragExit(DropTargetEvent dte) {
setColumnSelectionAllowed(true);
dropAccepted = false;
}
@Override
public synchronized void dragOver(DropTargetDragEvent dtde) {
int row = rowAtPoint(dtde.getLocation());
int selectedRow = getSelectionModel().getMinSelectionIndex();
if (row >= 0 && row != selectedRow) {
getSelectionModel().setSelectionInterval(row, row);
}
}
@Override
@SuppressWarnings("unchecked")
public synchronized void drop(DropTargetDropEvent dtde) {
try {
if (!dropAccepted) return;
if ((dtde.getSourceActions() & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
return;
}
List<PrimitiveId> ids;
ids = (List<PrimitiveId>) dtde.getTransferable().getTransferData(PrimitiveIdTransferable.PRIMITIVE_ID_LIST_FLAVOR);
try {
model.getRelationMemberEditorModel().insertMembers(ids);
} catch (IllegalArgumentException e) {
e.printStackTrace();
// FIXME: provide user feedback
}
} catch (IOException e) {
e.printStackTrace();
} catch (UnsupportedFlavorException e) {
e.printStackTrace();
} finally {
setColumnSelectionAllowed(true);
}
}
@Override
public synchronized void dropActionChanged(DropTargetDragEvent dtde) {
if ((dtde.getSourceActions() & DnDConstants.ACTION_COPY_OR_MOVE) == 0) {
dtde.rejectDrag();
} else {
dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
}
}
}
}