001 package gizmoball.staffui;
002
003 import java.awt.Component;
004 import java.awt.event.KeyListener;
005 import java.awt.event.KeyEvent;
006 import javax.swing.SwingUtilities;
007 import java.util.Set;
008 import java.util.HashSet;
009 import java.util.Iterator;
010
011 /****************************************************************************
012 * Copyright (C) 2001 by the Massachusetts Institute of Technology,
013 * Cambridge, Massachusetts.
014 *
015 * All Rights Reserved
016 *
017 * Permission to use, copy, modify, and distribute this software and
018 * its documentation for any purpose and without fee is hereby
019 * granted, provided that the above copyright notice appear in all
020 * copies and that both that copyright notice and this permission
021 * notice appear in supporting documentation, and that MIT's name not
022 * be used in advertising or publicity pertaining to distribution of
023 * the software without specific, written prior permission.
024 *
025 * THE MASSACHUSETTS INSTITUTE OF TECHNOLOGY DISCLAIMS ALL WARRANTIES
026 * WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF
027 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE MASSACHUSETTS
028 * INSTITUTE OF TECHNOLOGY BE LIABLE FOR ANY SPECIAL, INDIRECT OR
029 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
030 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
031 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
032 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
033 *
034 *
035 * @author: Jeremy Nimmer (jwnimmer@alum.mit.edu)
036 * Spring 2001
037 *
038 * Version: $Id: MagicKeyListener.java,v 1.1 2007/03/06 16:25:30 ctsims Exp $
039 *
040 ***************************************************************************/
041
042 /**
043 * A MagicKeyListener is decorator for a KeyListener.
044 *
045 * <p>This class adds three pieces of functionality. First, it delays
046 * key events (moving them to the back of the event queue). Second,
047 * it maintains state so that when a press-and-release event pair is
048 * sitting in the queue, neither event is propogated to the adaptee
049 * (decoratee). Finally, it can (optionally) add to the semantics so
050 * that any release event implies that any still-pressed keys have
051 * also been released.
052 *
053 * <p>Together, these additions may provide more meaningful semantics
054 * of key listening in an environment where a key being held down
055 * generates repeated key events, or where multiple keys pressed
056 * generate a release event for only one of them.
057 **/
058 public class MagicKeyListener
059 implements KeyListener
060 {
061
062 /**
063 * @requires adaptee != null
064 *
065 * @effects creates a new MagicKeyListener without the generation of
066 * additional key release events (the third option given in the
067 * class overview is disabled).
068 **/
069 public MagicKeyListener(KeyListener adaptee)
070 {
071 this(adaptee, false);
072 }
073
074 /**
075 * @requires adaptee != null
076 *
077 * @param assumeAllReleased enables the third option listed in the
078 * class overview, namely that any key release event implies that
079 * all keys have been released.
080 *
081 * @effects creates a new MagicKeyListener.
082 **/
083 public MagicKeyListener(KeyListener adaptee, boolean assumeAllReleased)
084 {
085 if (adaptee == null) throw new IllegalArgumentException();
086 this.adaptee = adaptee;
087 this.assumeAllReleased = assumeAllReleased;
088 }
089
090 private final KeyListener adaptee;
091 private final Set<Integer> real = new HashSet<Integer>();
092 private final Set<Integer> announced = new HashSet<Integer>();
093 private final boolean assumeAllReleased;
094
095 //
096 // Rep Invariant:
097 // adaptee, real, announced != null;
098 //
099
100 //
101 // Abstration Function:
102 // We represent a wrapper around <adaptee>. We know that the keys
103 // in <real> are currently pressed by the user, but we have only
104 // passed enough state to the adaptee for it to know that the keys
105 // in <announced> are currently pressed by the user.
106 //
107
108 /**
109 * @returns an immutable object which is representative of the key
110 * associated with the given event
111 **/
112 private static Integer marker(KeyEvent e)
113 {
114 return new Integer(e.getKeyCode());
115 }
116
117 /**
118 * @returns an event which is constructed from the given immutable
119 * key (from the marker method) and a template event.
120 **/
121 private static KeyEvent eventFromMarker(Integer marker, KeyEvent e)
122 {
123 Component source = e.getComponent();
124 int id = e.getID();
125 long when = e.getWhen();
126 int modifiers = e.getModifiers();
127 int keyCode = marker.intValue();
128 char keyChar = e.getKeyChar();
129
130 return new KeyEvent(source, id, when, modifiers, keyCode, keyChar);
131 }
132
133 /**
134 * @effects Acts on the given event as specified in the class overview.
135 **/
136 public void keyPressed(KeyEvent e)
137 {
138 real.add(marker(e));
139 SwingUtilities.invokeLater(new KeyPressedLater(e));
140 }
141
142 /**
143 * @effects Acts on the given event as specified in the class overview.
144 **/
145 public void keyReleased(KeyEvent e)
146 {
147 real.remove(marker(e));
148 SwingUtilities.invokeLater(new KeyReleasedLater(e));
149
150 if (assumeAllReleased) {
151 while (!real.isEmpty()) {
152 Integer marker;
153 {
154 Iterator<Integer> chooser = real.iterator();
155 marker = chooser.next();
156 chooser.remove();
157 }
158 KeyEvent event = eventFromMarker(marker, e);
159 SwingUtilities.invokeLater(new KeyReleasedLater(event));
160 }
161 }
162 }
163
164 /**
165 * @effects Acts on the given event as specified in the class overview
166 **/
167 public void keyTyped(KeyEvent e)
168 {
169 SwingUtilities.invokeLater(new KeyTypedLater(e));
170 }
171
172 /**
173 * A simple class which forms a closure around a key typed event.
174 * When run, it fires the event to the keyTyped method of the
175 * adaptee.
176 **/
177 private class KeyTypedLater
178 implements Runnable
179 {
180 private final KeyEvent event;
181 private KeyTypedLater(KeyEvent event) {
182 this.event = event;
183 }
184 public void run() {
185 adaptee.keyTyped(event);
186 }
187 }
188
189 /**
190 * A simple class which forms a closure around an key pressed event.
191 * When run, it fires the event to the keyPressed method of the
192 * adaptee only if the pressed keyset still contains this key and
193 * the adaptee has not already been informed.
194 **/
195 private class KeyPressedLater
196 implements Runnable
197 {
198 private final KeyEvent event;
199 private KeyPressedLater(KeyEvent event) {
200 this.event = event;
201 }
202 public void run() {
203 Integer key = marker(event);
204 if (real.contains(key) && !announced.contains(key)) {
205 announced.add(key);
206 adaptee.keyPressed(event);
207 }
208 }
209 }
210
211 /**
212 * A simple class which forms a closure around an key released
213 * event. When run, it fires the event to the keyReleased method of
214 * the adaptee only if the pressed keyset does not contains this key
215 * and the adaptee has not already been informed of the release.
216 **/
217 private class KeyReleasedLater
218 implements Runnable
219 {
220 private final KeyEvent event;
221 private KeyReleasedLater(KeyEvent event) {
222 this.event = event;
223 }
224 public void run() {
225 Integer key = marker(event);
226 if (!real.contains(key) && announced.contains(key)) {
227 announced.remove(key);
228 adaptee.keyReleased(event);
229 }
230 }
231 }
232
233 }