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

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

add Ant target to run PMD (only few rules for now), fix violations

  • Property svn:eol-style set to native
File size: 16.0 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 private 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 public Cursor cursor() {
78 return c;
79 }
80 }
81
82 private static class DeleteParameters {
83 private DeleteMode mode;
84 private Node nearestNode;
85 private WaySegment nearestSegment;
86 }
87
88 /**
89 * Construct a new DeleteAction. Mnemonic is the delete - key.
90 * @since 11713
91 */
92 public DeleteAction() {
93 super(tr("Delete Mode"),
94 "delete",
95 tr("Delete nodes or ways."),
96 Shortcut.registerShortcut("mapmode:delete", tr("Mode: {0}", tr("Delete")),
97 KeyEvent.VK_DELETE, Shortcut.CTRL),
98 ImageProvider.getCursor("normal", "delete"));
99 }
100
101 @Override
102 public void enterMode() {
103 super.enterMode();
104 if (!isEnabled())
105 return;
106
107 drawTargetHighlight = Main.pref.getBoolean("draw.target-highlight", true);
108
109 Main.map.mapView.addMouseListener(this);
110 Main.map.mapView.addMouseMotionListener(this);
111 // This is required to update the cursors when ctrl/shift/alt is pressed
112 Main.map.keyDetector.addModifierListener(this);
113 }
114
115 @Override
116 public void exitMode() {
117 super.exitMode();
118 Main.map.mapView.removeMouseListener(this);
119 Main.map.mapView.removeMouseMotionListener(this);
120 Main.map.keyDetector.removeModifierListener(this);
121 removeHighlighting();
122 }
123
124 @Override
125 public void actionPerformed(ActionEvent e) {
126 super.actionPerformed(e);
127 doActionPerformed(e);
128 }
129
130 /**
131 * Invoked when the action occurs.
132 * @param e Action event
133 */
134 public static void doActionPerformed(ActionEvent e) {
135 MainLayerManager lm = Main.getLayerManager();
136 OsmDataLayer editLayer = lm.getEditLayer();
137 if (editLayer == null) {
138 return;
139 }
140
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(editLayer, lm.getEditDataSet().getSelected());
147 } else {
148 c = DeleteCommand.delete(editLayer, lm.getEditDataSet().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 //FIXME: This should not be required, DeleteCommand should update the selection, otherwise undo/redo won't work.
154 lm.getEditDataSet().setSelected();
155 }
156 }
157
158 @Override
159 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
168 public void mouseMoved(MouseEvent e) {
169 oldEvent = e;
170 giveUserFeedback(e);
171 }
172
173 /**
174 * removes any highlighting that may have been set beforehand.
175 */
176 private void removeHighlighting() {
177 highlightHelper.clear();
178 DataSet ds = getLayerManager().getEditDataSet();
179 if (ds != null) {
180 ds.clearHighlightedWaySegments();
181 }
182 }
183
184 /**
185 * handles everything related to highlighting primitives and way
186 * segments for the given pointer position (via MouseEvent) and modifiers.
187 * @param e current mouse event
188 * @param modifiers mouse modifiers, not necessarly taken from the given mouse event
189 */
190 private void addHighlighting(MouseEvent e, int modifiers) {
191 if (!drawTargetHighlight)
192 return;
193
194 Set<OsmPrimitive> newHighlights = new HashSet<>();
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 safely do the following
207 for (OsmPrimitive osm : delCmd.getParticipatingPrimitives()) {
208 newHighlights.add(osm);
209 }
210 }
211 repaintIfRequired(newHighlights, null);
212 }
213 }
214
215 private void repaintIfRequired(Set<OsmPrimitive> newHighlights, WaySegment newHighlightedWaySegment) {
216 boolean needsRepaint = false;
217 OsmDataLayer editLayer = getLayerManager().getEditLayer();
218
219 if (newHighlightedWaySegment == null && oldHighlightedWaySegment != null) {
220 if (editLayer != null) {
221 editLayer.data.clearHighlightedWaySegments();
222 needsRepaint = true;
223 }
224 oldHighlightedWaySegment = null;
225 } else if (newHighlightedWaySegment != null && !newHighlightedWaySegment.equals(oldHighlightedWaySegment)) {
226 if (editLayer != null) {
227 editLayer.data.setHighlightedWaySegments(Collections.singleton(newHighlightedWaySegment));
228 needsRepaint = true;
229 }
230 oldHighlightedWaySegment = newHighlightedWaySegment;
231 }
232 needsRepaint |= highlightHelper.highlightOnly(newHighlights);
233 if (needsRepaint && editLayer != null) {
234 editLayer.invalidate();
235 }
236 }
237
238 /**
239 * This function handles all work related to updating the cursor and highlights
240 *
241 * @param e current mouse event
242 * @param modifiers mouse modifiers, not necessarly taken from the given mouse event
243 */
244 private void updateCursor(MouseEvent e, int modifiers) {
245 if (!Main.isDisplayingMapView())
246 return;
247 if (!Main.map.mapView.isActiveLayerVisible() || e == null)
248 return;
249
250 DeleteParameters parameters = getDeleteParameters(e, modifiers);
251 Main.map.mapView.setNewCursor(parameters.mode.cursor(), this);
252 }
253
254 /**
255 * Gives the user feedback for the action he/she is about to do. Currently
256 * calls the cursor and target highlighting routines. Allows for modifiers
257 * not taken from the given mouse event.
258 *
259 * Normally the mouse event also contains the modifiers. However, when the
260 * mouse is not moved and only modifier keys are pressed, no mouse event
261 * occurs. We can use AWTEvent to catch those but still lack a proper
262 * mouseevent. Instead we copy the previous event and only update the modifiers.
263 * @param e mouse event
264 * @param modifiers mouse 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 * @param e mouse event
276 */
277 private void giveUserFeedback(MouseEvent e) {
278 giveUserFeedback(e, e.getModifiers());
279 }
280
281 /**
282 * If user clicked with the left button, delete the nearest object.
283 */
284 @Override
285 public void mouseReleased(MouseEvent e) {
286 if (e.getButton() != MouseEvent.BUTTON1)
287 return;
288 if (!Main.map.mapView.isActiveLayerVisible())
289 return;
290
291 // request focus in order to enable the expected keyboard shortcuts
292 //
293 Main.map.mapView.requestFocus();
294
295 Command c = buildDeleteCommands(e, e.getModifiers(), false);
296 if (c != null) {
297 Main.main.undoRedo.add(c);
298 }
299
300 getLayerManager().getEditDataSet().setSelected();
301 giveUserFeedback(e);
302 }
303
304 @Override
305 public String getModeHelpText() {
306 // CHECKSTYLE.OFF: LineLength
307 return tr("Click to delete. Shift: delete way segment. Alt: do not delete unused nodes when deleting a way. Ctrl: delete referring objects.");
308 // CHECKSTYLE.ON: LineLength
309 }
310
311 @Override
312 public boolean layerIsSupported(Layer l) {
313 return l instanceof OsmDataLayer;
314 }
315
316 @Override
317 protected void updateEnabledState() {
318 setEnabled(Main.isDisplayingMapView() && Main.map.mapView.isActiveLayerDrawable());
319 }
320
321 /**
322 * Deletes the relation in the context of the given layer.
323 *
324 * @param layer the layer in whose context the relation is deleted. Must not be null.
325 * @param toDelete the relation to be deleted. Must not be null.
326 * @throws IllegalArgumentException if layer is null
327 * @throws IllegalArgumentException if toDelete is null
328 */
329 public static void deleteRelation(OsmDataLayer layer, Relation toDelete) {
330 deleteRelations(layer, Collections.singleton(toDelete));
331 }
332
333 /**
334 * Deletes the relations in the context of the given layer.
335 *
336 * @param layer the layer in whose context the relations are deleted. Must not be null.
337 * @param toDelete the relations to be deleted. Must not be null.
338 * @throws IllegalArgumentException if layer is null
339 * @throws IllegalArgumentException if toDelete is null
340 */
341 public static void deleteRelations(OsmDataLayer layer, Collection<Relation> toDelete) {
342 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
343 CheckParameterUtil.ensureParameterNotNull(toDelete, "toDelete");
344
345 final Command cmd = DeleteCommand.delete(layer, toDelete);
346 if (cmd != null) {
347 // cmd can be null if the user cancels dialogs DialogCommand displays
348 Main.main.undoRedo.add(cmd);
349 for (Relation relation : toDelete) {
350 if (layer.data.getSelectedRelations().contains(relation)) {
351 layer.data.toggleSelected(relation);
352 }
353 RelationDialogManager.getRelationDialogManager().close(layer, relation);
354 }
355 }
356 }
357
358 private DeleteParameters getDeleteParameters(MouseEvent e, int modifiers) {
359 updateKeyModifiers(modifiers);
360
361 DeleteParameters result = new DeleteParameters();
362
363 result.nearestNode = Main.map.mapView.getNearestNode(e.getPoint(), OsmPrimitive::isSelectable);
364 if (result.nearestNode == null) {
365 result.nearestSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive::isSelectable);
366 if (result.nearestSegment != null) {
367 if (shift) {
368 result.mode = DeleteMode.segment;
369 } else if (ctrl) {
370 result.mode = DeleteMode.way_with_references;
371 } else {
372 result.mode = alt ? DeleteMode.way : DeleteMode.way_with_nodes;
373 }
374 } else {
375 result.mode = DeleteMode.none;
376 }
377 } else if (ctrl) {
378 result.mode = DeleteMode.node_with_references;
379 } else {
380 result.mode = DeleteMode.node;
381 }
382
383 return result;
384 }
385
386 /**
387 * This function takes any mouse event argument and builds the list of elements
388 * that should be deleted but does not actually delete them.
389 * @param e MouseEvent from which modifiers and position are taken
390 * @param modifiers For explanation, see {@link #updateCursor}
391 * @param silent Set to true if the user should not be bugged with additional dialogs
392 * @return delete command
393 */
394 private Command buildDeleteCommands(MouseEvent e, int modifiers, boolean silent) {
395 DeleteParameters parameters = getDeleteParameters(e, modifiers);
396 OsmDataLayer editLayer = getLayerManager().getEditLayer();
397 switch (parameters.mode) {
398 case node:
399 return DeleteCommand.delete(editLayer, Collections.singleton(parameters.nearestNode), false, silent);
400 case node_with_references:
401 return DeleteCommand.deleteWithReferences(editLayer, Collections.singleton(parameters.nearestNode), silent);
402 case segment:
403 return DeleteCommand.deleteWaySegment(editLayer, parameters.nearestSegment);
404 case way:
405 return DeleteCommand.delete(editLayer, Collections.singleton(parameters.nearestSegment.way), false, silent);
406 case way_with_nodes:
407 return DeleteCommand.delete(editLayer, Collections.singleton(parameters.nearestSegment.way), true, silent);
408 case way_with_references:
409 return DeleteCommand.deleteWithReferences(editLayer, Collections.singleton(parameters.nearestSegment.way), true);
410 default:
411 return null;
412 }
413 }
414
415 /**
416 * This is required to update the cursors when ctrl/shift/alt is pressed
417 */
418 @Override
419 public void modifiersChanged(int modifiers) {
420 if (oldEvent == null)
421 return;
422 // We don't have a mouse event, so we pass the old mouse event but the new modifiers.
423 giveUserFeedback(oldEvent, modifiers);
424 }
425}
Note: See TracBrowser for help on using the repository browser.