001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.event.ActionEvent;
008import java.awt.event.KeyEvent;
009import java.util.Collection;
010import java.util.Locale;
011
012import javax.swing.JOptionPane;
013
014import org.openstreetmap.josm.command.MoveCommand;
015import org.openstreetmap.josm.data.UndoRedoHandler;
016import org.openstreetmap.josm.data.coor.EastNorth;
017import org.openstreetmap.josm.data.osm.DataSet;
018import org.openstreetmap.josm.data.osm.Node;
019import org.openstreetmap.josm.data.osm.OsmPrimitive;
020import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor;
021import org.openstreetmap.josm.gui.MainApplication;
022import org.openstreetmap.josm.gui.MapView;
023import org.openstreetmap.josm.tools.Shortcut;
024
025/**
026 * Moves the selection
027 *
028 * @author Frederik Ramm
029 */
030public class MoveAction extends JosmAction {
031
032    /**
033     * Move direction.
034     */
035    public enum Direction {
036        /** Move up */
037        UP(tr("up"), "up", KeyEvent.VK_UP),
038        /* SHORTCUT(Move objects up, core:moveup, SHIFT, UP) */
039        /** Move left */
040        LEFT(tr("left"), "previous", KeyEvent.VK_LEFT),
041        /* SHORTCUT(Move objects left, core:moveleft, SHIFT, LEFT) */
042        /** Move right */
043        RIGHT(tr("right"), "next", KeyEvent.VK_RIGHT),
044        /* SHORTCUT(Move objects right, core:moveright, SHIFT, RIGHT) */
045        /** Move down */
046        DOWN(tr("down"), "down", KeyEvent.VK_DOWN);
047        /* SHORTCUT(Move objects down, core:movedown, SHIFT, DOWN) */
048
049        private final String localizedName;
050        private final String icon;
051        private final int shortcutKey;
052
053        Direction(String localizedName, String icon, int shortcutKey) {
054            this.localizedName = localizedName;
055            this.icon = icon;
056            this.shortcutKey = shortcutKey;
057        }
058
059        String getId() {
060            return name().toLowerCase(Locale.ENGLISH);
061        }
062
063        String getLocalizedName() {
064            return localizedName;
065        }
066
067        String getIcon() {
068            return "dialogs/" + icon;
069        }
070
071        String getToolbarName() {
072            return "action/move/" + getId();
073        }
074
075        int getShortcutKey() {
076            return shortcutKey;
077        }
078
079        Shortcut getShortcut() {
080            return Shortcut.registerShortcut(/* NO-SHORTCUT - adapt definition above when modified */
081                    "core:move" + getId(), tr("Move objects {0}", getLocalizedName()), getShortcutKey(), Shortcut.SHIFT);
082        }
083    }
084
085    private final Direction myDirection;
086
087    /**
088     * Constructs a new {@code MoveAction}.
089     * @param dir direction
090     */
091    public MoveAction(Direction dir) {
092        super(tr("Move {0}", dir.getLocalizedName()), dir.getIcon(),
093                tr("Moves Objects {0}", dir.getLocalizedName()),
094                dir.getShortcut(), true, dir.getToolbarName(), true);
095        myDirection = dir;
096        setHelpId(ht("/Action/Move"));
097    }
098
099    /**
100     * Find out how many "real" units the objects have to be moved in order to achieve an 1-pixel movement
101     * @param mapView map view
102     * @return move offset
103     */
104    private EastNorth getOffset(MapView mapView) {
105        EastNorth en1 = mapView.getEastNorth(100, 100);
106        EastNorth en2 = mapView.getEastNorth(101, 101);
107
108        double distx = en2.east() - en1.east();
109        double disty = en2.north() - en1.north();
110
111        switch (myDirection) {
112        case UP:
113            distx = 0;
114            disty = -disty;
115            break;
116        case DOWN:
117            distx = 0;
118            break;
119        case LEFT:
120            disty = 0;
121            distx = -distx;
122            break;
123        default:
124            disty = 0;
125        }
126
127        return new EastNorth(distx, disty);
128    }
129
130    @Override
131    public void actionPerformed(ActionEvent event) {
132        DataSet ds = getLayerManager().getEditDataSet();
133
134        if (!MainApplication.isDisplayingMapView() || ds == null)
135            return;
136
137        MapView mapView = MainApplication.getMap().mapView;
138        final EastNorth dist = getOffset(mapView);
139
140        Collection<OsmPrimitive> selection = ds.getSelected();
141        Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection);
142
143        MoveCommand cmd = ds.update(c -> {
144            MoveCommand moveCmd;
145            if (c instanceof MoveCommand && ds.equals(c.getAffectedDataSet())
146                    && affectedNodes.equals(((MoveCommand) c).getParticipatingPrimitives())) {
147                moveCmd = (MoveCommand) c;
148                moveCmd.moveAgain(dist.east(), dist.north());
149            } else {
150                moveCmd = new MoveCommand(ds, selection, dist.east(), dist.north());
151                UndoRedoHandler.getInstance().add(moveCmd);
152            }
153            return moveCmd;
154        }, UndoRedoHandler.getInstance().getLastCommand());
155
156        for (Node n : affectedNodes) {
157            if (n.isLatLonKnown() && n.isOutSideWorld()) {
158                // Revert move
159                cmd.moveAgain(-dist.east(), -dist.north());
160                JOptionPane.showMessageDialog(
161                        MainApplication.getMainFrame(),
162                        tr("Cannot move objects outside of the world."),
163                        tr("Warning"),
164                        JOptionPane.WARNING_MESSAGE
165                );
166                return;
167            }
168        }
169
170        mapView.repaint();
171    }
172
173    @Override
174    protected void updateEnabledState() {
175        updateEnabledStateOnCurrentSelection();
176    }
177
178    @Override
179    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
180        updateEnabledStateOnModifiableSelection(selection);
181    }
182}