package jDraft;
/*
* Copyright 2007 Kustaa Nyholm
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import jApp.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.FocusManager;
import java.util.*;
/**
* Implements a simple API to attach callbacks to global even dispatching.
* <p>
* GlobalEventHook makes it possible to attach global event listeners and
* actions for <code>MouseEvent</code> and <code>KeyEvent</code> events.
*
* While most event handling is best done using the standard AWT/Swing methods,
* there isfunctionality that is cumbersome to implement using the standard
* event handling framework.
* <p>
* Many applications implement mouse actions that can be modified by holding one
* or more of the modifiers keys down. For an example consider a zoom command
* where clicking with the mouse causes zoom in or out depending on the state of
* the ALT-key. Good UI design practice dictates that the cursor shape changes
* accordingly. This is easily implemented by creating a <code>setCursor</code>
* -method that is called from within the mouse listener code. However if the
* user does not touch the mouse but simply presses the ALT-key the cursor does
* not change shape accordingly as no mouse events are generated for pressing or
* releasing modifier keys. Getting the mouse listener code to receive key
* events can be tricky because of the complexities of key event dispatching and
* focus management.
* <p>
* One of the main functions for the <code>GlobalEventHook</code> is to
* generate those missing mouse moved events when the modifier keys change
* state.
* <p>
* Also there are times at which some far removed code in the application
* necessiates the update of the cursor. Instead of implementing this manually
* it would be much better just fake a mouse movement in effect taking advantage
* of the standard mouse event dispatching and chain of command.
* <p>
* The <code>GlobalEventHook</code> implements
* {@link #postMouseNotMovedEvent()} just for that purpose.
* <p>
* An other service provided by the <code>GlobalEventHook</code> is access to
* the current state of the modifier keys. While most of the time the modifiers
* are available in the context where they are needed, in the key and mouse
* listeners, this is not always the case.
* <p>
* The state of the current modifiers is available from the
* <code>InputEvent</code> return by the {@link #getLastInputEvent()}.
* <p>
* Yet an other use for the <code>GlobalEventHook</code> is to create global
* short cuts that are always available regardless of the focus management.
* Obviously this should be used sparingly as it completely bypasses the focus
* management system but there are conditions in which this is justfiable.
* <p>
* For this purpose a global <code>InputMap</code> and <code>ActionMap</code>
* is available through the {@link #getActionMap()} and {@link #getInputMap()}
* methods.
*/
final public class GlobalEventHook {
private static EventQueueHook m_Hook;
private static InputMap m_InputMap = new InputMap();
private static ActionMap m_Actions = new ActionMap();
private static AWTEvent m_LastEvent;
private static InputEvent m_LastInputEvent;
private static KeyEvent m_LastKeyEvent;
private static MouseEvent m_LastMouseEvent;
private static LinkedList<KeyListener> m_KeyListeners = new LinkedList();
private static LinkedList<MouseListener> m_MouseListeners = new LinkedList();
private static LinkedList<MouseMotionListener> m_MouseMotionListeners = new LinkedList();
private GlobalEventHook() {
}
/**
* Gets the global <code>InputMap</code> that maps <code>KyeStroke</code>s
* to action keys.
*
* @return the global input map
*/
static public InputMap getInputMap() {
return m_InputMap;
}
/**
* Gets the global <code>ActionMap</code> that maps
* <code>action keys</code>s to <code>Action</action>.
* @return the global action map
*/
static public ActionMap getActionMap() {
return m_Actions;
}
/**
* Returns the last event dispatched.
*
* @return the last event dispatched
*/
static public AWTEvent getLastEvent() {
return m_LastEvent;
}
/**
* Returns the last input event dispatched.
*
* @return the last input event dispatched
*/
static public InputEvent getLastInputEvent() {
return m_LastInputEvent;
}
/**
* Returns the last key event dispatched.
*
* @return the last key event dispatched
*/
static public KeyEvent getLastKeyEvent() {
return m_LastKeyEvent;
}
/**
* Returns the last mouse event dispatched.
*
* @return the last mouse event dispatched
*/
static public MouseEvent getLastMouseEvent() {
return m_LastMouseEvent;
}
/**
* Adds a global key listener
*
* @param l
* the key listener to add
*/
static public void addKeyListener(KeyListener l) {
m_KeyListeners.add(l);
}
/**
* Removes a global key listener
*
* @param l
* the key listener to remove
*/
static public void removeKeyListener(KeyListener l) {
m_KeyListeners.remove(l);
}
/**
* Adds a global mouse listener
*
* @param l
* the mouse listener to add
*/
static public void addMouseListener(MouseListener l) {
m_MouseListeners.add(l);
}
/**
* Removes a global mouse listener
*
* @param l
* the mouse listener to remove
*/
static public void removeMouseListener(MouseListener l) {
m_MouseListeners.remove(l);
}
/**
* Adds a global mouse motion listener
*
* @param l
* the mouse motion listener to add
*/
static public void addMouseMotionListener(MouseMotionListener l) {
m_MouseMotionListeners.add(l);
}
/**
* Removes a global mouse motion listener
*
* @param l
* the mouse motion listener to remove
*/
static public void removeMouseMotionListener(MouseMotionListener l) {
m_MouseMotionListeners.remove(l);
}
private static void dispatchMouseEvent(MouseEvent me) {
switch (me.getID()) {
case MouseEvent.MOUSE_CLICKED:
for (MouseListener ml : m_MouseListeners)
ml.mouseClicked(me);
break;
case MouseEvent.MOUSE_ENTERED:
for (MouseListener ml : m_MouseListeners)
ml.mouseEntered(me);
break;
case MouseEvent.MOUSE_EXITED:
for (MouseListener ml : m_MouseListeners)
ml.mouseExited(me);
break;
case MouseEvent.MOUSE_PRESSED:
for (MouseListener ml : m_MouseListeners)
ml.mousePressed(me);
break;
case MouseEvent.MOUSE_RELEASED:
for (MouseListener ml : m_MouseListeners)
ml.mouseReleased(me);
break;
case MouseEvent.MOUSE_DRAGGED:
for (MouseMotionListener ml : m_MouseMotionListeners)
ml.mouseDragged(me);
break;
case MouseEvent.MOUSE_MOVED:
for (MouseMotionListener ml : m_MouseMotionListeners)
ml.mouseMoved(me);
break;
}
}
private static void dispatchKeyEvent(KeyEvent ke) {
switch (ke.getID()) {
case KeyEvent.KEY_PRESSED:
for (KeyListener kl : m_KeyListeners)
kl.keyPressed(ke);
break;
case KeyEvent.KEY_TYPED:
for (KeyListener kl : m_KeyListeners)
kl.keyTyped(ke);
break;
case KeyEvent.KEY_RELEASED:
for (KeyListener kl : m_KeyListeners)
kl.keyReleased(ke);
break;
}
}
private static void fakeMouseNotMovedEvent() {
if (m_LastMouseEvent == null)
return;
int em = m_LastMouseEvent.getModifiersEx();
boolean down = (em & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) != 0;
m_Hook.postEvent(new MouseEvent(//
(Component) m_LastMouseEvent.getSource(), //
down ? MouseEvent.MOUSE_DRAGGED : MouseEvent.MOUSE_MOVED, //
m_LastMouseEvent.getWhen(), m_LastMouseEvent.getModifiers(), //
m_LastMouseEvent.getX(), //
m_LastMouseEvent.getY(),//
0, //
false, //
MouseEvent.NOBUTTON));
}
public static void postMouseNotMovedEvent() {
fakeMouseNotMovedEvent();
}
/**
* Initilizes the GlobalEventHook single instance.
* <p>
* Calling this method should be one of the first actions when an
* application starts up, certainly before any AWT/UI code is executed.
* GlobalEventHook works by extending EventQueue and pushing an instance of
* itself to the front of the system event queue thus having a first go at
* each event as they are dispatched.
*
*
*/
public static void init() {
if (m_Hook == null) {
m_Hook = new EventQueueHook();
Toolkit.getDefaultToolkit().getSystemEventQueue().push(m_Hook);
}
}
private static class EventQueueHook extends EventQueue {
protected void dispatchEvent(AWTEvent event) {
MouseEvent fakedEvent = null;
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) event;
if ((m_LastKeyEvent == null || keyEvent.getModifiers() != m_LastKeyEvent.getModifiers()) && m_LastMouseEvent != null) {
int em = m_LastMouseEvent.getModifiersEx();
boolean down = (em & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) != 0;
fakedEvent = new MouseEvent(//
(Component) m_LastMouseEvent.getSource(), //
down ? MouseEvent.MOUSE_DRAGGED : MouseEvent.MOUSE_MOVED, //
keyEvent.getWhen(), //
keyEvent.getModifiers(), //
m_LastMouseEvent.getX(), //
m_LastMouseEvent.getY(),//
0, //
false, //
MouseEvent.NOBUTTON);
}
}
if (event instanceof KeyEvent)
m_LastKeyEvent = (KeyEvent) event;
if (event instanceof InputEvent)
m_LastInputEvent = (InputEvent) event;
if (event instanceof MouseEvent)
m_LastMouseEvent = (MouseEvent) event;
if (fakedEvent != null) {
super.dispatchEvent(fakedEvent);
}
if (event instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) event;
KeyStroke ks = KeyStroke.getKeyStrokeForEvent((KeyEvent) event);
Object actionKey = m_InputMap.get(ks);
if (actionKey != null) {
Action action = m_Actions.get(actionKey);
if (action != null && action.isEnabled()) {
String actionString = null;
if (actionKey instanceof String)
actionString = (String) actionKey;
action.actionPerformed(new ActionEvent(event.getSource(), event.getID(), actionString, ((KeyEvent) event).getModifiers()));
return;
}
}
}
if (event instanceof KeyEvent) {
dispatchKeyEvent(m_LastKeyEvent);
}
if (event instanceof MouseEvent) {
dispatchMouseEvent(m_LastMouseEvent);
}
m_LastEvent = event;
super.dispatchEvent(event);
}
}
}