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

Last change on this file since 5909 was 5909, checked in by stoecker, 11 years ago

fix all remaining javadoc warnings

  • Property svn:eol-style set to native
File size: 15.3 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions.mapmode;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.AWTEvent;
7import java.awt.Cursor;
8import java.awt.Toolkit;
9import java.awt.event.AWTEventListener;
10import java.awt.event.ActionEvent;
11import java.awt.event.InputEvent;
12import java.awt.event.KeyEvent;
13import java.awt.event.MouseEvent;
14import java.util.Collections;
15import java.util.HashSet;
16import java.util.Set;
17
18import org.openstreetmap.josm.Main;
19import org.openstreetmap.josm.command.Command;
20import org.openstreetmap.josm.command.DeleteCommand;
21import org.openstreetmap.josm.data.osm.DataSet;
22import org.openstreetmap.josm.data.osm.Node;
23import org.openstreetmap.josm.data.osm.OsmPrimitive;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.WaySegment;
26import org.openstreetmap.josm.gui.MapFrame;
27import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager;
28import org.openstreetmap.josm.gui.layer.Layer;
29import org.openstreetmap.josm.gui.layer.OsmDataLayer;
30import org.openstreetmap.josm.tools.CheckParameterUtil;
31import org.openstreetmap.josm.tools.ImageProvider;
32import org.openstreetmap.josm.tools.Shortcut;
33
34/**
35 * A map mode that enables the user to delete nodes and other objects.
36 *
37 * The user can click on an object, which gets deleted if possible. When Ctrl is
38 * pressed when releasing the button, the objects and all its references are
39 * deleted.
40 *
41 * If the user did not press Ctrl and the object has any references, the user
42 * is informed and nothing is deleted.
43 *
44 * If the user enters the mapmode and any object is selected, all selected
45 * objects are deleted, if possible.
46 *
47 * @author imi
48 */
49public class DeleteAction extends MapMode implements AWTEventListener {
50 // Cache previous mouse event (needed when only the modifier keys are
51 // pressed but the mouse isn't moved)
52 private MouseEvent oldEvent = null;
53
54 /**
55 * elements that have been highlighted in the previous iteration. Used
56 * to remove the highlight from them again as otherwise the whole data
57 * set would have to be checked.
58 */
59 private Set<OsmPrimitive> oldHighlights = new HashSet<OsmPrimitive>();
60 private WaySegment oldHighlightedWaySegment = null;
61
62 private boolean drawTargetHighlight;
63
64 private enum DeleteMode {
65 none("delete"),
66 segment("delete_segment"),
67 node("delete_node"),
68 node_with_references("delete_node"),
69 way("delete_way_only"),
70 way_with_references("delete_way_normal"),
71 way_with_nodes("delete_way_node_only");
72
73 private final Cursor c;
74
75 private DeleteMode(String cursorName) {
76 c = ImageProvider.getCursor("normal", cursorName);
77 }
78
79 public Cursor cursor() {
80 return c;
81 }
82 }
83
84 private static class DeleteParameters {
85 DeleteMode mode;
86 Node nearestNode;
87 WaySegment nearestSegment;
88 }
89
90 /**
91 * Construct a new DeleteAction. Mnemonic is the delete - key.
92 * @param mapFrame The frame this action belongs to.
93 */
94 public DeleteAction(MapFrame mapFrame) {
95 super(tr("Delete Mode"),
96 "delete",
97 tr("Delete nodes or ways."),
98 Shortcut.registerShortcut("mapmode:delete", tr("Mode: {0}",tr("Delete")),
99 KeyEvent.VK_DELETE, Shortcut.CTRL),
100 mapFrame,
101 ImageProvider.getCursor("normal", "delete"));
102 }
103
104 @Override public void enterMode() {
105 super.enterMode();
106 if (!isEnabled())
107 return;
108
109 drawTargetHighlight = Main.pref.getBoolean("draw.target-highlight", true);
110
111 Main.map.mapView.addMouseListener(this);
112 Main.map.mapView.addMouseMotionListener(this);
113 // This is required to update the cursors when ctrl/shift/alt is pressed
114 try {
115 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
116 } catch (SecurityException ex) {
117 System.out.println(ex);
118 }
119 }
120
121 @Override public void exitMode() {
122 super.exitMode();
123 Main.map.mapView.removeMouseListener(this);
124 Main.map.mapView.removeMouseMotionListener(this);
125 try {
126 Toolkit.getDefaultToolkit().removeAWTEventListener(this);
127 } catch (SecurityException ex) {
128 System.out.println(ex);
129 }
130 removeHighlighting();
131 }
132
133 @Override public void actionPerformed(ActionEvent e) {
134 super.actionPerformed(e);
135 doActionPerformed(e);
136 }
137
138 static public void doActionPerformed(ActionEvent e) {
139 if(!Main.map.mapView.isActiveLayerDrawable())
140 return;
141 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
142 boolean alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0;
143
144 Command c;
145 if (ctrl) {
146 c = DeleteCommand.deleteWithReferences(getEditLayer(),getCurrentDataSet().getSelected());
147 } else {
148 c = DeleteCommand.delete(getEditLayer(),getCurrentDataSet().getSelected(), !alt /* also delete nodes in way */);
149 }
150 // if c is null, an error occurred or the user aborted. Don't do anything in that case.
151 if (c != null) {
152 Main.main.undoRedo.add(c);
153 getCurrentDataSet().setSelected();
154 Main.map.repaint();
155 }
156 }
157
158 @Override public void mouseDragged(MouseEvent e) {
159 mouseMoved(e);
160 }
161
162 /**
163 * Listen to mouse move to be able to update the cursor (and highlights)
164 * @param e The mouse event that has been captured
165 */
166 @Override public void mouseMoved(MouseEvent e) {
167 oldEvent = e;
168 giveUserFeedback(e);
169 }
170
171 /**
172 * removes any highlighting that may have been set beforehand.
173 */
174 private void removeHighlighting() {
175 for(OsmPrimitive prim : oldHighlights) {
176 prim.setHighlighted(false);
177 }
178 oldHighlights = new HashSet<OsmPrimitive>();
179 DataSet ds = getCurrentDataSet();
180 if(ds != null) {
181 ds.clearHighlightedWaySegments();
182 }
183 }
184
185 /**
186 * handles everything related to highlighting primitives and way
187 * segments for the given pointer position (via MouseEvent) and
188 * modifiers.
189 * @param e
190 * @param modifiers
191 */
192 private void addHighlighting(MouseEvent e, int modifiers) {
193 if(!drawTargetHighlight)
194 return;
195
196 Set<OsmPrimitive> newHighlights = new HashSet<OsmPrimitive>();
197 DeleteParameters parameters = getDeleteParameters(e, modifiers);
198
199 if(parameters.mode == DeleteMode.segment) {
200 // deleting segments is the only action not working on OsmPrimitives
201 // so we have to handle them separately.
202 repaintIfRequired(newHighlights, parameters.nearestSegment);
203 } else {
204 // don't call buildDeleteCommands for DeleteMode.segment because it doesn't support
205 // silent operation and SplitWayAction will show dialogs. A lot.
206 Command delCmd = buildDeleteCommands(e, modifiers, true);
207 if(delCmd != null) {
208 // all other cases delete OsmPrimitives directly, so we can
209 // safely do the following
210 for(OsmPrimitive osm : delCmd.getParticipatingPrimitives()) {
211 newHighlights.add(osm);
212 }
213 }
214 repaintIfRequired(newHighlights, null);
215 }
216 }
217
218 private void repaintIfRequired(Set<OsmPrimitive> newHighlights, WaySegment newHighlightedWaySegment) {
219 boolean needsRepaint = false;
220 DataSet ds = getCurrentDataSet();
221
222 if(newHighlightedWaySegment == null && oldHighlightedWaySegment != null) {
223 if(ds != null) {
224 ds.clearHighlightedWaySegments();
225 needsRepaint = true;
226 }
227 oldHighlightedWaySegment = null;
228 } else if(newHighlightedWaySegment != null && !newHighlightedWaySegment.equals(oldHighlightedWaySegment)) {
229 if(ds != null) {
230 ds.setHighlightedWaySegments(Collections.singleton(newHighlightedWaySegment));
231 needsRepaint = true;
232 }
233 oldHighlightedWaySegment = newHighlightedWaySegment;
234 }
235
236 for(OsmPrimitive x : newHighlights) {
237 if(oldHighlights.contains(x)) {
238 continue;
239 }
240 needsRepaint = true;
241 x.setHighlighted(true);
242 }
243 oldHighlights.removeAll(newHighlights);
244 for(OsmPrimitive x : oldHighlights) {
245 x.setHighlighted(false);
246 needsRepaint = true;
247 }
248 oldHighlights = newHighlights;
249 if(needsRepaint) {
250 Main.map.mapView.repaint();
251 }
252 }
253
254 /**
255 * This function handles all work related to updating the cursor and
256 * highlights
257 *
258 * @param e
259 * @param modifiers
260 */
261 private void updateCursor(MouseEvent e, int modifiers) {
262 if (!Main.isDisplayingMapView())
263 return;
264 if(!Main.map.mapView.isActiveLayerVisible() || e == null)
265 return;
266
267 DeleteParameters parameters = getDeleteParameters(e, modifiers);
268 Main.map.mapView.setNewCursor(parameters.mode.cursor(), this);
269 }
270 /**
271 * Gives the user feedback for the action he/she is about to do. Currently
272 * calls the cursor and target highlighting routines. Allows for modifiers
273 * not taken from the given mouse event.
274 *
275 * Normally the mouse event also contains the modifiers. However, when the
276 * mouse is not moved and only modifier keys are pressed, no mouse event
277 * occurs. We can use AWTEvent to catch those but still lack a proper
278 * mouseevent. Instead we copy the previous event and only update the
279 * modifiers.
280 */
281 private void giveUserFeedback(MouseEvent e, int modifiers) {
282 updateCursor(e, modifiers);
283 addHighlighting(e, modifiers);
284 }
285
286 /**
287 * Gives the user feedback for the action he/she is about to do. Currently
288 * calls the cursor and target highlighting routines. Extracts modifiers
289 * from mouse event.
290 */
291 private void giveUserFeedback(MouseEvent e) {
292 giveUserFeedback(e, e.getModifiers());
293 }
294
295 /**
296 * If user clicked with the left button, delete the nearest object.
297 * position.
298 */
299 @Override public void mouseReleased(MouseEvent e) {
300 if (e.getButton() != MouseEvent.BUTTON1)
301 return;
302 if(!Main.map.mapView.isActiveLayerVisible())
303 return;
304
305 // request focus in order to enable the expected keyboard shortcuts
306 //
307 Main.map.mapView.requestFocus();
308
309 Command c = buildDeleteCommands(e, e.getModifiers(), false);
310 if (c != null) {
311 Main.main.undoRedo.add(c);
312 }
313
314 getCurrentDataSet().setSelected();
315 giveUserFeedback(e);
316 }
317
318 @Override public String getModeHelpText() {
319 return tr("Click to delete. Shift: delete way segment. Alt: do not delete unused nodes when deleting a way. Ctrl: delete referring objects.");
320 }
321
322 @Override public boolean layerIsSupported(Layer l) {
323 return l instanceof OsmDataLayer;
324 }
325
326 @Override
327 protected void updateEnabledState() {
328 setEnabled(Main.isDisplayingMapView() && Main.map.mapView.isActiveLayerDrawable());
329 }
330
331 /**
332 * Deletes the relation in the context of the given layer.
333 *
334 * @param layer the layer in whose context the relation is deleted. Must not be null.
335 * @param toDelete the relation to be deleted. Must not be null.
336 * @exception IllegalArgumentException thrown if layer is null
337 * @exception IllegalArgumentException thrown if toDelete is nul
338 */
339 public static void deleteRelation(OsmDataLayer layer, Relation toDelete) {
340 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
341 CheckParameterUtil.ensureParameterNotNull(toDelete, "toDelete");
342
343 Command cmd = DeleteCommand.delete(layer, Collections.singleton(toDelete));
344 if (cmd != null) {
345 // cmd can be null if the user cancels dialogs DialogCommand displays
346 Main.main.undoRedo.add(cmd);
347 if (getCurrentDataSet().getSelectedRelations().contains(toDelete)) {
348 getCurrentDataSet().toggleSelected(toDelete);
349 }
350 RelationDialogManager.getRelationDialogManager().close(layer, toDelete);
351 }
352 }
353
354 private DeleteParameters getDeleteParameters(MouseEvent e, int modifiers) {
355 updateKeyModifiers(modifiers);
356
357 DeleteParameters result = new DeleteParameters();
358
359 result.nearestNode = Main.map.mapView.getNearestNode(e.getPoint(), OsmPrimitive.isSelectablePredicate);
360 if (result.nearestNode == null) {
361 result.nearestSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
362 if (result.nearestSegment != null) {
363 if (shift) {
364 result.mode = DeleteMode.segment;
365 } else if (ctrl) {
366 result.mode = DeleteMode.way_with_references;
367 } else {
368 result.mode = alt?DeleteMode.way:DeleteMode.way_with_nodes;
369 }
370 } else {
371 result.mode = DeleteMode.none;
372 }
373 } else if (ctrl) {
374 result.mode = DeleteMode.node_with_references;
375 } else {
376 result.mode = DeleteMode.node;
377 }
378
379 return result;
380 }
381
382 /**
383 * This function takes any mouse event argument and builds the list of elements
384 * that should be deleted but does not actually delete them.
385 * @param e MouseEvent from which modifiers and position are taken
386 * @param modifiers For explanation, see {@link #updateCursor}
387 * @param silent Set to true if the user should not be bugged with additional
388 * dialogs
389 * @return delete command
390 */
391 private Command buildDeleteCommands(MouseEvent e, int modifiers, boolean silent) {
392 DeleteParameters parameters = getDeleteParameters(e, modifiers);
393 switch (parameters.mode) {
394 case node:
395 return DeleteCommand.delete(getEditLayer(),Collections.singleton(parameters.nearestNode), false, silent);
396 case node_with_references:
397 return DeleteCommand.deleteWithReferences(getEditLayer(),Collections.singleton(parameters.nearestNode), silent);
398 case segment:
399 return DeleteCommand.deleteWaySegment(getEditLayer(), parameters.nearestSegment);
400 case way:
401 return DeleteCommand.delete(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), false, silent);
402 case way_with_nodes:
403 return DeleteCommand.delete(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), true, silent);
404 case way_with_references:
405 return DeleteCommand.deleteWithReferences(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), true);
406 default:
407 return null;
408 }
409 }
410
411 /**
412 * This is required to update the cursors when ctrl/shift/alt is pressed
413 */
414 public void eventDispatched(AWTEvent e) {
415 if(oldEvent == null)
416 return;
417 // We don't have a mouse event, so we pass the old mouse event but the
418 // new modifiers.
419 giveUserFeedback(oldEvent, ((InputEvent) e).getModifiers());
420 }
421}
Note: See TracBrowser for help on using the repository browser.