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    }