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.ChangeCommand;
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 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        setHelpId(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        UndoRedoHandler.getInstance().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}