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

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