001: package abbot.util;
002:
003: import java.awt.*;
004: import java.awt.event.*;
005: import javax.swing.text.*;
006: import java.text.*;
007: import java.util.*;
008:
009: import javax.swing.KeyStroke;
010: import javax.swing.SwingUtilities;
011:
012: import abbot.Log;
013:
014: /** Provide an AWTEventListener which normalizes the event stream.
015: <ul>
016: <li>removes modifier key repeats on w32
017: <li>sends a single WINDOW_CLOSED, instead of one every time dispose is
018: called
019: <li>removes some spurious key events on OSX
020: <li>catches sun.awt.dnd.SunDropTargetEvents during native drags
021: </ul>
022: */
023: public class EventNormalizer implements AWTEventListener {
024:
025: // Normally we want to ignore these (w32 generates them)
026: private static boolean captureModifierRepeats = Boolean
027: .getBoolean("abbot.capture_modifier_repeats");
028:
029: private AWTEventListener listener;
030: private WeakAWTEventListener weakListener;
031: private long modifiers;
032: private Map disposedWindows = new WeakHashMap();
033: private DragAwareEventQueue queue;
034: private long mask;
035: private boolean trackDrag;
036:
037: public EventNormalizer() {
038: this (false);
039: }
040:
041: public EventNormalizer(boolean trackDrag) {
042: this .trackDrag = trackDrag;
043: }
044:
045: public void startListening(AWTEventListener listener, long mask) {
046: Log.debug("start listening, mask=0x"
047: + Integer.toHexString((int) mask));
048: fnKeyDown = false;
049: lastKeyPress = lastKeyRelease = KeyEvent.VK_UNDEFINED;
050: lastKeyStroke = null;
051: lastKeyChar = KeyEvent.VK_UNDEFINED;
052: lastKeyComponent = null;
053: modifiers = 0;
054: this .listener = listener;
055: this .mask = mask;
056: weakListener = new WeakAWTEventListener(this , mask);
057: if (trackDrag) {
058: queue = new DragAwareEventQueue();
059: try {
060: SwingUtilities.invokeAndWait(new Runnable() {
061: public void run() {
062: Toolkit.getDefaultToolkit()
063: .getSystemEventQueue().push(queue);
064: }
065: });
066: } catch (Exception e) {
067: Log.warn(e);
068: }
069: }
070: Log.debug("normalizer now listening");
071: }
072:
073: public void stopListening() {
074: if (queue != null) {
075: try {
076: queue.pop();
077: } catch (EmptyStackException e) {
078: Log.warn(e);
079: }
080: queue = null;
081: }
082: if (weakListener != null) {
083: weakListener.dispose();
084: weakListener = null;
085: }
086: listener = null;
087: modifiers = 0;
088: }
089:
090: /** For OSX pre-1.4 laptops... */
091: private boolean fnKeyDown;
092: /** These aid in culling duplicate key events, pre-1.4. */
093: private int lastKeyPress = KeyEvent.VK_UNDEFINED;
094: private int lastKeyRelease = KeyEvent.VK_UNDEFINED;
095: private KeyStroke lastKeyStroke;
096: private char lastKeyChar = KeyEvent.VK_UNDEFINED;
097: private Component lastKeyComponent;
098:
099: /** Returns whether the event is spurious and should be discarded. */
100: private boolean isSpuriousEvent(AWTEvent event) {
101: return isDuplicateKeyEvent(event) || isOSXFunctionKey(event)
102: || isDuplicateDispose(event);
103: }
104:
105: // TODO: maybe make this an AWT event listener instead, so we can use one
106: // instance instead of one per window.
107: private class DisposalWatcher extends ComponentAdapter {
108: private Map map;
109:
110: public DisposalWatcher(Map map) {
111: this .map = map;
112: }
113:
114: public void componentShown(ComponentEvent e) {
115: e.getComponent().removeComponentListener(this );
116: map.remove(e.getComponent());
117: }
118: }
119:
120: // We want to ignore consecutive event indicating window disposal; there
121: // needs to be an intervening SHOWN/OPEN before we're interested again.
122: private boolean isDuplicateDispose(AWTEvent event) {
123: if (event instanceof WindowEvent) {
124: WindowEvent we = (WindowEvent) event;
125: switch (we.getID()) {
126: case WindowEvent.WINDOW_CLOSED:
127: Window w = we.getWindow();
128: if (disposedWindows.containsKey(w)) {
129: return true;
130: }
131: disposedWindows.put(w, Boolean.TRUE);
132: w.addComponentListener(new DisposalWatcher(
133: disposedWindows));
134: break;
135: case WindowEvent.WINDOW_CLOSING:
136: break;
137: default:
138: disposedWindows.remove(we.getWindow());
139: break;
140: }
141: }
142:
143: return false;
144: }
145:
146: /** Flag duplicate key events on pre-1.4 VMs, and repeated modifiers. */
147: private boolean isDuplicateKeyEvent(AWTEvent event) {
148: int id = event.getID();
149: if (id == KeyEvent.KEY_PRESSED) {
150: KeyEvent ke = (KeyEvent) event;
151: lastKeyRelease = KeyEvent.VK_UNDEFINED;
152: int code = ke.getKeyCode();
153:
154: if (code == lastKeyPress) {
155: // Discard duplicate key events; they don't add any
156: // information.
157: // A duplicate key event is sent to the parent frame on
158: // components that don't otherwise consume it (JButton)
159: if (event.getSource() != lastKeyComponent) {
160: lastKeyPress = KeyEvent.VK_UNDEFINED;
161: lastKeyComponent = null;
162: return true;
163: }
164: }
165: lastKeyPress = code;
166: lastKeyComponent = ke.getComponent();
167:
168: // Don't pass on key repeats for modifier keys (w32)
169: if (AWT.isModifier(code)) {
170: int mask = AWT.keyCodeToMask(code);
171: if ((mask & modifiers) != 0 && !captureModifierRepeats) {
172: return true;
173: }
174: }
175: modifiers = ke.getModifiers();
176: } else if (id == KeyEvent.KEY_RELEASED) {
177: KeyEvent ke = (KeyEvent) event;
178: lastKeyPress = KeyEvent.VK_UNDEFINED;
179: int code = ke.getKeyCode();
180: if (code == lastKeyRelease) {
181: if (event.getSource() != lastKeyComponent) {
182: lastKeyRelease = KeyEvent.VK_UNDEFINED;
183: lastKeyComponent = null;
184: return true;
185: }
186: }
187: lastKeyRelease = code;
188: lastKeyComponent = ke.getComponent();
189: modifiers = ke.getModifiers();
190: } else if (id == KeyEvent.KEY_TYPED) {
191: KeyStroke ks = KeyStroke
192: .getKeyStrokeForEvent((KeyEvent) event);
193: char ch = ((KeyEvent) event).getKeyChar();
194: if (ks.equals(lastKeyStroke) || ch == lastKeyChar) {
195: if (event.getSource() != lastKeyComponent) {
196: lastKeyStroke = null;
197: lastKeyChar = KeyEvent.VK_UNDEFINED;
198: lastKeyComponent = null;
199: return true;
200: }
201: }
202: lastKeyStroke = ks;
203: lastKeyChar = ch;
204: lastKeyComponent = ((KeyEvent) event).getComponent();
205: } else {
206: lastKeyPress = lastKeyRelease = KeyEvent.VK_UNDEFINED;
207: lastKeyComponent = null;
208: }
209:
210: return false;
211: }
212:
213: /** Discard function key press/release on 1.3.1 OSX laptops. */
214: // FIXME fn pressed after arrow keys results in a RELEASE event
215: private boolean isOSXFunctionKey(AWTEvent event) {
216: if (event.getID() == KeyEvent.KEY_RELEASED) {
217: if (((KeyEvent) event).getKeyCode() == KeyEvent.VK_CONTROL
218: && fnKeyDown) {
219: fnKeyDown = false;
220: return true;
221: }
222: } else if (event.getID() == KeyEvent.KEY_PRESSED) {
223: if (((KeyEvent) event).getKeyCode() == KeyEvent.VK_CONTROL) {
224: int mods = ((KeyEvent) event).getModifiers();
225: if ((mods & KeyEvent.CTRL_MASK) == 0) {
226: fnKeyDown = true;
227: return true;
228: }
229: }
230: }
231: return false;
232: }
233:
234: protected void delegate(AWTEvent e) {
235: if (Bugs.hasInputMethodInsteadOfKeyTyped()) {
236: if (e.getSource() instanceof JTextComponent
237: && e.getID() == InputMethodEvent.INPUT_METHOD_TEXT_CHANGED) {
238: InputMethodEvent im = (InputMethodEvent) e;
239: if (im.getCommittedCharacterCount() > 0) {
240: AttributedCharacterIterator iter = im.getText();
241: for (char ch = iter.first(); ch != CharacterIterator.DONE; ch = iter
242: .next()) {
243: e = new KeyEvent((Component) e.getSource(),
244: KeyEvent.KEY_TYPED, System
245: .currentTimeMillis(),
246: (int) modifiers, KeyEvent.VK_UNDEFINED,
247: ch);
248: listener.eventDispatched(e);
249: }
250: return;
251: }
252: }
253: }
254: listener.eventDispatched(e);
255: }
256:
257: /** Event reception callback. */
258: public void eventDispatched(AWTEvent event) {
259: boolean discard = isSpuriousEvent(event);
260: if (!discard && listener != null) {
261: delegate(event);
262: }
263: }
264:
265: /** Catches native drop target events, which are normally hidden
266: * from AWTEventListeners.
267: */
268: private class DragAwareEventQueue extends EventQueue {
269: protected void relayDnDEvent(MouseEvent e) {
270: int id = e.getID();
271: if (id == MouseEvent.MOUSE_MOVED
272: || id == MouseEvent.MOUSE_DRAGGED) {
273: if ((mask & InputEvent.MOUSE_MOTION_EVENT_MASK) != 0) {
274: eventDispatched(e);
275: }
276: } else if (id >= MouseEvent.MOUSE_FIRST
277: && id <= MouseEvent.MOUSE_LAST) {
278: if ((mask & InputEvent.MOUSE_EVENT_MASK) != 0) {
279: eventDispatched(e);
280: }
281: }
282: }
283:
284: public void pop() throws EmptyStackException {
285: if (Toolkit.getDefaultToolkit().getSystemEventQueue() == this )
286: super .pop();
287: }
288:
289: /** Dispatch native drag/drop events the same way non-native drags
290: * are reported. Enter/Exit are reported with the appropriate source,
291: * while drag and release events use the drag source as the source.<p>
292: * TODO: implement enter/exit events
293: * TODO: change source to drag source, not mouse under
294: */
295: protected void dispatchEvent(AWTEvent e) {
296: Log.debug("dispatch " + e);
297: if (e.getClass().getName().indexOf("SunDropTargetEvent") != -1) {
298: MouseEvent me = (MouseEvent) e;
299: Component target = SwingUtilities
300: .getDeepestComponentAt(me.getComponent(), me
301: .getX(), me.getY());
302: if (target != me.getSource()) {
303: Log.debug("Change drag event target");
304: me = SwingUtilities.convertMouseEvent(me
305: .getComponent(), me, target);
306: }
307: Log.debug("relay " + me);
308: relayDnDEvent(me);
309: }
310: super.dispatchEvent(e);
311: }
312: }
313: }
|