source: josm/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java@ 23

Last change on this file since 23 was 23, checked in by imi, 19 years ago
  • added commands to support undo later
  • added Edit-Layer concept
  • painting of deleted objects
File size: 11.3 KB
Line 
1package org.openstreetmap.josm.actions.mapmode;
2
3import java.awt.event.KeyEvent;
4import java.awt.event.MouseEvent;
5import java.util.ArrayList;
6import java.util.Collection;
7import java.util.LinkedList;
8
9import javax.swing.JOptionPane;
10
11import org.openstreetmap.josm.Main;
12import org.openstreetmap.josm.command.CombineAndDeleteCommand;
13import org.openstreetmap.josm.command.DeleteCommand;
14import org.openstreetmap.josm.command.CombineAndDeleteCommand.LineSegmentCombineEntry;
15import org.openstreetmap.josm.data.osm.LineSegment;
16import org.openstreetmap.josm.data.osm.Node;
17import org.openstreetmap.josm.data.osm.OsmPrimitive;
18import org.openstreetmap.josm.data.osm.Track;
19import org.openstreetmap.josm.gui.MapFrame;
20
21/**
22 * An action that enables the user to delete nodes and other objects.
23 *
24 * The user can click on an object, which get deleted if possible. When Ctrl is
25 * pressed when releasing the button, the objects and all its references are
26 * deleted as well. The exact definition of "all its references" are in
27 * @see #deleteWithReferences(OsmPrimitive)
28 *
29 * Pressing Alt will select the track instead of a line segment, as usual.
30 *
31 * If the user presses Ctrl, no combining is possible. Otherwise, DeleteAction
32 * tries to combine the referencing objects as follows:
33 *
34 * If a node is part of exactly two line segments from a track, the two line
35 * segments are combined into one. The first line segment spans now to the end
36 * of the second and the second line segment gets deleted. This is checked for
37 * every track.
38 *
39 * If a node is the end of the ending line segment of one track and the start of
40 * exactly one other tracks start segment, the tracks are combined into one track,
41 * deleting the second track and keeping the first one. The ending line segment
42 * of the fist track is combined with the starting line segment of the second
43 * track.
44 *
45 * Combining is only possible, if both objects that should be combined have no
46 * key with a different property value. The remaining keys are merged together.
47 *
48 * If a node is part of an area with more than 3 nodes, the node is removed from
49 * the area and the area has now one fewer node.
50 *
51 * If combining fails, the node has still references and the user did not hold
52 * Ctrl down, the deleting fails, the action informs the user and nothing is
53 * deleted.
54 *
55 *
56 * If the user enters the mapmode and any object is selected, all selected
57 * objects get deleted. Combining applies to the selected objects.
58 *
59 * @author imi
60 */
61public class DeleteAction extends MapMode {
62
63 /**
64 * Construct a new DeleteAction. Mnemonic is the delete - key.
65 * @param mapFrame The frame this action belongs to.
66 */
67 public DeleteAction(MapFrame mapFrame) {
68 super("Delete", "delete", "Delete nodes, streets or areas.", KeyEvent.VK_DELETE, mapFrame);
69 }
70
71 @Override
72 public void registerListener() {
73 super.registerListener();
74 mv.addMouseListener(this);
75 }
76
77 @Override
78 public void unregisterListener() {
79 super.unregisterListener();
80 mv.removeMouseListener(this);
81 }
82
83 /**
84 * If user clicked with the left button, delete the nearest object.
85 * position.
86 */
87 @Override
88 public void mouseClicked(MouseEvent e) {
89 if (e.getButton() != MouseEvent.BUTTON1)
90 return;
91
92 OsmPrimitive sel = mv.getNearest(e.getPoint(), (e.getModifiersEx() & MouseEvent.ALT_DOWN_MASK) != 0);
93 if (sel == null)
94 return;
95
96 if ((e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0)
97 deleteWithReferences(sel);
98 else
99 delete(sel);
100
101 mv.repaint();
102 }
103
104 /**
105 * Delete the primitive and everything it references or beeing directly
106 * referenced by, except of nodes which are deleted only if passed
107 * directly or become unreferenced while deleting other objects.
108 *
109 * Nothing is combined as in @see #delete(OsmPrimitive).
110 *
111 * Example (A is a track of line segment a and b. z is a node):
112 *
113 * A
114 * B x z
115 * -----*--------+-----
116 * | a b
117 * |C
118 * |
119 * *y
120 *
121 * If you delete C, C and y (since now unreferenced) gets deleted.
122 * If you delete A, then A, a, b and z (since now unreferenced) gets deleted.
123 * If you delete y, then y and C gets deleted.
124 * TODO If you delete x, then a,B,C and x gets deleted. A now consist of b only.
125 * If you delete a or b, then A, a, b and z gets deleted.
126 *
127 * @param osm The object to delete.
128 */
129 private void deleteWithReferences(OsmPrimitive osm) {
130 // collect all tracks, areas and pending line segments that should be deleted
131 ArrayList<Track> tracksToDelete = new ArrayList<Track>();
132 ArrayList<LineSegment> lineSegmentsToDelete = new ArrayList<LineSegment>();
133
134 if (osm instanceof Node) {
135 // delete any track and line segment the node is in.
136 for (Track t : Main.main.ds.tracks)
137 for (LineSegment ls : t.segments)
138 if (ls.start == osm || ls.end == osm)
139 tracksToDelete.add(t);
140 for (LineSegment ls : Main.main.ds.pendingLineSegments)
141 if (ls.start == osm || ls.end == osm)
142 lineSegmentsToDelete.add(ls);
143
144 } else if (osm instanceof LineSegment) {
145 LineSegment lineSegment = (LineSegment)osm;
146 lineSegmentsToDelete.add(lineSegment);
147 for (Track t : Main.main.ds.tracks)
148 for (LineSegment ls : t.segments)
149 if (lineSegment == ls)
150 tracksToDelete.add(t);
151 } else if (osm instanceof Track) {
152 tracksToDelete.add((Track)osm);
153 }
154 // collect all nodes, that could be unreferenced after deletion
155 ArrayList<Node> checkUnreferencing = new ArrayList<Node>();
156 for (Track t : tracksToDelete) {
157 for (LineSegment ls : t.segments) {
158 checkUnreferencing.add(ls.start);
159 checkUnreferencing.add(ls.end);
160 }
161 }
162 for (LineSegment ls : lineSegmentsToDelete) {
163 checkUnreferencing.add(ls.start);
164 checkUnreferencing.add(ls.end);
165 }
166
167 Collection<OsmPrimitive> deleteData = new LinkedList<OsmPrimitive>();
168 deleteData.addAll(tracksToDelete);
169 deleteData.addAll(lineSegmentsToDelete);
170 // removing all unreferenced nodes
171 for (Node n : checkUnreferencing)
172 if (!isReferenced(n))
173 deleteData.add(n);
174 // now, all references are killed. Delete the node (if it was a node)
175 if (osm instanceof Node)
176 deleteData.add(osm);
177
178 mv.editLayer().add(new DeleteCommand(deleteData));
179 }
180
181 /**
182 * Try to delete the given primitive. If the primitive is a node and
183 * used somewhere, try to combine the references to make the node unused.
184 * If this fails, inform the user and do not delete.
185 *
186 * @param osm The object to delete.
187 */
188 private void delete(OsmPrimitive osm) {
189 if (osm instanceof Node && isReferenced((Node)osm)) {
190 combineAndDelete((Node)osm);
191 return;
192 }
193 Collection<OsmPrimitive> c = new LinkedList<OsmPrimitive>();
194 c.add(osm);
195 mv.editLayer().add(new DeleteCommand(c));
196 }
197
198
199 /**
200 * Return <code>true</code>, if the node is used by anything in the map.
201 * @param n The node to check.
202 * @return Whether the node is used by a track or area.
203 */
204 private boolean isReferenced(Node n) {
205 for (Track t : Main.main.ds.tracks)
206 for (LineSegment ls : t.segments)
207 if (ls.start == n || ls.end == n)
208 return true;
209 for (LineSegment ls : Main.main.ds.pendingLineSegments)
210 if (ls.start == n || ls.end == n)
211 return true;
212 // TODO areas
213 return false;
214 }
215
216 /**
217 * Try to combine all objects when deleting the node n. If combining is not
218 * possible, return an error string why. Otherwise, combine it and return
219 * <code>null</code>.
220 *
221 * @param n The node that is going to be deleted.
222 * @return <code>null</code> if combining suceded or an error string if there
223 * are problems combining the node.
224 */
225 private void combineAndDelete(Node n) {
226 // first, check for pending line segments
227 for (LineSegment ls : Main.main.ds.pendingLineSegments)
228 if (n == ls.start || n == ls.end) {
229 JOptionPane.showMessageDialog(Main.main, "Node used by a line segment which is not part of any track. Remove this first.");
230 return;
231 }
232
233 // These line segments must be combined within the track combining
234 ArrayList<LineSegment> pendingLineSegmentsForTrack = new ArrayList<LineSegment>();
235
236 // try to combine line segments
237
238 // These line segments are combinable. The inner arraylist has always
239 // two elements. The keys maps to the track, the line segments are in.
240 Collection<LineSegmentCombineEntry> lineSegments = new ArrayList<LineSegmentCombineEntry>();
241
242 for (Track t : Main.main.ds.tracks) {
243 ArrayList<LineSegment> current = new ArrayList<LineSegment>();
244 for (LineSegment ls : t.segments)
245 if (ls.start == n || ls.end == n)
246 current.add(ls);
247 if (!current.isEmpty()) {
248 if (current.size() > 2) {
249 JOptionPane.showMessageDialog(Main.main, "Node used by more than two line segments.");
250 return;
251 }
252 if (current.size() == 1 &&
253 (current.get(0) == t.getStartingSegment() || current.get(0) == t.getEndingSegment()))
254 pendingLineSegmentsForTrack.add(current.get(0));
255 else if (current.get(0).end != current.get(1).start &&
256 current.get(1).end != current.get(0).start) {
257 JOptionPane.showMessageDialog(Main.main, "Node used by line segments that points together.");
258 return;
259 } else if (!current.get(0).keyPropertiesMergable(current.get(1))) {
260 JOptionPane.showMessageDialog(Main.main, "Node used by line segments with different properties.");
261 return;
262 } else {
263 LineSegmentCombineEntry e = new LineSegmentCombineEntry();
264 e.first = current.get(0);
265 e.second = current.get(1);
266 e.track = t;
267 lineSegments.add(e);
268 }
269 }
270 }
271
272 // try to combine tracks
273 ArrayList<Track> tracks = new ArrayList<Track>();
274 for (Track t : Main.main.ds.tracks)
275 if (t.getStartingNode() == n || t.getEndingNode() == n)
276 tracks.add(t);
277 if (!tracks.isEmpty()) {
278 if (tracks.size() > 2) {
279 JOptionPane.showMessageDialog(Main.main, "Node used by more than two tracks.");
280 return;
281 }
282 if (tracks.size() == 1) {
283 JOptionPane.showMessageDialog(Main.main, "Node used by a track.");
284 return;
285 }
286 Track t1 = tracks.get(0);
287 Track t2 = tracks.get(1);
288 if (t1.getStartingNode() != t2.getEndingNode() &&
289 t2.getStartingNode() != t1.getEndingNode()) {
290 if (t1.getStartingNode() == t2.getStartingNode() ||
291 t1.getEndingNode() == t2.getEndingNode()) {
292 JOptionPane.showMessageDialog(Main.main, "Node used by tracks that point together.");
293 return;
294 }
295 JOptionPane.showMessageDialog(Main.main, "Node used by tracks that cannot be combined.");
296 return;
297 }
298 if (!t1.keyPropertiesMergable(t2)) {
299 JOptionPane.showMessageDialog(Main.main, "Node used by tracks with different properties.");
300 return;
301 }
302 }
303
304 // try to match the pending line segments
305 if (pendingLineSegmentsForTrack.size() == 2) {
306 LineSegment l1 = pendingLineSegmentsForTrack.get(0);
307 LineSegment l2 = pendingLineSegmentsForTrack.get(1);
308 if (l1.start == l2.start || l1.end == l2.end) {
309 JOptionPane.showMessageDialog(Main.main, "Node used by line segments that points together.");
310 return;
311 }
312 if (l1.start == l2.end || l2.start == l1.end)
313 pendingLineSegmentsForTrack.clear(); // resolved.
314 }
315
316 // still pending line segments?
317 if (!pendingLineSegmentsForTrack.isEmpty()) {
318 JOptionPane.showMessageDialog(Main.main, "Node used by tracks that cannot be combined.");
319 return;
320 }
321
322 // Ok, we can combine. Do it.
323 Track firstTrack = tracks.isEmpty() ? null : tracks.get(0);
324 Track secondTrack = tracks.isEmpty() ? null : tracks.get(1);
325 mv.editLayer().add(new CombineAndDeleteCommand(n, lineSegments, firstTrack, secondTrack));
326 }
327}
Note: See TracBrowser for help on using the repository browser.