source: josm/trunk/src/org/openstreetmap/josm/command/DeleteCommand.java@ 2323

Last change on this file since 2323 was 2323, checked in by Gubaer, 15 years ago

Added explicit help topics
See also current list of help topics with links to source files and to help pages

  • Property svn:eol-style set to native
File size: 19.4 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.command;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.GridBagLayout;
8import java.awt.geom.Area;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.HashSet;
13import java.util.Iterator;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Set;
17
18import javax.swing.JLabel;
19import javax.swing.JOptionPane;
20import javax.swing.JPanel;
21import javax.swing.tree.DefaultMutableTreeNode;
22import javax.swing.tree.MutableTreeNode;
23
24import org.openstreetmap.josm.Main;
25import org.openstreetmap.josm.data.osm.BackreferencedDataSet;
26import org.openstreetmap.josm.data.osm.Node;
27import org.openstreetmap.josm.data.osm.OsmPrimitive;
28import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
29import org.openstreetmap.josm.data.osm.Relation;
30import org.openstreetmap.josm.data.osm.Way;
31import org.openstreetmap.josm.data.osm.WaySegment;
32import org.openstreetmap.josm.data.osm.BackreferencedDataSet.RelationToChildReference;
33import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
34import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
35import org.openstreetmap.josm.gui.DefaultNameFormatter;
36import org.openstreetmap.josm.gui.actionsupport.DeleteFromRelationConfirmationDialog;
37import org.openstreetmap.josm.gui.layer.OsmDataLayer;
38import org.openstreetmap.josm.tools.ImageProvider;
39
40/**
41 * A command to delete a number of primitives from the dataset.
42 *
43 */
44public class DeleteCommand extends Command {
45 /**
46 * The primitives that get deleted.
47 */
48 private final Collection<? extends OsmPrimitive> toDelete;
49
50 /**
51 * Constructor. Deletes a collection of primitives in the current edit layer.
52 *
53 * @param data the primitives to delete
54 */
55 public DeleteCommand(Collection<? extends OsmPrimitive> data) {
56 if (data == null) {
57 data = Collections.emptyList();
58 }
59 this.toDelete = data;
60 }
61
62 /**
63 * Constructor. Deletes a single primitive in the current edit layer.
64 *
65 * @param data the primitive to delete. Must not be null.
66 * @throws IllegalArgumentException thrown if data is null
67 */
68 public DeleteCommand(OsmPrimitive data) throws IllegalArgumentException {
69 if (data == null)
70 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "data"));
71 this.toDelete = Collections.singleton(data);
72 }
73
74 /**
75 * Constructor for a single data item. Use the collection constructor to delete multiple
76 * objects.
77 *
78 * @param layer the layer context for deleting this primitive. Must not be null.
79 * @param data the primitive to delete. Must not be null.
80 * @throws IllegalArgumentException thrown if data is null
81 * @throws IllegalArgumentException thrown if layer is null
82 */
83 public DeleteCommand(OsmDataLayer layer, OsmPrimitive data) throws IllegalArgumentException {
84 super(layer);
85 if (data == null)
86 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "data"));
87 this.toDelete = Collections.singleton(data);
88 }
89
90 /**
91 * Constructor for a collection of data to be deleted in the context of
92 * a specific layer
93 *
94 * @param layer the layer context for deleting these primitives. Must not be null.
95 * @param data the primitives to delete
96 * @throws IllegalArgumentException thrown if layer is null
97 */
98 public DeleteCommand(OsmDataLayer layer, Collection<? extends OsmPrimitive> data) throws IllegalArgumentException{
99 super(layer);
100 if (data == null) {
101 data = Collections.emptyList();
102 }
103 this.toDelete = data;
104 }
105
106 protected void removeNewNodesFromDeletedWay(Way w) {
107 // #2707: ways to be deleted can include new nodes (with node.id == 0).
108 // Remove them from the way before the way is deleted. Otherwise the
109 // deleted way is saved (or sent to the API) with a dangling reference to a node
110 // Example:
111 // <node id='2' action='delete' visible='true' version='1' ... />
112 // <node id='1' action='delete' visible='true' version='1' ... />
113 // <!-- missing node with id -1 because new deleted nodes are not persisted -->
114 // <way id='3' action='delete' visible='true' version='1'>
115 // <nd ref='1' />
116 // <nd ref='-1' /> <!-- here's the problem -->
117 // <nd ref='2' />
118 // </way>
119 if (w.isNew())
120 return; // process existing ways only
121 List<Node> nodesToKeep = new ArrayList<Node>();
122 // lookup new nodes which have been added to the set of deleted
123 // nodes ...
124 Iterator<Node> it = nodesToKeep.iterator();
125 while(it.hasNext()) {
126 Node n = it.next();
127 if (n.isNew()) {
128 it.remove();
129 }
130 }
131 w.setNodes(nodesToKeep);
132 }
133
134 @Override
135 public boolean executeCommand() {
136 super.executeCommand();
137 for (OsmPrimitive osm : toDelete) {
138 osm.setDeleted(true);
139 if (osm instanceof Way) {
140 removeNewNodesFromDeletedWay((Way)osm);
141 }
142 }
143 return true;
144 }
145
146 @Override
147 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
148 Collection<OsmPrimitive> added) {
149 deleted.addAll(toDelete);
150 }
151
152 @Override
153 public MutableTreeNode description() {
154 if (toDelete.size() == 1) {
155 OsmPrimitive primitive = toDelete.iterator().next();
156 String msg = "";
157 switch(OsmPrimitiveType.from(primitive)) {
158 case NODE: msg = "Delete node {0}"; break;
159 case WAY: msg = "Delete way {0}"; break;
160 case RELATION:msg = "Delete relation {0}"; break;
161 }
162
163 return new DefaultMutableTreeNode(new JLabel(tr(msg, primitive.getDisplayName(DefaultNameFormatter.getInstance())),
164 ImageProvider.get(OsmPrimitiveType.from(primitive)), JLabel.HORIZONTAL));
165 }
166
167 Set<OsmPrimitiveType> typesToDelete = new HashSet<OsmPrimitiveType>();
168 for (OsmPrimitive osm : toDelete) {
169 typesToDelete.add(OsmPrimitiveType.from(osm));
170 }
171 String msg = "";
172 String apiname = "object";
173 if (typesToDelete.size() > 1) {
174 msg = trn("Delete {0} object", "Delete {0} objects", toDelete.size(), toDelete.size());
175 } else {
176 OsmPrimitiveType t = typesToDelete.iterator().next();
177 apiname = t.getAPIName();
178 switch(t) {
179 case NODE: msg = trn("Delete {0} node", "Delete {0} nodes", toDelete.size(), toDelete.size()); break;
180 case WAY: msg = trn("Delete {0} way", "Delete {0} ways", toDelete.size(), toDelete.size()); break;
181 case RELATION: msg = trn("Delete {0} relation", "Delete {0} relations", toDelete.size(), toDelete.size()); break;
182 }
183 }
184 DefaultMutableTreeNode root = new DefaultMutableTreeNode(
185 new JLabel(msg, ImageProvider.get("data", apiname), JLabel.HORIZONTAL)
186 );
187 for (OsmPrimitive osm : toDelete) {
188 root.add(new DefaultMutableTreeNode(new JLabel(
189 osm.getDisplayName(DefaultNameFormatter.getInstance()),
190 ImageProvider.get(OsmPrimitiveType.from(osm)), JLabel.HORIZONTAL)));
191 }
192 return root;
193 }
194
195 /**
196 * Delete the primitives and everything they reference.
197 *
198 * If a node is deleted, the node and all ways and relations the node is part of are deleted as
199 * well.
200 *
201 * If a way is deleted, all relations the way is member of are also deleted.
202 *
203 * If a way is deleted, only the way and no nodes are deleted.
204 *
205 * @param layer the {@see OsmDataLayer} in whose context primitives are deleted. Must not be null.
206 * @param selection The list of all object to be deleted.
207 * @param silent Set to true if the user should not be bugged with additional dialogs
208 * @return command A command to perform the deletions, or null of there is nothing to delete.
209 * @throws IllegalArgumentException thrown if layer is null
210 */
211 public static Command deleteWithReferences(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection, boolean silent) throws IllegalArgumentException {
212 if (layer == null)
213 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null", "layer"));
214 if (selection == null || selection.isEmpty()) return null;
215 CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data);
216 v.initialize();
217 for (OsmPrimitive osm : selection) {
218 osm.visit(v);
219 }
220 v.getData().addAll(selection);
221 if (v.getData().isEmpty())
222 return null;
223 if (!checkAndConfirmOutlyingDeletes(layer,v.getData()) && !silent)
224 return null;
225 return new DeleteCommand(layer,v.getData());
226 }
227
228 public static Command deleteWithReferences(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection) {
229 return deleteWithReferences(layer, selection, false);
230 }
231
232 public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection) {
233 return delete(layer, selection, true, false);
234 }
235
236 /**
237 * Replies the collection of nodes referred to by primitives in <code>primitivesToDelete</code> which
238 * can be deleted too. A node can be deleted if
239 * <ul>
240 * <li>it is untagged (see {@see Node#isTagged()}</li>
241 * <li>it is not referred to by other non-deleted primitives outside of <code>primitivesToDelete</code></li>
242 * <ul>
243 * @param backreferences backreference data structure
244 * @param layer the layer in whose context primitives are deleted
245 * @param primitivesToDelete the primitives to delete
246 * @return the collection of nodes referred to by primitives in <code>primitivesToDelete</code> which
247 * can be deleted too
248 */
249 protected static Collection<Node> computeNodesToDelete(BackreferencedDataSet backreferences, OsmDataLayer layer, Collection<OsmPrimitive> primitivesToDelete) {
250 Collection<Node> nodesToDelete = new HashSet<Node>();
251 //CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
252 for (Way way : OsmPrimitive.getFilteredList(primitivesToDelete, Way.class)) {
253 for (Node n : way.getNodes()) {
254 if (n.isTagged()) {
255 continue;
256 }
257 //v.initialize();
258 //n.visit(v);
259 Collection<OsmPrimitive> referringPrimitives = backreferences.getParents(n);
260 referringPrimitives.removeAll(primitivesToDelete);
261 int count = 0;
262 for (OsmPrimitive p : referringPrimitives) {
263 if (!p.isDeleted()) {
264 count++;
265 }
266 }
267 if (count == 0) {
268 nodesToDelete.add(n);
269 }
270 }
271 }
272 return nodesToDelete;
273 }
274
275 /**
276 * Try to delete all given primitives.
277 *
278 * If a node is used by a way, it's removed from that way. If a node or a way is used by a
279 * relation, inform the user and do not delete.
280 *
281 * If this would cause ways with less than 2 nodes to be created, delete these ways instead. If
282 * they are part of a relation, inform the user and do not delete.
283 *
284 * @param layer the {@see OsmDataLayer} in whose context the primitives are deleted
285 * @param selection the objects to delete.
286 * @param alsoDeleteNodesInWay <code>true</code> if nodes should be deleted as well
287 * @return command a command to perform the deletions, or null if there is nothing to delete.
288 */
289 public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection,
290 boolean alsoDeleteNodesInWay) {
291 return delete(layer, selection, alsoDeleteNodesInWay, false /* not silent */);
292 }
293
294 /**
295 * Try to delete all given primitives.
296 *
297 * If a node is used by a way, it's removed from that way. If a node or a way is used by a
298 * relation, inform the user and do not delete.
299 *
300 * If this would cause ways with less than 2 nodes to be created, delete these ways instead. If
301 * they are part of a relation, inform the user and do not delete.
302 *
303 * @param layer the {@see OsmDataLayer} in whose context the primitives are deleted
304 * @param selection the objects to delete.
305 * @param alsoDeleteNodesInWay <code>true</code> if nodes should be deleted as well
306 * @param silent set to true if the user should not be bugged with additional questions
307 * @return command a command to perform the deletions, or null if there is nothing to delete.
308 */
309 public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection,
310 boolean alsoDeleteNodesInWay, boolean silent) {
311 if (selection == null || selection.isEmpty())
312 return null;
313
314 BackreferencedDataSet backreferences = new BackreferencedDataSet(layer.data);
315 backreferences.build();
316
317 Set<OsmPrimitive> primitivesToDelete = new HashSet<OsmPrimitive>(selection);
318 Collection<Way> waysToBeChanged = new HashSet<Way>();
319
320 if (alsoDeleteNodesInWay) {
321 // delete untagged nodes only referenced by primitives in primitivesToDelete,
322 // too
323 Collection<Node> nodesToDelete = computeNodesToDelete(backreferences, layer, primitivesToDelete);
324 primitivesToDelete.addAll(nodesToDelete);
325 }
326
327 if (!silent && !checkAndConfirmOutlyingDeletes(layer,primitivesToDelete))
328 return null;
329
330 waysToBeChanged.addAll(OsmPrimitive.getFilteredSet(backreferences.getParents(primitivesToDelete), Way.class));
331
332 Collection<Command> cmds = new LinkedList<Command>();
333 for (Way w : waysToBeChanged) {
334 Way wnew = new Way(w);
335 wnew.removeNodes(primitivesToDelete);
336 if (wnew.getNodesCount() < 2) {
337 primitivesToDelete.add(w);
338 } else {
339 cmds.add(new ChangeCommand(w, wnew));
340 }
341 }
342
343 // get a confirmation that the objects to delete can be removed from their parent
344 // relations
345 //
346 if (!silent) {
347 Set<RelationToChildReference> references = backreferences.getRelationToChildReferences(primitivesToDelete);
348 Iterator<RelationToChildReference> it = references.iterator();
349 while(it.hasNext()) {
350 RelationToChildReference ref = it.next();
351 if (ref.getParent().isDeleted()) {
352 it.remove();
353 }
354 }
355 if (!references.isEmpty()) {
356 DeleteFromRelationConfirmationDialog dialog = DeleteFromRelationConfirmationDialog.getInstance();
357 dialog.getModel().populate(references);
358 dialog.setVisible(true);
359 if (dialog.isCanceled())
360 return null;
361 }
362 }
363
364 // remove the objects from their parent relations
365 //
366 Iterator<Relation> iterator = OsmPrimitive.getFilteredSet(backreferences.getParents(primitivesToDelete), Relation.class).iterator();
367 while (iterator.hasNext()) {
368 Relation cur = iterator.next();
369 Relation rel = new Relation(cur);
370 rel.removeMembersFor(primitivesToDelete);
371 cmds.add(new ChangeCommand(cur, rel));
372 }
373
374 // build the delete command
375 //
376 if (!primitivesToDelete.isEmpty()) {
377 cmds.add(new DeleteCommand(layer,primitivesToDelete));
378 }
379
380 return new SequenceCommand(tr("Delete"), cmds);
381 }
382
383 public static Command deleteWaySegment(OsmDataLayer layer, WaySegment ws) {
384 if (ws.way.getNodesCount() < 3)
385 return new DeleteCommand(layer, Collections.singleton(ws.way));
386
387 if (ws.way.firstNode() == ws.way.lastNode()) {
388 // If the way is circular (first and last nodes are the same),
389 // the way shouldn't be splitted
390
391 List<Node> n = new ArrayList<Node>();
392
393 n.addAll(ws.way.getNodes().subList(ws.lowerIndex + 1, ws.way.getNodesCount() - 1));
394 n.addAll(ws.way.getNodes().subList(0, ws.lowerIndex + 1));
395
396 Way wnew = new Way(ws.way);
397 wnew.setNodes(n);
398
399 return new ChangeCommand(ws.way, wnew);
400 }
401
402 List<Node> n1 = new ArrayList<Node>(), n2 = new ArrayList<Node>();
403
404 n1.addAll(ws.way.getNodes().subList(0, ws.lowerIndex + 1));
405 n2.addAll(ws.way.getNodes().subList(ws.lowerIndex + 1, ws.way.getNodesCount()));
406
407 Way wnew = new Way(ws.way);
408
409 if (n1.size() < 2) {
410 wnew.setNodes(n2);
411 return new ChangeCommand(ws.way, wnew);
412 } else if (n2.size() < 2) {
413 wnew.setNodes(n1);
414 return new ChangeCommand(ws.way, wnew);
415 } else {
416 Collection<Command> cmds = new LinkedList<Command>();
417
418 wnew.setNodes(n1);
419 cmds.add(new ChangeCommand(ws.way, wnew));
420
421 Way wnew2 = new Way();
422 wnew2.setKeys(wnew.getKeys());
423 wnew2.setNodes(n2);
424 cmds.add(new AddCommand(wnew2));
425
426 // FIXME: relation memberships are not handled
427
428 return new SequenceCommand(tr("Split way segment"), cmds);
429 }
430 }
431
432 /**
433 * Check whether user is about to delete data outside of the download area. Request confirmation
434 * if he is.
435 *
436 * @param layer the layer in whose context data is deleted
437 * @param primitivesToDelete the primitives to delete
438 * @return true, if deleting outlying primitives is OK; false, otherwise
439 */
440 private static boolean checkAndConfirmOutlyingDeletes(OsmDataLayer layer, Collection<OsmPrimitive> primitivesToDelete) {
441 Area a = layer.data.getDataSourceArea();
442 if (a != null) {
443 for (OsmPrimitive osm : primitivesToDelete) {
444 if (osm instanceof Node && !osm.isNew()) {
445 Node n = (Node) osm;
446 if (!a.contains(n.getCoor())) {
447 JPanel msg = new JPanel(new GridBagLayout());
448 msg.add(new JLabel(
449 "<html>" +
450 // leave message in one tr() as there is a grammatical
451 // connection.
452 tr("You are about to delete nodes outside of the area you have downloaded."
453 + "<br>"
454 + "This can cause problems because other objects (that you don't see) might use them."
455 + "<br>" + "Do you really want to delete?") + "</html>"));
456 return ConditionalOptionPaneUtil.showConfirmationDialog(
457 "delete_outside_nodes",
458 Main.parent,
459 msg,
460 tr("Delete confirmation"),
461 JOptionPane.YES_NO_OPTION,
462 JOptionPane.QUESTION_MESSAGE,
463 JOptionPane.YES_OPTION
464 );
465 }
466 }
467 }
468 }
469 return true;
470 }
471}
Note: See TracBrowser for help on using the repository browser.