/******************************************************************************* * Copyright (c) MOBAC developers * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. ******************************************************************************/ package mobac.gui.mapview.controller; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.util.Timer; import java.util.TimerTask; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.ComponentInputMap; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.KeyStroke; import mobac.gui.mapview.PreviewMap; /** * Implements the GUI logic for the preview map panel that manages the map * movement by mouse and actions triggered by key strokes. */ public class MapKeyboardController extends JMapController { /** 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; private final InputMap inputMap; public MapKeyboardController(PreviewMap map, boolean enabled) { super(map); inputMap = new ComponentInputMap(map); ActionMap actionMap = map.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"); // map selection inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.CTRL_DOWN_MASK, false), "PREVIOUS_MAP"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.META_DOWN_MASK, false), "PREVIOUS_MAP"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, InputEvent.ALT_DOWN_MASK, false), "PREVIOUS_MAP"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.CTRL_DOWN_MASK, false), "NEXT_MAP"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.META_DOWN_MASK, false), "NEXT_MAP"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, InputEvent.ALT_DOWN_MASK, false), "NEXT_MAP"); inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0, true), "REFRESH"); // action mapping actionMap.put("MOVE_RIGHT", new MoveRightAction()); actionMap.put("MOVE_LEFT", new MoveLeftAction()); actionMap.put("MOVE_UP", new MoveUpAction()); actionMap.put("MOVE_DOWN", new MoveDownAction()); actionMap.put("STOP_MOVE_HORIZONTALLY", new StopMoveHorizontallyAction()); actionMap.put("STOP_MOVE_VERTICALLY", new StopMoveVerticallyAction()); actionMap.put("ZOOM_IN", new ZoomInAction()); actionMap.put("ZOOM_OUT", new ZoomOutAction()); actionMap.put("NEXT_MAP", new NextMapAction()); actionMap.put("PREVIOUS_MAP", new PreviousMapAction()); actionMap.put("REFRESH", new RefreshAction()); if (enabled) enable(); } @Override public void disable() { map.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, new ComponentInputMap(map)); } @Override public void enable() { map.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, inputMap); } private class MoveRightAction extends AbstractAction { private static final long serialVersionUID = -6758721144600926744L; public void actionPerformed(ActionEvent e) { moveTask.setDirectionX(1); } } private class MoveLeftAction extends AbstractAction { private static final long serialVersionUID = 2695221718338284951L; public void actionPerformed(ActionEvent e) { moveTask.setDirectionX(-1); } } private class MoveUpAction extends AbstractAction { private static final long serialVersionUID = -8414310977137213707L; public void actionPerformed(ActionEvent e) { moveTask.setDirectionY(-1); } } private class MoveDownAction extends AbstractAction { private static final long serialVersionUID = -5360890019457799681L; public void actionPerformed(ActionEvent e) { moveTask.setDirectionY(1); } } private class StopMoveHorizontallyAction extends AbstractAction { private static final long serialVersionUID = -5360890019457799681L; public void actionPerformed(ActionEvent e) { moveTask.setDirectionX(0); } } private class StopMoveVerticallyAction extends AbstractAction { private static final long serialVersionUID = -5360890019457799681L; public void actionPerformed(ActionEvent e) { moveTask.setDirectionY(0); } } /** 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) map.moveMap(moveX, moveY); } } private class ZoomInAction extends AbstractAction { private static final long serialVersionUID = 1471739991027644588L; public void actionPerformed(ActionEvent e) { map.zoomIn(); } } private class ZoomOutAction extends AbstractAction { private static final long serialVersionUID = 1471739991027644588L; public void actionPerformed(ActionEvent e) { map.zoomOut(); } } private class PreviousMapAction extends AbstractAction { private static final long serialVersionUID = -1492075614917423363L; public void actionPerformed(ActionEvent e) { ((PreviewMap) map).selectPreviousMap(); } } private class NextMapAction extends AbstractAction { private static final long serialVersionUID = -1491235614917423363L; public void actionPerformed(ActionEvent e) { ((PreviewMap) map).selectNextMap(); } } private class RefreshAction extends AbstractAction { private static final long serialVersionUID = -7235666079485033823L; public void actionPerformed(ActionEvent e) { ((PreviewMap) map).refreshMap(); } } }