001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.command;
003
004import static org.openstreetmap.josm.tools.I18n.trn;
005
006import java.util.Collection;
007import java.util.Collections;
008import java.util.Iterator;
009import java.util.LinkedList;
010import java.util.List;
011
012import javax.swing.Icon;
013
014import org.openstreetmap.josm.data.coor.EastNorth;
015import org.openstreetmap.josm.data.coor.LatLon;
016import org.openstreetmap.josm.data.osm.Node;
017import org.openstreetmap.josm.data.osm.OsmPrimitive;
018import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor;
019import org.openstreetmap.josm.data.projection.Projections;
020import org.openstreetmap.josm.tools.ImageProvider;
021
022/**
023 * MoveCommand moves a set of OsmPrimitives along the map. It can be moved again
024 * to collect several MoveCommands into one command.
025 *
026 * @author imi
027 */
028public class MoveCommand extends Command {
029    /**
030     * The objects that should be moved.
031     */
032    private Collection<Node> nodes = new LinkedList<Node>();
033    /**
034     * Starting position, base command point, current (mouse-drag) position = startEN + (x,y) =
035     */
036    private EastNorth startEN;
037
038    /**
039     * x difference movement. Coordinates are in northern/eastern
040     */
041    private double x;
042    /**
043     * y difference movement. Coordinates are in northern/eastern
044     */
045    private double y;
046
047    private double backupX;
048    private double backupY;
049
050    /**
051     * List of all old states of the objects.
052     */
053    private List<OldNodeState> oldState = new LinkedList<OldNodeState>();
054
055    public MoveCommand(OsmPrimitive osm, double x, double y) {
056        this(Collections.singleton(osm), x, y);
057    }
058
059    public MoveCommand(Node node, LatLon position) {
060        this(Collections.singleton((OsmPrimitive) node), node.getEastNorth().sub(Projections.project(position)));
061    }
062
063    public MoveCommand(Collection<OsmPrimitive> objects, EastNorth offset) {
064        this(objects, offset.getX(), offset.getY());
065    }
066
067    /**
068     * Create a MoveCommand and assign the initial object set and movement vector.
069     */
070    public MoveCommand(Collection<OsmPrimitive> objects, double x, double y) {
071        super();
072        startEN = null;
073        saveCheckpoint(); // (0,0) displacement will be saved
074        this.x = x;
075        this.y = y;
076        this.nodes = AllNodesVisitor.getAllNodes(objects);
077        for (Node n : this.nodes) {
078            oldState.add(new OldNodeState(n));
079        }
080    }
081
082    public MoveCommand(Collection<OsmPrimitive> objects, EastNorth start, EastNorth end) {
083        this(objects, end.getX()-start.getX(), end.getY()-start.getY());
084        startEN =  start;
085    }
086
087    public MoveCommand(OsmPrimitive p, EastNorth start, EastNorth end) {
088        this(Collections.singleton(p), end.getX()-start.getX(), end.getY()-start.getY());
089        startEN =  start;
090    }
091
092    /**
093     * Move the same set of objects again by the specified vector. The vectors
094     * are added together and so the resulting will be moved to the previous
095     * vector plus this one.
096     *
097     * The move is immediately executed and any undo will undo both vectors to
098     * the original position the objects had before first moving.
099     */
100    public void moveAgain(double x, double y) {
101        for (Node n : nodes) {
102            n.setEastNorth(n.getEastNorth().add(x, y));
103        }
104        this.x += x;
105        this.y += y;
106    }
107
108    public void moveAgainTo(double x, double y) {
109        moveAgain(x - this.x, y - this.y);
110    }
111
112    /**
113     * Change the displacement vector to have endpoint @param currentEN
114     * starting point is  startEN
115     */
116    public void applyVectorTo(EastNorth currentEN) {
117        if (startEN == null)
118            return;
119        x = currentEN.getX() - startEN.getX();
120        y = currentEN.getY() - startEN.getY();
121        updateCoordinates();
122    }
123
124    /**
125     * Changes base point of movement
126     * @param newDraggedStartPoint - new starting point after movement (where user clicks to start new drag)
127     */
128    public void changeStartPoint(EastNorth newDraggedStartPoint) {
129        startEN = new EastNorth(newDraggedStartPoint.getX()-x, newDraggedStartPoint.getY()-y);
130    }
131
132    /**
133     * Save curent displacement to restore in case of some problems
134     */
135    public void saveCheckpoint() {
136        backupX = x;
137        backupY = y;
138    }
139
140    /**
141     * Restore old displacement in case of some problems
142     */
143    public void resetToCheckpoint() {
144        x = backupX;
145        y = backupY;
146        updateCoordinates();
147    }
148
149    private void updateCoordinates() {
150        Iterator<OldNodeState> it = oldState.iterator();
151        for (Node n : nodes) {
152            OldNodeState os = it.next();
153            if (os.eastNorth != null) {
154                n.setEastNorth(os.eastNorth.add(x, y));
155            }
156        }
157    }
158
159    @Override public boolean executeCommand() {
160        for (Node n : nodes) {
161            // in case #3892 happens again
162            if (n == null)
163                throw new AssertionError("null detected in node list");
164            EastNorth en = n.getEastNorth();
165            if (en != null) {
166                n.setEastNorth(en.add(x, y));
167                n.setModified(true);
168            }
169        }
170        return true;
171    }
172
173    @Override public void undoCommand() {
174        Iterator<OldNodeState> it = oldState.iterator();
175        for (Node n : nodes) {
176            OldNodeState os = it.next();
177            n.setCoor(os.latlon);
178            n.setModified(os.modified);
179        }
180    }
181
182    @Override public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
183        for (OsmPrimitive osm : nodes) {
184            modified.add(osm);
185        }
186    }
187
188    @Override
189    public String getDescriptionText() {
190        return trn("Move {0} node", "Move {0} nodes", nodes.size(), nodes.size());
191    }
192
193    @Override
194    public Icon getDescriptionIcon() {
195        return ImageProvider.get("data", "node");
196    }
197
198    @Override
199    public Collection<Node> getParticipatingPrimitives() {
200        return nodes;
201    }
202}