1 | package org.openstreetmap.josm.actions.mapmode;
|
---|
2 |
|
---|
3 | import java.awt.event.ActionEvent;
|
---|
4 | import java.awt.event.KeyEvent;
|
---|
5 | import java.awt.event.MouseEvent;
|
---|
6 | import java.util.ArrayList;
|
---|
7 | import java.util.Collection;
|
---|
8 | import java.util.HashSet;
|
---|
9 | import java.util.Iterator;
|
---|
10 | import java.util.LinkedList;
|
---|
11 |
|
---|
12 | import javax.swing.JOptionPane;
|
---|
13 |
|
---|
14 | import org.openstreetmap.josm.Main;
|
---|
15 | import org.openstreetmap.josm.command.CombineAndDeleteCommand;
|
---|
16 | import org.openstreetmap.josm.command.DeleteCommand;
|
---|
17 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
18 | import org.openstreetmap.josm.data.osm.LineSegment;
|
---|
19 | import org.openstreetmap.josm.data.osm.Node;
|
---|
20 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
21 | import org.openstreetmap.josm.data.osm.Track;
|
---|
22 | import 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 | */
|
---|
57 | public 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 | }
|
---|