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    }