--- /dev/null
+package org.simantics.debug.graphical;
+
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * <p>
+ * This class wraps your KeyAdapter and suppresses spurious repeated events.
+ * </p>
+ * <p>
+ * Written by <a href="http://www.brianziman.com/">Brian Ziman</a> and available
+ * for download and comments at <a href="http://www.brianziman.com/r/post/stuff-200910092100">http://www.brianziman.com/r/post/stuff-200910092100</a>.
+ * </p>
+ * <h1>Rationale</h1>
+ * <p>
+ * When running in an X11 environment, it appears as though either Java or
+ * the windowing system stupidly generate lots of repeated key events when
+ * you hold a key down. As it is impractical to get users to reconfigure their
+ * systems, and Sun seems unable to fix this issue (having been reported nearly
+ * ten years ago). This adapter is an attempt to filter out the useless events.
+ * </p>
+ * <p>
+ * Many people have whined about this, but few have fixed it. Some have tried,
+ * but their solutions are not as clean as my OCD nature requires. Here are
+ * some examples of what this is meant to handle:
+ * </p>
+ * <ul>
+ * <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>
+ * <li><a href="http://forums.sun.com/thread.jspa?threadID=5382946">http://forums.sun.com/thread.jspa?threadID=5382946</a></li>
+ * <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>
+ * </ul>
+ * <p>
+ *
+ * The goal is to generate a KeyPressed event when a key is pressed,
+ * and to generate a KeyReleased event when a key is released. And
+ * ONLY when a key is pressed or released.
+ * </p>
+ * <h1>Compatibility</h1>
+ * <p>
+ * The class is tested with Java 1.6.0 on Ubuntu 8.10, but should theoretically
+ * work on any platform, even if that platform issues events properly anyway.
+ * That is, this SHOULD work in a cross platform manner. If it doesn't,
+ * please let me know and I may fix it.
+ * </p>
+ * <h1>Usage</h1>
+ * <p>
+ * You don't actually call any of the methods on this class, directly. When Java
+ * thinks it is receiving an event, it will call the methods in this class,
+ * which will, in turn, decide whether an event has actually occurred, and
+ * call YOUR methods when you probably intended them to be called.
+ * </p>
+ * <p>
+ * Replace:
+ * </p>
+ * <pre>
+ * addKeyListener(new KeyAdapter() {
+ * public void keyPressed(KeyEvent e) { ... }
+ * public void keyReleased(KeyEvent e) { ... }
+ * });
+ * </pre>
+ * <p>
+ * with
+ * </p>
+ * <pre>
+ * addKeyListener(new UsefulKeyAdapter(new KeyAdapter() {
+ * public void keyPressed(KeyEvent e) { ... }
+ * public void keyReleased(KeyEvent e) { ... }
+ * }));
+ * </pre>
+ * <p>
+ * Don't use this for keyTyped() events. If you are interested in these
+ * sorts of events, the default behaviour is probably what you actually
+ * want.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <h1>Algorithm:</h1>
+ * <p>This algorithm queues up keyboard events as the system reports them,
+ * and then uses a separate thread to handle the desired events, and
+ * disregard the spurious events.</p>
+ * <p>Testing has indicated that there is zero delay between the key released
+ * event and the subsequent key pressed event when that pair are generated
+ * spuriously, and that is the case in which they are ignored. The default
+ * tolerance is 10 ms, as it is possible that the system will not always
+ * generate spurious key events instantly, but that is still substantially
+ * faster than a physical keyboard is able to perform multiple physical
+ * actions.</p>
+ * <p>This code uses an ArrayBlockingQueue so the thread will not spin while
+ * waiting for input. Most events are handled immediately, and no event
+ * will be delayed more than the specified maximum age (default 10 ms).
+ * </ul>
+ * <h1>License</h1>
+ * <p>
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * </p>
+ * <p>
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * </p>
+ * <p>
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a>.
+ * </p>
+ * <p>
+ * Copyright 2009-2010 <a href="http://www.brianziman.com/">Brian Ziman</a>
+ * </p>
+ * <p>
+ * Please contact me if you find flaws in this software, or have any other
+ * comments or suggestions.
+ * </p>
+ * <h1>Version History</h1>
+ * <ul>
+ * <li>1/4/2010 - Reworked algorithm to eliminate spinning by using java.util.concurrent framework, and to improve reliability.
+ * <li>10/9/2009 - Initial Revision</li>
+ * </ul>
+ */
+public class UsefulKeyAdapter implements KeyListener, Runnable {
+
+ private final KeyListener gKeyListener;
+
+ // if we queue up more than 100 events, we're screwed!
+ private final BlockingQueue<KeyEvent> gQueue = new ArrayBlockingQueue<KeyEvent>(100);
+
+ private final Thread gRunner;
+ private boolean gRunning = true;
+ private int gMaxAge; // default 10 ms
+
+ /**
+ * Creates a KeyListener implementation that delegates event handling
+ * to the specified KeyListener, after filtering out spurious key pressed
+ * and key released events. In a sequence of events, events are considered
+ * spurious if a key pressed event immediately follows a key released event,
+ * for the same key, and within the specified maximum age. The
+ * default maximum age is 10 ms.
+ */
+ public UsefulKeyAdapter(KeyListener keyListener) {
+ this(keyListener, 10);
+ }
+
+ /**
+ * <p>The run loop processes events as they are queued up.</p>
+ * <p>New algorithm:</p>
+ * <p>All events get queued up in sequence.</p>
+ * <p>
+ * A separate thread pulls events off in a tight loop.</p>
+ * <p>If the head event is a key released event:</p>
+ * <ul><li>pull another event (waiting up to 10 ms for one to appear)</li>
+ * <li>if the next event is a key pressed, suppress both,</li>
+ * <li>otherwise dispatch both.</li>
+ * </ul>
+ */
+ public void run() {
+ while(gRunning) {
+ try {
+ KeyEvent ev = gQueue.take(); // block until event is available
+ if(ev.getID() == KeyEvent.KEY_RELEASED) {
+ KeyEvent ev2 = gQueue.poll(gMaxAge, TimeUnit.MILLISECONDS);
+ if(!matches(ev, ev2)) {
+ dispatchEvent(ev);
+ dispatchEvent(ev2); // might be null, that's okay.
+ } // otherwise suppress them both
+ } else {
+ dispatchEvent(ev); // no need to suppress anything else
+ }
+ } catch (InterruptedException ie) {
+ // might be triggered when adapter is shut down.
+ }
+ }
+ }
+
+ /**
+ * Creates a KeyListener implementation that delegates event handling
+ * to the specified KeyListener, after filtering out spurious key pressed
+ * and key released events. In a sequence of events, events are considered
+ * spurious if a key pressed event immediately follows a key released event,
+ * for the same key, and within the specified maximum age. The
+ * default maximum age is 10 ms.
+ */
+ public UsefulKeyAdapter(KeyListener keyListener, int maxAge) {
+ this.gKeyListener = keyListener;
+ this.gMaxAge = maxAge;
+ final UsefulKeyAdapter adapter = this;
+
+ gRunner = new Thread(this);
+ gRunner.setName("UsefulKeyAdapter-Thread");
+ gRunner.setDaemon(true);
+ gRunner.start();
+
+ }
+
+ /**
+ * Delegates the key pressed event.
+ */
+ public void keyPressed(KeyEvent e) {
+ gQueue.offer(e);
+ }
+
+ /**
+ * Delegates the key released event.
+ */
+ public void keyReleased(KeyEvent e) {
+ gQueue.offer(e);
+ }
+ /**
+ * Don't use the UsefulKeyAdapter for keyTyped events.
+ */
+ public void keyTyped(KeyEvent e) {
+ gKeyListener.keyTyped(e);
+ }
+
+ private void dispatchEvent(KeyEvent e) {
+ if(e == null) return; // do nothing
+ if(e.getID() == KeyEvent.KEY_PRESSED) gKeyListener.keyPressed(e);
+ else if(e.getID() == KeyEvent.KEY_RELEASED) gKeyListener.keyReleased(e);
+ }
+ /** If two events represent the same key and within 10 ms */
+ private boolean matches(KeyEvent e1, KeyEvent e2) {
+ if(e1 == e2) return true;
+ if(e2 == null) return false;
+ return (e1.getID() == KeyEvent.KEY_RELEASED)
+ && (e2.getID() == KeyEvent.KEY_PRESSED)
+ && e1.getKeyCode() == e2.getKeyCode()
+ && e1.getKeyChar() == e2.getKeyChar()
+ && e2.getWhen() <= e1.getWhen() + gMaxAge;
+ }
+
+
+ /**
+ * Disable the background thread when the adapter goes out of scope.
+ * Not guaranteed to be called, but it's polite to clean up resources.
+ */
+ @Override
+ protected void finalize() throws Throwable {
+ gRunning = false;
+ gRunner.interrupt();
+ super.finalize();
+ }
+
+}