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

Last change on this file since 13755 was 13434, checked in by Don-vip, 6 years ago

see #8039, see #10456 - support read-only data layers

  • Property svn:eol-style set to native
File size: 16.2 KB
RevLine 
[6380]1// License: GPL. For details, see LICENSE file.
[626]2package org.openstreetmap.josm.actions.mapmode;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
[2026]6import java.awt.Cursor;
[626]7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.awt.event.MouseEvent;
[9472]10import java.util.Collection;
[626]11import java.util.Collections;
[4327]12import java.util.HashSet;
13import java.util.Set;
[626]14
[988]15import org.openstreetmap.josm.command.Command;
16import org.openstreetmap.josm.command.DeleteCommand;
[4327]17import org.openstreetmap.josm.data.osm.DataSet;
[2026]18import org.openstreetmap.josm.data.osm.Node;
[3177]19import org.openstreetmap.josm.data.osm.OsmPrimitive;
[1856]20import org.openstreetmap.josm.data.osm.Relation;
[626]21import org.openstreetmap.josm.data.osm.WaySegment;
[12630]22import org.openstreetmap.josm.gui.MainApplication;
23import org.openstreetmap.josm.gui.MapFrame;
24import org.openstreetmap.josm.gui.MapView;
[1856]25import org.openstreetmap.josm.gui.dialogs.relation.RelationDialogManager;
[1379]26import org.openstreetmap.josm.gui.layer.Layer;
[10448]27import org.openstreetmap.josm.gui.layer.MainLayerManager;
[1379]28import org.openstreetmap.josm.gui.layer.OsmDataLayer;
[6026]29import org.openstreetmap.josm.gui.util.HighlightHelper;
[12517]30import org.openstreetmap.josm.gui.util.ModifierExListener;
[12846]31import org.openstreetmap.josm.spi.preferences.Config;
[2842]32import org.openstreetmap.josm.tools.CheckParameterUtil;
[988]33import org.openstreetmap.josm.tools.ImageProvider;
[1084]34import org.openstreetmap.josm.tools.Shortcut;
[626]35
36/**
[5275]37 * A map mode that enables the user to delete nodes and other objects.
[626]38 *
[1023]39 * The user can click on an object, which gets deleted if possible. When Ctrl is
[8470]40 * pressed when releasing the button, the objects and all its references are deleted.
[626]41 *
42 * If the user did not press Ctrl and the object has any references, the user
43 * is informed and nothing is deleted.
44 *
45 * If the user enters the mapmode and any object is selected, all selected
[5275]46 * objects are deleted, if possible.
[1023]47 *
[626]48 * @author imi
49 */
[12517]50public class DeleteAction extends MapMode implements ModifierExListener {
[8470]51 // Cache previous mouse event (needed when only the modifier keys are pressed but the mouse isn't moved)
[8840]52 private MouseEvent oldEvent;
[2026]53
[4327]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 */
[8840]59 private transient WaySegment oldHighlightedWaySegment;
[4327]60
[12537]61 private static final HighlightHelper HIGHLIGHT_HELPER = new HighlightHelper();
[4327]62 private boolean drawTargetHighlight;
63
[11978]64 enum DeleteMode {
[7668]65 none(/* ICON(cursor/modifier/) */ "delete"),
66 segment(/* ICON(cursor/modifier/) */ "delete_segment"),
67 node(/* ICON(cursor/modifier/) */ "delete_node"),
68 node_with_references(/* ICON(cursor/modifier/) */ "delete_node"),
69 way(/* ICON(cursor/modifier/) */ "delete_way_only"),
70 way_with_references(/* ICON(cursor/modifier/) */ "delete_way_normal"),
71 way_with_nodes(/* ICON(cursor/modifier/) */ "delete_way_node_only");
[2026]72
[2521]73 private final Cursor c;
74
[8674]75 DeleteMode(String cursorName) {
[2521]76 c = ImageProvider.getCursor("normal", cursorName);
77 }
78
[11978]79 /**
80 * Returns the mode cursor.
81 * @return the mode cursor
82 */
[2026]83 public Cursor cursor() {
84 return c;
85 }
86 }
87
[2521]88 private static class DeleteParameters {
[8285]89 private DeleteMode mode;
90 private Node nearestNode;
91 private WaySegment nearestSegment;
[2521]92 }
93
[1169]94 /**
95 * Construct a new DeleteAction. Mnemonic is the delete - key.
[11713]96 * @since 11713
[1169]97 */
[11713]98 public DeleteAction() {
[1169]99 super(tr("Delete Mode"),
100 "delete",
101 tr("Delete nodes or ways."),
[8510]102 Shortcut.registerShortcut("mapmode:delete", tr("Mode: {0}", tr("Delete")),
[4982]103 KeyEvent.VK_DELETE, Shortcut.CTRL),
[1169]104 ImageProvider.getCursor("normal", "delete"));
105 }
[626]106
[11713]107 @Override
108 public void enterMode() {
[1169]109 super.enterMode();
[1821]110 if (!isEnabled())
111 return;
[2026]112
[12846]113 drawTargetHighlight = Config.getPref().getBoolean("draw.target-highlight", true);
[4327]114
[12630]115 MapFrame map = MainApplication.getMap();
116 map.mapView.addMouseListener(this);
117 map.mapView.addMouseMotionListener(this);
[2026]118 // This is required to update the cursors when ctrl/shift/alt is pressed
[12630]119 map.keyDetector.addModifierExListener(this);
[1169]120 }
[626]121
[6889]122 @Override
123 public void exitMode() {
[1169]124 super.exitMode();
[12630]125 MapFrame map = MainApplication.getMap();
126 map.mapView.removeMouseListener(this);
127 map.mapView.removeMouseMotionListener(this);
128 map.keyDetector.removeModifierExListener(this);
[4327]129 removeHighlighting();
[1169]130 }
[626]131
[6889]132 @Override
133 public void actionPerformed(ActionEvent e) {
[1169]134 super.actionPerformed(e);
135 doActionPerformed(e);
136 }
[768]137
[7434]138 /**
139 * Invoked when the action occurs.
140 * @param e Action event
141 */
[12504]142 public void doActionPerformed(ActionEvent e) {
[12636]143 MainLayerManager lm = MainApplication.getLayerManager();
[10467]144 OsmDataLayer editLayer = lm.getEditLayer();
145 if (editLayer == null) {
[1169]146 return;
[10467]147 }
148
[12526]149 updateKeyModifiers(e);
[626]150
[1169]151 Command c;
152 if (ctrl) {
[12718]153 c = DeleteCommand.deleteWithReferences(lm.getEditDataSet().getSelected());
[1169]154 } else {
[12718]155 c = DeleteCommand.delete(lm.getEditDataSet().getSelected(), !alt /* also delete nodes in way */);
[1169]156 }
[5076]157 // if c is null, an error occurred or the user aborted. Don't do anything in that case.
[1169]158 if (c != null) {
[12641]159 MainApplication.undoRedo.add(c);
[10467]160 //FIXME: This should not be required, DeleteCommand should update the selection, otherwise undo/redo won't work.
[10448]161 lm.getEditDataSet().setSelected();
[1169]162 }
163 }
[626]164
[7434]165 @Override
166 public void mouseDragged(MouseEvent e) {
[2692]167 mouseMoved(e);
168 }
169
[1169]170 /**
[2026]171 * Listen to mouse move to be able to update the cursor (and highlights)
[5275]172 * @param e The mouse event that has been captured
[2026]173 */
[7434]174 @Override
175 public void mouseMoved(MouseEvent e) {
[2026]176 oldEvent = e;
[4327]177 giveUserFeedback(e);
[2026]178 }
179
180 /**
[4327]181 * removes any highlighting that may have been set beforehand.
182 */
[10448]183 private void removeHighlighting() {
[12537]184 HIGHLIGHT_HELPER.clear();
[10448]185 DataSet ds = getLayerManager().getEditDataSet();
[8510]186 if (ds != null) {
[4327]187 ds.clearHighlightedWaySegments();
188 }
189 }
190
191 /**
192 * handles everything related to highlighting primitives and way
[8470]193 * segments for the given pointer position (via MouseEvent) and modifiers.
194 * @param e current mouse event
[12517]195 * @param modifiers extended mouse modifiers, not necessarly taken from the given mouse event
[4327]196 */
197 private void addHighlighting(MouseEvent e, int modifiers) {
[8510]198 if (!drawTargetHighlight)
[4327]199 return;
200
[7005]201 Set<OsmPrimitive> newHighlights = new HashSet<>();
[4327]202 DeleteParameters parameters = getDeleteParameters(e, modifiers);
203
[8510]204 if (parameters.mode == DeleteMode.segment) {
[4327]205 // deleting segments is the only action not working on OsmPrimitives
206 // so we have to handle them separately.
[5102]207 repaintIfRequired(newHighlights, parameters.nearestSegment);
[4327]208 } else {
209 // don't call buildDeleteCommands for DeleteMode.segment because it doesn't support
210 // silent operation and SplitWayAction will show dialogs. A lot.
211 Command delCmd = buildDeleteCommands(e, modifiers, true);
[8510]212 if (delCmd != null) {
[8470]213 // all other cases delete OsmPrimitives directly, so we can safely do the following
[8510]214 for (OsmPrimitive osm : delCmd.getParticipatingPrimitives()) {
[5102]215 newHighlights.add(osm);
216 }
[4327]217 }
[5102]218 repaintIfRequired(newHighlights, null);
219 }
220 }
[4327]221
[5102]222 private void repaintIfRequired(Set<OsmPrimitive> newHighlights, WaySegment newHighlightedWaySegment) {
223 boolean needsRepaint = false;
[10467]224 OsmDataLayer editLayer = getLayerManager().getEditLayer();
[5102]225
[8510]226 if (newHighlightedWaySegment == null && oldHighlightedWaySegment != null) {
[10467]227 if (editLayer != null) {
228 editLayer.data.clearHighlightedWaySegments();
[5102]229 needsRepaint = true;
[4327]230 }
[5102]231 oldHighlightedWaySegment = null;
[8510]232 } else if (newHighlightedWaySegment != null && !newHighlightedWaySegment.equals(oldHighlightedWaySegment)) {
[10467]233 if (editLayer != null) {
234 editLayer.data.setHighlightedWaySegments(Collections.singleton(newHighlightedWaySegment));
[5102]235 needsRepaint = true;
236 }
237 oldHighlightedWaySegment = newHighlightedWaySegment;
[4327]238 }
[12537]239 needsRepaint |= HIGHLIGHT_HELPER.highlightOnly(newHighlights);
[10467]240 if (needsRepaint && editLayer != null) {
241 editLayer.invalidate();
[5101]242 }
243 }
244
[4327]245 /**
[8470]246 * This function handles all work related to updating the cursor and highlights
[2512]247 *
[8470]248 * @param e current mouse event
[12517]249 * @param modifiers extended mouse modifiers, not necessarly taken from the given mouse event
[2026]250 */
251 private void updateCursor(MouseEvent e, int modifiers) {
[12630]252 if (!MainApplication.isDisplayingMapView())
[2343]253 return;
[12630]254 MapFrame map = MainApplication.getMap();
255 if (!map.mapView.isActiveLayerVisible() || e == null)
[2026]256 return;
257
[2521]258 DeleteParameters parameters = getDeleteParameters(e, modifiers);
[12630]259 map.mapView.setNewCursor(parameters.mode.cursor(), this);
[2026]260 }
[7434]261
[4327]262 /**
263 * Gives the user feedback for the action he/she is about to do. Currently
264 * calls the cursor and target highlighting routines. Allows for modifiers
265 * not taken from the given mouse event.
[6069]266 *
[4327]267 * Normally the mouse event also contains the modifiers. However, when the
268 * mouse is not moved and only modifier keys are pressed, no mouse event
269 * occurs. We can use AWTEvent to catch those but still lack a proper
[8470]270 * mouseevent. Instead we copy the previous event and only update the modifiers.
[9230]271 * @param e mouse event
272 * @param modifiers mouse modifiers
[4327]273 */
274 private void giveUserFeedback(MouseEvent e, int modifiers) {
275 updateCursor(e, modifiers);
276 addHighlighting(e, modifiers);
277 }
[2026]278
279 /**
[4327]280 * Gives the user feedback for the action he/she is about to do. Currently
281 * calls the cursor and target highlighting routines. Extracts modifiers
282 * from mouse event.
[9230]283 * @param e mouse event
[4327]284 */
285 private void giveUserFeedback(MouseEvent e) {
[12517]286 giveUserFeedback(e, e.getModifiersEx());
[4327]287 }
288
289 /**
[1169]290 * If user clicked with the left button, delete the nearest object.
291 */
[7434]292 @Override
293 public void mouseReleased(MouseEvent e) {
[1169]294 if (e.getButton() != MouseEvent.BUTTON1)
295 return;
[12630]296 MapFrame map = MainApplication.getMap();
297 if (!map.mapView.isActiveLayerVisible())
[1169]298 return;
[1935]299
300 // request focus in order to enable the expected keyboard shortcuts
301 //
[12630]302 map.mapView.requestFocus();
[1935]303
[12526]304 Command c = buildDeleteCommands(e, e.getModifiersEx(), false);
[1169]305 if (c != null) {
[12641]306 MainApplication.undoRedo.add(c);
[1169]307 }
[626]308
[10382]309 getLayerManager().getEditDataSet().setSelected();
[4327]310 giveUserFeedback(e);
[1169]311 }
[1023]312
[7434]313 @Override
314 public String getModeHelpText() {
[8540]315 // CHECKSTYLE.OFF: LineLength
[2842]316 return tr("Click to delete. Shift: delete way segment. Alt: do not delete unused nodes when deleting a way. Ctrl: delete referring objects.");
[8540]317 // CHECKSTYLE.ON: LineLength
[1169]318 }
[1677]319
[7434]320 @Override
321 public boolean layerIsSupported(Layer l) {
[13434]322 return isEditableDataLayer(l);
[1379]323 }
[1821]324
325 @Override
326 protected void updateEnabledState() {
[12630]327 setEnabled(MainApplication.isDisplayingMapView() && MainApplication.getMap().mapView.isActiveLayerDrawable());
[1821]328 }
[1856]329
330 /**
[5275]331 * Deletes the relation in the context of the given layer.
[2512]332 *
[1856]333 * @param layer the layer in whose context the relation is deleted. Must not be null.
[9472]334 * @param toDelete the relation to be deleted. Must not be null.
[8291]335 * @throws IllegalArgumentException if layer is null
[9472]336 * @throws IllegalArgumentException if toDelete is null
[1856]337 */
338 public static void deleteRelation(OsmDataLayer layer, Relation toDelete) {
[9472]339 deleteRelations(layer, Collections.singleton(toDelete));
340 }
341
342 /**
343 * Deletes the relations in the context of the given layer.
344 *
345 * @param layer the layer in whose context the relations are deleted. Must not be null.
[9496]346 * @param toDelete the relations to be deleted. Must not be null.
[9472]347 * @throws IllegalArgumentException if layer is null
348 * @throws IllegalArgumentException if toDelete is null
349 */
350 public static void deleteRelations(OsmDataLayer layer, Collection<Relation> toDelete) {
[2842]351 CheckParameterUtil.ensureParameterNotNull(layer, "layer");
352 CheckParameterUtil.ensureParameterNotNull(toDelete, "toDelete");
[2026]353
[12718]354 final Command cmd = DeleteCommand.delete(toDelete);
[1856]355 if (cmd != null) {
356 // cmd can be null if the user cancels dialogs DialogCommand displays
[12641]357 MainApplication.undoRedo.add(cmd);
[9472]358 for (Relation relation : toDelete) {
[9496]359 if (layer.data.getSelectedRelations().contains(relation)) {
360 layer.data.toggleSelected(relation);
[9472]361 }
362 RelationDialogManager.getRelationDialogManager().close(layer, relation);
[4475]363 }
[1856]364 }
365 }
[2026]366
[2521]367 private DeleteParameters getDeleteParameters(MouseEvent e, int modifiers) {
[12517]368 updateKeyModifiersEx(modifiers);
[2026]369
[2521]370 DeleteParameters result = new DeleteParameters();
371
[12630]372 MapView mapView = MainApplication.getMap().mapView;
373 result.nearestNode = mapView.getNearestNode(e.getPoint(), OsmPrimitive::isSelectable);
[2521]374 if (result.nearestNode == null) {
[12630]375 result.nearestSegment = mapView.getNearestWaySegment(e.getPoint(), OsmPrimitive::isSelectable);
[2521]376 if (result.nearestSegment != null) {
[2026]377 if (shift) {
[2521]378 result.mode = DeleteMode.segment;
[2026]379 } else if (ctrl) {
[2521]380 result.mode = DeleteMode.way_with_references;
[2026]381 } else {
[8510]382 result.mode = alt ? DeleteMode.way : DeleteMode.way_with_nodes;
[2026]383 }
[2521]384 } else {
385 result.mode = DeleteMode.none;
[2026]386 }
387 } else if (ctrl) {
[2521]388 result.mode = DeleteMode.node_with_references;
[2026]389 } else {
[2521]390 result.mode = DeleteMode.node;
[2026]391 }
392
[2521]393 return result;
[2026]394 }
395
396 /**
[2521]397 * This function takes any mouse event argument and builds the list of elements
398 * that should be deleted but does not actually delete them.
399 * @param e MouseEvent from which modifiers and position are taken
[5909]400 * @param modifiers For explanation, see {@link #updateCursor}
[8470]401 * @param silent Set to true if the user should not be bugged with additional dialogs
[5909]402 * @return delete command
[2521]403 */
404 private Command buildDeleteCommands(MouseEvent e, int modifiers, boolean silent) {
405 DeleteParameters parameters = getDeleteParameters(e, modifiers);
406 switch (parameters.mode) {
407 case node:
[12718]408 return DeleteCommand.delete(Collections.singleton(parameters.nearestNode), false, silent);
[2521]409 case node_with_references:
[12718]410 return DeleteCommand.deleteWithReferences(Collections.singleton(parameters.nearestNode), silent);
[2521]411 case segment:
[12718]412 return DeleteCommand.deleteWaySegment(parameters.nearestSegment);
[2521]413 case way:
[12718]414 return DeleteCommand.delete(Collections.singleton(parameters.nearestSegment.way), false, silent);
[2521]415 case way_with_nodes:
[12718]416 return DeleteCommand.delete(Collections.singleton(parameters.nearestSegment.way), true, silent);
[2521]417 case way_with_references:
[12718]418 return DeleteCommand.deleteWithReferences(Collections.singleton(parameters.nearestSegment.way), true);
[2521]419 default:
420 return null;
421 }
422 }
423
424 /**
[2026]425 * This is required to update the cursors when ctrl/shift/alt is pressed
426 */
[6084]427 @Override
[12517]428 public void modifiersExChanged(int modifiers) {
[7434]429 if (oldEvent == null)
[4327]430 return;
[7434]431 // We don't have a mouse event, so we pass the old mouse event but the new modifiers.
[7217]432 giveUserFeedback(oldEvent, modifiers);
[2026]433 }
[626]434}
Note: See TracBrowser for help on using the repository browser.