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.Collection;
010import java.util.HashSet;
011import java.util.LinkedList;
012
013import javax.swing.JOptionPane;
014
015import org.openstreetmap.josm.Main;
016import org.openstreetmap.josm.command.Command;
017import org.openstreetmap.josm.command.MoveCommand;
018import org.openstreetmap.josm.command.SequenceCommand;
019import org.openstreetmap.josm.data.osm.Node;
020import org.openstreetmap.josm.data.osm.OsmPrimitive;
021import org.openstreetmap.josm.data.osm.Way;
022import org.openstreetmap.josm.gui.Notification;
023import org.openstreetmap.josm.tools.Shortcut;
024
025/**
026 * Mirror the selected nodes or ways along the vertical axis
027 *
028 * Note: If a ways are selected, their nodes are mirrored
029 *
030 * @author Teemu Koskinen
031 */
032public final class MirrorAction extends JosmAction {
033
034    /**
035     * Constructs a new {@code MirrorAction}.
036     */
037    public MirrorAction() {
038        super(tr("Mirror"), "mirror", tr("Mirror selected nodes and ways."),
039                Shortcut.registerShortcut("tools:mirror", tr("Tool: {0}", tr("Mirror")),
040                        KeyEvent.VK_M, Shortcut.SHIFT), true);
041        putValue("help", ht("/Action/Mirror"));
042    }
043
044    @Override
045    public void actionPerformed(ActionEvent e) {
046        Collection<OsmPrimitive> sel = getCurrentDataSet().getSelected();
047        HashSet<Node> nodes = new HashSet<>();
048
049        for (OsmPrimitive osm : sel) {
050            if (osm instanceof Node) {
051                nodes.add((Node)osm);
052            } else if (osm instanceof Way) {
053                nodes.addAll(((Way)osm).getNodes());
054            }
055        }
056
057        if (nodes.isEmpty()) {
058            new Notification(
059                    tr("Please select at least one node or way."))
060                    .setIcon(JOptionPane.INFORMATION_MESSAGE)
061                    .setDuration(Notification.TIME_SHORT)
062                    .show();
063            return;
064        }
065
066        double minEast = 20000000000.0;
067        double maxEast = -20000000000.0;
068        for (Node n : nodes) {
069            double east = n.getEastNorth().east();
070            minEast = Math.min(minEast, east);
071            maxEast = Math.max(maxEast, east);
072        }
073        double middle = (minEast + maxEast) / 2;
074
075        Collection<Command> cmds = new LinkedList<>();
076
077        for (Node n : nodes) {
078            cmds.add(new MoveCommand(n, 2 * (middle - n.getEastNorth().east()), 0.0));
079        }
080
081        Main.main.undoRedo.add(new SequenceCommand(tr("Mirror"), cmds));
082        Main.map.repaint();
083    }
084
085    @Override
086    protected void updateEnabledState() {
087        if (getCurrentDataSet() == null) {
088            setEnabled(false);
089        } else {
090            updateEnabledState(getCurrentDataSet().getSelected());
091        }
092    }
093
094    @Override
095    protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
096        setEnabled(selection != null && !selection.isEmpty());
097    }
098}