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

Last change on this file since 12464 was 11978, checked in by Don-vip, 7 years ago

improve coverage and javadoc of enum classes for package actions

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