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.data.coor.EastNorth; 025import org.openstreetmap.josm.tools.Destroyable; 026import org.openstreetmap.josm.tools.PlatformHookOsx; 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 (action.equals(".") || action.equals(",")) { 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, MouseWheelEvent.WHEEL_UNIT_SCROLL, 1, action.equals(",") ? -1 : 1); 049 mouseWheelMoved(we); 050 } else { 051 EastNorth center = nc.getCenter(); 052 EastNorth newcenter = nc.getEastNorth(nc.getWidth()/2+nc.getWidth()/5, nc.getHeight()/2+nc.getHeight()/5); 053 if (action.equals("left")) 054 nc.zoomTo(new EastNorth(2*center.east()-newcenter.east(), center.north())); 055 else if (action.equals("right")) 056 nc.zoomTo(new EastNorth(newcenter.east(), center.north())); 057 else if (action.equals("up")) 058 nc.zoomTo(new EastNorth(center.east(), 2*center.north()-newcenter.north())); 059 else if (action.equals("down")) 060 nc.zoomTo(new EastNorth(center.east(), newcenter.north())); 061 } 062 } 063 } 064 065 /** 066 * The point in the map that was the under the mouse point 067 * when moving around started. 068 */ 069 private EastNorth mousePosMove; 070 /** 071 * The map to move around. 072 */ 073 private final NavigatableComponent nc; 074 private final JPanel contentPane; 075 076 private boolean movementInPlace = false; 077 078 /** 079 * Create a new MapMover 080 */ 081 public MapMover(NavigatableComponent navComp, JPanel contentPane) { 082 this.nc = navComp; 083 this.contentPane = contentPane; 084 nc.addMouseListener(this); 085 nc.addMouseMotionListener(this); 086 nc.addMouseWheelListener(this); 087 088 if (contentPane != null) { 089 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 090 Shortcut.registerShortcut("system:movefocusright", tr("Map: {0}", tr("Move right")), KeyEvent.VK_RIGHT, Shortcut.CTRL).getKeyStroke(), 091 "MapMover.Zoomer.right"); 092 contentPane.getActionMap().put("MapMover.Zoomer.right", new ZoomerAction("right")); 093 094 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 095 Shortcut.registerShortcut("system:movefocusleft", tr("Map: {0}", tr("Move left")), KeyEvent.VK_LEFT, Shortcut.CTRL).getKeyStroke(), 096 "MapMover.Zoomer.left"); 097 contentPane.getActionMap().put("MapMover.Zoomer.left", new ZoomerAction("left")); 098 099 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 100 Shortcut.registerShortcut("system:movefocusup", tr("Map: {0}", tr("Move up")), KeyEvent.VK_UP, Shortcut.CTRL).getKeyStroke(), 101 "MapMover.Zoomer.up"); 102 contentPane.getActionMap().put("MapMover.Zoomer.up", new ZoomerAction("up")); 103 104 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 105 Shortcut.registerShortcut("system:movefocusdown", tr("Map: {0}", tr("Move down")), KeyEvent.VK_DOWN, Shortcut.CTRL).getKeyStroke(), 106 "MapMover.Zoomer.down"); 107 contentPane.getActionMap().put("MapMover.Zoomer.down", new ZoomerAction("down")); 108 109 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 110 Shortcut.registerShortcut("view:zoominalternate", tr("Map: {0}", tr("Zoom in")), KeyEvent.VK_COMMA, Shortcut.CTRL).getKeyStroke(), 111 "MapMover.Zoomer.in"); 112 contentPane.getActionMap().put("MapMover.Zoomer.in", new ZoomerAction(",")); 113 114 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 115 Shortcut.registerShortcut("view:zoomoutalternate", tr("Map: {0}", tr("Zoom out")), KeyEvent.VK_PERIOD, Shortcut.CTRL).getKeyStroke(), 116 "MapMover.Zoomer.out"); 117 contentPane.getActionMap().put("MapMover.Zoomer.out", new ZoomerAction(".")); 118 } 119 } 120 121 /** 122 * If the right (and only the right) mouse button is pressed, move the map 123 */ 124 @Override 125 public void mouseDragged(MouseEvent e) { 126 int offMask = MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK; 127 if ((e.getModifiersEx() & (MouseEvent.BUTTON3_DOWN_MASK | offMask)) == MouseEvent.BUTTON3_DOWN_MASK) { 128 if (mousePosMove == null) 129 startMovement(e); 130 EastNorth center = nc.getCenter(); 131 EastNorth mouseCenter = nc.getEastNorth(e.getX(), e.getY()); 132 nc.zoomTo(new EastNorth( 133 mousePosMove.east() + center.east() - mouseCenter.east(), 134 mousePosMove.north() + center.north() - mouseCenter.north())); 135 } else 136 endMovement(); 137 } 138 139 /** 140 * Start the movement, if it was the 3rd button (right button). 141 */ 142 @Override public void mousePressed(MouseEvent e) { 143 int offMask = MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK; 144 int macMouseMask = MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK; 145 if (e.getButton() == MouseEvent.BUTTON3 && (e.getModifiersEx() & offMask) == 0) { 146 startMovement(e); 147 } else if (isPlatformOsx() && e.getModifiersEx() == macMouseMask) { 148 startMovement(e); 149 } 150 } 151 152 /** 153 * Change the cursor back to it's pre-move cursor. 154 */ 155 @Override public void mouseReleased(MouseEvent e) { 156 if (e.getButton() == MouseEvent.BUTTON3) { 157 endMovement(); 158 } else if (isPlatformOsx() && e.getButton() == MouseEvent.BUTTON1) { 159 endMovement(); 160 } 161 } 162 163 /** 164 * Start movement by setting a new cursor and remember the current mouse 165 * position. 166 * @param e The mouse event that leat to the movement from. 167 */ 168 private void startMovement(MouseEvent e) { 169 if (movementInPlace) 170 return; 171 movementInPlace = true; 172 mousePosMove = nc.getEastNorth(e.getX(), e.getY()); 173 nc.setNewCursor(Cursor.MOVE_CURSOR, this); 174 } 175 176 /** 177 * End the movement. Setting back the cursor and clear the movement variables 178 */ 179 private void endMovement() { 180 if (!movementInPlace) 181 return; 182 movementInPlace = false; 183 nc.resetCursor(this); 184 mousePosMove = null; 185 } 186 187 /** 188 * Zoom the map by 1/5th of current zoom per wheel-delta. 189 * @param e The wheel event. 190 */ 191 @Override 192 public void mouseWheelMoved(MouseWheelEvent e) { 193 nc.zoomToFactor(e.getX(), e.getY(), Math.pow(Math.sqrt(2), e.getWheelRotation())); 194 } 195 196 /** 197 * Emulates dragging on Mac OSX 198 */ 199 @Override 200 public void mouseMoved(MouseEvent e) { 201 if (!movementInPlace) 202 return; 203 // Mac OSX simulates with ctrl + mouse 1 the second mouse button hence no dragging events get fired. 204 // Is only the selected mouse button pressed? 205 if (isPlatformOsx()) { 206 if (e.getModifiersEx() == MouseEvent.CTRL_DOWN_MASK) { 207 if (mousePosMove == null) { 208 startMovement(e); 209 } 210 EastNorth center = nc.getCenter(); 211 EastNorth mouseCenter = nc.getEastNorth(e.getX(), e.getY()); 212 nc.zoomTo(new EastNorth(mousePosMove.east() + center.east() - mouseCenter.east(), mousePosMove.north() 213 + center.north() - mouseCenter.north())); 214 } else { 215 endMovement(); 216 } 217 } 218 } 219 220 /** 221 * Replies true if we are currently running on OSX 222 * 223 * @return true if we are currently running on OSX 224 */ 225 public static boolean isPlatformOsx() { 226 return Main.platform instanceof PlatformHookOsx; 227 } 228 229 @Override 230 public void destroy() { 231 if (this.contentPane != null) { 232 InputMap inputMap = contentPane.getInputMap(); 233 KeyStroke[] inputKeys = inputMap.keys(); 234 if (inputKeys != null) { 235 for (KeyStroke key : inputKeys) { 236 Object binding = inputMap.get(key); 237 if (binding instanceof String && ((String)binding).startsWith("MapMover.")) { 238 inputMap.remove(key); 239 } 240 } 241 } 242 ActionMap actionMap = contentPane.getActionMap(); 243 Object[] actionsKeys = actionMap.keys(); 244 if (actionsKeys != null) { 245 for (Object key : actionsKeys) { 246 if (key instanceof String && ((String)key).startsWith("MapMover.")) { 247 actionMap.remove(key); 248 } 249 } 250 } 251 } 252 } 253}