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

Last change on this file since 5076 was 5076, checked in by xeen, 12 years ago

fix #6780. Please report if this caused any repaint problems.

  • Property svn:eol-style set to native
File size: 14.3 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions.mapmode;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.AWTEvent;
7import java.awt.Cursor;
8import java.awt.Toolkit;
9import java.awt.event.AWTEventListener;
10import java.awt.event.ActionEvent;
11import java.awt.event.InputEvent;
12import java.awt.event.KeyEvent;
13import java.awt.event.MouseEvent;
14import java.util.Collections;
15import java.util.HashSet;
16import java.util.Set;
17
18import org.openstreetmap.josm.Main;
19import org.openstreetmap.josm.command.Command;
20import org.openstreetmap.josm.command.DeleteCommand;
21import org.openstreetmap.josm.data.osm.DataSet;
22import org.openstreetmap.josm.data.osm.Node;
23import org.openstreetmap.josm.data.osm.OsmPrimitive;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.WaySegment;
26import org.openstreetmap.josm.gui.MapFrame;
27import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager;
28import org.openstreetmap.josm.gui.layer.Layer;
29import org.openstreetmap.josm.gui.layer.OsmDataLayer;
30import org.openstreetmap.josm.tools.CheckParameterUtil;
31import org.openstreetmap.josm.tools.ImageProvider;
32import org.openstreetmap.josm.tools.Shortcut;
33
34/**
35 * An action 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
39 * 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 that can be deleted will.
46 *
47 * @author imi
48 */
49public class DeleteAction extends MapMode implements AWTEventListener {
50 // Cache previous mouse event (needed when only the modifier keys are
51 // pressed but the mouse isn't moved)
52 private MouseEvent oldEvent = null;
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 Set<OsmPrimitive> oldHighlights = new HashSet<OsmPrimitive>();
60
61 private boolean drawTargetHighlight;
62
63 private enum DeleteMode {
64 none("delete"),
65 segment("delete_segment"),
66 node("delete_node"),
67 node_with_references("delete_node"),
68 way("delete_way_only"),
69 way_with_references("delete_way_normal"),
70 way_with_nodes("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 DeleteMode mode;
85 Node nearestNode;
86 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 try {
114 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
115 } catch (SecurityException ex) {
116 System.out.println(ex);
117 }
118 }
119
120 @Override public void exitMode() {
121 super.exitMode();
122 Main.map.mapView.removeMouseListener(this);
123 Main.map.mapView.removeMouseMotionListener(this);
124 try {
125 Toolkit.getDefaultToolkit().removeAWTEventListener(this);
126 } catch (SecurityException ex) {
127 System.out.println(ex);
128 }
129 removeHighlighting();
130 }
131
132 @Override public void actionPerformed(ActionEvent e) {
133 super.actionPerformed(e);
134 doActionPerformed(e);
135 }
136
137 static public void doActionPerformed(ActionEvent e) {
138 if(!Main.map.mapView.isActiveLayerDrawable())
139 return;
140 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
141 boolean alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0;
142
143 Command c;
144 if (ctrl) {
145 c = DeleteCommand.deleteWithReferences(getEditLayer(),getCurrentDataSet().getSelected());
146 } else {
147 c = DeleteCommand.delete(getEditLayer(),getCurrentDataSet().getSelected(), !alt /* also delete nodes in way */);
148 }
149 // if c is null, an error occurred or the user aborted. Don't do anything in that case.
150 if (c != null) {
151 Main.main.undoRedo.add(c);
152 getCurrentDataSet().setSelected();
153 Main.map.repaint();
154 }
155 }
156
157 @Override public void mouseDragged(MouseEvent e) {
158 mouseMoved(e);
159 }
160
161 /**
162 * Listen to mouse move to be able to update the cursor (and highlights)
163 * @param MouseEvent The mouse event that has been captured
164 */
165 @Override 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 for(OsmPrimitive prim : oldHighlights) {
175 prim.setHighlighted(false);
176 }
177 oldHighlights = new HashSet<OsmPrimitive>();
178 DataSet ds = getCurrentDataSet();
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
187 * modifiers.
188 * @param e
189 * @param modifiers
190 */
191 private void addHighlighting(MouseEvent e, int modifiers) {
192 if(!drawTargetHighlight)
193 return;
194 removeHighlighting();
195
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 DataSet ds = getCurrentDataSet();
202 if(ds != null) {
203 ds.setHighlightedWaySegments(Collections.singleton(parameters.nearestSegment));
204 }
205 } else {
206 // don't call buildDeleteCommands for DeleteMode.segment because it doesn't support
207 // silent operation and SplitWayAction will show dialogs. A lot.
208 Command delCmd = buildDeleteCommands(e, modifiers, true);
209 if(delCmd == null) {
210 Main.map.mapView.repaint();
211 return;
212 }
213
214 // all other cases delete OsmPrimitives directly, so we can
215 // safely do the following
216 for(OsmPrimitive osm : delCmd.getParticipatingPrimitives()) {
217 osm.setHighlighted(true);
218 oldHighlights.add(osm);
219 }
220 }
221 Main.map.mapView.repaint();
222 }
223
224 /**
225 * This function handles all work related to updating the cursor and
226 * highlights
227 *
228 * @param MouseEvent
229 * @param int modifiers
230 */
231 private void updateCursor(MouseEvent e, int modifiers) {
232 if (!Main.isDisplayingMapView())
233 return;
234 if(!Main.map.mapView.isActiveLayerVisible() || e == null)
235 return;
236
237 DeleteParameters parameters = getDeleteParameters(e, modifiers);
238 Main.map.mapView.setNewCursor(parameters.mode.cursor(), this);
239 }
240 /**
241 * Gives the user feedback for the action he/she is about to do. Currently
242 * calls the cursor and target highlighting routines. Allows for modifiers
243 * not taken from the given mouse event.
244 *
245 * Normally the mouse event also contains the modifiers. However, when the
246 * mouse is not moved and only modifier keys are pressed, no mouse event
247 * occurs. We can use AWTEvent to catch those but still lack a proper
248 * mouseevent. Instead we copy the previous event and only update the
249 * modifiers.
250 */
251 private void giveUserFeedback(MouseEvent e, int modifiers) {
252 updateCursor(e, modifiers);
253 addHighlighting(e, modifiers);
254 }
255
256 /**
257 * Gives the user feedback for the action he/she is about to do. Currently
258 * calls the cursor and target highlighting routines. Extracts modifiers
259 * from mouse event.
260 */
261 private void giveUserFeedback(MouseEvent e) {
262 giveUserFeedback(e, e.getModifiers());
263 }
264
265 /**
266 * If user clicked with the left button, delete the nearest object.
267 * position.
268 */
269 @Override public void mouseReleased(MouseEvent e) {
270 if (e.getButton() != MouseEvent.BUTTON1)
271 return;
272 if(!Main.map.mapView.isActiveLayerVisible())
273 return;
274
275 // request focus in order to enable the expected keyboard shortcuts
276 //
277 Main.map.mapView.requestFocus();
278
279 Command c = buildDeleteCommands(e, e.getModifiers(), false);
280 if (c != null) {
281 Main.main.undoRedo.add(c);
282 }
283
284 getCurrentDataSet().setSelected();
285 giveUserFeedback(e);
286 Main.map.mapView.repaint();
287 }
288
289 @Override public String getModeHelpText() {
290 return tr("Click to delete. Shift: delete way segment. Alt: do not delete unused nodes when deleting a way. Ctrl: delete referring objects.");
291 }
292
293 @Override public boolean layerIsSupported(Layer l) {
294 return l instanceof OsmDataLayer;
295 }
296
297 @Override
298 protected void updateEnabledState() {
299 setEnabled(Main.map != null && Main.map.mapView != null && Main.map.mapView.isActiveLayerDrawable());
300 }
301
302 /**
303 * Deletes the relation in the context of the given layer. Also notifies
304 * {@see RelationDialogManager} and {@see OsmDataLayer#fireDataChange()} events.
305 *
306 * @param layer the layer in whose context the relation is deleted. Must not be null.
307 * @param toDelete the relation to be deleted. Must not be null.
308 * @exception IllegalArgumentException thrown if layer is null
309 * @exception IllegalArgumentException thrown if toDelete is nul
310 */
311 public static void deleteRelation(OsmDataLayer layer, Relation toDelete) {
312 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
313 CheckParameterUtil.ensureParameterNotNull(toDelete, "toDelete");
314
315 Command cmd = DeleteCommand.delete(layer, Collections.singleton(toDelete));
316 if (cmd != null) {
317 // cmd can be null if the user cancels dialogs DialogCommand displays
318 Main.main.undoRedo.add(cmd);
319 if (getCurrentDataSet().getSelectedRelations().contains(toDelete)) {
320 getCurrentDataSet().toggleSelected(toDelete);
321 }
322 RelationDialogManager.getRelationDialogManager().close(layer, toDelete);
323 }
324 }
325
326 private DeleteParameters getDeleteParameters(MouseEvent e, int modifiers) {
327 updateKeyModifiers(modifiers);
328
329 DeleteParameters result = new DeleteParameters();
330
331 result.nearestNode = Main.map.mapView.getNearestNode(e.getPoint(), OsmPrimitive.isSelectablePredicate);
332 if (result.nearestNode == null) {
333 result.nearestSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
334 if (result.nearestSegment != null) {
335 if (shift) {
336 result.mode = DeleteMode.segment;
337 } else if (ctrl) {
338 result.mode = DeleteMode.way_with_references;
339 } else {
340 result.mode = alt?DeleteMode.way:DeleteMode.way_with_nodes;
341 }
342 } else {
343 result.mode = DeleteMode.none;
344 }
345 } else if (ctrl) {
346 result.mode = DeleteMode.node_with_references;
347 } else {
348 result.mode = DeleteMode.node;
349 }
350
351 return result;
352 }
353
354 /**
355 * This function takes any mouse event argument and builds the list of elements
356 * that should be deleted but does not actually delete them.
357 * @param e MouseEvent from which modifiers and position are taken
358 * @param int modifiers For explanation: @see updateCursor
359 * @param silet Set to true if the user should not be bugged with additional
360 * dialogs
361 * @return
362 */
363 private Command buildDeleteCommands(MouseEvent e, int modifiers, boolean silent) {
364 DeleteParameters parameters = getDeleteParameters(e, modifiers);
365 switch (parameters.mode) {
366 case node:
367 return DeleteCommand.delete(getEditLayer(),Collections.singleton(parameters.nearestNode), false, silent);
368 case node_with_references:
369 return DeleteCommand.deleteWithReferences(getEditLayer(),Collections.singleton(parameters.nearestNode), silent);
370 case segment:
371 return DeleteCommand.deleteWaySegment(getEditLayer(), parameters.nearestSegment);
372 case way:
373 return DeleteCommand.delete(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), false, silent);
374 case way_with_nodes:
375 return DeleteCommand.delete(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), true, silent);
376 case way_with_references:
377 return DeleteCommand.deleteWithReferences(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), true);
378 default:
379 return null;
380 }
381 }
382
383 /**
384 * This is required to update the cursors when ctrl/shift/alt is pressed
385 */
386 public void eventDispatched(AWTEvent e) {
387 if(oldEvent == null)
388 return;
389 // We don't have a mouse event, so we pass the old mouse event but the
390 // new modifiers.
391 giveUserFeedback(oldEvent, ((InputEvent) e).getModifiers());
392 }
393}
Note: See TracBrowser for help on using the repository browser.