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    }