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 }