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