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

Last change on this file since 2986 was 2986, checked in by jttt, 14 years ago

Fix some of FindBugs warnings

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