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