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 }