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

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