package org.expeditee.gio; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Set; import org.expeditee.core.Point; import org.expeditee.gio.TimeoutQueue.TimeoutHandle; import org.expeditee.gio.gesture.Gesture; import org.expeditee.gio.gesture.GestureListener; import org.expeditee.gio.input.InputEvent; import org.expeditee.gio.input.InputEvent.InputType; import org.expeditee.gio.input.InputEventListener; import org.expeditee.gio.input.InputEventToGestureTranslator; import org.expeditee.gio.input.TimeoutInputEvent; import org.expeditee.items.widgets.Widget; /** * Receives input events from the system and distributes them within Expeditee. * Abstract as platform-specific implementation must be provided to handle receiving * input events from the system. * * Native Input * | * | /---------\ * `------->| Widgets | * ,--------| | * | \---------/ * v * /-------------\ * | Input | * | Translation | * \-------------/ * | * Expeditee Input * | * | /-----------\ * `------->| Input | * ,--------| Listeners | * | \-----------/ * | * v * /-------------\ * Context---->| Gesture | * | Translation | * \-------------/ * | * Gestures * | /-----------\ * `------->| Gesture | * | Listeners | * \-----------/ * * @author cts16 */ public abstract class InputManager { protected InputManager() { // Initialise the list of window-event listeners _windowEventListeners = new LinkedList(); // Initialise the timeout queue _timeoutQueue = new TimeoutQueue(); // Initialise the list of widgets _widgets = new LinkedList(); // Initialise the list of input event listeners _inputEventListeners = new LinkedList(); // Initialise the map of input-event -> gesture translators _translators = new HashMap>(InputType.values().length); for (InputType type : InputType.values()) _translators.put(type, new LinkedList()); // Initialise the list of gesture listeners _gestureListeners = new LinkedList(); // Initialise the cursor position _cursorPosition = getRealCursorPosition().clone(); } /* * * Window-specific events. * */ /** The types of window events that can be listened for. */ public enum WindowEventType { WINDOW_RESIZED, WINDOW_CLOSED, MOUSE_EXITED_WINDOW, MOUSE_ENTERED_WINDOW } /** The interface window-event listeners must implement to receive events. */ public static interface WindowEventListener { public void onWindowEvent(WindowEventType type); } /** The list of window-event listeners. */ private List _windowEventListeners; /** Adds a new window-event listener. */ public void addWindowEventListener(WindowEventListener listener) { _windowEventListeners.add(listener); } /** * Should be called by platform-specific input managers to * notify listeners of a window event. */ protected void distributeWindowEvent(WindowEventType type) { ListIterator it = _windowEventListeners.listIterator(_windowEventListeners.size()); // Run event handlers in reverse order while (it.hasPrevious()) it.previous().onWindowEvent(type); } /* * * Cursor-control methods. * */ /** The position of the mouse cursor. */ private Point _cursorPosition; /** Gets the current position of the cursor inside the Expeditee window. */ public Point getCursorPosition() { return _cursorPosition.clone(); } /** Gets the position of the cursor (for initialisation purposes). */ protected abstract Point getRealCursorPosition(); /** Should be called by the platform-specific input manager to keep the cursor position up-to-date. */ protected void updateCursorPosition(int x, int y) { _cursorPosition.set(x, y); } /** Moves the cursor to the given position in the window. */ public abstract void setCursorPosition(Point position); /** Moves the cursor by the given amount. */ public final void moveCursor(int x, int y) { //System.err.println("Input manager position to: " + x + "," + y); setCursorPosition(getCursorPosition().clone().add(x, y)); } /* * * Timeout-event handling. * */ /** TODO: Comment. cts16 */ private TimeoutQueue _timeoutQueue; /** TODO: Comment. cts16 */ public TimeoutHandle addTimeout(int milliseconds) { TimeoutHandle handle = _timeoutQueue.addTimeout(milliseconds); Long nextTimeout = _timeoutQueue.getNextTimeout(); if (nextTimeout != null) updateTimer(nextTimeout); return handle; } /** TODO: Comment. cts16 */ public void cancelTimeout(TimeoutHandle handle) { _timeoutQueue.cancelTimeout(handle); } /** TODO: Comment. cts16 */ protected abstract void updateTimer(long nextTimeout); /** TODO: Comment. cts16 */ public final void triggerTimeoutEvents() { Set expiredHandles; while (!(expiredHandles = _timeoutQueue.popExpiredTimeouts()).isEmpty()) { triggerTimeoutEvents(expiredHandles); } Long nextTimeout = _timeoutQueue.getNextTimeout(); if (nextTimeout != null) updateTimer(nextTimeout); } /** TODO: Comment. cts16 */ protected final void triggerTimeoutEvents(Collection handles) { if (handles == null) return; for (TimeoutHandle handle : handles) triggerTimeoutEvent(handle); } /** TODO: Comment. cts16 */ protected final void triggerTimeoutEvent(TimeoutHandle handle) { if (handle == null) return; TimeoutInputEvent event = new TimeoutInputEvent(handle); distributeInputEvent(event); } /* * * Native-input-layer distribution methods. * */ /** The widgets enrolled with the input manager. */ protected List _widgets; /** Registers a widget with the manager. */ public void addInteractiveWidget(Widget iw) { if (iw == null) return; if (!_widgets.contains(iw)) _widgets.add(iw); } /** Unregisters a widget with the manager. */ public void removeInteractiveWidget(Widget iw) { if (iw == null) return; if (_widgets.contains(iw)) _widgets.remove(iw); } /* * * Expeditee-input-layer distribution methods. * */ /** The registered input event listeners. */ private LinkedList _inputEventListeners; /** * Registers the given input event listener to intercept input events. * Priority of interception is in order of registration. */ public final void registerInputEventListener(InputEventListener listener) { if (listener == null) return; _inputEventListeners.add(listener); } /** Unregisters an input event listener. */ public final void removeInputEventListener(InputEventListener listener) { if (listener == null) return; _inputEventListeners.remove(listener); } /** * Lets any registered input-event listeners view/consume input. If none * consume the input, it gets passed to the gesture-translation system. This method * should be called by the implementation-native input system to get input into * Expeditee. */ protected final void distributeInputEvent(InputEvent event) { // Allow listeners to optionally consume input before gesture translation for (InputEventListener listener : _inputEventListeners) { if (listener.onInputEvent(event)) return; } // Get the relevant translators List translators = getInputEventToGestureTranslators(event.getInputType()); // Translate the input event in reverse order, only taking the gestures from the first // translator to respond List gestures = null; ListIterator it = translators.listIterator(translators.size()); while (it.hasPrevious()) { InputEventToGestureTranslator translator = it.previous(); List translatorGestures = translator.onInputEvent(event); if (gestures == null && translatorGestures != null && !translatorGestures.isEmpty()) gestures = translatorGestures; } // Perform the gestures distributeGestures(gestures, false); } /* * * Gesture-layer distribution methods. * */ /** The registered InputEventToGestureTranslators. */ private HashMap> _translators; /** The registered gesture listeners. */ private LinkedList _gestureListeners; /** * Sets the translator to use to generate gestures for a given type of input. * @return True if the input type is supported by this Input Manager, false if not. */ public final void addInputEventToGestureTranslator(InputEventToGestureTranslator translator) { if (translator == null) return; Set types = translator.getMonitoredInputTypes(); for (InputType type : types) _translators.get(type).add(translator); } /** * Gets the input-event -> gesture translator that was registered with the * given input type. */ public final List getInputEventToGestureTranslators(InputType type) { if (type == null) return null; return _translators.get(type); } /** * Should return true if the input manager supports the given * type of input and false if not. */ protected abstract boolean isInputTypeSupported(InputType type); /** Registers a new listener to receive gestures from the input system. */ public final void registerGestureListener(GestureListener listener) { if (listener == null) return; _gestureListeners.add(listener); } /** Notifies all gesture listeners of the given gestures. */ public void distributeGestures(List gestures) { distributeGestures(gestures, true); } /** Notifies all gesture listeners of the given gestures. */ protected void distributeGestures(List gestures, boolean robotic) { if (gestures == null) return; for (Gesture gesture : gestures) distributeGesture(gesture, robotic); } /** Notifies all gesture listeners of the given gesture. */ public void distributeGesture(Gesture gesture) { distributeGesture(gesture, true); } /** Notifies all gesture listeners of the given gesture. */ protected void distributeGesture(final Gesture gesture, final boolean robotic) { if (gesture == null) { return; } // Ensure robotic callers don't try to spoof being non-robotic if (robotic) { gesture.setRobotic(true); } final Gesture clone = gesture.clone(); for(final GestureListener listener : _gestureListeners) { listener.preGesture(clone); } for (final GestureListener listener : _gestureListeners) { listener.onGesture(clone); } for (final GestureListener listener : _gestureListeners) { listener.postGesture(clone); } } }