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

Last change on this file since 2692 was 2692, checked in by Gubaer, 14 years ago

fixed #4242: patch by Pekka Lampila: Clicks are ignored in draw mode, if mouse is moving at all

  • Property svn:eol-style set to native
File size: 13.7 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.EventQueue;
9import java.awt.Toolkit;
10import java.awt.event.AWTEventListener;
11import java.awt.event.ActionEvent;
12import java.awt.event.InputEvent;
13import java.awt.event.KeyEvent;
14import java.awt.event.MouseEvent;
15import java.util.Collections;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.command.Command;
19import org.openstreetmap.josm.command.DeleteCommand;
20import org.openstreetmap.josm.data.osm.Node;
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.tools.ImageProvider;
28import org.openstreetmap.josm.tools.Shortcut;
29
30/**
31 * An action that enables the user to delete nodes and other objects.
32 *
33 * The user can click on an object, which gets deleted if possible. When Ctrl is
34 * pressed when releasing the button, the objects and all its references are
35 * deleted.
36 *
37 * If the user did not press Ctrl and the object has any references, the user
38 * is informed and nothing is deleted.
39 *
40 * If the user enters the mapmode and any object is selected, all selected
41 * objects that can be deleted will.
42 *
43 * @author imi
44 */
45
46/**
47 * This class contains stubs for highlighting affected primitives when affected.
48 * However, way segments can be deleted as well, but cannot be highlighted
49 * alone. If the highlight feature for this delete action is to be implemented
50 * properly, highlighting way segments must be possible first. --xeen, 2009-09-02
51 */
52public class DeleteAction extends MapMode implements AWTEventListener {
53 //private boolean drawTargetHighlight;
54 private boolean drawTargetCursor;
55 //private Collection<? extends OsmPrimitive> oldPrims = null;
56
57 // Cache previous mouse event (needed when only the modifier keys are
58 // pressed but the mouse isn't moved)
59 private MouseEvent oldEvent = null;
60
61 private enum DeleteMode {
62 none("delete"),
63 segment("delete_segment"),
64 node("delete_node"),
65 node_with_references("delete_node"),
66 way("delete_way_only"),
67 way_with_references("delete_way_normal"),
68 way_with_nodes("delete_way_node_only");
69
70 private final Cursor c;
71
72 private DeleteMode(String cursorName) {
73 c = ImageProvider.getCursor("normal", cursorName);
74 }
75
76 public Cursor cursor() {
77 return c;
78 }
79 }
80 private DeleteMode currentMode = DeleteMode.none;
81
82 private static class DeleteParameters {
83 DeleteMode mode;
84 Node nearestNode;
85 WaySegment nearestSegment;
86 }
87
88 /**
89 * Construct a new DeleteAction. Mnemonic is the delete - key.
90 * @param mapFrame The frame this action belongs to.
91 */
92 public DeleteAction(MapFrame mapFrame) {
93 super(tr("Delete Mode"),
94 "delete",
95 tr("Delete nodes or ways."),
96 Shortcut.registerShortcut("mapmode:delete", tr("Mode: {0}",tr("Delete")), KeyEvent.VK_D, Shortcut.GROUP_EDIT),
97 mapFrame,
98 ImageProvider.getCursor("normal", "delete"));
99 }
100
101 @Override public void enterMode() {
102 super.enterMode();
103 if (!isEnabled())
104 return;
105 //drawTargetHighlight = Main.pref.getBoolean("draw.target-highlight", true);
106 drawTargetCursor = Main.pref.getBoolean("draw.target-cursor", 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 try {
112 Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
113 } catch (SecurityException ex) {
114 System.out.println(ex);
115 }
116
117 currentMode = DeleteMode.none;
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 }
130
131 @Override public void actionPerformed(ActionEvent e) {
132 super.actionPerformed(e);
133 if(!Main.map.mapView.isActiveLayerDrawable())
134 return;
135 doActionPerformed(e);
136 }
137
138 public void doActionPerformed(ActionEvent e) {
139 if(!Main.map.mapView.isActiveLayerDrawable())
140 return;
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(getEditLayer(),getCurrentDataSet().getSelected());
147 } else {
148 c = DeleteCommand.delete(getEditLayer(),getCurrentDataSet().getSelected(), !alt /* also delete nodes in way */);
149 }
150 if (c != null) {
151 Main.main.undoRedo.add(c);
152 }
153
154 getCurrentDataSet().setSelected();
155 Main.map.repaint();
156 }
157
158 @Override public void mouseDragged(MouseEvent e) {
159 mouseMoved(e);
160 }
161
162 /**
163 * Listen to mouse move to be able to update the cursor (and highlights)
164 * @param MouseEvent The mouse event that has been captured
165 */
166 @Override public void mouseMoved(MouseEvent e) {
167 oldEvent = e;
168 updateCursor(e, e.getModifiers());
169 }
170
171 /**
172 * This function handles all work related to updating the cursor and
173 * highlights. For now, only the cursor is enabled because highlighting
174 * requires WaySegment to be highlightable.
175 *
176 * Normally the mouse event also contains the modifiers. However, when the
177 * mouse is not moved and only modifier keys are pressed, no mouse event
178 * occurs. We can use AWTEvent to catch those but still lack a proper
179 * mouseevent. Instead we copy the previous event and only update the
180 * modifiers.
181 *
182 * @param MouseEvent
183 * @param int modifiers
184 */
185 private void updateCursor(MouseEvent e, int modifiers) {
186 if (!Main.isDisplayingMapView())
187 return;
188 if(!Main.map.mapView.isActiveLayerVisible() || e == null)
189 return;
190
191 // Clean old highlights
192 //cleanOldHighlights();
193
194 DeleteParameters parameters = getDeleteParameters(e, modifiers);
195 setCursor(parameters.mode);
196
197 // Needs to implement WaySegment highlight first
198 /*if(drawTargetHighlight) {
199 // Add new highlights
200 for(OsmPrimitive p : prims) {
201 p.highlighted = true;
202 }
203 oldPrims = prims;
204 }*/
205
206 // We only need to repaint if the highlights changed
207 //Main.map.mapView.repaint();
208 }
209
210 /**
211 * Small helper function that cleans old highlights
212 */
213 /*private void cleanOldHighlights() {
214 if(oldPrims == null)
215 return;
216 for(OsmPrimitive p: oldPrims) {
217 p.highlighted = false;
218 }
219 }*/
220
221 /**
222 * If user clicked with the left button, delete the nearest object.
223 * position.
224 */
225 @Override public void mouseReleased(MouseEvent e) {
226 if (e.getButton() != MouseEvent.BUTTON1)
227 return;
228 if(!Main.map.mapView.isActiveLayerVisible())
229 return;
230
231 // request focus in order to enable the expected keyboard shortcuts
232 //
233 Main.map.mapView.requestFocus();
234
235 Command c = buildDeleteCommands(e, e.getModifiers(), false);
236 if (c != null) {
237 Main.main.undoRedo.add(c);
238 }
239
240 getCurrentDataSet().setSelected();
241 Main.map.mapView.repaint();
242 }
243
244 @Override public String getModeHelpText() {
245 return tr("Click to delete. Shift: delete way segment. Alt: don't delete unused nodes when deleting a way. Ctrl: delete referring objects.");
246 }
247
248 @Override public boolean layerIsSupported(Layer l) {
249 return l instanceof OsmDataLayer;
250 }
251
252 @Override
253 protected void updateEnabledState() {
254 setEnabled(Main.map != null && Main.map.mapView != null && Main.map.mapView.isActiveLayerDrawable());
255 }
256
257 /**
258 * Deletes the relation in the context of the given layer. Also notifies
259 * {@see RelationDialogManager} and {@see OsmDataLayer#fireDataChange()} events.
260 *
261 * @param layer the layer in whose context the relation is deleted. Must not be null.
262 * @param toDelete the relation to be deleted. Must not be null.
263 * @exception IllegalArgumentException thrown if layer is null
264 * @exception IllegalArgumentException thrown if toDelete is nul
265 */
266 public static void deleteRelation(OsmDataLayer layer, Relation toDelete) {
267 if (layer == null)
268 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "layer"));
269 if (toDelete == null)
270 throw new IllegalArgumentException(tr("Parameter ''{0}'' must not be null.", "toDelete"));
271
272 Command cmd = DeleteCommand.delete(layer, Collections.singleton(toDelete));
273 if (cmd != null) {
274 // cmd can be null if the user cancels dialogs DialogCommand displays
275 Main.main.undoRedo.add(cmd);
276 RelationDialogManager.getRelationDialogManager().close(layer, toDelete);
277 layer.fireDataChange();
278 }
279 }
280
281 private DeleteParameters getDeleteParameters(MouseEvent e, int modifiers) {
282 // Note: CTRL is the only modifier that is checked in MouseMove, don't
283 // forget updating it there
284 boolean ctrl = (modifiers & ActionEvent.CTRL_MASK) != 0;
285 boolean shift = (modifiers & ActionEvent.SHIFT_MASK) != 0;
286 boolean alt = (modifiers & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0;
287
288 DeleteParameters result = new DeleteParameters();
289
290 result.nearestNode = Main.map.mapView.getNearestNode(e.getPoint());
291 if (result.nearestNode == null) {
292 result.nearestSegment = Main.map.mapView.getNearestWaySegment(e.getPoint());
293 if (result.nearestSegment != null) {
294 if (shift) {
295 result.mode = DeleteMode.segment;
296 } else if (ctrl) {
297 result.mode = DeleteMode.way_with_references;
298 } else {
299 result.mode = alt?DeleteMode.way:DeleteMode.way_with_nodes;
300 }
301 } else {
302 result.mode = DeleteMode.none;
303 }
304 } else if (ctrl) {
305 result.mode = DeleteMode.node_with_references;
306 } else {
307 result.mode = DeleteMode.node;
308 }
309
310 return result;
311 }
312
313 /**
314 * This function takes any mouse event argument and builds the list of elements
315 * that should be deleted but does not actually delete them.
316 * @param e MouseEvent from which modifiers and position are taken
317 * @param int modifiers For explanation: @see updateCursor
318 * @param silet Set to true if the user should not be bugged with additional
319 * dialogs
320 * @return
321 */
322 private Command buildDeleteCommands(MouseEvent e, int modifiers, boolean silent) {
323 DeleteParameters parameters = getDeleteParameters(e, modifiers);
324 switch (parameters.mode) {
325 case node:
326 return DeleteCommand.delete(getEditLayer(),Collections.singleton(parameters.nearestNode), false, silent);
327 case node_with_references:
328 return DeleteCommand.deleteWithReferences(getEditLayer(),Collections.singleton(parameters.nearestNode));
329 case segment:
330 return DeleteCommand.deleteWaySegment(getEditLayer(), parameters.nearestSegment);
331 case way:
332 return DeleteCommand.delete(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), false, silent);
333 case way_with_nodes:
334 return DeleteCommand.delete(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), true, silent);
335 case way_with_references:
336 return DeleteCommand.deleteWithReferences(getEditLayer(),Collections.singleton(parameters.nearestSegment.way),true);
337 default:
338 return null;
339 }
340 }
341
342 /**
343 * This function sets the given cursor in a safe way. This implementation
344 * differs from the on in DrawAction (it is favorable, too).
345 * FIXME: Update DrawAction to use this "setCursor-style" and move function
346 * to MapMode.
347 * @param c
348 */
349 private void setCursor(final DeleteMode c) {
350 if(currentMode.equals(c) || (!drawTargetCursor && currentMode.equals(DeleteMode.none)))
351 return;
352 try {
353 // We invoke this to prevent strange things from happening
354 EventQueue.invokeLater(new Runnable() {
355 public void run() {
356 // Don't change cursor when mode has changed already
357 if(!(Main.map.mapMode instanceof DeleteAction))
358 return;
359
360 Main.map.mapView.setCursor(c.cursor());
361 //System.out.println("Set cursor to: " + c.name());
362 }
363 });
364 currentMode = c;
365 } catch(Exception e) {}
366 }
367
368 /**
369 * This is required to update the cursors when ctrl/shift/alt is pressed
370 */
371 public void eventDispatched(AWTEvent e) {
372 // We don't have a mouse event, so we pass the old mouse event but the
373 // new modifiers.
374 updateCursor(oldEvent, ((InputEvent)e).getModifiers());
375 }
376}
Note: See TracBrowser for help on using the repository browser.