001    /*
002     * Created on Mar 28, 2008
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
005     * the License. You may obtain a copy of the License at
006     *
007     * http://www.apache.org/licenses/LICENSE-2.0
008     *
009     * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
010     * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
011     * specific language governing permissions and limitations under the License.
012     *
013     * Copyright @2008-2010 the original author or authors.
014     */
015    package org.fest.swing.input;
016    
017    import static java.awt.AWTEvent.*;
018    import static javax.swing.SwingUtilities.getDeepestComponentAt;
019    import static org.fest.swing.awt.AWT.locationOnScreenOf;
020    import static org.fest.swing.input.MouseInfo.BUTTON_MASK;
021    
022    import java.awt.*;
023    import java.awt.event.*;
024    
025    import net.jcip.annotations.GuardedBy;
026    import net.jcip.annotations.ThreadSafe;
027    
028    import org.fest.swing.exception.UnexpectedException;
029    import org.fest.swing.listener.EventDispatchThreadedEventListener;
030    
031    /**
032     * Class to keep track of a given input state. Includes mouse/pointer position and keyboard modifier key state.
033     * <p>
034     * Synchronization assumes that any given instance might be called from more than one event dispatch thread.
035     * </p>
036     */
037    // TODO: add a BitSet with the full keyboard key press state
038    @ThreadSafe
039    public class InputState {
040    
041      @GuardedBy("this") private final MouseInfo mouseInfo = new MouseInfo();
042      @GuardedBy("this") private final DragDropInfo dragDropInfo = new DragDropInfo();
043    
044      @GuardedBy("this") private int modifiers;
045      @GuardedBy("this") private long lastEventTime;
046    
047      private EventNormalizer normalizer;
048    
049      public InputState(Toolkit toolkit) {
050        long mask = MOUSE_MOTION_EVENT_MASK | MOUSE_EVENT_MASK | KEY_EVENT_MASK;
051        AWTEventListener listener = new EventDispatchThreadedEventListener() {
052          protected void processEvent(AWTEvent event) {
053            update(event);
054          }
055        };
056        normalizer = new EventNormalizer();
057        normalizer.startListening(toolkit, listener, mask);
058      }
059    
060      public synchronized void clear() {
061        mouseInfo.clear();
062        dragDropInfo.clear();
063        modifiers = 0;
064        lastEventTime = 0;
065      }
066    
067      public void dispose() {
068        normalizer.stopListening();
069        normalizer = null;
070      }
071    
072      /**
073       * Explicitly update the internal state.
074       * @param event the event to use to update the internal state.
075       */
076      public void update(AWTEvent event) {
077        if (event instanceof KeyEvent) updateState((KeyEvent) event);
078        if (event instanceof MouseEvent) updateState((MouseEvent) event);
079      }
080    
081      private void updateState(KeyEvent event) {
082        if (isOld(event)) return;
083        synchronized (this) {
084          lastEventTime(event);
085          modifiers(event.getModifiers());
086          // FIXME add state of individual keys
087        }
088      }
089    
090      private void updateState(MouseEvent event) {
091        if (isOld(event)) return;
092        // childAt and locationOnScreenOf want the tree lock, so be careful not to use any additional locks at the same time
093        // to avoid deadlock.
094        Point eventScreenLocation = null;
095        // Determine the current mouse position in screen coordinates
096        try {
097          eventScreenLocation = locationOnScreenOf(event.getComponent());
098        } catch (IllegalComponentStateException e) {
099          // component might be hidden by the time we process this event
100        } catch (UnexpectedException e) {
101          if (!(e.getCause() instanceof IllegalComponentStateException)) throw e;
102        }
103        synchronized (this) {
104          lastEventTime(event);
105          dragDropInfo.update(event);
106          mouseInfo.modifiers(modifiers);
107          mouseInfo.update(event, eventScreenLocation);
108          modifiers(mouseInfo.modifiers());
109        }
110      }
111    
112      private boolean isOld(InputEvent event) {
113        return event.getWhen() < lastEventTime();
114      }
115    
116      private void lastEventTime(InputEvent event) {
117        lastEventTime = event.getWhen();
118      }
119    
120      private void modifiers(int newModifiers) {
121        modifiers = newModifiers;
122      }
123    
124      /**
125       * Returns the most deeply nested component which currently contains the pointer.
126       * @return the most deeply nested component which currently contains the pointer.
127       */
128      public synchronized Component deepestComponentUnderMousePointer() {
129        Component c = mouseComponent();
130        if (c != null) c = childAt(c, mouseLocation());
131        return c;
132      }
133    
134      /**
135       * Returns the last known Component to contain the pointer, or <code>null</code> if none. Note that this may not
136       * correspond to the component that actually shows up in AWTEvents.
137       * @return the last known Component to contain the pointer, or <code>null</code> if none.
138       */
139      public synchronized Component mouseComponent() {
140        return mouseInfo.component();
141      }
142    
143      /**
144       * Returns the component under the given coordinates in the given parent component. Events are often generated only
145       * for the outermost container, so we have to determine if the pointer is actually within a child. Basically the same
146       * as Component.getComponentAt, but recurses to the lowest-level component instead of only one level. Point is in
147       * component coordinates.
148       * <p>
149       * The default Component.getComponentAt can return invisible components (JRootPane has an invisible JPanel (glass
150       * pane?) which will otherwise swallow everything).
151       * <p>
152       * NOTE: childAt grabs the TreeLock, so this should *only* be invoked on the event dispatch thread, preferably with no
153       * other locks held. Use it elsewhere at your own risk.
154       * <p>
155       * @param parent the given parent.
156       * @param where the given coordinates.
157       * @return the component under the given coordinates in the given parent component.
158       */
159      public static Component childAt(Component parent, Point where) {
160        return getDeepestComponentAt(parent, where.x, where.y);
161      }
162    
163      /**
164       * Indicates there is a drag operation in progress.
165       * @return <code>true</code> if there is a drag operation in progress, <code>false</code> otherwise.
166       */
167      public synchronized boolean dragInProgress() {
168        return dragDropInfo.isDragging();
169      }
170    
171      /**
172       * Returns the <code>{@link Component}</code> where a drag operation started.
173       * @return the <code>Component</code> where a drag operation started.
174       */
175      public synchronized Component dragSource() {
176        return dragDropInfo.source();
177      }
178    
179      /**
180       * Returns the coordinates where a drag operation started.
181       * @return the coordinates where a drag operation started.
182       */
183      public synchronized Point dragOrigin() {
184        return dragDropInfo.origin();
185      }
186    
187      /**
188       * Indicates the number of times a mouse button was clicked.
189       * @return the number of times a mouse button was clicked.
190       */
191      public synchronized int clickCount() {
192        return mouseInfo.clickCount();
193      }
194    
195      /**
196       * Returns the time when the last input event occurred.
197       * @return the time when the last input event occurred.
198       */
199      public synchronized long lastEventTime() {
200        return lastEventTime;
201      }
202    
203      /**
204       * Returns all currently active modifiers.
205       * @return all currently active modifiers.
206       */
207      public synchronized int modifiers() {
208        return modifiers;
209      }
210    
211      /**
212       * Returns the currently pressed key modifiers.
213       * @return the currently pressed key modifiers.
214       */
215      public synchronized int keyModifiers() {
216        return modifiers & ~BUTTON_MASK;
217      }
218    
219      /**
220       * Returns the mouse buttons used in the last input event.
221       * @return the mouse buttons used in the last input event.
222       */
223      public synchronized int buttons() {
224        return mouseInfo.buttons();
225      }
226    
227      /**
228       * Returns the mouse location relative to the component that currently contains the pointer, or <code>null</code> if
229       * outside all components.
230       * @return the mouse location relative to the component that currently contains the pointer, or <code>null</code> if
231       *         outside all components.
232       */
233      public synchronized Point mouseLocation() {
234        return mouseInfo.location();
235      }
236    
237      /**
238       * Returns the last known mouse location.
239       * @return the last known mouse location.
240       */
241      public synchronized Point mouseLocationOnScreen() {
242        return mouseInfo.locationOnScreen();
243      }
244    
245      /**
246       * Indicates whether there is a native drag/drop operation in progress.
247       * @return <code>true</code> if there is a native drag/drop operation in progress, <code>false</code> otherwise.
248       */
249      public boolean isNativeDragActive() {
250        return dragDropInfo.isNativeDragActive();
251      }
252    }