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

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

see #11924 - fix last warnings about extended modifiers

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