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