source: josm/trunk/src/org/openstreetmap/josm/command/Command.java@ 6302

Last change on this file since 6302 was 6173, checked in by Don-vip, 11 years ago

fix #8987 - immutable coordinates (patch by shinigami)

  • Property svn:eol-style set to native
File size: 9.0 KB
Line 
1//License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.command;
3
4import java.awt.GridBagLayout;
5import java.awt.geom.Area;
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.HashMap;
9import java.util.LinkedHashMap;
10import java.util.Map;
11import java.util.Map.Entry;
12
13import javax.swing.JLabel;
14import javax.swing.JOptionPane;
15import javax.swing.JPanel;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.data.coor.EastNorth;
19import org.openstreetmap.josm.data.coor.LatLon;
20import org.openstreetmap.josm.data.osm.Node;
21import org.openstreetmap.josm.data.osm.OsmPrimitive;
22import org.openstreetmap.josm.data.osm.PrimitiveData;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.Way;
25import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
26import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
27import org.openstreetmap.josm.gui.layer.Layer;
28import org.openstreetmap.josm.gui.layer.OsmDataLayer;
29import org.openstreetmap.josm.tools.CheckParameterUtil;
30
31/**
32 * Classes implementing Command modify a dataset in a specific way. A command is
33 * one atomic action on a specific dataset, such as move or delete.
34 *
35 * The command remembers the {@link OsmDataLayer} it is operating on.
36 *
37 * @author imi
38 */
39abstract public class Command extends PseudoCommand {
40
41 private static final class CloneVisitor extends AbstractVisitor {
42 public final Map<OsmPrimitive, PrimitiveData> orig = new LinkedHashMap<OsmPrimitive, PrimitiveData>();
43
44 @Override
45 public void visit(Node n) {
46 orig.put(n, n.save());
47 }
48 @Override
49 public void visit(Way w) {
50 orig.put(w, w.save());
51 }
52 @Override
53 public void visit(Relation e) {
54 orig.put(e, e.save());
55 }
56 }
57
58 /**
59 * Small helper for holding the interesting part of the old data state of the objects.
60 */
61 public static class OldNodeState {
62
63 final LatLon latlon;
64 final EastNorth eastNorth; // cached EastNorth to be used for applying exact displacement
65 final boolean modified;
66
67 /**
68 * Constructs a new {@code OldNodeState} for the given node.
69 * @param node The node whose state has to be remembered
70 */
71 public OldNodeState(Node node){
72 latlon = node.getCoor();
73 eastNorth = node.getEastNorth();
74 modified = node.isModified();
75 }
76 }
77
78 /** the map of OsmPrimitives in the original state to OsmPrimitives in cloned state */
79 private Map<OsmPrimitive, PrimitiveData> cloneMap = new HashMap<OsmPrimitive, PrimitiveData>();
80
81 /** the layer which this command is applied to */
82 private final OsmDataLayer layer;
83
84 /**
85 * Creates a new command in the context of the current edit layer, if any
86 */
87 public Command() {
88 this.layer = Main.map != null && Main.map.mapView != null ? Main.map.mapView.getEditLayer() : null;
89 }
90
91 /**
92 * Creates a new command in the context of a specific data layer
93 *
94 * @param layer the data layer. Must not be null.
95 * @throws IllegalArgumentException thrown if layer is null
96 */
97 public Command(OsmDataLayer layer) throws IllegalArgumentException {
98 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
99 this.layer = layer;
100 }
101
102 /**
103 * Executes the command on the dataset. This implementation will remember all
104 * primitives returned by fillModifiedData for restoring them on undo.
105 * @return true
106 */
107 public boolean executeCommand() {
108 CloneVisitor visitor = new CloneVisitor();
109 Collection<OsmPrimitive> all = new ArrayList<OsmPrimitive>();
110 fillModifiedData(all, all, all);
111 for (OsmPrimitive osm : all) {
112 osm.accept(visitor);
113 }
114 cloneMap = visitor.orig;
115 return true;
116 }
117
118 /**
119 * Undoes the command.
120 * It can be assumed that all objects are in the same state they were before.
121 * It can also be assumed that executeCommand was called exactly once before.
122 *
123 * This implementation undoes all objects stored by a former call to executeCommand.
124 */
125 public void undoCommand() {
126 for (Entry<OsmPrimitive, PrimitiveData> e : cloneMap.entrySet()) {
127 OsmPrimitive primitive = e.getKey();
128 if (primitive.getDataSet() != null) {
129 e.getKey().load(e.getValue());
130 }
131 }
132 }
133
134 /**
135 * Called when a layer has been removed to have the command remove itself from
136 * any buffer if it is not longer applicable to the dataset (e.g. it was part of
137 * the removed layer)
138 *
139 * @param oldLayer the old layer
140 * @return true if this command
141 */
142 public boolean invalidBecauselayerRemoved(Layer oldLayer) {
143 if (!(oldLayer instanceof OsmDataLayer))
144 return false;
145 return layer == oldLayer;
146 }
147
148 /**
149 * Lets other commands access the original version
150 * of the object. Usually for undoing.
151 * @param osm The requested OSM object
152 * @return The original version of the requested object, if any
153 */
154 public PrimitiveData getOrig(OsmPrimitive osm) {
155 return cloneMap.get(osm);
156 }
157
158 /**
159 * Replies the layer this command is (or was) applied to.
160 *
161 */
162 protected OsmDataLayer getLayer() {
163 return layer;
164 }
165
166 /**
167 * Fill in the changed data this command operates on.
168 * Add to the lists, don't clear them.
169 *
170 * @param modified The modified primitives
171 * @param deleted The deleted primitives
172 * @param added The added primitives
173 */
174 abstract public void fillModifiedData(Collection<OsmPrimitive> modified,
175 Collection<OsmPrimitive> deleted,
176 Collection<OsmPrimitive> added);
177
178 /**
179 * Return the primitives that take part in this command.
180 */
181 @Override public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
182 return cloneMap.keySet();
183 }
184
185 /**
186 * Check whether user is about to operate on data outside of the download area.
187 * Request confirmation if he is.
188 *
189 * @param operation the operation name which is used for setting some preferences
190 * @param dialogTitle the title of the dialog being displayed
191 * @param outsideDialogMessage the message text to be displayed when data is outside of the download area
192 * @param incompleteDialogMessage the message text to be displayed when data is incomplete
193 * @param area the area used to determine whether data is outlying
194 * @param primitives the primitives to operate on
195 * @param ignore {@code null} or a primitive to be ignored
196 * @return true, if operating on outlying primitives is OK; false, otherwise
197 */
198 public static boolean checkAndConfirmOutlyingOperation(String operation,
199 String dialogTitle, String outsideDialogMessage, String incompleteDialogMessage,
200 Area area, Collection<? extends OsmPrimitive> primitives,
201 Collection<? extends OsmPrimitive> ignore) {
202 boolean outside = false;
203 boolean incomplete = false;
204 for (OsmPrimitive osm : primitives) {
205 if (osm.isIncomplete()) {
206 incomplete = true;
207 } else if (area != null && isOutlying(osm, area)
208 && (ignore == null || !ignore.contains(osm))) {
209 outside = true;
210 }
211 }
212 if (outside) {
213 JPanel msg = new JPanel(new GridBagLayout());
214 msg.add(new JLabel("<html>" + outsideDialogMessage + "</html>"));
215 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
216 operation + "_outside_nodes",
217 Main.parent,
218 msg,
219 dialogTitle,
220 JOptionPane.YES_NO_OPTION,
221 JOptionPane.QUESTION_MESSAGE,
222 JOptionPane.YES_OPTION);
223 if(!answer)
224 return false;
225 }
226 if (incomplete) {
227 JPanel msg = new JPanel(new GridBagLayout());
228 msg.add(new JLabel("<html>" + incompleteDialogMessage + "</html>"));
229 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
230 operation + "_incomplete",
231 Main.parent,
232 msg,
233 dialogTitle,
234 JOptionPane.YES_NO_OPTION,
235 JOptionPane.QUESTION_MESSAGE,
236 JOptionPane.YES_OPTION);
237 if(!answer)
238 return false;
239 }
240 return true;
241 }
242
243 private static boolean isOutlying(OsmPrimitive osm, Area area) {
244 if (osm instanceof Node && !osm.isNewOrUndeleted()) {
245 return !((Node) osm).getCoor().isIn(area);
246 } else if (osm instanceof Way) {
247 for (Node n : ((Way) osm).getNodes()) {
248 if (isOutlying(n, area)) {
249 return true;
250 }
251 }
252 return false;
253 }
254 return false;
255 }
256}
Note: See TracBrowser for help on using the repository browser.