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

Last change on this file since 16626 was 16626, checked in by simon04, 4 years ago

see #19334 - https://errorprone.info/bugpattern/ImmutableEnumChecker

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