001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Cursor; 007import java.awt.Point; 008import java.awt.event.ActionEvent; 009import java.awt.event.KeyEvent; 010import java.awt.event.MouseAdapter; 011import java.awt.event.MouseEvent; 012import java.awt.event.MouseMotionListener; 013import java.awt.event.MouseWheelEvent; 014import java.awt.event.MouseWheelListener; 015 016import javax.swing.AbstractAction; 017import javax.swing.ActionMap; 018import javax.swing.InputMap; 019import javax.swing.JComponent; 020import javax.swing.JPanel; 021import javax.swing.KeyStroke; 022 023import org.openstreetmap.josm.Main; 024import org.openstreetmap.josm.actions.mapmode.SelectAction; 025import org.openstreetmap.josm.data.coor.EastNorth; 026import org.openstreetmap.josm.tools.Destroyable; 027import org.openstreetmap.josm.tools.Shortcut; 028 029/** 030 * Enables moving of the map by holding down the right mouse button and drag 031 * the mouse. Also, enables zooming by the mouse wheel. 032 * 033 * @author imi 034 */ 035public class MapMover extends MouseAdapter implements MouseMotionListener, MouseWheelListener, Destroyable { 036 037 private final class ZoomerAction extends AbstractAction { 038 private final String action; 039 public ZoomerAction(String action) { 040 this.action = action; 041 } 042 @Override 043 public void actionPerformed(ActionEvent e) { 044 if (".".equals(action) || ",".equals(action)) { 045 Point mouse = nc.getMousePosition(); 046 if (mouse == null) 047 mouse = new Point((int)nc.getBounds().getCenterX(), (int)nc.getBounds().getCenterY()); 048 MouseWheelEvent we = new MouseWheelEvent(nc, e.getID(), e.getWhen(), e.getModifiers(), mouse.x, mouse.y, 0, false, 049 MouseWheelEvent.WHEEL_UNIT_SCROLL, 1, ",".equals(action) ? -1 : 1); 050 mouseWheelMoved(we); 051 } else { 052 EastNorth center = nc.getCenter(); 053 EastNorth newcenter = nc.getEastNorth(nc.getWidth()/2+nc.getWidth()/5, nc.getHeight()/2+nc.getHeight()/5); 054 switch(action) { 055 case "left": 056 nc.zoomTo(new EastNorth(2*center.east()-newcenter.east(), center.north())); 057 break; 058 case "right": 059 nc.zoomTo(new EastNorth(newcenter.east(), center.north())); 060 break; 061 case "up": 062 nc.zoomTo(new EastNorth(center.east(), 2*center.north()-newcenter.north())); 063 break; 064 case "down": 065 nc.zoomTo(new EastNorth(center.east(), newcenter.north())); 066 break; 067 } 068 } 069 } 070 } 071 072 /** 073 * The point in the map that was the under the mouse point 074 * when moving around started. 075 */ 076 private EastNorth mousePosMove; 077 /** 078 * The map to move around. 079 */ 080 private final NavigatableComponent nc; 081 private final JPanel contentPane; 082 083 private boolean movementInPlace = false; 084 085 /** 086 * Constructs a new {@code MapMover}. 087 * @param navComp the navigatable component 088 * @param contentPane the content pane 089 */ 090 public MapMover(NavigatableComponent navComp, JPanel contentPane) { 091 this.nc = navComp; 092 this.contentPane = contentPane; 093 nc.addMouseListener(this); 094 nc.addMouseMotionListener(this); 095 nc.addMouseWheelListener(this); 096 097 if (contentPane != null) { 098 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 099 Shortcut.registerShortcut("system:movefocusright", tr("Map: {0}", tr("Move right")), KeyEvent.VK_RIGHT, Shortcut.CTRL).getKeyStroke(), 100 "MapMover.Zoomer.right"); 101 contentPane.getActionMap().put("MapMover.Zoomer.right", new ZoomerAction("right")); 102 103 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 104 Shortcut.registerShortcut("system:movefocusleft", tr("Map: {0}", tr("Move left")), KeyEvent.VK_LEFT, Shortcut.CTRL).getKeyStroke(), 105 "MapMover.Zoomer.left"); 106 contentPane.getActionMap().put("MapMover.Zoomer.left", new ZoomerAction("left")); 107 108 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 109 Shortcut.registerShortcut("system:movefocusup", tr("Map: {0}", tr("Move up")), KeyEvent.VK_UP, Shortcut.CTRL).getKeyStroke(), 110 "MapMover.Zoomer.up"); 111 contentPane.getActionMap().put("MapMover.Zoomer.up", new ZoomerAction("up")); 112 113 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 114 Shortcut.registerShortcut("system:movefocusdown", tr("Map: {0}", tr("Move down")), KeyEvent.VK_DOWN, Shortcut.CTRL).getKeyStroke(), 115 "MapMover.Zoomer.down"); 116 contentPane.getActionMap().put("MapMover.Zoomer.down", new ZoomerAction("down")); 117 118 // see #10592 - Disable these alternate shortcuts on OS X because of conflict with system shortcut 119 if (!Main.isPlatformOsx()) { 120 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 121 Shortcut.registerShortcut("view:zoominalternate", tr("Map: {0}", tr("Zoom in")), KeyEvent.VK_COMMA, Shortcut.CTRL).getKeyStroke(), 122 "MapMover.Zoomer.in"); 123 contentPane.getActionMap().put("MapMover.Zoomer.in", new ZoomerAction(",")); 124 125 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 126 Shortcut.registerShortcut("view:zoomoutalternate", tr("Map: {0}", tr("Zoom out")), KeyEvent.VK_PERIOD, Shortcut.CTRL).getKeyStroke(), 127 "MapMover.Zoomer.out"); 128 contentPane.getActionMap().put("MapMover.Zoomer.out", new ZoomerAction(".")); 129 } 130 } 131 } 132 133 /** 134 * If the right (and only the right) mouse button is pressed, move the map. 135 */ 136 @Override 137 public void mouseDragged(MouseEvent e) { 138 int offMask = MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK; 139 int macMouseMask = MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK; 140 boolean stdMovement = (e.getModifiersEx() & (MouseEvent.BUTTON3_DOWN_MASK | offMask)) == MouseEvent.BUTTON3_DOWN_MASK; 141 boolean macMovement = Main.isPlatformOsx() && e.getModifiersEx() == macMouseMask; 142 boolean allowedMode = !Main.map.mapModeSelect.equals(Main.map.mapMode) 143 || SelectAction.Mode.SELECT.equals(Main.map.mapModeSelect.getMode()); 144 if (stdMovement || (macMovement && allowedMode)) { 145 if (mousePosMove == null) 146 startMovement(e); 147 EastNorth center = nc.getCenter(); 148 EastNorth mouseCenter = nc.getEastNorth(e.getX(), e.getY()); 149 nc.zoomTo(new EastNorth( 150 mousePosMove.east() + center.east() - mouseCenter.east(), 151 mousePosMove.north() + center.north() - mouseCenter.north())); 152 } else { 153 endMovement(); 154 } 155 } 156 157 /** 158 * Start the movement, if it was the 3rd button (right button). 159 */ 160 @Override 161 public void mousePressed(MouseEvent e) { 162 int offMask = MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK; 163 int macMouseMask = MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK; 164 if (e.getButton() == MouseEvent.BUTTON3 && (e.getModifiersEx() & offMask) == 0 || 165 Main.isPlatformOsx() && e.getModifiersEx() == macMouseMask) { 166 startMovement(e); 167 } 168 } 169 170 /** 171 * Change the cursor back to it's pre-move cursor. 172 */ 173 @Override 174 public void mouseReleased(MouseEvent e) { 175 if (e.getButton() == MouseEvent.BUTTON3 || Main.isPlatformOsx() && e.getButton() == MouseEvent.BUTTON1) { 176 endMovement(); 177 } 178 } 179 180 /** 181 * Start movement by setting a new cursor and remember the current mouse 182 * position. 183 * @param e The mouse event that leat to the movement from. 184 */ 185 private void startMovement(MouseEvent e) { 186 if (movementInPlace) 187 return; 188 movementInPlace = true; 189 mousePosMove = nc.getEastNorth(e.getX(), e.getY()); 190 nc.setNewCursor(Cursor.MOVE_CURSOR, this); 191 } 192 193 /** 194 * End the movement. Setting back the cursor and clear the movement variables 195 */ 196 private void endMovement() { 197 if (!movementInPlace) 198 return; 199 movementInPlace = false; 200 nc.resetCursor(this); 201 mousePosMove = null; 202 } 203 204 /** 205 * Zoom the map by 1/5th of current zoom per wheel-delta. 206 * @param e The wheel event. 207 */ 208 @Override 209 public void mouseWheelMoved(MouseWheelEvent e) { 210 nc.zoomToFactor(e.getX(), e.getY(), Math.pow(Math.sqrt(2), e.getWheelRotation())); 211 } 212 213 /** 214 * Emulates dragging on Mac OSX. 215 */ 216 @Override 217 public void mouseMoved(MouseEvent e) { 218 if (!movementInPlace) 219 return; 220 // Mac OSX simulates with ctrl + mouse 1 the second mouse button hence no dragging events get fired. 221 // Is only the selected mouse button pressed? 222 if (Main.isPlatformOsx()) { 223 if (e.getModifiersEx() == MouseEvent.CTRL_DOWN_MASK) { 224 if (mousePosMove == null) { 225 startMovement(e); 226 } 227 EastNorth center = nc.getCenter(); 228 EastNorth mouseCenter = nc.getEastNorth(e.getX(), e.getY()); 229 nc.zoomTo(new EastNorth(mousePosMove.east() + center.east() - mouseCenter.east(), mousePosMove.north() 230 + center.north() - mouseCenter.north())); 231 } else { 232 endMovement(); 233 } 234 } 235 } 236 237 @Override 238 public void destroy() { 239 if (this.contentPane != null) { 240 InputMap inputMap = contentPane.getInputMap(); 241 KeyStroke[] inputKeys = inputMap.keys(); 242 if (inputKeys != null) { 243 for (KeyStroke key : inputKeys) { 244 Object binding = inputMap.get(key); 245 if (binding instanceof String && ((String)binding).startsWith("MapMover.")) { 246 inputMap.remove(key); 247 } 248 } 249 } 250 ActionMap actionMap = contentPane.getActionMap(); 251 Object[] actionsKeys = actionMap.keys(); 252 if (actionsKeys != null) { 253 for (Object key : actionsKeys) { 254 if (key instanceof String && ((String)key).startsWith("MapMover.")) { 255 actionMap.remove(key); 256 } 257 } 258 } 259 } 260 } 261}