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

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

removed dependencies to Main.ds, removed Main.ds
removed AddVisitor, NameVisitor, DeleteVisitor - unnecessary double dispatching for these simple cases

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