package staffui;
import java.awt.Component;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import javax.swing.SwingUtilities;
import java.util.Set;
import java.util.HashSet;
import java.util.Iterator;
/****************************************************************************
* Copyright (C) 2001 by the Massachusetts Institute of Technology,
* Cambridge, Massachusetts.
*
* All Rights Reserved
*
* Permission to use, copy, modify, and distribute this software and
* its documentation for any purpose and without fee is hereby
* granted, provided that the above copyright notice appear in all
* copies and that both that copyright notice and this permission
* notice appear in supporting documentation, and that MIT's name not
* be used in advertising or publicity pertaining to distribution of
* the software without specific, written prior permission.
*
* THE MASSACHUSETTS INSTITUTE OF TECHNOLOGY DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE MASSACHUSETTS
* INSTITUTE OF TECHNOLOGY BE LIABLE FOR ANY SPECIAL, INDIRECT OR
* CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
* NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
***************************************************************************/
/**
* A MagicKeyListener is decorator for a KeyListener.
*
*
This class adds three pieces of functionality. First, it delays
* key events (moving them to the back of the event queue). Second,
* it maintains state so that when a press-and-release event pair is
* sitting in the queue, neither event is propogated to the adaptee
* (decoratee). Finally, it can (optionally) add to the semantics so
* that any release event implies that any still-pressed keys have
* also been released.
*
*
Together, these additions may provide more meaningful semantics
* of key listening in an environment where a key being held down
* generates repeated key events, or where multiple keys pressed
* generate a release event for only one of them.
*/
public class MagicKeyListener
implements KeyListener
{
/**
* @requires adaptee != null
*
* @effects creates a new MagicKeyListener without the generation of
* additional key release events (the third option given in the
* class overview is disabled).
*/
public MagicKeyListener(KeyListener adaptee)
{
this(adaptee, false);
}
/**
* @requires adaptee != null
*
* @param assumeAllReleased enables the third option listed in the
* class overview, namely that any key release event implies that
* all keys have been released.
*
* @effects creates a new MagicKeyListener.
*/
public MagicKeyListener(KeyListener adaptee, boolean assumeAllReleased)
{
if (adaptee == null) throw new IllegalArgumentException();
this.adaptee = adaptee;
this.assumeAllReleased = assumeAllReleased;
}
private final KeyListener adaptee;
private final Set real = new HashSet();
private final Set announced = new HashSet();
private final boolean assumeAllReleased;
//
// Rep Invariant:
// adaptee, real, announced != null;
//
//
// Abstration Function:
// We represent a wrapper around . We know that the keys
// in are currently pressed by the user, but we have only
// passed enough state to the adaptee for it to know that the keys
// in are currently pressed by the user.
//
/**
* @returns an immutable object which is representative of the key
* associated with the given event
*/
private static Integer marker(KeyEvent e)
{
return new Integer(e.getKeyCode());
}
/**
* @returns an event which is constructed from the given immutable
* key (from the marker method) and a template event.
*/
private static KeyEvent eventFromMarker(Integer marker, KeyEvent e)
{
Component source = e.getComponent();
int id = e.getID();
long when = e.getWhen();
int modifiers = e.getModifiers();
int keyCode = marker.intValue();
char keyChar = e.getKeyChar();
return new KeyEvent(source, id, when, modifiers, keyCode, keyChar);
}
/**
* @effects Acts on the given event as specified in the class overview.
*/
public void keyPressed(KeyEvent e)
{
real.add(marker(e));
SwingUtilities.invokeLater(new KeyPressedLater(e));
}
/**
* @effects Acts on the given event as specified in the class overview.
*/
public void keyReleased(KeyEvent e)
{
real.remove(marker(e));
SwingUtilities.invokeLater(new KeyReleasedLater(e));
if (assumeAllReleased) {
while (!real.isEmpty()) {
Integer marker;
{
Iterator chooser = real.iterator();
marker = chooser.next();
chooser.remove();
}
KeyEvent event = eventFromMarker(marker, e);
SwingUtilities.invokeLater(new KeyReleasedLater(event));
}
}
}
/**
* @effects Acts on the given event as specified in the class overview
*/
public void keyTyped(KeyEvent e)
{
SwingUtilities.invokeLater(new KeyTypedLater(e));
}
/**
* A simple class which forms a closure around a key typed event.
* When run, it fires the event to the keyTyped method of the
* adaptee.
*/
private class KeyTypedLater
implements Runnable
{
private final KeyEvent event;
private KeyTypedLater(KeyEvent event) {
this.event = event;
}
public void run() {
adaptee.keyTyped(event);
}
}
/**
* A simple class which forms a closure around an key pressed event.
* When run, it fires the event to the keyPressed method of the
* adaptee only if the pressed keyset still contains this key and
* the adaptee has not already been informed.
*/
private class KeyPressedLater
implements Runnable
{
private final KeyEvent event;
private KeyPressedLater(KeyEvent event) {
this.event = event;
}
public void run() {
Integer key = marker(event);
if (real.contains(key) && !announced.contains(key)) {
announced.add(key);
adaptee.keyPressed(event);
}
}
}
/**
* A simple class which forms a closure around an key released
* event. When run, it fires the event to the keyReleased method of
* the adaptee only if the pressed keyset does not contains this key
* and the adaptee has not already been informed of the release.
*/
private class KeyReleasedLater
implements Runnable
{
private final KeyEvent event;
private KeyReleasedLater(KeyEvent event) {
this.event = event;
}
public void run() {
Integer key = marker(event);
if (!real.contains(key) && announced.contains(key)) {
announced.remove(key);
adaptee.keyReleased(event);
}
}
}
}