]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.debug.graphical/src/org/simantics/debug/graphical/UsefulKeyAdapter.java
Support Json content as drop data in Graphical Debugger
[simantics/platform.git] / bundles / org.simantics.debug.graphical / src / org / simantics / debug / graphical / UsefulKeyAdapter.java
1 package org.simantics.debug.graphical;
2
3 import java.awt.event.KeyEvent;
4 import java.awt.event.KeyListener;
5 import java.util.concurrent.ArrayBlockingQueue;
6 import java.util.concurrent.BlockingQueue;
7 import java.util.concurrent.TimeUnit;
8
9 /**
10  * <p>
11  * This class wraps your KeyAdapter and suppresses spurious repeated events.
12  * </p>
13  * <p>
14  * Written by <a href="http://www.brianziman.com/">Brian Ziman</a> and available
15  * for download and comments at <a href="http://www.brianziman.com/r/post/stuff-200910092100">http://www.brianziman.com/r/post/stuff-200910092100</a>.
16  * </p>
17  * <h1>Rationale</h1>
18  * <p>
19  * When running in an X11 environment, it appears as though either Java or
20  * the windowing system stupidly generate lots of repeated key events when 
21  * you hold a key down. As it is impractical to get users to reconfigure their
22  * systems, and Sun seems unable to fix this issue (having been reported nearly
23  * ten years ago). This adapter is an attempt to filter out the useless events.
24  * </p>
25  * <p>
26  * Many people have whined about this, but few have fixed it. Some have tried,
27  * but their solutions are not as clean as my OCD nature requires. Here are
28  * some examples of what this is meant to handle:
29  * </p>
30  * <ul>
31  * <li><a href="http://bugs.sun.com/view_bug.do?bug_id=4153069">http://bugs.sun.com/view_bug.do?bug_id=4153069</a></li>
32  * <li><a href="http://forums.sun.com/thread.jspa?threadID=5382946">http://forums.sun.com/thread.jspa?threadID=5382946</a></li>
33  * <li><a href="http://stackoverflow.com/questions/1457071/how-to-know-when-a-user-has-really-released-a-key-in-java">http://stackoverflow.com/questions/1457071/how-to-know-when-a-user-has-really-released-a-key-in-java</a></li>
34  * </ul>
35  * <p>
36  *
37  * The goal is to generate a KeyPressed event when a key is pressed,
38  * and to generate a KeyReleased event when a key is released.  And 
39  * ONLY when a key is pressed or released.
40  * </p>
41  * <h1>Compatibility</h1>
42  * <p>
43  * The class is tested with Java 1.6.0 on Ubuntu 8.10, but should theoretically
44  * work on any platform, even if that platform issues events properly anyway.
45  * That is, this SHOULD work in a cross platform manner.  If it doesn't,
46  * please let me know and I may fix it.
47  * </p>
48  * <h1>Usage</h1>
49  * <p>
50  * You don't actually call any of the methods on this class, directly. When Java
51  * thinks it is receiving an event, it will call the methods in this class,
52  * which will, in turn, decide whether an event has actually occurred, and
53  * call YOUR methods when you probably intended them to be called.
54  * </p>
55  * <p>
56  * Replace:
57  * </p>
58  * <pre>
59  * addKeyListener(new KeyAdapter() {
60  *      public void keyPressed(KeyEvent e) { ... }
61  *      public void keyReleased(KeyEvent e) { ... }
62  * });
63  * </pre>
64  * <p>
65  * with
66  * </p>
67  * <pre>
68  * addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {
69  *      public void keyPressed(KeyEvent e) { ... }
70  *      public void keyReleased(KeyEvent e) { ... }
71  * }));
72  * </pre>
73  * <p>
74  * Don't use this for keyTyped() events.  If you are interested in these
75  * sorts of events, the default behaviour is probably what you actually
76  * want.
77  * </p>
78  * <p>
79  * See <a href="http://java.sun.com/docs/books/tutorial/uiswing/events/keylistener.html">How to Write a Key Listener</a> on Sun's web site for more details.
80  * </p>
81  * <h1>Algorithm:</h1>
82  * <p>This algorithm queues up keyboard events as the system reports them,
83  *    and then uses a separate thread to handle the desired events, and
84  *    disregard the spurious events.</p>
85  * <p>Testing has indicated that there is zero delay between the key released
86  *    event and the subsequent key pressed event when that pair are generated
87  *    spuriously, and that is the case in which they are ignored. The default
88  *    tolerance is 10 ms, as it is possible that the system will not always
89  *    generate spurious key events instantly, but that is still substantially
90  *    faster than a physical keyboard is able to perform multiple physical
91  *    actions.</p>
92  * <p>This code uses an ArrayBlockingQueue so the thread will not spin while
93  *    waiting for input. Most events are handled immediately, and no event
94  *    will be delayed more than the specified maximum age (default 10 ms).
95  * </ul>
96  * <h1>License</h1>
97  * <p>
98  * This program is free software: you can redistribute it and/or modify
99  * it under the terms of the GNU General Public License as published by
100  * the Free Software Foundation, either version 3 of the License, or
101  * (at your option) any later version.
102  * </p>
103  * <p>
104  * This program is distributed in the hope that it will be useful,
105  * but WITHOUT ANY WARRANTY; without even the implied warranty of
106  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
107  * GNU General Public License for more details.
108  * </p>
109  * <p>
110  * You should have received a copy of the GNU General Public License
111  * along with this program.  If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
112  * </p>
113  * <p>
114  * Copyright 2009-2010 <a href="http://www.brianziman.com/">Brian Ziman</a>
115  * </p>
116  * <p>
117  * Please contact me if you find flaws in this software, or have any other
118  * comments or suggestions.
119  * </p>
120  * <h1>Version History</h1>
121  * <ul>
122  * <li>1/4/2010 - Reworked algorithm to eliminate spinning by using java.util.concurrent framework, and to improve reliability.
123  * <li>10/9/2009 - Initial Revision</li>
124  * </ul>
125  */
126 public class UsefulKeyAdapter implements KeyListener, Runnable {
127
128     private final KeyListener gKeyListener;
129
130     // if we queue up more than 100 events, we're screwed!
131     private final BlockingQueue<KeyEvent> gQueue = new ArrayBlockingQueue<KeyEvent>(100);
132
133     private final Thread gRunner;
134     private boolean gRunning = true;
135     private int gMaxAge; // default 10 ms
136
137     /**
138      * Creates a KeyListener implementation that delegates event handling
139      * to the specified KeyListener, after filtering out spurious key pressed
140      * and key released events. In a sequence of events, events are considered
141      * spurious if a key pressed event immediately follows a key released event,
142      * for the same key, and within the specified maximum age. The
143      * default maximum age is 10 ms.
144      */
145     public UsefulKeyAdapter(KeyListener keyListener) {
146         this(keyListener, 10);
147     }
148
149     /**
150      * <p>The run loop processes events as they are queued up.</p>
151      * <p>New algorithm:</p>
152      * <p>All events get queued up in sequence.</p>
153      * <p>
154      * A separate thread pulls events off in a tight loop.</p>
155      * <p>If the head event is a key released event:</p>
156      * <ul><li>pull another event (waiting up to 10 ms for one to appear)</li>
157      *     <li>if the next event is a key pressed, suppress both,</li>
158      *     <li>otherwise dispatch both.</li>
159      * </ul>
160      */
161     public void run() {
162         while(gRunning) {
163             try {
164                 KeyEvent ev = gQueue.take(); // block until event is available
165                 if(ev.getID() == KeyEvent.KEY_RELEASED) {
166                     KeyEvent ev2 = gQueue.poll(gMaxAge, TimeUnit.MILLISECONDS);
167                     if(!matches(ev, ev2)) {
168                         dispatchEvent(ev);
169                         dispatchEvent(ev2); // might be null, that's okay.
170                     } // otherwise suppress them both
171                 } else {
172                     dispatchEvent(ev); // no need to suppress anything else
173                 }
174             } catch (InterruptedException ie) {
175                 // might be triggered when adapter is shut down.
176             }
177         }
178     }
179
180     /**
181      * Creates a KeyListener implementation that delegates event handling
182      * to the specified KeyListener, after filtering out spurious key pressed
183      * and key released events. In a sequence of events, events are considered
184      * spurious if a key pressed event immediately follows a key released event,
185      * for the same key, and within the specified maximum age. The
186      * default maximum age is 10 ms.
187      */
188     public UsefulKeyAdapter(KeyListener keyListener, int maxAge) {
189         this.gKeyListener = keyListener;
190         this.gMaxAge = maxAge;
191         final UsefulKeyAdapter adapter = this;
192
193         gRunner = new Thread(this);
194         gRunner.setName("UsefulKeyAdapter-Thread");
195         gRunner.setDaemon(true);
196         gRunner.start();
197
198     }
199
200     /**
201      * Delegates the key pressed event.
202      */
203     public void keyPressed(KeyEvent e) {
204         gQueue.offer(e);
205     }
206
207     /**
208      * Delegates the key released event.
209      */
210     public void keyReleased(KeyEvent e) {
211         gQueue.offer(e);
212     }
213     /**
214      * Don't use the UsefulKeyAdapter for keyTyped events.
215      */
216     public void keyTyped(KeyEvent e) {
217         gKeyListener.keyTyped(e);
218     }
219
220     private void dispatchEvent(KeyEvent e) {
221         if(e == null) return; // do nothing
222         if(e.getID() == KeyEvent.KEY_PRESSED) gKeyListener.keyPressed(e);
223         else if(e.getID() == KeyEvent.KEY_RELEASED) gKeyListener.keyReleased(e);
224     }
225     /** If two events represent the same key and within 10 ms */
226     private boolean matches(KeyEvent e1, KeyEvent e2) {
227         if(e1 == e2) return true;
228         if(e2 == null) return false;
229         return (e1.getID() == KeyEvent.KEY_RELEASED)
230             && (e2.getID() == KeyEvent.KEY_PRESSED)
231             && e1.getKeyCode() == e2.getKeyCode()
232             && e1.getKeyChar() == e2.getKeyChar()
233             && e2.getWhen() <= e1.getWhen() + gMaxAge;
234     }
235
236
237     /**
238      * Disable the background thread when the adapter goes out of scope.
239      * Not guaranteed to be called, but it's polite to clean up resources.
240      */
241     @Override
242     protected void finalize() throws Throwable {
243         gRunning = false;
244         gRunner.interrupt();
245         super.finalize();
246     }
247
248 }