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

Last change on this file since 6084 was 6084, checked in by bastiK, 11 years ago

see #8902 - add missing @Override annotations (patch by shinigami)

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