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); } } } }