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

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

fixed #3261: Use the "name:$CURRENT_LOCALE" name in the JOSM UI instead of "name" when it exists
new: new checkbox in LAF preferences for enabling/disabling localized names for primitives

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