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

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

see #15182 - deprecate Main.map and Main.isDisplayingMapView(). Replacements: gui.MainApplication.getMap() / gui.MainApplication.isDisplayingMapView()

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