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

Last change on this file since 1622 was 1499, checked in by stoecker, 15 years ago

close #2302 - patch by jttt - optimizations and encapsulation

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