001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import java.awt.MouseInfo;
005import java.awt.Point;
006import java.awt.PointerInfo;
007import java.awt.datatransfer.FlavorEvent;
008import java.awt.datatransfer.FlavorListener;
009import java.awt.datatransfer.Transferable;
010import java.awt.event.ActionEvent;
011
012import org.openstreetmap.josm.data.coor.EastNorth;
013import org.openstreetmap.josm.gui.MainApplication;
014import org.openstreetmap.josm.gui.MapView;
015import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;
016import org.openstreetmap.josm.gui.datatransfer.OsmTransferHandler;
017import org.openstreetmap.josm.tools.Logging;
018import org.openstreetmap.josm.tools.Shortcut;
019
020/**
021 * This is the base class for all actions that paste objects.
022 * @author Michael Zangl
023 * @since 10765
024 */
025public abstract class AbstractPasteAction extends JosmAction implements FlavorListener {
026
027    protected final OsmTransferHandler transferHandler;
028
029    /**
030     * Constructs a new {@link AbstractPasteAction}.
031     * @param name the action's text as displayed on the menu (if it is added to a menu)
032     * @param iconName the filename of the icon to use
033     * @param tooltip  a longer description of the action that will be displayed in the tooltip. Please note
034     *           that html is not supported for menu actions on some platforms.
035     * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
036     *            do want a shortcut, remember you can always register it with group=none, so you
037     *            won't be assigned a shortcut unless the user configures one. If you pass null here,
038     *            the user CANNOT configure a shortcut for your action.
039     * @param registerInToolbar register this action for the toolbar preferences?
040     */
041    public AbstractPasteAction(String name, String iconName, String tooltip, Shortcut shortcut,
042            boolean registerInToolbar) {
043        this(name, iconName, tooltip, shortcut, registerInToolbar, null);
044    }
045
046    /**
047     * Constructs a new {@link AbstractPasteAction}.
048     * @param name the action's text as displayed on the menu (if it is added to a menu)
049     * @param iconName the filename of the icon to use
050     * @param tooltip  a longer description of the action that will be displayed in the tooltip. Please note
051     *           that html is not supported for menu actions on some platforms.
052     * @param shortcut a ready-created shortcut object or null if you don't want a shortcut. But you always
053     *            do want a shortcut, remember you can always register it with group=none, so you
054     *            won't be assigned a shortcut unless the user configures one. If you pass null here,
055     *            the user CANNOT configure a shortcut for your action.
056     * @param registerInToolbar register this action for the toolbar preferences?
057     * @param toolbarId identifier for the toolbar preferences. The iconName is used, if this parameter is null
058     */
059    public AbstractPasteAction(String name, String iconName, String tooltip, Shortcut shortcut,
060            boolean registerInToolbar, String toolbarId) {
061        super(name, iconName, tooltip, shortcut, registerInToolbar, toolbarId, true);
062        transferHandler = new OsmTransferHandler();
063        ClipboardUtils.getClipboard().addFlavorListener(this);
064    }
065
066    /**
067     * Compute the location the objects should be pasted at.
068     * @param e The action event that triggered the paste
069     * @return The paste position.
070     */
071    protected EastNorth computePastePosition(ActionEvent e) {
072        // default to paste in center of map (pasted via menu or cursor not in MapView)
073        MapView mapView = MainApplication.getMap().mapView;
074        EastNorth mPosition = mapView.getCenter();
075        // We previously checked for modifier to know if the action has been trigerred via shortcut or via menu
076        // But this does not work if the shortcut is changed to a single key (see #9055)
077        // Observed behaviour: getActionCommand() returns Action.NAME when triggered via menu, but shortcut text when triggered with it
078        if (e != null && !getValue(NAME).equals(e.getActionCommand())) {
079            try {
080                final PointerInfo pointerInfo = MouseInfo.getPointerInfo();
081                if (pointerInfo != null) {
082                    final Point mp = pointerInfo.getLocation();
083                    final Point tl = mapView.getLocationOnScreen();
084                    final Point pos = new Point(mp.x-tl.x, mp.y-tl.y);
085                    if (mapView.contains(pos)) {
086                        mPosition = mapView.getEastNorth(pos.x, pos.y);
087                    }
088                }
089            } catch (SecurityException ex) {
090                Logging.log(Logging.LEVEL_ERROR, "Unable to get mouse pointer info", ex);
091            }
092        }
093        return mPosition;
094    }
095
096    @Override
097    public void actionPerformed(ActionEvent e) {
098        doPaste(e, ClipboardUtils.getClipboardContent());
099    }
100
101    protected void doPaste(ActionEvent e, Transferable contents) {
102        transferHandler.pasteOn(getLayerManager().getEditLayer(), computePastePosition(e), contents);
103    }
104
105    @Override
106    protected void updateEnabledState() {
107        setEnabled(getLayerManager().getEditDataSet() != null && transferHandler != null && transferHandler.isDataAvailable());
108    }
109
110    @Override
111    public void flavorsChanged(FlavorEvent e) {
112        updateEnabledState();
113    }
114}