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 }