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