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

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

fix #13037 - Small fixes for unit tests (patch by michael2402) - gsoc-core

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