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
RevLine 
[626]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
[2026]6import java.awt.AWTEvent;
7import java.awt.Cursor;
8import java.awt.EventQueue;
9import java.awt.Toolkit;
10import java.awt.event.AWTEventListener;
[626]11import java.awt.event.ActionEvent;
[1457]12import java.awt.event.InputEvent;
[626]13import java.awt.event.KeyEvent;
14import java.awt.event.MouseEvent;
15import java.util.Collections;
16
17import org.openstreetmap.josm.Main;
[988]18import org.openstreetmap.josm.command.Command;
19import org.openstreetmap.josm.command.DeleteCommand;
[2026]20import org.openstreetmap.josm.data.osm.Node;
[3177]21import org.openstreetmap.josm.data.osm.OsmPrimitive;
[1856]22import org.openstreetmap.josm.data.osm.Relation;
[626]23import org.openstreetmap.josm.data.osm.WaySegment;
24import org.openstreetmap.josm.gui.MapFrame;
[1856]25import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager;
[1379]26import org.openstreetmap.josm.gui.layer.Layer;
27import org.openstreetmap.josm.gui.layer.OsmDataLayer;
[2842]28import org.openstreetmap.josm.tools.CheckParameterUtil;
[988]29import org.openstreetmap.josm.tools.ImageProvider;
[1084]30import org.openstreetmap.josm.tools.Shortcut;
[626]31
32/**
33 * An action that enables the user to delete nodes and other objects.
34 *
[1023]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
[1415]37 * deleted.
[626]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.
[1023]44 *
[626]45 * @author imi
46 */
47
[2026]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
[2521]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");
[2026]71
[2521]72 private final Cursor c;
73
74 private DeleteMode(String cursorName) {
75 c = ImageProvider.getCursor("normal", cursorName);
76 }
77
[2026]78 public Cursor cursor() {
79 return c;
80 }
81 }
[2521]82 private DeleteMode currentMode = DeleteMode.none;
[2026]83
[2521]84 private static class DeleteParameters {
85 DeleteMode mode;
86 Node nearestNode;
87 WaySegment nearestSegment;
88 }
89
[1169]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 }
[626]102
[1169]103 @Override public void enterMode() {
104 super.enterMode();
[1821]105 if (!isEnabled())
106 return;
[2026]107 //drawTargetHighlight = Main.pref.getBoolean("draw.target-highlight", true);
108 drawTargetCursor = Main.pref.getBoolean("draw.target-cursor", true);
109
[1169]110 Main.map.mapView.addMouseListener(this);
[2026]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);
[2521]115 } catch (SecurityException ex) {
116 System.out.println(ex);
117 }
[2026]118
[2521]119 currentMode = DeleteMode.none;
[1169]120 }
[626]121
[1169]122 @Override public void exitMode() {
123 super.exitMode();
124 Main.map.mapView.removeMouseListener(this);
[2026]125 Main.map.mapView.removeMouseMotionListener(this);
126 try {
127 Toolkit.getDefaultToolkit().removeAWTEventListener(this);
[2521]128 } catch (SecurityException ex) {
129 System.out.println(ex);
130 }
[1169]131 }
[626]132
[1169]133 @Override public void actionPerformed(ActionEvent e) {
134 super.actionPerformed(e);
[1750]135 if(!Main.map.mapView.isActiveLayerDrawable())
[1169]136 return;
137 doActionPerformed(e);
138 }
[768]139
[1169]140 public void doActionPerformed(ActionEvent e) {
[1750]141 if(!Main.map.mapView.isActiveLayerDrawable())
[1169]142 return;
143 boolean ctrl = (e.getModifiers() & ActionEvent.CTRL_MASK) != 0;
[1457]144 boolean alt = (e.getModifiers() & (ActionEvent.ALT_MASK|InputEvent.ALT_GRAPH_MASK)) != 0;
[626]145
[1169]146 Command c;
147 if (ctrl) {
[1856]148 c = DeleteCommand.deleteWithReferences(getEditLayer(),getCurrentDataSet().getSelected());
[1169]149 } else {
[2308]150 c = DeleteCommand.delete(getEditLayer(),getCurrentDataSet().getSelected(), !alt /* also delete nodes in way */);
[1169]151 }
152 if (c != null) {
153 Main.main.undoRedo.add(c);
154 }
[626]155
[1814]156 getCurrentDataSet().setSelected();
[1169]157 Main.map.repaint();
158 }
[626]159
[2692]160 @Override public void mouseDragged(MouseEvent e) {
161 mouseMoved(e);
162 }
163
[1169]164 /**
[2026]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.
[2512]177 *
[2026]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.
[2512]183 *
[2026]184 * @param MouseEvent
[2521]185 * @param int modifiers
[2026]186 */
187 private void updateCursor(MouseEvent e, int modifiers) {
[2521]188 if (!Main.isDisplayingMapView())
[2343]189 return;
[2026]190 if(!Main.map.mapView.isActiveLayerVisible() || e == null)
191 return;
192
193 // Clean old highlights
194 //cleanOldHighlights();
195
[2521]196 DeleteParameters parameters = getDeleteParameters(e, modifiers);
197 setCursor(parameters.mode);
[2026]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 /**
[1169]224 * If user clicked with the left button, delete the nearest object.
225 * position.
226 */
[2692]227 @Override public void mouseReleased(MouseEvent e) {
[1169]228 if (e.getButton() != MouseEvent.BUTTON1)
229 return;
[1750]230 if(!Main.map.mapView.isActiveLayerVisible())
[1169]231 return;
[1935]232
233 // request focus in order to enable the expected keyboard shortcuts
234 //
235 Main.map.mapView.requestFocus();
236
[2026]237 Command c = buildDeleteCommands(e, e.getModifiers(), false);
[1169]238 if (c != null) {
239 Main.main.undoRedo.add(c);
240 }
[626]241
[1814]242 getCurrentDataSet().setSelected();
[1169]243 Main.map.mapView.repaint();
244 }
[1023]245
[1169]246 @Override public String getModeHelpText() {
[2842]247 return tr("Click to delete. Shift: delete way segment. Alt: do not delete unused nodes when deleting a way. Ctrl: delete referring objects.");
[1169]248 }
[1677]249
[1379]250 @Override public boolean layerIsSupported(Layer l) {
251 return l instanceof OsmDataLayer;
252 }
[1821]253
254 @Override
255 protected void updateEnabledState() {
256 setEnabled(Main.map != null && Main.map.mapView != null && Main.map.mapView.isActiveLayerDrawable());
257 }
[1856]258
259 /**
260 * Deletes the relation in the context of the given layer. Also notifies
261 * {@see RelationDialogManager} and {@see OsmDataLayer#fireDataChange()} events.
[2512]262 *
[1856]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) {
[2842]269 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
270 CheckParameterUtil.ensureParameterNotNull(toDelete, "toDelete");
[2026]271
[1856]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 }
[2026]279
[2521]280 private DeleteParameters getDeleteParameters(MouseEvent e, int modifiers) {
[2026]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
[2521]287 DeleteParameters result = new DeleteParameters();
288
[3177]289 result.nearestNode = Main.map.mapView.getNearestNode(e.getPoint(), OsmPrimitive.isSelectablePredicate);
[2521]290 if (result.nearestNode == null) {
[3177]291 result.nearestSegment = Main.map.mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive.isSelectablePredicate);
[2521]292 if (result.nearestSegment != null) {
[2026]293 if (shift) {
[2521]294 result.mode = DeleteMode.segment;
[2026]295 } else if (ctrl) {
[2521]296 result.mode = DeleteMode.way_with_references;
[2026]297 } else {
[2521]298 result.mode = alt?DeleteMode.way:DeleteMode.way_with_nodes;
[2026]299 }
[2521]300 } else {
301 result.mode = DeleteMode.none;
[2026]302 }
303 } else if (ctrl) {
[2521]304 result.mode = DeleteMode.node_with_references;
[2026]305 } else {
[2521]306 result.mode = DeleteMode.node;
[2026]307 }
308
[2521]309 return result;
[2026]310 }
311
312 /**
[2521]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 /**
[2026]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 */
[2521]348 private void setCursor(final DeleteMode c) {
349 if(currentMode.equals(c) || (!drawTargetCursor && currentMode.equals(DeleteMode.none)))
[2026]350 return;
[2986]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;
[2026]357
[2986]358 Main.map.mapView.setCursor(c.cursor());
359 //System.out.println("Set cursor to: " + c.name());
360 }
361 });
362 currentMode = c;
[2026]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 }
[626]373}
Note: See TracBrowser for help on using the repository browser.