content: package aliview.gui;
  
  
  import java.awt.AWTEvent;
  import java.awt.Component;
  import java.awt.EventQueue;
  import java.awt.Toolkit;
  import java.awt.event.AWTEventListener;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.KeyEvent;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  
  import javax.swing.Timer;
  
  /**
   * This {@link AWTEventListener} tries to work around for KEY_PRESSED / KEY_TYPED/     KEY_RELEASED repeaters.
   * 
   * If you wish to obtain only one pressed / typed / released, no repeatings (i.e., when the button is hold for a long time).
   * Use new RepeatingKeyEventsFixer().install() as a first line in main() method.
   * 
   * Based on xxx
   * Which was done by Endre Stølsvik and inspired by xxx (hyperlinks stipped out due to stackoverflow policies)
   * 
   * Refined by Jakub Gemrot not only to fix KEY_RELEASED events but also KEY_PRESSED and KEY_TYPED repeatings. Tested under Win7.
   * 
   * If you wish to test the class, just uncomment all System.out.println(...)s.
   * 
   * @author Endre Stølsvik
   * @author Jakub Gemrot
   */
  public class RepeatingKeyEventsFixer implements AWTEventListener {
  
  	public static final int RELEASED_LAG_MILLIS = 1;
  
  	private static boolean assertEDT() {
  		if (!EventQueue.isDispatchThread()) {
  			throw new AssertionError("Not EDT, but [" + Thread.currentThread() + "].");
  		}
  		return true;
  	}
  
  	private Map<Integer, ReleasedAction> _releasedMap = new HashMap<Integer, ReleasedAction>();
  	private Set<Integer> _pressed = new HashSet<Integer>();
  	private Set<Character> _typed = new HashSet<Character>();
  
  	public void install() {
  		Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.KEY_EVENT_MASK);
  	}
  
  	public void remove() {
  		Toolkit.getDefaultToolkit().removeAWTEventListener(this);
  	}
  
  	public void eventDispatched(AWTEvent event) {
  		assert event instanceof KeyEvent : "Shall only listen to KeyEvents, so no other events shall come here";
  	assert assertEDT(); // REMEMBER THAT THIS IS SINGLE THREADED, so no need
  	// for synch.
  
  	// ?: Is this one of our synthetic RELEASED events?
  	if (event instanceof Reposted) {
  		//System.out.println("REPOSTED: " + ((KeyEvent)event).getKeyChar());
  		// -> Yes, so we shalln't process it again.
  		return;
  	}
  
  	final KeyEvent keyEvent = (KeyEvent) event;
  
  	// ?: Is this already consumed?
  	// (Note how events are passed on to all AWTEventListeners even though a
  	// previous one consumed it)
  	if (keyEvent.isConsumed()) {
  		return;
  	}
  
  	// ?: KEY_TYPED event? (We're only interested in KEY_PRESSED and
  	// KEY_RELEASED).
  	if (event.getID() == KeyEvent.KEY_TYPED) {
  		if (_typed.contains(keyEvent.getKeyChar())) {
  			// we're being retyped -> prevent!
  			//System.out.println("TYPED: " + keyEvent.getKeyChar() + " (CONSUMED)");
  			keyEvent.consume();  
  		} else {
  			// -> Yes, TYPED, for a first time
  			//System.out.println("TYPED: " + keyEvent.getKeyChar());
  			_typed.add(keyEvent.getKeyChar());
  		}
  		return;
  	} 
  
  	// ?: Is this RELEASED? (the problem we're trying to fix!)
  	if (keyEvent.getID() == KeyEvent.KEY_RELEASED) {
  		// -> Yes, so stick in wait
  		/*
  		 * Really just wait until "immediately", as the point is that the
  		 * subsequent PRESSED shall already have been posted on the event
  		 * queue, and shall thus be the direct next event no matter which
  		 * events are posted afterwards. The code with the ReleasedAction
  		 * handles if the Timer thread actually fires the action due to
  		 * lags, by cancelling the action itself upon the PRESSED.
  		 */
  		final Timer timer = new Timer(RELEASED_LAG_MILLIS, null);
  		ReleasedAction action = new ReleasedAction(keyEvent, timer);
  		timer.addActionListener(action);
  		timer.start();
  
  		ReleasedAction oldAction = (ReleasedAction)_releasedMap.put(Integer.valueOf(keyEvent.getKeyCode()), action);
  		if (oldAction != null) oldAction.cancel();
  
  		// Consume the original
  		keyEvent.consume();
  		//System.out.println("RELEASED: " + keyEvent.getKeyChar() + " (CONSUMED)");
  		return;
  	}
  
  	if (keyEvent.getID() == KeyEvent.KEY_PRESSED) {
  
  		if (_pressed.contains(keyEvent.getKeyCode())) {
  			// we're still being pressed
  			//System.out.println("PRESSED: " + keyEvent.getKeyChar() + " (CONSUMED)"); 
  			keyEvent.consume();
  		} else {   
  			// Remember that this is single threaded (EDT), so we can't have
  			// races.
  			ReleasedAction action = (ReleasedAction) _releasedMap.get(keyEvent.getKeyCode());
  			// ?: Do we have a corresponding RELEASED waiting?
  			if (action != null) {
  				// -> Yes, so dump it
  				action.cancel();
  
  			}
  			_pressed.add(keyEvent.getKeyCode());
  			//System.out.println("PRESSED: " + keyEvent.getKeyChar());    
  		}
  
  		return;
  	}
  
  	throw new AssertionError("All IDs should be covered.");
  	}
  
  	/**
  	 * The ActionListener that posts the RELEASED {@link RepostedKeyEvent} if
  	 * the {@link Timer} times out (and hence the repeat-action was over).
  	 */
  	protected class ReleasedAction implements ActionListener {
  
  		private final KeyEvent _originalKeyEvent;
  		private Timer _timer;
  
  		ReleasedAction(KeyEvent originalReleased, Timer timer) {
  			_timer = timer;
  			_originalKeyEvent = originalReleased;
  		}
  
  		void cancel() {
  			assert assertEDT();
  			_timer.stop();
  			_timer = null;
  			_releasedMap.remove(Integer.valueOf(_originalKeyEvent.getKeyCode()));   
  		}
  
  
  		public void actionPerformed(@SuppressWarnings("unused") ActionEvent e) {
  			assert assertEDT();
  			// ?: Are we already cancelled?
  			// (Judging by Timer and TimerQueue code, we can theoretically be
  			// raced to be posted onto EDT by TimerQueue,
  			// due to some lag, unfair scheduling)
  			if (_timer == null) {
  				// -> Yes, so don't post the new RELEASED event.
  				return;
  			}
  			//System.out.println("REPOST RELEASE: " + _originalKeyEvent.getKeyChar());
  			// Stop Timer and clean.
  			cancel();
  			// Creating new KeyEvent (we've consumed the original).
  			KeyEvent newEvent = new RepostedKeyEvent(
  					(Component) _originalKeyEvent.getSource(),
  					_originalKeyEvent.getID(), _originalKeyEvent.getWhen(),
  					_originalKeyEvent.getModifiers(), _originalKeyEvent
  					.getKeyCode(), _originalKeyEvent.getKeyChar(),
  					_originalKeyEvent.getKeyLocation());
  			// Posting to EventQueue.
  			_pressed.remove(_originalKeyEvent.getKeyCode());
  			_typed.remove(_originalKeyEvent.getKeyChar());
  			Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(newEvent);
  		}
  	}
  
  	/**
  	 * Marker interface that denotes that the {@link KeyEvent} in question is
  	 * reposted from some {@link AWTEventListener}, including this. It denotes
  	 * that the event shall not be "hack processed" by this class again. (The
  	 * problem is that it is not possible to state
  	 * "inject this event from this point in the pipeline" - one have to inject
  	 * it to the event queue directly, thus it will come through this
  	 * {@link AWTEventListener} too.
  	 */
  	public interface Reposted {
  		// marker
  	}
  
  	/**
  	 * Dead simple extension of {@link KeyEvent} that implements
  	 * {@link Reposted}.
  	 */
  	public static class RepostedKeyEvent extends KeyEvent implements Reposted {
  		public RepostedKeyEvent(@SuppressWarnings("hiding") Component source,
  				@SuppressWarnings("hiding") int id, long when, int modifiers,
  				int keyCode, char keyChar, int keyLocation) {
  			super(source, id, when, modifiers, keyCode, keyChar, keyLocation);
  		}
  	}
  
  }