001: package abbot.tester;
002:
003: import java.awt.*;
004: import java.awt.event.*;
005: import java.awt.dnd.*;
006: import java.lang.reflect.*;
007: import java.lang.ref.WeakReference;
008: import java.util.Stack;
009: import javax.swing.SwingUtilities;
010:
011: import abbot.Log;
012: import abbot.util.*;
013:
014: /** Class to keep track of a given input state. Includes mouse/pointer
015: * position and keyboard modifier key state.<p>
016: * Synchronization assumes that any given instance might be called from more
017: * than one event dispatch thread.<p>
018: * NOTE: Java 1.5 introduces MouseState, which provides current mouse coords
019: */
020: // TODO: add a BitSet with the full keyboard key press state
021: public class InputState {
022:
023: private static final int BUTTON_MASK = (MouseEvent.BUTTON1_MASK
024: | MouseEvent.BUTTON2_MASK | MouseEvent.BUTTON3_MASK);
025:
026: /** Current mouse position, in component coordinates. */
027: private Point mouseLocation = new Point(0, 0);
028: /** Current mouse position, in screen coordinates. */
029: private Point mouseLocationOnScreen = new Point(0, 0);
030: /** Keep a stack of mouse-entered components. Note that the pointer is
031: still considered within a frame when the mouse enters a contained
032: component.
033: */
034: private Stack componentStack = new Stack();
035: private Stack locationStack = new Stack();
036: private Stack screenLocationStack = new Stack();
037: private int buttonsDown;
038: private int modifiersDown;
039: private long lastEventTime;
040: private int clickCount;
041: private Component dragSource;
042: private int dragX, dragY;
043: private EventNormalizer normalizer;
044:
045: public InputState() {
046: long mask = MouseEvent.MOUSE_MOTION_EVENT_MASK
047: | MouseEvent.MOUSE_EVENT_MASK | KeyEvent.KEY_EVENT_MASK;
048: AWTEventListener listener = new SingleThreadedEventListener() {
049: protected void processEvent(AWTEvent event) {
050: update(event);
051: }
052: };
053: normalizer = new EventNormalizer();
054: normalizer.startListening(listener, mask);
055: }
056:
057: public synchronized void clear() {
058: componentStack.clear();
059: locationStack.clear();
060: buttonsDown = 0;
061: modifiersDown = 0;
062: lastEventTime = 0;
063: clickCount = 0;
064: dragSource = null;
065: }
066:
067: public void dispose() {
068: normalizer.stopListening();
069: normalizer = null;
070: }
071:
072: /** Explicitly update the internal state. Allows Robot to update the
073: * state with events it has just posted.
074: */
075: void update(AWTEvent event) {
076: if (event instanceof MouseEvent) {
077: updateState((MouseEvent) event);
078: } else if (event instanceof KeyEvent) {
079: updateState((KeyEvent) event);
080: }
081: }
082:
083: protected void updateState(KeyEvent ke) {
084: // Ignore "old" events
085: if (ke.getWhen() < lastEventTime)
086: return;
087:
088: synchronized (this ) {
089: setLastEventTime(ke.getWhen());
090: setModifiers(ke.getModifiers());
091: // FIXME add state of individual keys
092: }
093: }
094:
095: protected void updateState(MouseEvent me) {
096: // Ignore "old" events
097: if (me.getWhen() < lastEventTime) {
098: if (Log.isClassDebugEnabled(getClass()))
099: Log.debug("Ignoring " + Robot.toString(me));
100: return;
101: }
102: Point where = me.getPoint();
103: // getComponentAt and getLocationOnScreen want the tree lock, so be
104: // careful not to use any additional locks at the same time to avoid
105: // deadlock.
106: Point eventScreenLoc = null;
107: boolean screenLocationFound = true;
108: // Determine the current mouse position in screen coordinates
109: try {
110: eventScreenLoc = AWT.getLocationOnScreen(me.getComponent());
111: } catch (IllegalComponentStateException e) {
112: // component might be hidden by the time we process this event
113: screenLocationFound = false;
114: }
115: synchronized (this ) {
116: setLastEventTime(me.getWhen());
117: // When a button is released, only that button appears in the
118: // modifier mask
119: int whichButton = me.getModifiers() & BUTTON_MASK;
120: if (me.getID() == MouseEvent.MOUSE_RELEASED) {
121: buttonsDown &= ~whichButton;
122: modifiersDown &= ~whichButton;
123: } else if (me.getID() == MouseEvent.MOUSE_PRESSED) {
124: buttonsDown |= whichButton;
125: modifiersDown |= whichButton;
126: }
127: clickCount = me.getClickCount();
128: if (me.getID() == MouseEvent.MOUSE_PRESSED) {
129: dragSource = me.getComponent();
130: dragX = me.getX();
131: dragY = me.getY();
132: } else if (me.getID() == MouseEvent.MOUSE_RELEASED
133: || me.getID() == MouseEvent.MOUSE_MOVED) {
134: dragSource = null;
135: }
136:
137: if (me.getID() == MouseEvent.MOUSE_ENTERED) {
138: componentStack
139: .push(new WeakReference(me.getComponent()));
140: locationStack.push(me.getPoint());
141: screenLocationStack
142: .push(screenLocationFound ? eventScreenLoc : me
143: .getPoint());
144:
145: } else if (me.getID() == MouseEvent.MOUSE_EXITED) {
146: if (componentStack.empty()) {
147: if (Log.isClassDebugEnabled(getClass()))
148: Log.debug("Got " + Robot.toString(me)
149: + " but component not on stack");
150: } else {
151: componentStack.pop();
152: locationStack.pop();
153: screenLocationStack.pop();
154: }
155: }
156: if (screenLocationFound) {
157: if (componentStack.empty()) {
158: mouseLocation = null;
159: } else {
160: mouseLocation = new Point(where);
161: }
162: mouseLocationOnScreen.setLocation(eventScreenLoc);
163: mouseLocationOnScreen.translate(where.x, where.y);
164: }
165: }
166: }
167:
168: /** Return the component under the given coordinates in the given parent
169: component. Events are often generated only for the outermost
170: container, so we have to determine if the pointer is actually within a
171: child. Basically the same as Component.getComponentAt, but recurses
172: to the lowest-level component instead of only one level. Point is in
173: component coordinates.<p>
174: The default Component.getComponentAt can return invisible components
175: (JRootPane has an invisible JPanel (glass pane?) which will otherwise
176: swallow everything).<p>
177: NOTE: getComponentAt grabs the TreeLock, so this should *only* be
178: invoked on the event dispatch thread, preferably with no other locks
179: held. Use it elsewhere at your own risk.<p>
180: NOTE: What about drags outside a component?
181: */
182: public static Component getComponentAt(Component parent, Point p) {
183: Log.debug("Checking " + p + " in " + Robot.toString(parent));
184: Component c = SwingUtilities.getDeepestComponentAt(parent, p.x,
185: p.y);
186: Log.debug("Deepest is " + Robot.toString(c));
187: return c;
188: }
189:
190: /** Return the most deeply nested component which currently contains the
191: * pointer.
192: */
193: public synchronized Component getUltimateMouseComponent() {
194: Component c = getMouseComponent();
195: if (c != null) {
196: Point p = getMouseLocation();
197: c = getComponentAt(c, p);
198: }
199: return c;
200: }
201:
202: /** Return the last known Component to contain the pointer, or null if
203: none. Note that this may not correspond to the component that
204: actually shows up in AWTEvents.
205: */
206: public synchronized Component getMouseComponent() {
207: Component comp = null;
208: if (!componentStack.empty()) {
209: WeakReference ref = (WeakReference) componentStack.peek();
210: comp = (Component) ref.get();
211: // Make sure we don't return a component that has gone away.
212: if (comp == null || !comp.isShowing()) {
213: Log.debug("Discarding unavailable component");
214: componentStack.pop();
215: locationStack.pop();
216: screenLocationStack.pop();
217: comp = getMouseComponent();
218: if (comp != null) {
219: mouseLocation = (Point) locationStack.peek();
220: mouseLocationOnScreen = (Point) screenLocationStack
221: .peek();
222: }
223: }
224: }
225: if (Log.isClassDebugEnabled(getClass()))
226: Log.debug("Current component is " + Robot.toString(comp));
227: return comp;
228: }
229:
230: public synchronized boolean isDragging() {
231: return dragSource != null;
232: }
233:
234: public synchronized Component getDragSource() {
235: return dragSource;
236: }
237:
238: public synchronized void setDragSource(Component c) {
239: dragSource = c;
240: }
241:
242: public synchronized Point getDragOrigin() {
243: return new Point(dragX, dragY);
244: }
245:
246: public synchronized int getClickCount() {
247: return clickCount;
248: }
249:
250: protected synchronized void setClickCount(int count) {
251: clickCount = count;
252: }
253:
254: public synchronized long getLastEventTime() {
255: return lastEventTime;
256: }
257:
258: protected synchronized void setLastEventTime(long t) {
259: lastEventTime = t;
260: }
261:
262: /** Returns all currently active modifiers. */
263: public synchronized int getModifiers() {
264: return modifiersDown;
265: }
266:
267: protected synchronized void setModifiers(int m) {
268: modifiersDown = m;
269: }
270:
271: /** Returns the currently pressed key modifiers. */
272: public synchronized int getKeyModifiers() {
273: return modifiersDown & ~BUTTON_MASK;
274: }
275:
276: public synchronized int getButtons() {
277: return buttonsDown;
278: }
279:
280: protected synchronized void setButtons(int b) {
281: buttonsDown = b;
282: }
283:
284: /** Returns the mouse location relative to the component that currently
285: contains the pointer, or null if outside all components.
286: */
287: public synchronized Point getMouseLocation() {
288: return mouseLocation != null ? new Point(mouseLocation) : null;
289: }
290:
291: /** Returns the last known mouse location. */
292: public synchronized Point getMouseLocationOnScreen() {
293: return new Point(mouseLocationOnScreen);
294: }
295:
296: /** Return whether there is a native drag/drop operation in progress. */
297: public boolean isNativeDragActive() {
298: try {
299: Class cls = Class
300: .forName("sun.awt.dnd.SunDragSourceContextPeer");
301: Method m = cls.getMethod("checkDragDropInProgress", null);
302: try {
303: m.invoke(null, null);
304: return false;
305: } catch (InvocationTargetException e) {
306: if (e.getTargetException() instanceof InvalidDnDOperationException) {
307: return true;
308: }
309: }
310: } catch (Exception e) {
311: }
312: return false;
313: }
314: }
|