001 // This code has been adapted and copied from code that has been written by Immanuel Scholz and others for JOSM. 002 // License: GPL. Copyright 2007 by Tim Haussmann 003 package org.openstreetmap.josm.gui.bbox; 004 005 import java.awt.Point; 006 import java.awt.event.ActionEvent; 007 import java.awt.event.InputEvent; 008 import java.awt.event.KeyEvent; 009 import java.awt.event.MouseAdapter; 010 import java.awt.event.MouseEvent; 011 import java.awt.event.MouseListener; 012 import java.awt.event.MouseMotionListener; 013 import java.util.Timer; 014 import java.util.TimerTask; 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 024 /** 025 * This class controls the user input by listening to mouse and key events. 026 * Currently implemented is: - zooming in and out with scrollwheel - zooming in 027 * and centering by double clicking - selecting an area by clicking and dragging 028 * the mouse 029 * 030 * @author Tim Haussmann 031 */ 032 public class SlippyMapControler extends MouseAdapter implements MouseMotionListener, MouseListener { 033 034 /** A Timer for smoothly moving the map area */ 035 private static final Timer timer = new Timer(true); 036 037 /** Does the moving */ 038 private MoveTask moveTask = new MoveTask(); 039 040 /** How often to do the moving (milliseconds) */ 041 private static long timerInterval = 20; 042 043 /** The maximum speed (pixels per timer interval) */ 044 private static final double MAX_SPEED = 20; 045 046 /** The speed increase per timer interval when a cursor button is clicked */ 047 private static final double ACCELERATION = 0.10; 048 049 // start and end point of selection rectangle 050 private Point iStartSelectionPoint; 051 private Point iEndSelectionPoint; 052 053 // the SlippyMapChooserComponent 054 private final SlippyMapBBoxChooser iSlippyMapChooser; 055 056 private SizeButton iSizeButton = null; 057 private SourceButton iSourceButton = null; 058 059 private boolean isSelecting; 060 061 /** 062 * Create a new OsmMapControl 063 */ 064 public SlippyMapControler(SlippyMapBBoxChooser navComp, JPanel contentPane, SizeButton sizeButton, SourceButton sourceButton) { 065 this.iSlippyMapChooser = navComp; 066 iSlippyMapChooser.addMouseListener(this); 067 iSlippyMapChooser.addMouseMotionListener(this); 068 069 String[] n = { ",", ".", "up", "right", "down", "left" }; 070 int[] k = { KeyEvent.VK_COMMA, KeyEvent.VK_PERIOD, KeyEvent.VK_UP, KeyEvent.VK_RIGHT, KeyEvent.VK_DOWN, 071 KeyEvent.VK_LEFT }; 072 073 if (contentPane != null) { 074 for (int i = 0; i < n.length; ++i) { 075 contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( 076 KeyStroke.getKeyStroke(k[i], KeyEvent.CTRL_DOWN_MASK), "MapMover.Zoomer." + n[i]); 077 } 078 } 079 iSizeButton = sizeButton; 080 iSourceButton = sourceButton; 081 082 isSelecting = false; 083 084 InputMap inputMap = navComp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); 085 ActionMap actionMap = navComp.getActionMap(); 086 087 // map moving 088 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "MOVE_RIGHT"); 089 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "MOVE_LEFT"); 090 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "MOVE_UP"); 091 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "MOVE_DOWN"); 092 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "STOP_MOVE_HORIZONTALLY"); 093 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "STOP_MOVE_HORIZONTALLY"); 094 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "STOP_MOVE_VERTICALLY"); 095 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "STOP_MOVE_VERTICALLY"); 096 097 // zooming. To avoid confusion about which modifier key to use, 098 // we just add all keys left of the space bar 099 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK, false), "ZOOM_IN"); 100 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.META_DOWN_MASK, false), "ZOOM_IN"); 101 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK, false), "ZOOM_IN"); 102 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ADD, 0, false), "ZOOM_IN"); 103 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK, false), "ZOOM_OUT"); 104 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.META_DOWN_MASK, false), "ZOOM_OUT"); 105 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK, false), "ZOOM_OUT"); 106 inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, 0, false), "ZOOM_OUT"); 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 /** 120 * Start drawing the selection rectangle if it was the 1st button (left 121 * button) 122 */ 123 @Override 124 public void mousePressed(MouseEvent e) { 125 if (e.getButton() == MouseEvent.BUTTON1) { 126 if (!iSizeButton.hit(e.getPoint())) { 127 iStartSelectionPoint = e.getPoint(); 128 iEndSelectionPoint = e.getPoint(); 129 } 130 } 131 132 } 133 134 @Override 135 public void mouseDragged(MouseEvent e) { 136 if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == MouseEvent.BUTTON1_DOWN_MASK) { 137 if (iStartSelectionPoint != null) { 138 iEndSelectionPoint = e.getPoint(); 139 iSlippyMapChooser.setSelection(iStartSelectionPoint, iEndSelectionPoint); 140 isSelecting = true; 141 } 142 } 143 } 144 145 /** 146 * When dragging the map change the cursor back to it's pre-move cursor. If 147 * a double-click occurs center and zoom the map on the clicked location. 148 */ 149 @Override 150 public void mouseReleased(MouseEvent e) { 151 if (e.getButton() == MouseEvent.BUTTON1) { 152 153 if (isSelecting && e.getClickCount() == 1) { 154 iSlippyMapChooser.setSelection(iStartSelectionPoint, e.getPoint()); 155 156 // reset the selections start and end 157 iEndSelectionPoint = null; 158 iStartSelectionPoint = null; 159 isSelecting = false; 160 161 } else { 162 int sourceButton = iSourceButton.hit(e.getPoint()); 163 164 if (iSizeButton.hit(e.getPoint())) { 165 iSizeButton.toggle(); 166 iSlippyMapChooser.resizeSlippyMap(); 167 } else if (iSlippyMapChooser.handleAttribution(e.getPoint(), true)) { 168 /* do nothing, handleAttribution() already did the work */ 169 } else if (sourceButton == SourceButton.HIDE_OR_SHOW) { 170 iSourceButton.toggle(); 171 iSlippyMapChooser.repaint(); 172 } else if (sourceButton != 0) { 173 iSlippyMapChooser.toggleMapSource(iSourceButton.hitToTileSource(sourceButton)); 174 } 175 } 176 } 177 } 178 179 @Override 180 public void mouseMoved(MouseEvent e) { 181 iSlippyMapChooser.handleAttribution(e.getPoint(), false); 182 } 183 184 private class MoveXAction extends AbstractAction { 185 186 int direction; 187 188 public MoveXAction(int direction) { 189 this.direction = direction; 190 } 191 192 public void actionPerformed(ActionEvent e) { 193 moveTask.setDirectionX(direction); 194 } 195 } 196 197 private class MoveYAction extends AbstractAction { 198 199 int direction; 200 201 public MoveYAction(int direction) { 202 this.direction = direction; 203 } 204 205 public void actionPerformed(ActionEvent e) { 206 moveTask.setDirectionY(direction); 207 } 208 } 209 210 /** Moves the map depending on which cursor keys are pressed (or not) */ 211 private class MoveTask extends TimerTask { 212 /** The current x speed (pixels per timer interval) */ 213 private double speedX = 1; 214 215 /** The current y speed (pixels per timer interval) */ 216 private double speedY = 1; 217 218 /** The horizontal direction of movement, -1:left, 0:stop, 1:right */ 219 private int directionX = 0; 220 221 /** The vertical direction of movement, -1:up, 0:stop, 1:down */ 222 private int directionY = 0; 223 224 /** 225 * Indicated if <code>moveTask</code> is currently enabled (periodically 226 * executed via timer) or disabled 227 */ 228 protected boolean scheduled = false; 229 230 protected void setDirectionX(int directionX) { 231 this.directionX = directionX; 232 updateScheduleStatus(); 233 } 234 235 protected void setDirectionY(int directionY) { 236 this.directionY = directionY; 237 updateScheduleStatus(); 238 } 239 240 private void updateScheduleStatus() { 241 boolean newMoveTaskState = !(directionX == 0 && directionY == 0); 242 243 if (newMoveTaskState != scheduled) { 244 scheduled = newMoveTaskState; 245 if (newMoveTaskState) { 246 timer.schedule(this, 0, timerInterval); 247 } else { 248 // We have to create a new instance because rescheduling a 249 // once canceled TimerTask is not possible 250 moveTask = new MoveTask(); 251 cancel(); // Stop this TimerTask 252 } 253 } 254 } 255 256 @Override 257 public void run() { 258 // update the x speed 259 switch (directionX) { 260 case -1: 261 if (speedX > -1) { 262 speedX = -1; 263 } 264 if (speedX > -1 * MAX_SPEED) { 265 speedX -= ACCELERATION; 266 } 267 break; 268 case 0: 269 speedX = 0; 270 break; 271 case 1: 272 if (speedX < 1) { 273 speedX = 1; 274 } 275 if (speedX < MAX_SPEED) { 276 speedX += ACCELERATION; 277 } 278 break; 279 } 280 281 // update the y speed 282 switch (directionY) { 283 case -1: 284 if (speedY > -1) { 285 speedY = -1; 286 } 287 if (speedY > -1 * MAX_SPEED) { 288 speedY -= ACCELERATION; 289 } 290 break; 291 case 0: 292 speedY = 0; 293 break; 294 case 1: 295 if (speedY < 1) { 296 speedY = 1; 297 } 298 if (speedY < MAX_SPEED) { 299 speedY += ACCELERATION; 300 } 301 break; 302 } 303 304 // move the map 305 int moveX = (int) Math.floor(speedX); 306 int moveY = (int) Math.floor(speedY); 307 if (moveX != 0 || moveY != 0) { 308 iSlippyMapChooser.moveMap(moveX, moveY); 309 } 310 } 311 } 312 313 private class ZoomInAction extends AbstractAction { 314 315 public void actionPerformed(ActionEvent e) { 316 iSlippyMapChooser.zoomIn(); 317 } 318 } 319 320 private class ZoomOutAction extends AbstractAction { 321 322 public void actionPerformed(ActionEvent e) { 323 iSlippyMapChooser.zoomOut(); 324 } 325 } 326 }