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