/* * MapEventHandler.java * * Created on April 14, 2005, 9:34 AM */ package ika.gui; import ika.utils.FocusUtils; import java.awt.geom.*; import ika.map.tools.*; import java.awt.KeyboardFocusManager; import java.awt.event.KeyEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.util.ArrayList; /** * MapEventHandler - event listener for MapComponent. Receives key and mouse events. * @author Bernhard Jenny, Institute of Cartography, ETH Zurich */ public class MapEventHandler implements java.awt.event.MouseListener, java.awt.event.MouseMotionListener, java.awt.KeyEventDispatcher, MouseWheelListener { /** * Keep track whether the space key is pressed. */ private boolean spaceKeyDown = false; /** * Keep track whether the meta key is pressed. */ private boolean metaKeyDown = false; /** * Keep track whether the alt key is pressed. */ private boolean altKeyDown = false; /** * The MapTool that will be restored when a new MapTool is only temporarily * active. */ private MapTool temporarilySuspendedTool = null; /** * mapTool contains a reference on the currently active MapTool. * Exactly one MapTool can be active at any time, but mapTool can be null! */ private MapTool mapTool = null; /** * The MapComponent for which this MapEventHandler receives and treats events. */ private MapComponent mapComponent; /** * Keep track whether the user is currently dragging (i.e. move the mouse while * keeping the mouse button pressed). Avoid delegating mouseReleased and * mouseClicked events when dragging finishes. */ private boolean dragging = false; /** * Keep track whether the mouse is currently over the MapComponent. */ private boolean mouseOverComponent = false; private ArrayList mouseMotionListeners = new ArrayList(); /** * Creates a new instance of MapEventHandler * @param mapComponent The MapComponent for which this MapEventHandler receives events. */ public MapEventHandler (MapComponent mapComponent) { this.mapComponent = mapComponent; this.mapComponent.addMouseListener(this); this.mapComponent.addMouseMotionListener(this); this.mapComponent.addMouseWheelListener(this); KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager(); kfm.addKeyEventDispatcher(this); mapTool = new SelectionTool(mapComponent); } /** * Treat mouse pressed. Inform the current MapTool by calling its mouseDown method. * @param evt The mouse event to treat. */ public void mousePressed(java.awt.event.MouseEvent evt) { mouseOverComponent = true; dragging = false; if (this.mapTool != null) { mapTool.mouseDown(mapComponent.userToWorldSpace(evt.getPoint()), evt); } } /** * Treat mouse drag events. Inform the current MapTool by calling its * startDrag method if this is the first event in a dragging sequence. * Call updateDrag of the current MapTool if this is in the middle of a * dragging sequence. * @param evt The mouse event to treat. */ public void mouseDragged(java.awt.event.MouseEvent evt) { mouseOverComponent = true; final Point2D.Double point = mapComponent.userToWorldSpace(evt.getPoint()); if (this.mapTool != null) { if (dragging == false) { mapTool.startDrag(point, evt); } else { mapTool.updateDrag(point, evt); } } dragging = true; this.informMouseMotionListeners(point); } /** * Treat mouse released events. Inform the current MapTool by calling its * endDrag method if we have been dragging before. * @param evt The mouse event to treat. */ public void mouseReleased(java.awt.event.MouseEvent evt) { mouseOverComponent = true; if (this.mapTool != null && dragging == true) { mapTool.endDrag(mapComponent.userToWorldSpace(evt.getPoint()), evt); } dragging = false; } /** * Treat mouse clicked events. Inform the current MapTool by calling its * mouseClicked method. * @param evt The mouse event to treat. */ public void mouseClicked(java.awt.event.MouseEvent evt) { mouseOverComponent = true; if (this.mapTool != null) { mapTool.mouseClicked(mapComponent.userToWorldSpace(evt.getPoint()), evt); } dragging = false; } /** * Treat mouse entered events. Inform the current MapTool by calling its * mouseEntered method. * @param evt The mouse event to treat. */ public void mouseEntered(java.awt.event.MouseEvent evt) { mouseOverComponent = true; if (this.mapTool != null) { mapTool.mouseEntered(mapComponent.userToWorldSpace(evt.getPoint()), evt); } } /** * Treat mouse exited events. Inform the current MapTool by calling its * mouseExited method. * @param evt The mouse event to treat. */ public void mouseExited(java.awt.event.MouseEvent evt) { mouseOverComponent = false; if (this.mapTool != null) { mapTool.mouseExited(mapComponent.userToWorldSpace(evt.getPoint()), evt); } this.informMouseMotionListeners(null); } /** * Treat mouse moved events. Inform the current MapTool by calling its * mouseMoved method. * @param evt The mouse event to treat. */ public void mouseMoved(java.awt.event.MouseEvent evt) { mouseOverComponent = true; final Point2D.Double point = mapComponent.userToWorldSpace(evt.getPoint()); if (this.mapTool != null) { mapTool.mouseMoved(point, evt); } this.informMouseMotionListeners(point); } public void removeMouseMotionListener(MapToolMouseMotionListener listener) { mouseMotionListeners.remove(listener); } public void addMouseMotionListener(MapToolMouseMotionListener listener) { mouseMotionListeners.add(listener); } private void informMouseMotionListeners(Point2D.Double point) { for (int i = this.mouseMotionListeners.size() - 1; i >= 0; i--) { MapToolMouseMotionListener listener; listener = (MapToolMouseMotionListener)mouseMotionListeners.get(i); listener.mouseMoved(point, this.mapComponent); } } /** * Returns the currently active MapTool. May return null. * @return Returns a reference to the currently active MapTool. */ public MapTool getMapTool() { return mapTool; } /** * Set the active MapTool. * @param mapTool The new MapTool. * @param rememberCurrentTool If true, the current MapTool will be stored in * this.temporarilySuspendedTool. */ public void setMapTool(MapTool mapTool, boolean rememberCurrentTool) { if (rememberCurrentTool) { this.mapTool.pause(); temporarilySuspendedTool = this.mapTool; } else if (this.mapTool != null) { this.mapTool.deactivate(); } this.mapTool = mapTool; if (this.mapTool != null) { this.mapTool.activate(); this.mapTool.setDefaultCursor(); } } /** * Helper method that replaces the current MapTool by the previous MapTool * that has been temporarily suspended. */ private void restoreTemporarilySuspendedMapTool() { if (temporarilySuspendedTool != null){ setMapTool(temporarilySuspendedTool, false); this.mapTool.resume(); this.mapTool.setDefaultCursor(); } temporarilySuspendedTool = null; } /** * Determine the new MapTool based on the currently pressed keys. * @param keyCode The key code. * @return A new MapTool (not the current yet) that can be activated. */ private MapTool getNewMapTool(int keyCode) { final boolean zoomOutCurrent = mapTool instanceof ZoomOutTool; final boolean zoomInCurrent = mapTool instanceof ZoomInTool; final boolean panCurrent = mapTool instanceof PanTool; // pan tool with space key if (spaceKeyDown && !metaKeyDown && !altKeyDown && !panCurrent) { return new PanTool(mapComponent); } // zoom out with meta and alt key if (metaKeyDown && altKeyDown && !zoomOutCurrent){ return new ZoomOutTool(mapComponent); } // change to zoom out with alt key when zoom in was previous tool if (altKeyDown && zoomInCurrent){ return new ZoomOutTool(mapComponent); } // zoom in with meta key if (metaKeyDown && !zoomInCurrent) { return new ZoomInTool(mapComponent); } return null; } /** * Helper method that keeps track of the pressed special keys (space, meta * and alt key). * @param keyEvent The key event to analyze. */ private void updateKeyStates(KeyEvent keyEvent) { // update spaceKeyDown if (keyEvent.getKeyCode() == KeyEvent.VK_SPACE) { if (keyEvent.getID() == KeyEvent.KEY_RELEASED) { spaceKeyDown = false; } else if (keyEvent.getID() == KeyEvent.KEY_PRESSED) { spaceKeyDown = true; } } // update metaKeyDown if (keyEvent.getKeyCode() == KeyEvent.VK_META) { if (keyEvent.getID() == KeyEvent.KEY_RELEASED) { metaKeyDown = false; } else if (keyEvent.getID() == KeyEvent.KEY_PRESSED) { metaKeyDown = true; } } // update altKeyDown if (keyEvent.getKeyCode() == KeyEvent.VK_ALT) { if (keyEvent.getID() == KeyEvent.KEY_RELEASED) { altKeyDown = false; } else if (keyEvent.getID() == KeyEvent.KEY_PRESSED) { altKeyDown = true; } } } /** * Callback method required by KeyEventDispatcher interface. * This method receives key events before any other component can treat them. * The event can be consumed (return true) or be delegated to other * listeners (return false). * @param keyEvent The new key event. * @return True if the key event has been consumed, false otherwise. */ public boolean dispatchKeyEvent(KeyEvent keyEvent) { /* System.out.println("Modifiers: " + keyEvent.getModifiers()); System.out.println("Code: " + KeyEvent.getKeyText(keyEvent.getKeyCode())); System.out.println("Key ID: " + keyEvent.getID()); System.out.println("Meta: " + metaKeyDown); System.out.println("Alt: " + altKeyDown); System.out.println("Space: " + spaceKeyDown); System.out.println("Is Action Key: " + keyEvent.isActionKey()); System.out.println(); */ // remember the current key state. this.updateKeyStates(keyEvent); final boolean keyReleased = keyEvent.getID() == KeyEvent.KEY_RELEASED; final boolean keyPressed = keyEvent.getID() == KeyEvent.KEY_PRESSED; // treat backspace and delete keys final boolean isDeleteKey = keyEvent.getKeyCode() == KeyEvent.VK_DELETE; final boolean isBackspaceKey = keyEvent.getKeyCode() == KeyEvent.VK_BACK_SPACE; if (keyReleased && (isDeleteKey || isBackspaceKey)) { // make sure the parent window of the mapComponent owns the focus. if (!FocusUtils.parentWindowHasFocus(this.mapComponent)) { return false; } // make sure the component with the current focus does not react // on delete and backspace key strokes. E.g. text in editable text // fields could not be deleted anymore. if (FocusUtils.currentFocusOwnerListensForKey (keyEvent.getKeyCode())) { return false; } // ask the current map tool to treat the delete or backspace key event if (this.mapTool.keyEvent(keyEvent) == true) { return true; } // no other component is handling delete and backspace key strokes, // it is save to remove the currently selected objects from the map. boolean objectRemoved = this.mapComponent.removeSelectedGeoObjects(); if (objectRemoved) { this.mapComponent.addUndo("Delete"); return true; } } // give current map tool a chance to consume the key event if (this.mapTool.keyEvent(keyEvent) == true) { return true; } // the rest of this method changes the current map tool. // Only do this if the mouse is over the map component. if (!mouseOverComponent) { return false; } final boolean panCurrent = mapTool instanceof PanTool; final boolean panTemporarilySuspended = temporarilySuspendedTool instanceof PanTool; if (keyEvent.getKeyCode() == KeyEvent.VK_SPACE || keyEvent.getKeyCode() == KeyEvent.VK_META || keyEvent.getKeyCode() == KeyEvent.VK_ALT) { if (keyEvent.getKeyCode() == KeyEvent.VK_SPACE && keyReleased && panCurrent && !panTemporarilySuspended){ restoreTemporarilySuspendedMapTool(); return true; } MapTool newMapTool = this.getNewMapTool(keyEvent.getKeyCode()); if (newMapTool != null) { setMapTool(newMapTool, temporarilySuspendedTool==null); return true; } // restore previous tool if space, meta or alt key was released. if (keyReleased){ restoreTemporarilySuspendedMapTool(); } // consume space key to avoid problems with accessibility action key // should be done better. if (keyEvent.getKeyCode() == KeyEvent.VK_SPACE) { return true; } } return false; } public void mouseWheelMoved(MouseWheelEvent e) { int rotations = e.getWheelRotation(); System.out.println(e.getPoint()); Point2D.Double loc = mapComponent.userToWorldSpace(e.getPoint()); for (int i = 0; i < Math.abs(rotations); i++) { if (rotations < 0) { mapComponent.zoomIn(loc); } else { mapComponent.zoomOut(loc); } } } }