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

Last change on this file since 3747 was 3177, checked in by bastiK, 14 years ago

Filter: improved selection handling. (Don't allow to select filtered or disabled objects by clicking on them. Don't connect to hidden ways in add mode.)

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