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

Last change on this file since 2181 was 2181, checked in by stoecker, 15 years ago

lots of i18n fixes

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