Moved KeyDispatcher into Narya because it's generally useful (and needed
by the puzzle stuff). git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2877 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
//
|
||||
// $Id: KeyDispatcher.java,v 1.1 2003/11/26 01:42:57 mdb Exp $
|
||||
|
||||
package com.threerings.util;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.awt.Frame;
|
||||
import java.awt.KeyEventDispatcher;
|
||||
import java.awt.KeyboardFocusManager;
|
||||
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.KeyListener;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.awt.event.WindowFocusListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.event.AncestorEvent;
|
||||
import javax.swing.event.AncestorListener;
|
||||
import javax.swing.text.JTextComponent;
|
||||
|
||||
import com.samskivert.util.HashIntMap;
|
||||
|
||||
/**
|
||||
* Handles dispatching special global key pressed and released events to
|
||||
* those that care to monitor and process such things.
|
||||
*
|
||||
* <p> A {@link JTextComponent} may registered as a "chat grabber" via
|
||||
* {@link #pushChatGrabber} so as to capture typed chat characters
|
||||
* regardless of which component currently has the focus.
|
||||
*
|
||||
* <p> Components may also register as global {@link KeyListener}s via
|
||||
* {@link #addGlobalKeyListener} to always be notified of key press and
|
||||
* release events for all keys.
|
||||
*/
|
||||
public class KeyDispatcher
|
||||
implements KeyEventDispatcher, AncestorListener, WindowFocusListener
|
||||
{
|
||||
/**
|
||||
* Constructs a key dispatcher.
|
||||
*/
|
||||
public KeyDispatcher (Frame frame)
|
||||
{
|
||||
// save things off
|
||||
_frame = frame;
|
||||
|
||||
// listen to window events on our main frame so that we can
|
||||
// release keys when the mouse leaves the frame
|
||||
_frame.addWindowFocusListener(this);
|
||||
|
||||
// monitor key events from the central dispatch mechanism
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().
|
||||
addKeyEventDispatcher(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts down the key dispatcher. We currently have no plans to ever
|
||||
* shut this little guy down, and so we leave the following dead code
|
||||
* here, commented forevermore.
|
||||
*/
|
||||
// public void shutdown ()
|
||||
// {
|
||||
// // cease monitoring key events
|
||||
// KeyboardFocusManager.getCurrentKeyboardFocusManager().
|
||||
// removeKeyEventDispatcher(this);
|
||||
|
||||
// // cease observing our frame
|
||||
// _frame.removeWindowFocusListener(this);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Makes the specified component the new grabber of key typed events
|
||||
* that look like they're chat-related per {@link #isChatCharacter}.
|
||||
*/
|
||||
public void pushChatGrabber (JTextComponent comp)
|
||||
{
|
||||
// note this component as the new chat grabber
|
||||
_curChatGrabber = comp;
|
||||
|
||||
// add the component to the list of grabbers
|
||||
_chatGrabbers.addLast(comp);
|
||||
comp.addAncestorListener(this);
|
||||
|
||||
// and request to give the new component the focus since that's a
|
||||
// sensible thing to do as it's aiming to nab all key events
|
||||
// henceforth
|
||||
comp.requestFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified component from the list of chat grabbers so
|
||||
* that it will no longer be notified of chat-like key events.
|
||||
*/
|
||||
public void removeChatGrabber (JTextComponent comp)
|
||||
{
|
||||
// remove the component from the list of grabbers
|
||||
comp.removeAncestorListener(this);
|
||||
_chatGrabbers.remove(comp);
|
||||
|
||||
// update the current chat grabbing component
|
||||
_curChatGrabber = _chatGrabbers.isEmpty() ? null :
|
||||
(JTextComponent) _chatGrabbers.getLast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the key listener to receive all key events at all times.
|
||||
*/
|
||||
public void addGlobalKeyListener (KeyListener listener)
|
||||
{
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified global key listener.
|
||||
*/
|
||||
public void removeGlobalKeyListener (KeyListener listener)
|
||||
{
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
|
||||
// documentation inherited from interface KeyEventDispatcher
|
||||
public boolean dispatchKeyEvent (KeyEvent e)
|
||||
{
|
||||
int lsize = _listeners.size();
|
||||
|
||||
switch (e.getID()) {
|
||||
case KeyEvent.KEY_TYPED:
|
||||
// dispatch to all the global listeners
|
||||
for (int ii = 0; ii < lsize; ii++) {
|
||||
((KeyListener) _listeners.get(ii)).keyTyped(e);
|
||||
}
|
||||
|
||||
// see if a chat grabber needs to grab it
|
||||
if (_curChatGrabber != null) {
|
||||
Component target = e.getComponent();
|
||||
// if the key was typed on a non-text component or one
|
||||
// that wasn't editable...
|
||||
if (isChatCharacter(e.getKeyChar()) &&
|
||||
!isTypableTarget(target)) {
|
||||
// focus our grabby component, and redirect this
|
||||
// key event there
|
||||
_curChatGrabber.requestFocus();
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().
|
||||
redispatchEvent(_curChatGrabber, e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent.KEY_PRESSED:
|
||||
if (lsize > 0) {
|
||||
for (int ii = 0; ii < lsize; ii++) {
|
||||
((KeyListener) _listeners.get(ii)).keyPressed(e);
|
||||
}
|
||||
// remember the key event..
|
||||
_downKeys.put(e.getKeyCode(), e);
|
||||
}
|
||||
break;
|
||||
|
||||
case KeyEvent.KEY_RELEASED:
|
||||
if (lsize > 0) {
|
||||
for (int ii = 0; ii < lsize; ii++) {
|
||||
((KeyListener) _listeners.get(ii)).keyReleased(e);
|
||||
}
|
||||
// forget the key event
|
||||
_downKeys.remove(e.getKeyCode());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the specified target component supports being typed
|
||||
* into, and thus we shouldn't steal focus away from it if the user
|
||||
* starts typing.
|
||||
*/
|
||||
protected boolean isTypableTarget (Component target)
|
||||
{
|
||||
return target.isShowing() &&
|
||||
(((target instanceof JTextComponent) &&
|
||||
((JTextComponent) target).isEditable()) ||
|
||||
(target instanceof JTable));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the specified character is a chat character.
|
||||
*/
|
||||
protected boolean isChatCharacter (char c)
|
||||
{
|
||||
return (Character.isLetterOrDigit(c) || ('/' == c));
|
||||
}
|
||||
|
||||
// documentation inherited from interface WindowFocusListener
|
||||
public void windowGainedFocus (WindowEvent e)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
// documentation inherited from interface WindowFocusListener
|
||||
public void windowLostFocus (WindowEvent e)
|
||||
{
|
||||
// un-press any keys that were left down
|
||||
if (!_downKeys.isEmpty()) {
|
||||
long now = System.currentTimeMillis();
|
||||
for (Iterator iter = _downKeys.elements(); iter.hasNext(); ) {
|
||||
KeyEvent down = (KeyEvent) iter.next();
|
||||
KeyEvent up = new KeyEvent(
|
||||
down.getComponent(), KeyEvent.KEY_RELEASED, now,
|
||||
down.getModifiers(), down.getKeyCode(), down.getKeyChar(),
|
||||
down.getKeyLocation());
|
||||
|
||||
for (int ii = 0, nn = _listeners.size(); ii < nn; ii++) {
|
||||
((KeyListener) _listeners.get(ii)).keyReleased(up);
|
||||
}
|
||||
}
|
||||
_downKeys.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// documentation inherited from interface AncestorListener
|
||||
public void ancestorAdded (AncestorEvent ae)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
// documentation inherited from interface AncestorListener
|
||||
public void ancestorMoved (AncestorEvent ae)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
// documentation inherited from interface AncestorListener
|
||||
public void ancestorRemoved (AncestorEvent ae)
|
||||
{
|
||||
removeChatGrabber((JTextComponent) ae.getComponent());
|
||||
}
|
||||
|
||||
/** The main frame for which we're observing key events. */
|
||||
protected Frame _frame;
|
||||
|
||||
/** The current most-recently pushed component that wants to grab
|
||||
* alphanumeric key presses. */
|
||||
protected JTextComponent _curChatGrabber;
|
||||
|
||||
/** The stack of grabbers. */
|
||||
protected LinkedList _chatGrabbers = new LinkedList();
|
||||
|
||||
/** Global key listeners. */
|
||||
protected ArrayList _listeners = new ArrayList();
|
||||
|
||||
/** Keys that are currently held down. */
|
||||
protected HashIntMap _downKeys = new HashIntMap();
|
||||
}
|
||||
Reference in New Issue
Block a user