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.HashMap;
008import java.util.LinkedList;
009import java.util.Map;
010import java.util.Objects;
011
012import javax.swing.Icon;
013
014import org.openstreetmap.josm.data.coor.EastNorth;
015import org.openstreetmap.josm.data.osm.Node;
016import org.openstreetmap.josm.data.osm.OsmPrimitive;
017import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor;
018import org.openstreetmap.josm.tools.ImageProvider;
019
020/**
021 * Abstract class with common services for nodes rotation and scaling commands.
022 *
023 * @author Olivier Croquette <ocroquette@free.fr>
024 */
025public abstract class TransformNodesCommand extends Command {
026
027    /**
028     * The nodes to transform.
029     */
030    protected Collection<Node> nodes = new LinkedList<>();
031
032    /**
033     * List of all old states of the nodes.
034     */
035    protected Map<Node, OldNodeState> oldStates = new HashMap<>();
036
037    /**
038     * Stores the state of the nodes before the command.
039     */
040    protected final void storeOldState() {
041        for (Node n : this.nodes) {
042            oldStates.put(n, new OldNodeState(n));
043        }
044    }
045
046    /**
047     * Creates a TransformNodesObject.
048     * Find out the impacted nodes and store their initial state.
049     * @param objects objects to fetch nodes from
050     */
051    public TransformNodesCommand(Collection<? extends OsmPrimitive> objects) {
052        this.nodes = AllNodesVisitor.getAllNodes(objects);
053        storeOldState();
054    }
055
056    /**
057     * Handling of a mouse event (e.g. dragging event).
058     * @param currentEN the current world position of the mouse
059     */
060    public abstract void handleEvent(EastNorth currentEN);
061
062    /**
063     * Implementation for the nodes transformation.
064     * No parameters are given here, you should handle the user input in handleEvent()
065     * and store it internally.
066     */
067    protected abstract void transformNodes();
068
069    /**
070     * Finally apply the transformation of the nodes.
071     * This is called when the user is happy with the current state of the command
072     * and its effects.
073     */
074    @Override
075    public boolean executeCommand() {
076        transformNodes();
077        flagNodesAsModified();
078        return true;
079    }
080
081    /**
082     * Flag all nodes as modified.
083     */
084    public void flagNodesAsModified() {
085        for (Node n : nodes) {
086            n.setModified(true);
087        }
088    }
089
090    /**
091     * Restore the state of the nodes from the backup.
092     */
093    @Override
094    public void undoCommand() {
095        for (Node n : nodes) {
096            OldNodeState os = oldStates.get(n);
097            n.setCoor(os.getLatLon());
098            n.setModified(os.isModified());
099        }
100    }
101
102    @Override
103    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
104    }
105
106    @Override
107    public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
108        return nodes;
109    }
110
111    @Override
112    public String getDescriptionText() {
113        return trn("Transform {0} node", "Transform {0} nodes", nodes.size(), nodes.size());
114    }
115
116    @Override
117    public Icon getDescriptionIcon() {
118        return ImageProvider.get("data", "node");
119    }
120
121    /**
122     * Get the nodes with the current transformation applied.
123     * @return nodes with the current transformation applied
124     */
125    public Collection<Node> getTransformedNodes() {
126        return nodes;
127    }
128
129    /**
130     * Get the center of the nodes under modification.
131     * It's just the barycenter.
132     * @return center east/north of the nodes under modification
133     * @see org.openstreetmap.josm.tools.Geometry#getCentroid(java.util.List)
134     */
135    public EastNorth getNodesCenter() {
136        EastNorth sum = new EastNorth(0, 0);
137
138        for (Node n : nodes) {
139            EastNorth en = n.getEastNorth();
140            sum = sum.add(en.east(), en.north());
141        }
142        return new EastNorth(sum.east()/this.nodes.size(), sum.north()/this.nodes.size());
143
144    }
145
146    @Override
147    public int hashCode() {
148        return Objects.hash(super.hashCode(), nodes, oldStates);
149    }
150
151    @Override
152    public boolean equals(Object obj) {
153        if (this == obj) return true;
154        if (obj == null || getClass() != obj.getClass()) return false;
155        if (!super.equals(obj)) return false;
156        TransformNodesCommand that = (TransformNodesCommand) obj;
157        return Objects.equals(nodes, that.nodes) &&
158                Objects.equals(oldStates, that.oldStates);
159    }
160}