001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.event.ActionEvent; 008import java.awt.event.KeyEvent; 009import java.util.ArrayList; 010import java.util.Collection; 011import java.util.Collections; 012import java.util.LinkedHashSet; 013import java.util.LinkedList; 014import java.util.List; 015 016import javax.swing.JOptionPane; 017 018import org.openstreetmap.josm.actions.corrector.ReverseWayNoTagCorrector; 019import org.openstreetmap.josm.actions.corrector.ReverseWayTagCorrector; 020import org.openstreetmap.josm.command.ChangeNodesCommand; 021import org.openstreetmap.josm.command.Command; 022import org.openstreetmap.josm.command.SequenceCommand; 023import org.openstreetmap.josm.data.UndoRedoHandler; 024import org.openstreetmap.josm.data.osm.DataSet; 025import org.openstreetmap.josm.data.osm.Node; 026import org.openstreetmap.josm.data.osm.OsmPrimitive; 027import org.openstreetmap.josm.data.osm.Way; 028import org.openstreetmap.josm.gui.Notification; 029import org.openstreetmap.josm.spi.preferences.Config; 030import org.openstreetmap.josm.tools.Logging; 031import org.openstreetmap.josm.tools.Shortcut; 032import org.openstreetmap.josm.tools.UserCancelException; 033 034/** 035 * Reverses the ways that are currently selected by the user 036 */ 037public final class ReverseWayAction extends JosmAction { 038 039 /** 040 * The resulting way after reversing it and the commands to get there. 041 */ 042 public static class ReverseWayResult { 043 private final Collection<Command> tagCorrectionCommands; 044 private final Command reverseCommand; 045 046 /** 047 * Create a new {@link ReverseWayResult} 048 * @param tagCorrectionCommands The commands to correct the tags 049 * @param reverseCommand The command to reverse the way 050 */ 051 public ReverseWayResult(Collection<Command> tagCorrectionCommands, Command reverseCommand) { 052 this.tagCorrectionCommands = tagCorrectionCommands; 053 this.reverseCommand = reverseCommand; 054 } 055 056 /** 057 * Gets the commands that will be required to do a full way reversal including changing the tags 058 * @return The commands 059 */ 060 public Collection<Command> getCommands() { 061 List<Command> c = new ArrayList<>(); 062 c.addAll(tagCorrectionCommands); 063 c.add(reverseCommand); 064 return c; 065 } 066 067 /** 068 * Gets a single sequence command for reversing this way including changing the tags 069 * @return the command 070 */ 071 public Command getAsSequenceCommand() { 072 return new SequenceCommand(tr("Reverse way"), getCommands()); 073 } 074 075 /** 076 * Gets the basic reverse command that only changes the order of the nodes. 077 * @return The reorder nodes command 078 */ 079 public Command getReverseCommand() { 080 return reverseCommand; 081 } 082 083 /** 084 * Gets the command to change the tags of the way 085 * @return The command to reverse the tags 086 */ 087 public Collection<Command> getTagCorrectionCommands() { 088 return tagCorrectionCommands; 089 } 090 } 091 092 /** 093 * Creates a new {@link ReverseWayAction} and binds the shortcut 094 */ 095 public ReverseWayAction() { 096 super(tr("Reverse Ways"), "wayflip", tr("Reverse the direction of all selected ways."), 097 Shortcut.registerShortcut("tools:reverse", tr("Tools: {0}", tr("Reverse Ways")), KeyEvent.VK_R, Shortcut.DIRECT), true); 098 setHelpId(ht("/Action/ReverseWays")); 099 } 100 101 @Override 102 public void actionPerformed(ActionEvent e) { 103 DataSet ds = getLayerManager().getEditDataSet(); 104 if (!isEnabled() || ds == null) 105 return; 106 107 final Collection<Way> sel = new LinkedHashSet<>(ds.getSelectedWays()); 108 sel.removeIf(w -> w.isIncomplete() || w.isEmpty()); 109 if (sel.isEmpty()) { 110 new Notification( 111 tr("Please select at least one way.")) 112 .setIcon(JOptionPane.INFORMATION_MESSAGE) 113 .setDuration(Notification.TIME_SHORT) 114 .show(); 115 return; 116 } 117 118 Collection<Command> c = new LinkedList<>(); 119 for (Way w : sel) { 120 try { 121 c.addAll(reverseWay(w).getCommands()); 122 } catch (IllegalArgumentException ex) { 123 Logging.error(ex); 124 } catch (UserCancelException ex) { 125 Logging.trace(ex); 126 return; 127 } 128 } 129 UndoRedoHandler.getInstance().add(new SequenceCommand(tr("Reverse Ways"), c)); 130 } 131 132 /** 133 * Reverses a given way. 134 * @param w the way 135 * @return the reverse command and the tag correction commands 136 * @throws IllegalArgumentException if sanity checks fail 137 * @throws UserCancelException if user cancels a reverse warning dialog 138 */ 139 public static ReverseWayResult reverseWay(Way w) throws UserCancelException { 140 ReverseWayNoTagCorrector.checkAndConfirmReverseWay(w); 141 List<Node> nodesCopy = w.getNodes(); 142 Collections.reverse(nodesCopy); 143 144 Collection<Command> corrCmds = Collections.<Command>emptyList(); 145 if (Config.getPref().getBoolean("tag-correction.reverse-way", true)) { 146 corrCmds = new ReverseWayTagCorrector().execute(w, w); 147 } 148 return new ReverseWayResult(corrCmds, new ChangeNodesCommand(w, new ArrayList<>(nodesCopy))); 149 } 150 151 @Override 152 protected void updateEnabledState() { 153 updateEnabledStateOnCurrentSelection(); 154 } 155 156 @Override 157 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 158 setEnabled(selection.stream().anyMatch( 159 o -> o instanceof Way && !o.isIncomplete() && !o.getDataSet().isLocked())); 160 } 161}