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.command.ChangeCommand; 019import org.openstreetmap.josm.command.Command; 020import org.openstreetmap.josm.command.SequenceCommand; 021import org.openstreetmap.josm.corrector.ReverseWayNoTagCorrector; 022import org.openstreetmap.josm.corrector.ReverseWayTagCorrector; 023import org.openstreetmap.josm.data.osm.DataSet; 024import org.openstreetmap.josm.data.osm.Node; 025import org.openstreetmap.josm.data.osm.OsmPrimitive; 026import org.openstreetmap.josm.data.osm.Way; 027import org.openstreetmap.josm.gui.MainApplication; 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 Way newWay; 044 private final Collection<Command> tagCorrectionCommands; 045 private final Command reverseCommand; 046 047 /** 048 * Create a new {@link ReverseWayResult} 049 * @param newWay The new way primitive 050 * @param tagCorrectionCommands The commands to correct the tags 051 * @param reverseCommand The command to reverse the way 052 */ 053 public ReverseWayResult(Way newWay, Collection<Command> tagCorrectionCommands, Command reverseCommand) { 054 this.newWay = newWay; 055 this.tagCorrectionCommands = tagCorrectionCommands; 056 this.reverseCommand = reverseCommand; 057 } 058 059 /** 060 * Gets the new way object 061 * @return The new, reversed way 062 */ 063 public Way getNewWay() { 064 return newWay; 065 } 066 067 /** 068 * Gets the commands that will be required to do a full way reversal including changing the tags 069 * @return The comamnds 070 */ 071 public Collection<Command> getCommands() { 072 List<Command> c = new ArrayList<>(); 073 c.addAll(tagCorrectionCommands); 074 c.add(reverseCommand); 075 return c; 076 } 077 078 /** 079 * Gets a single sequence command for reversing this way including changing the tags 080 * @return the command 081 */ 082 public Command getAsSequenceCommand() { 083 return new SequenceCommand(tr("Reverse way"), getCommands()); 084 } 085 086 /** 087 * Gets the basic reverse command that only changes the order of the nodes. 088 * @return The reorder nodes command 089 */ 090 public Command getReverseCommand() { 091 return reverseCommand; 092 } 093 094 /** 095 * Gets the command to change the tags of the way 096 * @return The command to reverse the tags 097 */ 098 public Collection<Command> getTagCorrectionCommands() { 099 return tagCorrectionCommands; 100 } 101 } 102 103 /** 104 * Creates a new {@link ReverseWayAction} and binds the shortcut 105 */ 106 public ReverseWayAction() { 107 super(tr("Reverse Ways"), "wayflip", tr("Reverse the direction of all selected ways."), 108 Shortcut.registerShortcut("tools:reverse", tr("Tool: {0}", tr("Reverse Ways")), KeyEvent.VK_R, Shortcut.DIRECT), true); 109 putValue("help", ht("/Action/ReverseWays")); 110 } 111 112 @Override 113 public void actionPerformed(ActionEvent e) { 114 DataSet ds = getLayerManager().getEditDataSet(); 115 if (!isEnabled() || ds == null) 116 return; 117 118 final Collection<Way> sel = new LinkedHashSet<>(ds.getSelectedWays()); 119 sel.removeIf(Way::isIncomplete); 120 if (sel.isEmpty()) { 121 new Notification( 122 tr("Please select at least one way.")) 123 .setIcon(JOptionPane.INFORMATION_MESSAGE) 124 .setDuration(Notification.TIME_SHORT) 125 .show(); 126 return; 127 } 128 129 Collection<Command> c = new LinkedList<>(); 130 for (Way w : sel) { 131 ReverseWayResult revResult; 132 try { 133 revResult = reverseWay(w); 134 } catch (UserCancelException ex) { 135 Logging.trace(ex); 136 return; 137 } 138 c.addAll(revResult.getCommands()); 139 } 140 MainApplication.undoRedo.add(new SequenceCommand(tr("Reverse Ways"), c)); 141 } 142 143 /** 144 * Reverses a given way. 145 * @param w the way 146 * @return the reverse command and the tag correction commands 147 * @throws UserCancelException if user cancels a reverse warning dialog 148 */ 149 public static ReverseWayResult reverseWay(Way w) throws UserCancelException { 150 ReverseWayNoTagCorrector.checkAndConfirmReverseWay(w); 151 Way wnew = new Way(w); 152 List<Node> nodesCopy = wnew.getNodes(); 153 Collections.reverse(nodesCopy); 154 wnew.setNodes(nodesCopy); 155 156 Collection<Command> corrCmds = Collections.<Command>emptyList(); 157 if (Config.getPref().getBoolean("tag-correction.reverse-way", true)) { 158 corrCmds = (new ReverseWayTagCorrector()).execute(w, wnew); 159 } 160 return new ReverseWayResult(wnew, corrCmds, new ChangeCommand(w, wnew)); 161 } 162 163 @Override 164 protected void updateEnabledState() { 165 updateEnabledStateOnCurrentSelection(); 166 } 167 168 @Override 169 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 170 setEnabled(selection.stream().anyMatch( 171 o -> o instanceof Way && !o.isIncomplete() && !o.getDataSet().isLocked())); 172 } 173}