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

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

sonar - squid:S2325 - "private" methods that don't access instance data should be "static"

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