001 package 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/05/01 06:30:28 ruthdhan 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 }