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

Last change on this file since 12636 was 12636, checked in by Don-vip, 7 years ago

see #15182 - deprecate Main.getLayerManager(). Replacement: gui.MainApplication.getLayerManager()

  • Property svn:eol-style set to native
File size: 13.0 KB
RevLine 
[8378]1// License: GPL. For details, see LICENSE file.
[21]2package org.openstreetmap.josm.command;
3
[4458]4import java.awt.GridBagLayout;
[3034]5import java.util.ArrayList;
[22]6import java.util.Collection;
[146]7import java.util.HashMap;
[3034]8import java.util.LinkedHashMap;
[146]9import java.util.Map;
[86]10import java.util.Map.Entry;
[9371]11import java.util.Objects;
[21]12
[4458]13import javax.swing.JOptionPane;
14import javax.swing.JPanel;
[94]15
[630]16import org.openstreetmap.josm.Main;
[6173]17import org.openstreetmap.josm.data.coor.EastNorth;
18import org.openstreetmap.josm.data.coor.LatLon;
[10467]19import org.openstreetmap.josm.data.osm.DataSet;
[146]20import org.openstreetmap.josm.data.osm.Node;
[22]21import org.openstreetmap.josm.data.osm.OsmPrimitive;
[2284]22import org.openstreetmap.josm.data.osm.PrimitiveData;
[1523]23import org.openstreetmap.josm.data.osm.Relation;
[146]24import org.openstreetmap.josm.data.osm.Way;
[1523]25import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
[4458]26import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
[12636]27import org.openstreetmap.josm.gui.MainApplication;
[304]28import org.openstreetmap.josm.gui.layer.Layer;
29import org.openstreetmap.josm.gui.layer.OsmDataLayer;
[6901]30import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
[2844]31import org.openstreetmap.josm.tools.CheckParameterUtil;
[21]32
33/**
34 * Classes implementing Command modify a dataset in a specific way. A command is
[22]35 * one atomic action on a specific dataset, such as move or delete.
[21]36 *
[5266]37 * The command remembers the {@link OsmDataLayer} it is operating on.
[2284]38 *
[21]39 * @author imi
[10599]40 * @since 21 (creation)
41 * @since 10599 (signature)
[21]42 */
[10599]43public abstract class Command implements PseudoCommand {
[21]44
[10948]45 /** IS_OK : operation is okay */
46 public static final int IS_OK = 0;
47 /** IS_OUTSIDE : operation on element outside of download area */
48 public static final int IS_OUTSIDE = 1;
49 /** IS_INCOMPLETE: operation on incomplete target */
50 public static final int IS_INCOMPLETE = 2;
51
[1750]52 private static final class CloneVisitor extends AbstractVisitor {
[7005]53 public final Map<OsmPrimitive, PrimitiveData> orig = new LinkedHashMap<>();
[304]54
[6084]55 @Override
[1750]56 public void visit(Node n) {
[2284]57 orig.put(n, n.save());
[1750]58 }
[8510]59
[6084]60 @Override
[1750]61 public void visit(Way w) {
[2284]62 orig.put(w, w.save());
[1750]63 }
[8510]64
[6084]65 @Override
[1750]66 public void visit(Relation e) {
[2284]67 orig.put(e, e.save());
[1750]68 }
69 }
[304]70
[6173]71 /**
72 * Small helper for holding the interesting part of the old data state of the objects.
73 */
74 public static class OldNodeState {
75
[10248]76 private final LatLon latLon;
[8285]77 private final EastNorth eastNorth; // cached EastNorth to be used for applying exact displacement
78 private final boolean modified;
[6173]79
80 /**
81 * Constructs a new {@code OldNodeState} for the given node.
82 * @param node The node whose state has to be remembered
83 */
[8510]84 public OldNodeState(Node node) {
[10248]85 latLon = node.getCoor();
[6173]86 eastNorth = node.getEastNorth();
87 modified = node.isModified();
88 }
[8285]89
90 /**
91 * Returns old lat/lon.
92 * @return old lat/lon
93 * @see Node#getCoor()
[10248]94 * @since 10248
[8285]95 */
[10248]96 public final LatLon getLatLon() {
97 return latLon;
[8285]98 }
99
100 /**
101 * Returns old east/north.
102 * @return old east/north
103 * @see Node#getEastNorth()
104 */
105 public final EastNorth getEastNorth() {
106 return eastNorth;
107 }
108
109 /**
110 * Returns old modified state.
111 * @return old modified state
112 * @see Node #isModified()
113 */
114 public final boolean isModified() {
115 return modified;
116 }
[8447]117
118 @Override
119 public int hashCode() {
[10248]120 return Objects.hash(latLon, eastNorth, modified);
[8447]121 }
122
123 @Override
124 public boolean equals(Object obj) {
[9371]125 if (this == obj) return true;
126 if (obj == null || getClass() != obj.getClass()) return false;
127 OldNodeState that = (OldNodeState) obj;
128 return modified == that.modified &&
[10248]129 Objects.equals(latLon, that.latLon) &&
[9371]130 Objects.equals(eastNorth, that.eastNorth);
[8447]131 }
[6173]132 }
133
[1750]134 /** the map of OsmPrimitives in the original state to OsmPrimitives in cloned state */
[7005]135 private Map<OsmPrimitive, PrimitiveData> cloneMap = new HashMap<>();
[304]136
[1750]137 /** the layer which this command is applied to */
[5759]138 private final OsmDataLayer layer;
[22]139
[11240]140 /** the dataset which this command is applied to */
141 private final DataSet data;
142
[5759]143 /**
144 * Creates a new command in the context of the current edit layer, if any
145 */
[1750]146 public Command() {
[12636]147 this.layer = MainApplication.getLayerManager().getEditLayer();
[11240]148 this.data = layer != null ? layer.data : null;
[1750]149 }
[1856]150
[1750]151 /**
[1856]152 * Creates a new command in the context of a specific data layer
[2284]153 *
[2308]154 * @param layer the data layer. Must not be null.
[8291]155 * @throws IllegalArgumentException if layer is null
[1856]156 */
[8291]157 public Command(OsmDataLayer layer) {
[2844]158 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
[1856]159 this.layer = layer;
[11240]160 this.data = layer.data;
[1856]161 }
162
163 /**
[11240]164 * Creates a new command in the context of a specific data set, without data layer
165 *
166 * @param data the data set. Must not be null.
167 * @throws IllegalArgumentException if data is null
168 * @since 11240
169 */
170 public Command(DataSet data) {
171 CheckParameterUtil.ensureParameterNotNull(data, "data");
172 this.layer = null;
173 this.data = data;
174 }
175
176 /**
[1750]177 * Executes the command on the dataset. This implementation will remember all
178 * primitives returned by fillModifiedData for restoring them on undo.
[10452]179 * <p>
180 * The layer should be invalidated after execution so that it can be re-painted.
[5759]181 * @return true
[10452]182 * @see #invalidateAffectedLayers()
[1750]183 */
184 public boolean executeCommand() {
185 CloneVisitor visitor = new CloneVisitor();
[7005]186 Collection<OsmPrimitive> all = new ArrayList<>();
[1750]187 fillModifiedData(all, all, all);
188 for (OsmPrimitive osm : all) {
[6009]189 osm.accept(visitor);
[1750]190 }
191 cloneMap = visitor.orig;
192 return true;
193 }
[86]194
[1750]195 /**
196 * Undoes the command.
197 * It can be assumed that all objects are in the same state they were before.
198 * It can also be assumed that executeCommand was called exactly once before.
199 *
200 * This implementation undoes all objects stored by a former call to executeCommand.
201 */
202 public void undoCommand() {
[2284]203 for (Entry<OsmPrimitive, PrimitiveData> e : cloneMap.entrySet()) {
[2683]204 OsmPrimitive primitive = e.getKey();
205 if (primitive.getDataSet() != null) {
206 e.getKey().load(e.getValue());
207 }
[1750]208 }
209 }
[304]210
[1750]211 /**
212 * Called when a layer has been removed to have the command remove itself from
213 * any buffer if it is not longer applicable to the dataset (e.g. it was part of
214 * the removed layer)
[2284]215 *
[10663]216 * @param oldLayer the old layer that was removed
217 * @return true if this command is invalid after that layer is removed.
[1750]218 */
219 public boolean invalidBecauselayerRemoved(Layer oldLayer) {
220 return layer == oldLayer;
221 }
[304]222
[630]223 /**
224 * Lets other commands access the original version
225 * of the object. Usually for undoing.
[5759]226 * @param osm The requested OSM object
227 * @return The original version of the requested object, if any
[630]228 */
[2284]229 public PrimitiveData getOrig(OsmPrimitive osm) {
[5759]230 return cloneMap.get(osm);
[630]231 }
[94]232
[1750]233 /**
234 * Replies the layer this command is (or was) applied to.
[8931]235 * @return the layer this command is (or was) applied to
[1750]236 */
[6881]237 protected OsmDataLayer getLayer() {
[1750]238 return layer;
239 }
[630]240
[1750]241 /**
[10467]242 * Gets the data set this command affects.
243 * @return The data set. May be <code>null</code> if no layer was set and no edit layer was found.
244 * @since 10467
245 */
246 public DataSet getAffectedDataSet() {
[11240]247 return data;
[10467]248 }
249
250 /**
[1750]251 * Fill in the changed data this command operates on.
252 * Add to the lists, don't clear them.
253 *
254 * @param modified The modified primitives
255 * @param deleted The deleted primitives
256 * @param added The added primitives
257 */
[6883]258 public abstract void fillModifiedData(Collection<OsmPrimitive> modified,
[1750]259 Collection<OsmPrimitive> deleted,
260 Collection<OsmPrimitive> added);
261
[3262]262 /**
263 * Return the primitives that take part in this command.
[8945]264 * The collection is computed during execution.
[3262]265 */
[8931]266 @Override
267 public Collection<? extends OsmPrimitive> getParticipatingPrimitives() {
[3262]268 return cloneMap.keySet();
269 }
[1750]270
[3262]271 /**
[4458]272 * Check whether user is about to operate on data outside of the download area.
[10948]273 *
274 * @param primitives the primitives to operate on
275 * @param ignore {@code null} or a primitive to be ignored
276 * @return true, if operating on outlying primitives is OK; false, otherwise
277 */
[10970]278 public static int checkOutlyingOrIncompleteOperation(
[10948]279 Collection<? extends OsmPrimitive> primitives,
280 Collection<? extends OsmPrimitive> ignore) {
281 int res = 0;
282 for (OsmPrimitive osm : primitives) {
283 if (osm.isIncomplete()) {
284 res |= IS_INCOMPLETE;
285 } else if (osm.isOutsideDownloadArea()
286 && (ignore == null || !ignore.contains(osm))) {
287 res |= IS_OUTSIDE;
288 }
289 }
290 return res;
291 }
292
293 /**
294 * Check whether user is about to operate on data outside of the download area.
[4458]295 * Request confirmation if he is.
296 *
[4461]297 * @param operation the operation name which is used for setting some preferences
298 * @param dialogTitle the title of the dialog being displayed
299 * @param outsideDialogMessage the message text to be displayed when data is outside of the download area
300 * @param incompleteDialogMessage the message text to be displayed when data is incomplete
[4458]301 * @param primitives the primitives to operate on
[4461]302 * @param ignore {@code null} or a primitive to be ignored
303 * @return true, if operating on outlying primitives is OK; false, otherwise
[4458]304 */
305 public static boolean checkAndConfirmOutlyingOperation(String operation,
306 String dialogTitle, String outsideDialogMessage, String incompleteDialogMessage,
[6639]307 Collection<? extends OsmPrimitive> primitives,
[5060]308 Collection<? extends OsmPrimitive> ignore) {
[10970]309 int checkRes = checkOutlyingOrIncompleteOperation(primitives, ignore);
[10948]310 if ((checkRes & IS_OUTSIDE) != 0) {
[4458]311 JPanel msg = new JPanel(new GridBagLayout());
[6901]312 msg.add(new JMultilineLabel("<html>" + outsideDialogMessage + "</html>"));
[4458]313 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
314 operation + "_outside_nodes",
315 Main.parent,
316 msg,
317 dialogTitle,
318 JOptionPane.YES_NO_OPTION,
319 JOptionPane.QUESTION_MESSAGE,
320 JOptionPane.YES_OPTION);
[8510]321 if (!answer)
[4458]322 return false;
323 }
[10948]324 if ((checkRes & IS_INCOMPLETE) != 0) {
[4458]325 JPanel msg = new JPanel(new GridBagLayout());
[6901]326 msg.add(new JMultilineLabel("<html>" + incompleteDialogMessage + "</html>"));
[4458]327 boolean answer = ConditionalOptionPaneUtil.showConfirmationDialog(
328 operation + "_incomplete",
329 Main.parent,
330 msg,
331 dialogTitle,
332 JOptionPane.YES_NO_OPTION,
333 JOptionPane.QUESTION_MESSAGE,
334 JOptionPane.YES_OPTION);
[8510]335 if (!answer)
[4458]336 return false;
337 }
338 return true;
339 }
340
[12348]341 /**
342 * Ensures that all primitives that are participating in this command belong to the affected data set.
343 *
344 * Commands may use this in their update methods to check the consitency of the primitives they operate on.
345 * @throws AssertionError if no {@link DataSet} is set or if any primitive does not belong to that dataset.
346 */
347 protected void ensurePrimitivesAreInDataset() {
348 for (OsmPrimitive primitive : this.getParticipatingPrimitives()) {
349 if (primitive.getDataSet() != this.getAffectedDataSet()) {
350 throw new AssertionError("Primitive is of wrong data set for this command: " + primitive);
351 }
352 }
353 }
354
[8447]355 @Override
356 public int hashCode() {
[11243]357 return Objects.hash(cloneMap, layer, data);
[8447]358 }
359
360 @Override
361 public boolean equals(Object obj) {
[9371]362 if (this == obj) return true;
363 if (obj == null || getClass() != obj.getClass()) return false;
364 Command command = (Command) obj;
365 return Objects.equals(cloneMap, command.cloneMap) &&
[11240]366 Objects.equals(layer, command.layer) &&
367 Objects.equals(data, command.data);
[8447]368 }
[10452]369
370 /**
371 * Invalidate all layers that were affected by this command.
372 * @see Layer#invalidate()
373 */
374 public void invalidateAffectedLayers() {
375 OsmDataLayer layer = getLayer();
376 if (layer != null) {
377 layer.invalidate();
378 }
379 }
[21]380}
Note: See TracBrowser for help on using the repository browser.