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

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

applied #3389: patch by podolsir: "delete confirmation" message dialogue
cleanup of ExtendedDialog in DeleteCommand

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