/* * Copyright (c) 2008-2011, Piccolo2D project, http://piccolo2d.org * Copyright (c) 1998-2008, University of Maryland * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided * that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, this list of conditions * and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of conditions * and the following disclaimer in the documentation and/or other materials provided with the * distribution. * * None of the name of the University of Maryland, the name of the Piccolo2D project, or the names of its * contributors may be used to endorse or promote products derived from this software without specific * prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package edu.umd.cs.piccolo; import java.awt.event.FocusEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.awt.geom.Point2D; import edu.umd.cs.piccolo.event.PBasicInputEventHandler; import edu.umd.cs.piccolo.event.PInputEvent; import edu.umd.cs.piccolo.event.PInputEventListener; import edu.umd.cs.piccolo.util.PPickPath; /** * <b>PInputManager</b> is responsible for dispatching PInputEvents to node's * event listeners. Events are dispatched from PRoot's processInputs method. * <P> * * @see edu.umd.cs.piccolo.event.PInputEvent * @see PRoot * @version 1.0 * @author Jesse Grosjean */ public class PInputManager extends PBasicInputEventHandler implements PRoot.InputSource { /** Records the last known mouse position on the canvas. */ private final Point2D lastCanvasPosition; /** Records the current known mouse position on the canvas. */ private final Point2D currentCanvasPosition; /** The next InputEvent that needs to be processed. */ private InputEvent nextInput; /** The type of the next InputEvent that needs to be processed. */ private int nextType; /** The Input Source the next event to process came from. */ private PCamera nextInputSource; /** The current mouse focus. */ private PPickPath mouseFocus; /** The previous mouse focus. */ private PPickPath previousMouseFocus; /** Tracks where the mouse is right now on the canvas. */ private PPickPath mouseOver; /** Tracks the previous location of the mouse on the canvas. */ private PPickPath previousMouseOver; /** Tracks the input event listener that should receive keyboard events. */ private PInputEventListener keyboardFocus; /** Tracks the number mouse buttons currently pressed. */ private int buttonsPressed; /** * Creates a PInputManager and sets positions (last, current) to the origin * (0,0). */ public PInputManager() { lastCanvasPosition = new Point2D.Double(); currentCanvasPosition = new Point2D.Double(); } /** * Return the node that currently has the keyboard focus. This node receives * the key events. * * @return the current keyboard focus */ public PInputEventListener getKeyboardFocus() { return keyboardFocus; } /** * Set the node that should receive key events. * * @param eventHandler sets the keyboard event focus, may be null */ public void setKeyboardFocus(final PInputEventListener eventHandler) { final PInputEvent focusEvent = new PInputEvent(this, null); if (keyboardFocus != null) { dispatchEventToListener(focusEvent, FocusEvent.FOCUS_LOST, keyboardFocus); } keyboardFocus = eventHandler; if (keyboardFocus != null) { dispatchEventToListener(focusEvent, FocusEvent.FOCUS_GAINED, keyboardFocus); } } /** * Return the current Pick Path under the mouse focus. This will return the * path that received the current mouse pressed event, or null if the mouse * is not pressed. The mouse focus gets mouse dragged events even what the * mouse is not over the mouse focus. * * @return the current Pick Path under the mouse focus */ public PPickPath getMouseFocus() { return mouseFocus; } /** * Sets the current Pick Path under the mouse focus. The mouse focus gets * mouse dragged events even when the mouse is not over the mouse focus. * * @param path the new mouse focus */ public void setMouseFocus(final PPickPath path) { previousMouseFocus = mouseFocus; mouseFocus = path; } /** * Return the node the the mouse is currently over. * * @return the path over which the mouse currently is */ public PPickPath getMouseOver() { return mouseOver; } /** * Records the path which is directly below the mouse. * * @param path path over which the mouse has been moved */ public void setMouseOver(final PPickPath path) { mouseOver = path; } /** * Returns the position on the Canvas of the last event. * * @return position of last canvas event */ public Point2D getLastCanvasPosition() { return lastCanvasPosition; } /** * Returns the position of the current canvas event. * * @return position of current canvas event */ public Point2D getCurrentCanvasPosition() { return currentCanvasPosition; } // **************************************************************** // Event Handling - Methods for handling events // // The dispatch manager updates the focus nodes based on the // incoming events, and dispatches those events to the appropriate // focus nodes. // **************************************************************** /** {@inheritDoc} */ public void keyPressed(final PInputEvent event) { dispatchEventToListener(event, KeyEvent.KEY_PRESSED, keyboardFocus); } /** {@inheritDoc} */ public void keyReleased(final PInputEvent event) { dispatchEventToListener(event, KeyEvent.KEY_RELEASED, keyboardFocus); } /** {@inheritDoc} */ public void keyTyped(final PInputEvent event) { dispatchEventToListener(event, KeyEvent.KEY_TYPED, keyboardFocus); } /** {@inheritDoc} */ public void mouseClicked(final PInputEvent event) { dispatchEventToListener(event, MouseEvent.MOUSE_CLICKED, previousMouseFocus); } /** {@inheritDoc} */ public void mouseWheelRotated(final PInputEvent event) { setMouseFocus(getMouseOver()); dispatchEventToListener(event, MouseWheelEvent.WHEEL_UNIT_SCROLL, mouseOver); } /** {@inheritDoc} */ public void mouseWheelRotatedByBlock(final PInputEvent event) { setMouseFocus(getMouseOver()); dispatchEventToListener(event, MouseWheelEvent.WHEEL_BLOCK_SCROLL, mouseOver); } /** {@inheritDoc} */ public void mouseDragged(final PInputEvent event) { checkForMouseEnteredAndExited(event); dispatchEventToListener(event, MouseEvent.MOUSE_DRAGGED, mouseFocus); } /** {@inheritDoc} */ public void mouseEntered(final PInputEvent event) { dispatchEventToListener(event, MouseEvent.MOUSE_ENTERED, mouseOver); } /** {@inheritDoc} */ public void mouseExited(final PInputEvent event) { dispatchEventToListener(event, MouseEvent.MOUSE_EXITED, previousMouseOver); } /** {@inheritDoc} */ public void mouseMoved(final PInputEvent event) { checkForMouseEnteredAndExited(event); dispatchEventToListener(event, MouseEvent.MOUSE_MOVED, mouseOver); } /** {@inheritDoc} */ public void mousePressed(final PInputEvent event) { if (buttonsPressed == 0) { setMouseFocus(getMouseOver()); } buttonsPressed++; dispatchEventToListener(event, MouseEvent.MOUSE_PRESSED, mouseFocus); if (buttonsPressed < 1 || buttonsPressed > 3) { System.err.println("invalid pressedCount on mouse pressed: " + buttonsPressed); } } /** {@inheritDoc} */ public void mouseReleased(final PInputEvent event) { buttonsPressed--; checkForMouseEnteredAndExited(event); dispatchEventToListener(event, MouseEvent.MOUSE_RELEASED, mouseFocus); if (buttonsPressed == 0) { setMouseFocus(null); } if (buttonsPressed < 0 || buttonsPressed > 2) { System.err.println("invalid pressedCount on mouse released: " + buttonsPressed); } } /** * Fires events whenever the mouse moves from PNode to PNode. * * @param event to check to see if the top node has changed. */ protected void checkForMouseEnteredAndExited(final PInputEvent event) { final PNode currentNode = getPickedNode(mouseOver); final PNode previousNode = getPickedNode(previousMouseOver); if (currentNode != previousNode) { dispatchEventToListener(event, MouseEvent.MOUSE_EXITED, previousMouseOver); dispatchEventToListener(event, MouseEvent.MOUSE_ENTERED, mouseOver); previousMouseOver = mouseOver; } } /** * Returns picked node on pickPath if pickPath is not null, or null. * * @param pickPath from which to extract picked node * * @return the picked node or null if pickPath is null */ private PNode getPickedNode(final PPickPath pickPath) { if (pickPath == null) { return null; } else { return pickPath.getPickedNode(); } } // **************************************************************** // Event Dispatch. // **************************************************************** /** {@inheritDoc} */ public void processInput() { if (nextInput == null) { return; } final PInputEvent e = new PInputEvent(this, nextInput); Point2D newCurrentCanvasPosition = null; Point2D newLastCanvasPosition = null; if (e.isMouseEvent()) { if (e.isMouseEnteredOrMouseExited()) { final PPickPath aPickPath = nextInputSource.pick(((MouseEvent) nextInput).getX(), ((MouseEvent) nextInput).getY(), 1); setMouseOver(aPickPath); previousMouseOver = aPickPath; newCurrentCanvasPosition = (Point2D) currentCanvasPosition.clone(); newLastCanvasPosition = (Point2D) lastCanvasPosition.clone(); } else { lastCanvasPosition.setLocation(currentCanvasPosition); currentCanvasPosition.setLocation(((MouseEvent) nextInput).getX(), ((MouseEvent) nextInput).getY()); final PPickPath aPickPath = nextInputSource.pick(currentCanvasPosition.getX(), currentCanvasPosition .getY(), 1); setMouseOver(aPickPath); } } nextInput = null; nextInputSource = null; processEvent(e, nextType); if (newCurrentCanvasPosition != null && newLastCanvasPosition != null) { currentCanvasPosition.setLocation(newCurrentCanvasPosition); lastCanvasPosition.setLocation(newLastCanvasPosition); } } /** * Flags the given event as needing to be processed. * * @param event the event to be processed * @param type type of event to be processed * @param camera camera from which the event was dispatched */ public void processEventFromCamera(final InputEvent event, final int type, final PCamera camera) { // queue input nextInput = event; nextType = type; nextInputSource = camera; // tell root to process queued inputs camera.getRoot().processInputs(); } /** * Dispatches the given event to the listener, or does nothing if listener * is null. * * @param event event to be dispatched * @param type type of event to dispatch * @param listener target of dispatch */ private void dispatchEventToListener(final PInputEvent event, final int type, final PInputEventListener listener) { if (listener != null) { // clear the handled bit since the same event object is used to send // multiple events such as mouseEntered/mouseExited and mouseMove. event.setHandled(false); listener.processEvent(event, type); } } }