[6380] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[3094] | 2 | package org.openstreetmap.josm.gui.bbox;
|
---|
| 3 |
|
---|
| 4 | import java.awt.Point;
|
---|
| 5 | import java.awt.event.ActionEvent;
|
---|
| 6 | import java.awt.event.InputEvent;
|
---|
| 7 | import java.awt.event.KeyEvent;
|
---|
| 8 | import java.awt.event.MouseAdapter;
|
---|
| 9 | import java.awt.event.MouseEvent;
|
---|
| 10 | import java.util.Timer;
|
---|
| 11 | import java.util.TimerTask;
|
---|
| 12 |
|
---|
| 13 | import javax.swing.AbstractAction;
|
---|
| 14 | import javax.swing.ActionMap;
|
---|
| 15 | import javax.swing.InputMap;
|
---|
| 16 | import javax.swing.JComponent;
|
---|
| 17 | import javax.swing.JPanel;
|
---|
| 18 | import javax.swing.KeyStroke;
|
---|
[8285] | 19 |
|
---|
[7011] | 20 | import org.openstreetmap.josm.Main;
|
---|
[3094] | 21 |
|
---|
| 22 | /**
|
---|
| 23 | * This class controls the user input by listening to mouse and key events.
|
---|
| 24 | * Currently implemented is: - zooming in and out with scrollwheel - zooming in
|
---|
| 25 | * and centering by double clicking - selecting an area by clicking and dragging
|
---|
| 26 | * the mouse
|
---|
| 27 | *
|
---|
| 28 | * @author Tim Haussmann
|
---|
| 29 | */
|
---|
[10152] | 30 | public class SlippyMapControler extends MouseAdapter {
|
---|
[3094] | 31 |
|
---|
| 32 | /** A Timer for smoothly moving the map area */
|
---|
[12537] | 33 | private static final Timer TIMER = new Timer(true);
|
---|
[3094] | 34 |
|
---|
| 35 | /** Does the moving */
|
---|
| 36 | private MoveTask moveTask = new MoveTask();
|
---|
| 37 |
|
---|
| 38 | /** How often to do the moving (milliseconds) */
|
---|
| 39 | private static long timerInterval = 20;
|
---|
| 40 |
|
---|
| 41 | /** The maximum speed (pixels per timer interval) */
|
---|
| 42 | private static final double MAX_SPEED = 20;
|
---|
| 43 |
|
---|
| 44 | /** The speed increase per timer interval when a cursor button is clicked */
|
---|
| 45 | private static final double ACCELERATION = 0.10;
|
---|
[7509] | 46 |
|
---|
[7011] | 47 | private static final int MAC_MOUSE_BUTTON3_MASK = MouseEvent.CTRL_DOWN_MASK | MouseEvent.BUTTON1_DOWN_MASK;
|
---|
[3094] | 48 |
|
---|
[8846] | 49 | private static final String[] N = {
|
---|
| 50 | ",", ".", "up", "right", "down", "left"};
|
---|
| 51 | private static final int[] K = {
|
---|
| 52 | KeyEvent.VK_COMMA, KeyEvent.VK_PERIOD, KeyEvent.VK_UP, KeyEvent.VK_RIGHT, KeyEvent.VK_DOWN, KeyEvent.VK_LEFT};
|
---|
| 53 |
|
---|
[3094] | 54 | // start and end point of selection rectangle
|
---|
| 55 | private Point iStartSelectionPoint;
|
---|
| 56 | private Point iEndSelectionPoint;
|
---|
| 57 |
|
---|
| 58 | private final SlippyMapBBoxChooser iSlippyMapChooser;
|
---|
| 59 |
|
---|
[4474] | 60 | private boolean isSelecting;
|
---|
[3094] | 61 |
|
---|
| 62 | /**
|
---|
[6438] | 63 | * Constructs a new {@code SlippyMapControler}.
|
---|
[9243] | 64 | * @param navComp navigatable component
|
---|
| 65 | * @param contentPane content pane
|
---|
[3094] | 66 | */
|
---|
[6539] | 67 | public SlippyMapControler(SlippyMapBBoxChooser navComp, JPanel contentPane) {
|
---|
[6438] | 68 | iSlippyMapChooser = navComp;
|
---|
[3094] | 69 | iSlippyMapChooser.addMouseListener(this);
|
---|
| 70 | iSlippyMapChooser.addMouseMotionListener(this);
|
---|
| 71 |
|
---|
| 72 | if (contentPane != null) {
|
---|
[8846] | 73 | for (int i = 0; i < N.length; ++i) {
|
---|
[3094] | 74 | contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
|
---|
[8846] | 75 | KeyStroke.getKeyStroke(K[i], KeyEvent.CTRL_DOWN_MASK), "MapMover.Zoomer." + N[i]);
|
---|
[3094] | 76 | }
|
---|
| 77 | }
|
---|
[4474] | 78 | isSelecting = false;
|
---|
[3094] | 79 |
|
---|
| 80 | InputMap inputMap = navComp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
|
---|
| 81 | ActionMap actionMap = navComp.getActionMap();
|
---|
| 82 |
|
---|
| 83 | // map moving
|
---|
| 84 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "MOVE_RIGHT");
|
---|
| 85 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "MOVE_LEFT");
|
---|
| 86 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "MOVE_UP");
|
---|
| 87 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "MOVE_DOWN");
|
---|
| 88 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "STOP_MOVE_HORIZONTALLY");
|
---|
| 89 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "STOP_MOVE_HORIZONTALLY");
|
---|
| 90 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "STOP_MOVE_VERTICALLY");
|
---|
| 91 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "STOP_MOVE_VERTICALLY");
|
---|
| 92 |
|
---|
| 93 | // zooming. To avoid confusion about which modifier key to use,
|
---|
| 94 | // we just add all keys left of the space bar
|
---|
| 95 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK, false), "ZOOM_IN");
|
---|
| 96 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.META_DOWN_MASK, false), "ZOOM_IN");
|
---|
| 97 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK, false), "ZOOM_IN");
|
---|
[4866] | 98 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0, false), "ZOOM_IN");
|
---|
[6438] | 99 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, 0, false), "ZOOM_IN");
|
---|
[6710] | 100 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, 0, false), "ZOOM_IN");
|
---|
[6438] | 101 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, InputEvent.SHIFT_DOWN_MASK, false), "ZOOM_IN");
|
---|
[3094] | 102 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK, false), "ZOOM_OUT");
|
---|
| 103 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.META_DOWN_MASK, false), "ZOOM_OUT");
|
---|
| 104 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK, false), "ZOOM_OUT");
|
---|
[4866] | 105 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0, false), "ZOOM_OUT");
|
---|
[6438] | 106 | inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, 0, false), "ZOOM_OUT");
|
---|
[3094] | 107 |
|
---|
| 108 | // action mapping
|
---|
| 109 | actionMap.put("MOVE_RIGHT", new MoveXAction(1));
|
---|
| 110 | actionMap.put("MOVE_LEFT", new MoveXAction(-1));
|
---|
| 111 | actionMap.put("MOVE_UP", new MoveYAction(-1));
|
---|
| 112 | actionMap.put("MOVE_DOWN", new MoveYAction(1));
|
---|
| 113 | actionMap.put("STOP_MOVE_HORIZONTALLY", new MoveXAction(0));
|
---|
| 114 | actionMap.put("STOP_MOVE_VERTICALLY", new MoveYAction(0));
|
---|
| 115 | actionMap.put("ZOOM_IN", new ZoomInAction());
|
---|
| 116 | actionMap.put("ZOOM_OUT", new ZoomOutAction());
|
---|
| 117 | }
|
---|
| 118 |
|
---|
| 119 | /**
|
---|
[11386] | 120 | * Start drawing the selection rectangle if it was the 1st button (left button)
|
---|
[3094] | 121 | */
|
---|
| 122 | @Override
|
---|
| 123 | public void mousePressed(MouseEvent e) {
|
---|
[7011] | 124 | if (e.getButton() == MouseEvent.BUTTON1 && !(Main.isPlatformOsx() && e.getModifiersEx() == MAC_MOUSE_BUTTON3_MASK)) {
|
---|
[6539] | 125 | iStartSelectionPoint = e.getPoint();
|
---|
| 126 | iEndSelectionPoint = e.getPoint();
|
---|
[3094] | 127 | }
|
---|
| 128 | }
|
---|
| 129 |
|
---|
[3602] | 130 | @Override
|
---|
[3094] | 131 | public void mouseDragged(MouseEvent e) {
|
---|
[11452] | 132 | if (iStartSelectionPoint != null && (e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == MouseEvent.BUTTON1_DOWN_MASK
|
---|
| 133 | && !(Main.isPlatformOsx() && e.getModifiersEx() == MAC_MOUSE_BUTTON3_MASK)) {
|
---|
[11386] | 134 | iEndSelectionPoint = e.getPoint();
|
---|
| 135 | iSlippyMapChooser.setSelection(iStartSelectionPoint, iEndSelectionPoint);
|
---|
| 136 | isSelecting = true;
|
---|
[3094] | 137 | }
|
---|
| 138 | }
|
---|
| 139 |
|
---|
| 140 | /**
|
---|
| 141 | * When dragging the map change the cursor back to it's pre-move cursor. If
|
---|
| 142 | * a double-click occurs center and zoom the map on the clicked location.
|
---|
| 143 | */
|
---|
| 144 | @Override
|
---|
| 145 | public void mouseReleased(MouseEvent e) {
|
---|
| 146 | if (e.getButton() == MouseEvent.BUTTON1) {
|
---|
| 147 |
|
---|
[5925] | 148 | if (isSelecting && e.getClickCount() == 1) {
|
---|
[4474] | 149 | iSlippyMapChooser.setSelection(iStartSelectionPoint, e.getPoint());
|
---|
[3094] | 150 |
|
---|
[4474] | 151 | // reset the selections start and end
|
---|
| 152 | iEndSelectionPoint = null;
|
---|
| 153 | iStartSelectionPoint = null;
|
---|
| 154 | isSelecting = false;
|
---|
[6070] | 155 |
|
---|
[5925] | 156 | } else {
|
---|
[6539] | 157 | iSlippyMapChooser.handleAttribution(e.getPoint(), true);
|
---|
[5925] | 158 | }
|
---|
[3094] | 159 | }
|
---|
| 160 | }
|
---|
| 161 |
|
---|
[3602] | 162 | @Override
|
---|
[3094] | 163 | public void mouseMoved(MouseEvent e) {
|
---|
[4195] | 164 | iSlippyMapChooser.handleAttribution(e.getPoint(), false);
|
---|
[3094] | 165 | }
|
---|
| 166 |
|
---|
| 167 | private class MoveXAction extends AbstractAction {
|
---|
| 168 |
|
---|
[9078] | 169 | private final int direction;
|
---|
[3094] | 170 |
|
---|
[8836] | 171 | MoveXAction(int direction) {
|
---|
[3094] | 172 | this.direction = direction;
|
---|
| 173 | }
|
---|
| 174 |
|
---|
[6084] | 175 | @Override
|
---|
[3094] | 176 | public void actionPerformed(ActionEvent e) {
|
---|
| 177 | moveTask.setDirectionX(direction);
|
---|
| 178 | }
|
---|
| 179 | }
|
---|
| 180 |
|
---|
| 181 | private class MoveYAction extends AbstractAction {
|
---|
| 182 |
|
---|
[9078] | 183 | private final int direction;
|
---|
[3094] | 184 |
|
---|
[8836] | 185 | MoveYAction(int direction) {
|
---|
[3094] | 186 | this.direction = direction;
|
---|
| 187 | }
|
---|
| 188 |
|
---|
[6084] | 189 | @Override
|
---|
[3094] | 190 | public void actionPerformed(ActionEvent e) {
|
---|
| 191 | moveTask.setDirectionY(direction);
|
---|
| 192 | }
|
---|
| 193 | }
|
---|
| 194 |
|
---|
| 195 | /** Moves the map depending on which cursor keys are pressed (or not) */
|
---|
| 196 | private class MoveTask extends TimerTask {
|
---|
| 197 | /** The current x speed (pixels per timer interval) */
|
---|
| 198 | private double speedX = 1;
|
---|
| 199 |
|
---|
| 200 | /** The current y speed (pixels per timer interval) */
|
---|
| 201 | private double speedY = 1;
|
---|
| 202 |
|
---|
| 203 | /** The horizontal direction of movement, -1:left, 0:stop, 1:right */
|
---|
[8840] | 204 | private int directionX;
|
---|
[3094] | 205 |
|
---|
| 206 | /** The vertical direction of movement, -1:up, 0:stop, 1:down */
|
---|
[8840] | 207 | private int directionY;
|
---|
[3094] | 208 |
|
---|
| 209 | /**
|
---|
| 210 | * Indicated if <code>moveTask</code> is currently enabled (periodically
|
---|
| 211 | * executed via timer) or disabled
|
---|
| 212 | */
|
---|
[8840] | 213 | protected boolean scheduled;
|
---|
[3094] | 214 |
|
---|
| 215 | protected void setDirectionX(int directionX) {
|
---|
| 216 | this.directionX = directionX;
|
---|
| 217 | updateScheduleStatus();
|
---|
| 218 | }
|
---|
| 219 |
|
---|
| 220 | protected void setDirectionY(int directionY) {
|
---|
| 221 | this.directionY = directionY;
|
---|
| 222 | updateScheduleStatus();
|
---|
| 223 | }
|
---|
| 224 |
|
---|
| 225 | private void updateScheduleStatus() {
|
---|
| 226 | boolean newMoveTaskState = !(directionX == 0 && directionY == 0);
|
---|
| 227 |
|
---|
| 228 | if (newMoveTaskState != scheduled) {
|
---|
| 229 | scheduled = newMoveTaskState;
|
---|
| 230 | if (newMoveTaskState) {
|
---|
[12537] | 231 | TIMER.schedule(this, 0, timerInterval);
|
---|
[3094] | 232 | } else {
|
---|
| 233 | // We have to create a new instance because rescheduling a
|
---|
| 234 | // once canceled TimerTask is not possible
|
---|
| 235 | moveTask = new MoveTask();
|
---|
| 236 | cancel(); // Stop this TimerTask
|
---|
| 237 | }
|
---|
| 238 | }
|
---|
| 239 | }
|
---|
| 240 |
|
---|
| 241 | @Override
|
---|
| 242 | public void run() {
|
---|
| 243 | // update the x speed
|
---|
| 244 | switch (directionX) {
|
---|
| 245 | case -1:
|
---|
| 246 | if (speedX > -1) {
|
---|
| 247 | speedX = -1;
|
---|
| 248 | }
|
---|
| 249 | if (speedX > -1 * MAX_SPEED) {
|
---|
| 250 | speedX -= ACCELERATION;
|
---|
| 251 | }
|
---|
| 252 | break;
|
---|
| 253 | case 0:
|
---|
| 254 | speedX = 0;
|
---|
| 255 | break;
|
---|
| 256 | case 1:
|
---|
| 257 | if (speedX < 1) {
|
---|
| 258 | speedX = 1;
|
---|
| 259 | }
|
---|
| 260 | if (speedX < MAX_SPEED) {
|
---|
| 261 | speedX += ACCELERATION;
|
---|
| 262 | }
|
---|
| 263 | break;
|
---|
[10217] | 264 | default:
|
---|
| 265 | throw new IllegalStateException(Integer.toString(directionX));
|
---|
[3094] | 266 | }
|
---|
| 267 |
|
---|
| 268 | // update the y speed
|
---|
| 269 | switch (directionY) {
|
---|
| 270 | case -1:
|
---|
| 271 | if (speedY > -1) {
|
---|
| 272 | speedY = -1;
|
---|
| 273 | }
|
---|
| 274 | if (speedY > -1 * MAX_SPEED) {
|
---|
| 275 | speedY -= ACCELERATION;
|
---|
| 276 | }
|
---|
| 277 | break;
|
---|
| 278 | case 0:
|
---|
| 279 | speedY = 0;
|
---|
| 280 | break;
|
---|
| 281 | case 1:
|
---|
| 282 | if (speedY < 1) {
|
---|
| 283 | speedY = 1;
|
---|
| 284 | }
|
---|
| 285 | if (speedY < MAX_SPEED) {
|
---|
| 286 | speedY += ACCELERATION;
|
---|
| 287 | }
|
---|
| 288 | break;
|
---|
[10217] | 289 | default:
|
---|
| 290 | throw new IllegalStateException(Integer.toString(directionY));
|
---|
[3094] | 291 | }
|
---|
| 292 |
|
---|
| 293 | // move the map
|
---|
| 294 | int moveX = (int) Math.floor(speedX);
|
---|
| 295 | int moveY = (int) Math.floor(speedY);
|
---|
| 296 | if (moveX != 0 || moveY != 0) {
|
---|
| 297 | iSlippyMapChooser.moveMap(moveX, moveY);
|
---|
| 298 | }
|
---|
| 299 | }
|
---|
| 300 | }
|
---|
| 301 |
|
---|
| 302 | private class ZoomInAction extends AbstractAction {
|
---|
| 303 |
|
---|
[6084] | 304 | @Override
|
---|
[3094] | 305 | public void actionPerformed(ActionEvent e) {
|
---|
| 306 | iSlippyMapChooser.zoomIn();
|
---|
| 307 | }
|
---|
| 308 | }
|
---|
| 309 |
|
---|
| 310 | private class ZoomOutAction extends AbstractAction {
|
---|
| 311 |
|
---|
[6084] | 312 | @Override
|
---|
[3094] | 313 | public void actionPerformed(ActionEvent e) {
|
---|
| 314 | iSlippyMapChooser.zoomOut();
|
---|
| 315 | }
|
---|
| 316 | }
|
---|
| 317 | }
|
---|