1 package org.simantics.debug.graphical;
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;
11 * This class wraps your KeyAdapter and suppresses spurious repeated events.
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>.
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.
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:
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>
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.
41 * <h1>Compatibility</h1>
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.
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.
59 * addKeyListener(new KeyAdapter() {
60 * public void keyPressed(KeyEvent e) { ... }
61 * public void keyReleased(KeyEvent e) { ... }
68 * addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {
69 * public void keyPressed(KeyEvent e) { ... }
70 * public void keyReleased(KeyEvent e) { ... }
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
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.
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
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).
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.
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.
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>.
114 * Copyright 2009-2010 <a href="http://www.brianziman.com/">Brian Ziman</a>
117 * Please contact me if you find flaws in this software, or have any other
118 * comments or suggestions.
120 * <h1>Version History</h1>
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>
126 public class UsefulKeyAdapter implements KeyListener, Runnable {
128 private final KeyListener gKeyListener;
130 // if we queue up more than 100 events, we're screwed!
131 private final BlockingQueue<KeyEvent> gQueue = new ArrayBlockingQueue<KeyEvent>(100);
133 private final Thread gRunner;
134 private boolean gRunning = true;
135 private int gMaxAge; // default 10 ms
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.
145 public UsefulKeyAdapter(KeyListener keyListener) {
146 this(keyListener, 10);
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>
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>
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)) {
169 dispatchEvent(ev2); // might be null, that's okay.
170 } // otherwise suppress them both
172 dispatchEvent(ev); // no need to suppress anything else
174 } catch (InterruptedException ie) {
175 // might be triggered when adapter is shut down.
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.
188 public UsefulKeyAdapter(KeyListener keyListener, int maxAge) {
189 this.gKeyListener = keyListener;
190 this.gMaxAge = maxAge;
191 final UsefulKeyAdapter adapter = this;
193 gRunner = new Thread(this);
194 gRunner.setName("UsefulKeyAdapter-Thread");
195 gRunner.setDaemon(true);
201 * Delegates the key pressed event.
203 public void keyPressed(KeyEvent e) {
208 * Delegates the key released event.
210 public void keyReleased(KeyEvent e) {
214 * Don't use the UsefulKeyAdapter for keyTyped events.
216 public void keyTyped(KeyEvent e) {
217 gKeyListener.keyTyped(e);
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);
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;
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.
242 protected void finalize() throws Throwable {