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.Locale; 011 012import javax.swing.JOptionPane; 013 014import org.openstreetmap.josm.command.MoveCommand; 015import org.openstreetmap.josm.data.UndoRedoHandler; 016import org.openstreetmap.josm.data.coor.EastNorth; 017import org.openstreetmap.josm.data.osm.DataSet; 018import org.openstreetmap.josm.data.osm.Node; 019import org.openstreetmap.josm.data.osm.OsmPrimitive; 020import org.openstreetmap.josm.data.osm.visitor.AllNodesVisitor; 021import org.openstreetmap.josm.gui.MainApplication; 022import org.openstreetmap.josm.gui.MapView; 023import org.openstreetmap.josm.tools.Shortcut; 024 025/** 026 * Moves the selection 027 * 028 * @author Frederik Ramm 029 */ 030public class MoveAction extends JosmAction { 031 032 /** 033 * Move direction. 034 */ 035 public enum Direction { 036 /** Move up */ 037 UP(tr("up"), "up", KeyEvent.VK_UP), 038 /* SHORTCUT(Move objects up, core:moveup, SHIFT, UP) */ 039 /** Move left */ 040 LEFT(tr("left"), "previous", KeyEvent.VK_LEFT), 041 /* SHORTCUT(Move objects left, core:moveleft, SHIFT, LEFT) */ 042 /** Move right */ 043 RIGHT(tr("right"), "next", KeyEvent.VK_RIGHT), 044 /* SHORTCUT(Move objects right, core:moveright, SHIFT, RIGHT) */ 045 /** Move down */ 046 DOWN(tr("down"), "down", KeyEvent.VK_DOWN); 047 /* SHORTCUT(Move objects down, core:movedown, SHIFT, DOWN) */ 048 049 private final String localizedName; 050 private final String icon; 051 private final int shortcutKey; 052 053 Direction(String localizedName, String icon, int shortcutKey) { 054 this.localizedName = localizedName; 055 this.icon = icon; 056 this.shortcutKey = shortcutKey; 057 } 058 059 String getId() { 060 return name().toLowerCase(Locale.ENGLISH); 061 } 062 063 String getLocalizedName() { 064 return localizedName; 065 } 066 067 String getIcon() { 068 return "dialogs/" + icon; 069 } 070 071 String getToolbarName() { 072 return "action/move/" + getId(); 073 } 074 075 int getShortcutKey() { 076 return shortcutKey; 077 } 078 079 Shortcut getShortcut() { 080 return Shortcut.registerShortcut(/* NO-SHORTCUT - adapt definition above when modified */ 081 "core:move" + getId(), tr("Move objects {0}", getLocalizedName()), getShortcutKey(), Shortcut.SHIFT); 082 } 083 } 084 085 private final Direction myDirection; 086 087 /** 088 * Constructs a new {@code MoveAction}. 089 * @param dir direction 090 */ 091 public MoveAction(Direction dir) { 092 super(tr("Move {0}", dir.getLocalizedName()), dir.getIcon(), 093 tr("Moves Objects {0}", dir.getLocalizedName()), 094 dir.getShortcut(), true, dir.getToolbarName(), true); 095 myDirection = dir; 096 setHelpId(ht("/Action/Move")); 097 } 098 099 /** 100 * Find out how many "real" units the objects have to be moved in order to achieve an 1-pixel movement 101 * @param mapView map view 102 * @return move offset 103 */ 104 private EastNorth getOffset(MapView mapView) { 105 EastNorth en1 = mapView.getEastNorth(100, 100); 106 EastNorth en2 = mapView.getEastNorth(101, 101); 107 108 double distx = en2.east() - en1.east(); 109 double disty = en2.north() - en1.north(); 110 111 switch (myDirection) { 112 case UP: 113 distx = 0; 114 disty = -disty; 115 break; 116 case DOWN: 117 distx = 0; 118 break; 119 case LEFT: 120 disty = 0; 121 distx = -distx; 122 break; 123 default: 124 disty = 0; 125 } 126 127 return new EastNorth(distx, disty); 128 } 129 130 @Override 131 public void actionPerformed(ActionEvent event) { 132 DataSet ds = getLayerManager().getEditDataSet(); 133 134 if (!MainApplication.isDisplayingMapView() || ds == null) 135 return; 136 137 MapView mapView = MainApplication.getMap().mapView; 138 final EastNorth dist = getOffset(mapView); 139 140 Collection<OsmPrimitive> selection = ds.getSelected(); 141 Collection<Node> affectedNodes = AllNodesVisitor.getAllNodes(selection); 142 143 MoveCommand cmd = ds.update(c -> { 144 MoveCommand moveCmd; 145 if (c instanceof MoveCommand && ds.equals(c.getAffectedDataSet()) 146 && affectedNodes.equals(((MoveCommand) c).getParticipatingPrimitives())) { 147 moveCmd = (MoveCommand) c; 148 moveCmd.moveAgain(dist.east(), dist.north()); 149 } else { 150 moveCmd = new MoveCommand(ds, selection, dist.east(), dist.north()); 151 UndoRedoHandler.getInstance().add(moveCmd); 152 } 153 return moveCmd; 154 }, UndoRedoHandler.getInstance().getLastCommand()); 155 156 for (Node n : affectedNodes) { 157 if (n.isLatLonKnown() && n.isOutSideWorld()) { 158 // Revert move 159 cmd.moveAgain(-dist.east(), -dist.north()); 160 JOptionPane.showMessageDialog( 161 MainApplication.getMainFrame(), 162 tr("Cannot move objects outside of the world."), 163 tr("Warning"), 164 JOptionPane.WARNING_MESSAGE 165 ); 166 return; 167 } 168 } 169 170 mapView.repaint(); 171 } 172 173 @Override 174 protected void updateEnabledState() { 175 updateEnabledStateOnCurrentSelection(); 176 } 177 178 @Override 179 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 180 updateEnabledStateOnModifiableSelection(selection); 181 } 182}