// This code has been adapted and copied from code that has been written by Immanuel Scholz and others for JOSM. // License: GPL. Copyright 2007 by Tim Haussmann package org.openstreetmap.josm.gui.bbox; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.util.Timer; import java.util.TimerTask; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JPanel; import javax.swing.KeyStroke; /** * This class controls the user input by listening to mouse and key events. * Currently implemented is: - zooming in and out with scrollwheel - zooming in * and centering by double clicking - selecting an area by clicking and dragging * the mouse * * @author Tim Haussmann */ public class SlippyMapControler extends MouseAdapter implements MouseMotionListener, MouseListener { /** A Timer for smoothly moving the map area */ private static final Timer timer = new Timer(true); /** Does the moving */ private MoveTask moveTask = new MoveTask(); /** How often to do the moving (milliseconds) */ private static long timerInterval = 20; /** The maximum speed (pixels per timer interval) */ private static final double MAX_SPEED = 20; /** The speed increase per timer interval when a cursor button is clicked */ private static final double ACCELERATION = 0.10; // start and end point of selection rectangle private Point iStartSelectionPoint; private Point iEndSelectionPoint; // the SlippyMapChooserComponent private final SlippyMapBBoxChooser iSlippyMapChooser; private SizeButton iSizeButton = null; private SourceButton iSourceButton = null; /** * Create a new OsmMapControl */ public SlippyMapControler(SlippyMapBBoxChooser navComp, JPanel contentPane, SizeButton sizeButton, SourceButton sourceButton) { this.iSlippyMapChooser = navComp; iSlippyMapChooser.addMouseListener(this); iSlippyMapChooser.addMouseMotionListener(this); String[] n = { ",", ".", "up", "right", "down", "left" }; int[] k = { KeyEvent.VK_COMMA, KeyEvent.VK_PERIOD, KeyEvent.VK_UP, KeyEvent.VK_RIGHT, KeyEvent.VK_DOWN, KeyEvent.VK_LEFT }; if (contentPane != null) { for (int i = 0; i < n.length; ++i) { contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put( KeyStroke.getKeyStroke(k[i], KeyEvent.CTRL_DOWN_MASK), "MapMover.Zoomer." + n[i]); } } iSizeButton = sizeButton; iSourceButton = sourceButton; InputMap inputMap = navComp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); ActionMap actionMap = navComp.getActionMap(); // map moving inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), "MOVE_RIGHT"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), "MOVE_LEFT"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "MOVE_UP"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "MOVE_DOWN"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), "STOP_MOVE_HORIZONTALLY"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), "STOP_MOVE_HORIZONTALLY"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "STOP_MOVE_VERTICALLY"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "STOP_MOVE_VERTICALLY"); // zooming. To avoid confusion about which modifier key to use, // we just add all keys left of the space bar inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK, false), "ZOOM_IN"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.META_DOWN_MASK, false), "ZOOM_IN"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.ALT_DOWN_MASK, false), "ZOOM_IN"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK, false), "ZOOM_OUT"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.META_DOWN_MASK, false), "ZOOM_OUT"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.ALT_DOWN_MASK, false), "ZOOM_OUT"); // action mapping actionMap.put("MOVE_RIGHT", new MoveXAction(1)); actionMap.put("MOVE_LEFT", new MoveXAction(-1)); actionMap.put("MOVE_UP", new MoveYAction(-1)); actionMap.put("MOVE_DOWN", new MoveYAction(1)); actionMap.put("STOP_MOVE_HORIZONTALLY", new MoveXAction(0)); actionMap.put("STOP_MOVE_VERTICALLY", new MoveYAction(0)); actionMap.put("ZOOM_IN", new ZoomInAction()); actionMap.put("ZOOM_OUT", new ZoomOutAction()); } /** * Start drawing the selection rectangle if it was the 1st button (left * button) */ @Override public void mousePressed(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { if (!iSizeButton.hit(e.getPoint())) { iStartSelectionPoint = e.getPoint(); iEndSelectionPoint = e.getPoint(); } } } public void mouseDragged(MouseEvent e) { if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == MouseEvent.BUTTON1_DOWN_MASK) { if (iStartSelectionPoint != null) { iEndSelectionPoint = e.getPoint(); iSlippyMapChooser.setSelection(iStartSelectionPoint, iEndSelectionPoint); } } } /** * When dragging the map change the cursor back to it's pre-move cursor. If * a double-click occurs center and zoom the map on the clicked location. */ @Override public void mouseReleased(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { int sourceButton = iSourceButton.hit(e.getPoint()); if (iSizeButton.hit(e.getPoint())) { iSizeButton.toggle(); iSlippyMapChooser.resizeSlippyMap(); } else if (sourceButton == SourceButton.HIDE_OR_SHOW) { iSourceButton.toggle(); iSlippyMapChooser.repaint(); } else if (sourceButton == SourceButton.MAPNIK || sourceButton == SourceButton.OSMARENDER || sourceButton == SourceButton.CYCLEMAP) { iSlippyMapChooser.toggleMapSource(sourceButton); } else { if (e.getClickCount() == 1) { iSlippyMapChooser.setSelection(iStartSelectionPoint, e.getPoint()); // reset the selections start and end iEndSelectionPoint = null; iStartSelectionPoint = null; } } } } public void mouseMoved(MouseEvent e) { } private class MoveXAction extends AbstractAction { int direction; public MoveXAction(int direction) { this.direction = direction; } public void actionPerformed(ActionEvent e) { moveTask.setDirectionX(direction); } } private class MoveYAction extends AbstractAction { int direction; public MoveYAction(int direction) { this.direction = direction; } public void actionPerformed(ActionEvent e) { moveTask.setDirectionY(direction); } } /** Moves the map depending on which cursor keys are pressed (or not) */ private class MoveTask extends TimerTask { /** The current x speed (pixels per timer interval) */ private double speedX = 1; /** The current y speed (pixels per timer interval) */ private double speedY = 1; /** The horizontal direction of movement, -1:left, 0:stop, 1:right */ private int directionX = 0; /** The vertical direction of movement, -1:up, 0:stop, 1:down */ private int directionY = 0; /** * Indicated if <code>moveTask</code> is currently enabled (periodically * executed via timer) or disabled */ protected boolean scheduled = false; protected void setDirectionX(int directionX) { this.directionX = directionX; updateScheduleStatus(); } protected void setDirectionY(int directionY) { this.directionY = directionY; updateScheduleStatus(); } private void updateScheduleStatus() { boolean newMoveTaskState = !(directionX == 0 && directionY == 0); if (newMoveTaskState != scheduled) { scheduled = newMoveTaskState; if (newMoveTaskState) { timer.schedule(this, 0, timerInterval); } else { // We have to create a new instance because rescheduling a // once canceled TimerTask is not possible moveTask = new MoveTask(); cancel(); // Stop this TimerTask } } } @Override public void run() { // update the x speed switch (directionX) { case -1: if (speedX > -1) { speedX = -1; } if (speedX > -1 * MAX_SPEED) { speedX -= ACCELERATION; } break; case 0: speedX = 0; break; case 1: if (speedX < 1) { speedX = 1; } if (speedX < MAX_SPEED) { speedX += ACCELERATION; } break; } // update the y speed switch (directionY) { case -1: if (speedY > -1) { speedY = -1; } if (speedY > -1 * MAX_SPEED) { speedY -= ACCELERATION; } break; case 0: speedY = 0; break; case 1: if (speedY < 1) { speedY = 1; } if (speedY < MAX_SPEED) { speedY += ACCELERATION; } break; } // move the map int moveX = (int) Math.floor(speedX); int moveY = (int) Math.floor(speedY); if (moveX != 0 || moveY != 0) { iSlippyMapChooser.moveMap(moveX, moveY); } } } private class ZoomInAction extends AbstractAction { public void actionPerformed(ActionEvent e) { iSlippyMapChooser.zoomIn(); } } private class ZoomOutAction extends AbstractAction { public void actionPerformed(ActionEvent e) { iSlippyMapChooser.zoomOut(); } } }