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 }