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.Map;
009import java.util.NoSuchElementException;
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 final Collection<Node> nodes;
031
032    /**
033     * List of all old states of the nodes.
034     */
035    protected final 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. Must neither be null nor empty. Items must belong to a data set
050     * @throws NullPointerException if objects is null or contain null item
051     * @throws NoSuchElementException if objects is empty
052     */
053    public TransformNodesCommand(Collection<? extends OsmPrimitive> objects) {
054        super(objects.iterator().next().getDataSet());
055        this.nodes = AllNodesVisitor.getAllNodes(objects);
056        storeOldState();
057    }
058
059    /**
060     * Handling of a mouse event (e.g. dragging event).
061     * @param currentEN the current world position of the mouse
062     */
063    public abstract void handleEvent(EastNorth currentEN);
064
065    /**
066     * Implementation for the nodes transformation.
067     * No parameters are given here, you should handle the user input in handleEvent()
068     * and store it internally.
069     */
070    protected abstract void transformNodes();
071
072    /**
073     * Finally apply the transformation of the nodes.
074     * This is called when the user is happy with the current state of the command
075     * and its effects.
076     */
077    @Override
078    public boolean executeCommand() {
079        transformNodes();
080        flagNodesAsModified();
081        return true;
082    }
083
084    /**
085     * Flag all nodes as modified.
086     */
087    public void flagNodesAsModified() {
088        for (Node n : nodes) {
089            n.setModified(true);
090        }
091    }
092
093    /**
094     * Restore the state of the nodes from the backup.
095     */
096    @Override
097    public void undoCommand() {
098        for (Node n : nodes) {
099            OldNodeState os = oldStates.get(n);
100            n.setCoor(os.getLatLon());
101            n.setModified(os.isModified());
102        }
103    }
104
105    @Override
106    public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) {
107    }
108
109    @Override
110    public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
111        return nodes;
112    }
113
114    @Override
115    public String getDescriptionText() {
116        return trn("Transform {0} node", "Transform {0} nodes", nodes.size(), nodes.size());
117    }
118
119    @Override
120    public Icon getDescriptionIcon() {
121        return ImageProvider.get("data", "node");
122    }
123
124    /**
125     * Get the nodes with the current transformation applied.
126     * @return nodes with the current transformation applied
127     */
128    public Collection<Node> getTransformedNodes() {
129        return nodes;
130    }
131
132    /**
133     * Get the center of the nodes under modification.
134     * It's just the barycenter.
135     * @return center east/north of the nodes under modification
136     * @see org.openstreetmap.josm.tools.Geometry#getCentroid(java.util.List)
137     */
138    public EastNorth getNodesCenter() {
139        EastNorth sum = new EastNorth(0, 0);
140
141        for (Node n : nodes) {
142            EastNorth en = n.getEastNorth();
143            sum = sum.add(en.east(), en.north());
144        }
145        return new EastNorth(sum.east()/this.nodes.size(), sum.north()/this.nodes.size());
146
147    }
148
149    @Override
150    public int hashCode() {
151        return Objects.hash(super.hashCode(), nodes, oldStates);
152    }
153
154    @Override
155    public boolean equals(Object obj) {
156        if (this == obj) return true;
157        if (obj == null || getClass() != obj.getClass()) return false;
158        if (!super.equals(obj)) return false;
159        TransformNodesCommand that = (TransformNodesCommand) obj;
160        return Objects.equals(nodes, that.nodes) &&
161                Objects.equals(oldStates, that.oldStates);
162    }
163}