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

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

fix #6707 - Removing a empty polygon (removed shape first, then on validation error try to remove polygon)

  • Property svn:eol-style set to native
File size: 14.1 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")), 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
107 drawTargetHighlight = Main.pref.getBoolean("draw.target-highlight", 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
119 @Override public void exitMode() {
120 super.exitMode();
121 Main.map.mapView.removeMouseListener(this);
122 Main.map.mapView.removeMouseMotionListener(this);
123 try {
124 Toolkit.getDefaultToolkit().removeAWTEventListener(this);
125 } catch (SecurityException ex) {
126 System.out.println(ex);
127 }
128 removeHighlighting();
129 }
130
131 @Override public void actionPerformed(ActionEvent e) {
132 super.actionPerformed(e);
133 doActionPerformed(e);
134 }
135
136 static public void doActionPerformed(ActionEvent e) {
137 if(!Main.map.mapView.isActiveLayerDrawable())
138 return;
139 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
140 boolean alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0;
141
142 Command c;
143 if (ctrl) {
144 c = DeleteCommand.deleteWithReferences(getEditLayer(),getCurrentDataSet().getSelected());
145 } else {
146 c = DeleteCommand.delete(getEditLayer(),getCurrentDataSet().getSelected(), !alt /* also delete nodes in way */);
147 }
148 if (c != null) {
149 Main.main.undoRedo.add(c);
150 }
151
152 getCurrentDataSet().setSelected();
153 Main.map.repaint();
154 }
155
156 @Override 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 MouseEvent The mouse event that has been captured
163 */
164 @Override public void mouseMoved(MouseEvent e) {
165 oldEvent = e;
166 giveUserFeedback(e);
167 }
168
169 /**
170 * removes any highlighting that may have been set beforehand.
171 */
172 private void removeHighlighting() {
173 for(OsmPrimitive prim : oldHighlights) {
174 prim.setHighlighted(false);
175 }
176 oldHighlights = new HashSet<OsmPrimitive>();
177 DataSet ds = getCurrentDataSet();
178 if(ds != null) {
179 ds.clearHighlightedWaySegments();
180 }
181 }
182
183 /**
184 * handles everything related to highlighting primitives and way
185 * segments for the given pointer position (via MouseEvent) and
186 * modifiers.
187 * @param e
188 * @param modifiers
189 */
190 private void addHighlighting(MouseEvent e, int modifiers) {
191 if(!drawTargetHighlight)
192 return;
193 removeHighlighting();
194
195 DeleteParameters parameters = getDeleteParameters(e, modifiers);
196
197 if(parameters.mode == DeleteMode.segment) {
198 // deleting segments is the only action not working on OsmPrimitives
199 // so we have to handle them separately.
200 DataSet ds = getCurrentDataSet();
201 if(ds != null) {
202 ds.setHighlightedWaySegments(Collections.singleton(parameters.nearestSegment));
203 }
204 } else {
205 // don't call buildDeleteCommands for DeleteMode.segment because it doesn't support
206 // silent operation and SplitWayAction will show dialogs. A lot.
207 Command delCmd = buildDeleteCommands(e, modifiers, true);
208 if(delCmd == null) {
209 Main.map.mapView.repaint();
210 return;
211 }
212
213 // all other cases delete OsmPrimitives directly, so we can
214 // safely do the following
215 for(OsmPrimitive osm : delCmd.getParticipatingPrimitives()) {
216 osm.setHighlighted(true);
217 oldHighlights.add(osm);
218 }
219 }
220 Main.map.mapView.repaint();
221 }
222
223 /**
224 * This function handles all work related to updating the cursor and
225 * highlights
226 *
227 * @param MouseEvent
228 * @param int modifiers
229 */
230 private void updateCursor(MouseEvent e, int modifiers) {
231 if (!Main.isDisplayingMapView())
232 return;
233 if(!Main.map.mapView.isActiveLayerVisible() || e == null)
234 return;
235
236 DeleteParameters parameters = getDeleteParameters(e, modifiers);
237 Main.map.mapView.setNewCursor(parameters.mode.cursor(), this);
238 }
239 /**
240 * Gives the user feedback for the action he/she is about to do. Currently
241 * calls the cursor and target highlighting routines. Allows for modifiers
242 * not taken from the given mouse event.
243 *
244 * Normally the mouse event also contains the modifiers. However, when the
245 * mouse is not moved and only modifier keys are pressed, no mouse event
246 * occurs. We can use AWTEvent to catch those but still lack a proper
247 * mouseevent. Instead we copy the previous event and only update the
248 * modifiers.
249 */
250 private void giveUserFeedback(MouseEvent e, int modifiers) {
251 updateCursor(e, modifiers);
252 addHighlighting(e, modifiers);
253 }
254
255 /**
256 * Gives the user feedback for the action he/she is about to do. Currently
257 * calls the cursor and target highlighting routines. Extracts modifiers
258 * from mouse event.
259 */
260 private void giveUserFeedback(MouseEvent e) {
261 giveUserFeedback(e, e.getModifiers());
262 }
263
264 /**
265 * If user clicked with the left button, delete the nearest object.
266 * position.
267 */
268 @Override public void mouseReleased(MouseEvent e) {
269 if (e.getButton() != MouseEvent.BUTTON1)
270 return;
271 if(!Main.map.mapView.isActiveLayerVisible())
272 return;
273
274 // request focus in order to enable the expected keyboard shortcuts
275 //
276 Main.map.mapView.requestFocus();
277
278 Command c = buildDeleteCommands(e, e.getModifiers(), false);
279 if (c != null) {
280 Main.main.undoRedo.add(c);
281 }
282
283 getCurrentDataSet().setSelected();
284 giveUserFeedback(e);
285 Main.map.mapView.repaint();
286 }
287
288 @Override public String getModeHelpText() {
289 return tr("Click to delete. Shift: delete way segment. Alt: do not delete unused nodes when deleting a way. Ctrl: delete referring objects.");
290 }
291
292 @Override public boolean layerIsSupported(Layer l) {
293 return l instanceof OsmDataLayer;
294 }
295
296 @Override
297 protected void updateEnabledState() {
298 setEnabled(Main.map != null && Main.map.mapView != null && Main.map.mapView.isActiveLayerDrawable());
299 }
300
301 /**
302 * Deletes the relation in the context of the given layer. Also notifies
303 * {@see RelationDialogManager} and {@see OsmDataLayer#fireDataChange()} events.
304 *
305 * @param layer the layer in whose context the relation is deleted. Must not be null.
306 * @param toDelete the relation to be deleted. Must not be null.
307 * @exception IllegalArgumentException thrown if layer is null
308 * @exception IllegalArgumentException thrown if toDelete is nul
309 */
310 public static void deleteRelation(OsmDataLayer layer, Relation toDelete) {
311 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
312 CheckParameterUtil.ensureParameterNotNull(toDelete, "toDelete");
313
314 Command cmd = DeleteCommand.delete(layer, Collections.singleton(toDelete));
315 if (cmd != null) {
316 // cmd can be null if the user cancels dialogs DialogCommand displays
317 Main.main.undoRedo.add(cmd);
318 if (getCurrentDataSet().getSelectedRelations().contains(toDelete)) {
319 getCurrentDataSet().toggleSelected(toDelete);
320 }
321 RelationDialogManager.getRelationDialogManager().close(layer, toDelete);
322 }
323 }
324
325 private DeleteParameters getDeleteParameters(MouseEvent e, int modifiers) {
326 updateKeyModifiers(modifiers);
327
328 DeleteParameters result = new DeleteParameters();
329
330 result.nearestNode = Main.map.mapView.getNearestNode(e.getPoint(), OsmPrimitive.isSelectablePredicate);
331 if (result.nearestNode == null) {
332 result.nearestSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
333 if (result.nearestSegment != null) {
334 if (shift) {
335 result.mode = DeleteMode.segment;
336 } else if (ctrl) {
337 result.mode = DeleteMode.way_with_references;
338 } else {
339 result.mode = alt?DeleteMode.way:DeleteMode.way_with_nodes;
340 }
341 } else {
342 result.mode = DeleteMode.none;
343 }
344 } else if (ctrl) {
345 result.mode = DeleteMode.node_with_references;
346 } else {
347 result.mode = DeleteMode.node;
348 }
349
350 return result;
351 }
352
353 /**
354 * This function takes any mouse event argument and builds the list of elements
355 * that should be deleted but does not actually delete them.
356 * @param e MouseEvent from which modifiers and position are taken
357 * @param int modifiers For explanation: @see updateCursor
358 * @param silet Set to true if the user should not be bugged with additional
359 * dialogs
360 * @return
361 */
362 private Command buildDeleteCommands(MouseEvent e, int modifiers, boolean silent) {
363 DeleteParameters parameters = getDeleteParameters(e, modifiers);
364 switch (parameters.mode) {
365 case node:
366 return DeleteCommand.delete(getEditLayer(),Collections.singleton(parameters.nearestNode), false, silent);
367 case node_with_references:
368 return DeleteCommand.deleteWithReferences(getEditLayer(),Collections.singleton(parameters.nearestNode), silent);
369 case segment:
370 return DeleteCommand.deleteWaySegment(getEditLayer(), parameters.nearestSegment);
371 case way:
372 return DeleteCommand.delete(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), false, silent);
373 case way_with_nodes:
374 return DeleteCommand.delete(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), true, silent);
375 case way_with_references:
376 return DeleteCommand.deleteWithReferences(getEditLayer(), Collections.singleton(parameters.nearestSegment.way), true);
377 default:
378 return null;
379 }
380 }
381
382 /**
383 * This is required to update the cursors when ctrl/shift/alt is pressed
384 */
385 public void eventDispatched(AWTEvent e) {
386 if(oldEvent == null)
387 return;
388 // We don't have a mouse event, so we pass the old mouse event but the
389 // new modifiers.
390 giveUserFeedback(oldEvent, ((InputEvent) e).getModifiers());
391 }
392}
Note: See TracBrowser for help on using the repository browser.