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;
010
011import javax.swing.JOptionPane;
012
013import org.openstreetmap.josm.Main;
014import org.openstreetmap.josm.command.Command;
015import org.openstreetmap.josm.command.MoveCommand;
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,
038        /** Move left */
039        LEFT,
040        /** Move right */
041        RIGHT,
042        /** Move down */
043        DOWN
044    }
045
046    private final Direction myDirection;
047
048    // any better idea?
049    private static String calltosupermustbefirststatementinconstructortext(Direction dir) {
050        String directiontext;
051        if (dir == Direction.UP) {
052            directiontext = tr("up");
053        } else if (dir == Direction.DOWN) {
054            directiontext = tr("down");
055        } else if (dir == Direction.LEFT) {
056            directiontext = tr("left");
057        } else {
058            directiontext = tr("right");
059        }
060        return directiontext;
061    }
062
063    // any better idea?
064    private static Shortcut calltosupermustbefirststatementinconstructor(Direction dir) {
065        Shortcut sc;
066        // CHECKSTYLE.OFF: SingleSpaceSeparator
067        if (dir == Direction.UP) {
068            sc = Shortcut.registerShortcut("core:moveup",    tr("Move objects {0}", tr("up")),    KeyEvent.VK_UP,    Shortcut.SHIFT);
069        } else if (dir == Direction.DOWN) {
070            sc = Shortcut.registerShortcut("core:movedown",  tr("Move objects {0}", tr("down")),  KeyEvent.VK_DOWN,  Shortcut.SHIFT);
071        } else if (dir == Direction.LEFT) {
072            sc = Shortcut.registerShortcut("core:moveleft",  tr("Move objects {0}", tr("left")),  KeyEvent.VK_LEFT,  Shortcut.SHIFT);
073        } else { //dir == Direction.RIGHT
074            sc = Shortcut.registerShortcut("core:moveright", tr("Move objects {0}", tr("right")), KeyEvent.VK_RIGHT, Shortcut.SHIFT);
075        }
076        // CHECKSTYLE.ON: SingleSpaceSeparator
077        return sc;
078    }
079
080    /**
081     * Constructs a new {@code MoveAction}.
082     * @param dir direction
083     */
084    public MoveAction(Direction dir) {
085        super(tr("Move {0}", calltosupermustbefirststatementinconstructortext(dir)), null,
086                tr("Moves Objects {0}", calltosupermustbefirststatementinconstructortext(dir)),
087                calltosupermustbefirststatementinconstructor(dir), false);
088        myDirection = dir;
089        putValue("help", ht("/Action/Move"));
090        if (dir == Direction.UP) {
091            putValue("toolbar", "action/move/up");
092        } else if (dir == Direction.DOWN) {
093            putValue("toolbar", "action/move/down");
094        } else if (dir == Direction.LEFT) {
095            putValue("toolbar", "action/move/left");
096        } else { //dir == Direction.RIGHT
097            putValue("toolbar", "action/move/right");
098        }
099        MainApplication.getToolbar().register(this);
100    }
101
102    @Override
103    public void actionPerformed(ActionEvent event) {
104        DataSet ds = getLayerManager().getEditDataSet();
105
106        if (!MainApplication.isDisplayingMapView() || ds == null)
107            return;
108
109        // find out how many "real" units the objects have to be moved in order to
110        // achive an 1-pixel movement
111
112        MapView mapView = MainApplication.getMap().mapView;
113        EastNorth en1 = mapView.getEastNorth(100, 100);
114        EastNorth en2 = mapView.getEastNorth(101, 101);
115
116        double distx = en2.east() - en1.east();
117        double disty = en2.north() - en1.north();
118
119        switch (myDirection) {
120        case UP:
121            distx = 0;
122            disty = -disty;
123            break;
124        case DOWN:
125            distx = 0;
126            break;
127        case LEFT:
128            disty = 0;
129            distx = -distx;
130            break;
131        default:
132            disty = 0;
133        }
134
135        Collection<OsmPrimitive> selection = ds.getSelected();
136        Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection);
137
138        Command c = MainApplication.undoRedo.getLastCommand();
139
140        ds.beginUpdate();
141        try {
142            if (c instanceof MoveCommand && ds.equals(c.getAffectedDataSet())
143                    && affectedNodes.equals(((MoveCommand) c).getParticipatingPrimitives())) {
144                ((MoveCommand) c).moveAgain(distx, disty);
145            } else {
146                c = new MoveCommand(ds, selection, distx, disty);
147                MainApplication.undoRedo.add(c);
148            }
149        } finally {
150            ds.endUpdate();
151        }
152
153        for (Node n : affectedNodes) {
154            if (n.isLatLonKnown() && n.getCoor().isOutSideWorld()) {
155                // Revert move
156                ((MoveCommand) c).moveAgain(-distx, -disty);
157                JOptionPane.showMessageDialog(
158                        Main.parent,
159                        tr("Cannot move objects outside of the world."),
160                        tr("Warning"),
161                        JOptionPane.WARNING_MESSAGE
162                );
163                return;
164            }
165        }
166
167        mapView.repaint();
168    }
169
170    @Override
171    protected void updateEnabledState() {
172        updateEnabledStateOnCurrentSelection();
173    }
174
175    @Override
176    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
177        updateEnabledStateOnModifiableSelection(selection);
178    }
179}