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

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

fix sonar squid:S2039 - Member variable visibility should be specified

  • Property svn:eol-style set to native
File size: 14.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.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
38 * 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
50 // pressed but the mouse isn't moved)
51 private MouseEvent oldEvent = null;
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 WaySegment oldHighlightedWaySegment = null;
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 private 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 if (!Main.map.mapView.isActiveLayerDrawable())
137 return;
138 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
139 boolean alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0;
140
141 Command c;
142 if (ctrl) {
143 c = DeleteCommand.deleteWithReferences(getEditLayer(),getCurrentDataSet().getSelected());
144 } else {
145 c = DeleteCommand.delete(getEditLayer(),getCurrentDataSet().getSelected(), !alt /* also delete nodes in way */);
146 }
147 // if c is null, an error occurred or the user aborted. Don't do anything in that case.
148 if (c != null) {
149 Main.main.undoRedo.add(c);
150 getCurrentDataSet().setSelected();
151 Main.map.repaint();
152 }
153 }
154
155 @Override
156 public void mouseDragged(MouseEvent e) {
157 mouseMoved(e);
158 }
159
160 /**
161 * Listen to mouse move to be able to update the cursor (and highlights)
162 * @param e The mouse event that has been captured
163 */
164 @Override
165 public void mouseMoved(MouseEvent e) {
166 oldEvent = e;
167 giveUserFeedback(e);
168 }
169
170 /**
171 * removes any highlighting that may have been set beforehand.
172 */
173 private void removeHighlighting() {
174 highlightHelper.clear();
175 DataSet ds = getCurrentDataSet();
176 if(ds != null) {
177 ds.clearHighlightedWaySegments();
178 }
179 }
180
181 /**
182 * handles everything related to highlighting primitives and way
183 * segments for the given pointer position (via MouseEvent) and
184 * modifiers.
185 * @param e
186 * @param modifiers
187 */
188 private void addHighlighting(MouseEvent e, int modifiers) {
189 if(!drawTargetHighlight)
190 return;
191
192 Set<OsmPrimitive> newHighlights = new HashSet<>();
193 DeleteParameters parameters = getDeleteParameters(e, modifiers);
194
195 if(parameters.mode == DeleteMode.segment) {
196 // deleting segments is the only action not working on OsmPrimitives
197 // so we have to handle them separately.
198 repaintIfRequired(newHighlights, parameters.nearestSegment);
199 } else {
200 // don't call buildDeleteCommands for DeleteMode.segment because it doesn't support
201 // silent operation and SplitWayAction will show dialogs. A lot.
202 Command delCmd = buildDeleteCommands(e, modifiers, true);
203 if(delCmd != null) {
204 // all other cases delete OsmPrimitives directly, so we can
205 // safely do the following
206 for(OsmPrimitive osm : delCmd.getParticipatingPrimitives()) {
207 newHighlights.add(osm);
208 }
209 }
210 repaintIfRequired(newHighlights, null);
211 }
212 }
213
214 private void repaintIfRequired(Set<OsmPrimitive> newHighlights, WaySegment newHighlightedWaySegment) {
215 boolean needsRepaint = false;
216 DataSet ds = getCurrentDataSet();
217
218 if(newHighlightedWaySegment == null && oldHighlightedWaySegment != null) {
219 if(ds != null) {
220 ds.clearHighlightedWaySegments();
221 needsRepaint = true;
222 }
223 oldHighlightedWaySegment = null;
224 } else if(newHighlightedWaySegment != null && !newHighlightedWaySegment.equals(oldHighlightedWaySegment)) {
225 if(ds != null) {
226 ds.setHighlightedWaySegments(Collections.singleton(newHighlightedWaySegment));
227 needsRepaint = true;
228 }
229 oldHighlightedWaySegment = newHighlightedWaySegment;
230 }
231 needsRepaint |= highlightHelper.highlightOnly(newHighlights);
232 if(needsRepaint) {
233 Main.map.mapView.repaint();
234 }
235 }
236
237 /**
238 * This function handles all work related to updating the cursor and
239 * highlights
240 *
241 * @param e
242 * @param modifiers
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
263 * modifiers.
264 */
265 private void giveUserFeedback(MouseEvent e, int modifiers) {
266 updateCursor(e, modifiers);
267 addHighlighting(e, modifiers);
268 }
269
270 /**
271 * Gives the user feedback for the action he/she is about to do. Currently
272 * calls the cursor and target highlighting routines. Extracts modifiers
273 * from mouse event.
274 */
275 private void giveUserFeedback(MouseEvent e) {
276 giveUserFeedback(e, e.getModifiers());
277 }
278
279 /**
280 * If user clicked with the left button, delete the nearest object.
281 * position.
282 */
283 @Override
284 public void mouseReleased(MouseEvent e) {
285 if (e.getButton() != MouseEvent.BUTTON1)
286 return;
287 if(!Main.map.mapView.isActiveLayerVisible())
288 return;
289
290 // request focus in order to enable the expected keyboard shortcuts
291 //
292 Main.map.mapView.requestFocus();
293
294 Command c = buildDeleteCommands(e, e.getModifiers(), false);
295 if (c != null) {
296 Main.main.undoRedo.add(c);
297 }
298
299 getCurrentDataSet().setSelected();
300 giveUserFeedback(e);
301 }
302
303 @Override
304 public String getModeHelpText() {
305 return tr("Click to delete. Shift: delete way segment. Alt: do not delete unused nodes when deleting a way. Ctrl: delete referring objects.");
306 }
307
308 @Override
309 public boolean layerIsSupported(Layer l) {
310 return l instanceof OsmDataLayer;
311 }
312
313 @Override
314 protected void updateEnabledState() {
315 setEnabled(Main.isDisplayingMapView() && Main.map.mapView.isActiveLayerDrawable());
316 }
317
318 /**
319 * Deletes the relation in the context of the given layer.
320 *
321 * @param layer the layer in whose context the relation is deleted. Must not be null.
322 * @param toDelete the relation to be deleted. Must not be null.
323 * @exception IllegalArgumentException thrown if layer is null
324 * @exception IllegalArgumentException thrown if toDelete is nul
325 */
326 public static void deleteRelation(OsmDataLayer layer, Relation toDelete) {
327 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
328 CheckParameterUtil.ensureParameterNotNull(toDelete, "toDelete");
329
330 Command cmd = DeleteCommand.delete(layer, Collections.singleton(toDelete));
331 if (cmd != null) {
332 // cmd can be null if the user cancels dialogs DialogCommand displays
333 Main.main.undoRedo.add(cmd);
334 if (getCurrentDataSet().getSelectedRelations().contains(toDelete)) {
335 getCurrentDataSet().toggleSelected(toDelete);
336 }
337 RelationDialogManager.getRelationDialogManager().close(layer, toDelete);
338 }
339 }
340
341 private DeleteParameters getDeleteParameters(MouseEvent e, int modifiers) {
342 updateKeyModifiers(modifiers);
343
344 DeleteParameters result = new DeleteParameters();
345
346 result.nearestNode = Main.map.mapView.getNearestNode(e.getPoint(), OsmPrimitive.isSelectablePredicate);
347 if (result.nearestNode == null) {
348 result.nearestSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
349 if (result.nearestSegment != null) {
350 if (shift) {
351 result.mode = DeleteMode.segment;
352 } else if (ctrl) {
353 result.mode = DeleteMode.way_with_references;
354 } else {
355 result.mode = alt?DeleteMode.way:DeleteMode.way_with_nodes;
356 }
357 } else {
358 result.mode = DeleteMode.none;
359 }
360 } else if (ctrl) {
361 result.mode = DeleteMode.node_with_references;
362 } else {
363 result.mode = DeleteMode.node;
364 }
365
366 return result;
367 }
368
369 /**
370 * This function takes any mouse event argument and builds the list of elements
371 * that should be deleted but does not actually delete them.
372 * @param e MouseEvent from which modifiers and position are taken
373 * @param modifiers For explanation, see {@link #updateCursor}
374 * @param silent Set to true if the user should not be bugged with additional
375 * dialogs
376 * @return delete command
377 */
378 private Command buildDeleteCommands(MouseEvent e, int modifiers, boolean silent) {
379 DeleteParameters parameters = getDeleteParameters(e, modifiers);
380 switch (parameters.mode) {
381 case node:
382 return DeleteCommand.delete(getEditLayer(),Collections.singleton(parameters.nearestNode), false, silent);
383 case node_with_references:
384 return DeleteCommand.deleteWithReferences(getEditLayer(),Collections.singleton(parameters.nearestNode), silent);
385 case segment:
386 return DeleteCommand.deleteWaySegment(getEditLayer(), parameters.nearestSegment);
387 case way:
388 return DeleteCommand.delete(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), false, silent);
389 case way_with_nodes:
390 return DeleteCommand.delete(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), true, silent);
391 case way_with_references:
392 return DeleteCommand.deleteWithReferences(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), true);
393 default:
394 return null;
395 }
396 }
397
398 /**
399 * This is required to update the cursors when ctrl/shift/alt is pressed
400 */
401 @Override
402 public void modifiersChanged(int modifiers) {
403 if (oldEvent == null)
404 return;
405 // We don't have a mouse event, so we pass the old mouse event but the new modifiers.
406 giveUserFeedback(oldEvent, modifiers);
407 }
408}
Note: See TracBrowser for help on using the repository browser.