EventQueueMonitor.java revision 13430:5e8370fb3ed9
1/* 2 * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package com.sun.java.accessibility.util; 27 28import java.util.*; 29import java.awt.*; 30import java.awt.event.*; 31import javax.accessibility.*; 32import java.security.AccessController; 33import java.security.PrivilegedAction; 34 35/** 36 * The {@code EventQueueMonitor} class provides key core functionality for Assistive 37 * Technologies (and other system-level technologies that need some of the same 38 * things that Assistive Technology needs). 39 * 40 * @see AWTEventMonitor 41 * @see SwingEventMonitor 42 */ 43public class EventQueueMonitor 44 implements AWTEventListener { 45 46 // NOTE: All of the following properties are static. The reason 47 // for this is that there may be multiple EventQueue instances 48 // in use in the same VM. By making these properties static, 49 // we can guarantee we get the information from all of the 50 // EventQueue instances. 51 52 // The stuff that is cached. 53 // 54 static Vector<Container>topLevelWindows = new Vector<>(); 55 static Window topLevelWindowWithFocus = null; 56 static Point currentMousePosition = null; 57 static Component currentMouseComponent = null; 58 59 // Low-level listener interfaces 60 // 61 static GUIInitializedListener guiInitializedListener = null; 62 static TopLevelWindowListener topLevelWindowListener = null; 63 static MouseMotionListener mouseMotionListener = null; 64 65 /** 66 * Class variable stating whether the assistive technologies have 67 * been loaded yet or not. The assistive technologies won't be 68 * loaded until the first event is posted to the EventQueue. This 69 * gives the toolkit a chance to do all the necessary initialization 70 * it needs to do. 71 */ 72 73 /** 74 * Class variable stating whether the GUI subsystem has been initialized 75 * or not. 76 * 77 * @see #isGUIInitialized 78 */ 79 static boolean guiInitialized = false; 80 81 /** 82 * Queue that holds events for later processing. 83 */ 84 static EventQueueMonitorItem componentEventQueue = null; 85 86 /** 87 * Class that tells us what the component event dispatch thread is. 88 */ 89 static private ComponentEvtDispatchThread cedt = null; 90 91 /** 92 * Handle the synchronization between the thing that populates the 93 * component event dispatch thread ({@link #queueComponentEvent}) 94 * and the thing that processes the events ({@link ComponentEvtDispatchThread}). 95 */ 96 static Object componentEventQueueLock = new Object(); 97 98 /** 99 * Create a new {@code EventQueueMonitor} instance. Normally, this will 100 * be called only by the AWT Toolkit during initialization time. 101 * Assistive technologies should not create instances of 102 * EventQueueMonitor by themselves. Instead, they should either 103 * refer to it directly via the static methods in this class, e.g., 104 * {@link #getCurrentMousePosition} or obtain the instance by asking the 105 * Toolkit, e.g., {@link java.awt.Toolkit#getSystemEventQueue}. 106 */ 107 public EventQueueMonitor() { 108 if (cedt == null) { 109 cedt = new ComponentEvtDispatchThread("EventQueueMonitor-ComponentEvtDispatch"); 110 111 cedt.setDaemon(true); 112 cedt.start(); 113 } 114 } 115 116 /** 117 * Queue up a {@link java.awt.event.ComponentEvent ComponentEvent} for later 118 * processing by the {@link ComponentEvtDispatch} thread. 119 * 120 * @param e a {@code ComponentEvent} 121 */ 122 static void queueComponentEvent(ComponentEvent e) { 123 synchronized(componentEventQueueLock) { 124 EventQueueMonitorItem eqi = new EventQueueMonitorItem(e); 125 if (componentEventQueue == null) { 126 componentEventQueue = eqi; 127 } else { 128 EventQueueMonitorItem q = componentEventQueue; 129 while (true) { 130 if (q.next != null) { 131 q = q.next; 132 } else { 133 break; 134 } 135 } 136 q.next = eqi; 137 } 138 componentEventQueueLock.notifyAll(); 139 } 140 } 141 142 /** 143 * Tell the {@code EventQueueMonitor} to start listening for events. 144 */ 145 public static void maybeInitialize() { 146 if (cedt == null) { 147 java.security.AccessController.doPrivileged( 148 new java.security.PrivilegedAction<Void>() { 149 public Void run() { 150 try { 151 long eventMask = AWTEvent.WINDOW_EVENT_MASK | 152 AWTEvent.FOCUS_EVENT_MASK | 153 AWTEvent.MOUSE_MOTION_EVENT_MASK; 154 155 Toolkit.getDefaultToolkit().addAWTEventListener(new EventQueueMonitor(), eventMask); 156 } catch (Exception e) { 157 } 158 return null; 159 } 160 } 161 ); 162 } 163 } 164 165 /** 166 * Handle events as a result of registering a listener 167 * on the {@link java.awt.EventQueue EventQueue} in {@link #maybeInitialize}. 168 */ 169 public void eventDispatched(AWTEvent theEvent) { 170 processEvent(theEvent); 171 } 172 173 /** 174 * Assisitive technologies that have 175 * registered a {@link GUIInitializedListener} will be notified. 176 * 177 * @see #addGUIInitializedListener 178 */ 179 static void maybeNotifyAssistiveTechnologies() { 180 181 if (!guiInitialized) { 182 guiInitialized = true; 183 if (guiInitializedListener != null) { 184 guiInitializedListener.guiInitialized(); 185 } 186 } 187 188 } 189 190 /********************************************************************/ 191 /* */ 192 /* Package Private Methods */ 193 /* */ 194 /********************************************************************/ 195 196 /** 197 * Add a Container to the list of top-level containers 198 * in the cache. This follows the object's hierarchy up the 199 * tree until it finds the top most parent. If the parent is 200 * not already in the list of Containers, it adds it to the list. 201 * 202 * @param c the Container 203 */ 204 static void addTopLevelWindow(Component c) { 205 Container parent; 206 207 if (c == null) { 208 return; 209 } 210 211 if (!(c instanceof Window)) { 212 addTopLevelWindow(c.getParent()); 213 return; 214 } 215 216 if ((c instanceof Dialog) || (c instanceof Window)) { 217 parent = (Container) c; 218 } else { 219 parent = c.getParent(); 220 if (parent != null) { 221 addTopLevelWindow(parent); 222 return; 223 } 224 } 225 226 if (parent == null) { 227 parent = (Container) c; 228 } 229 230 // Because this method is static, do not make it synchronized because 231 // it can lock the whole class. Instead, just lock what needs to be 232 // locked. 233 // 234 synchronized (topLevelWindows) { 235 if ((parent != null) && !topLevelWindows.contains(parent)) { 236 topLevelWindows.addElement(parent); 237 if (topLevelWindowListener != null) { 238 topLevelWindowListener.topLevelWindowCreated((Window) parent); 239 } 240 } 241 } 242 } 243 244 /** 245 * Removes a container from the list of top level containers in the cache. 246 * 247 * @param c the top level container to remove 248 */ 249 static void removeTopLevelWindow(Window w) { 250 251 // Because this method is static, do not make it synchronized because 252 // it can lock the whole class. Instead, just lock what needs to be 253 // locked. 254 // 255 synchronized (topLevelWindows) { 256 if (topLevelWindows.contains(w)) { 257 topLevelWindows.removeElement(w); 258 if (topLevelWindowListener != null) { 259 topLevelWindowListener.topLevelWindowDestroyed(w); 260 } 261 } 262 } 263 } 264 265 /** 266 * Update current mouse position. 267 * 268 * @param mouseEvent the MouseEvent that holds the new mouse position. 269 */ 270 static void updateCurrentMousePosition(MouseEvent mouseEvent) { 271 Point oldMousePos = currentMousePosition; 272 // Be careful here. The component in the event might be 273 // hidden by the time we process the event. 274 try { 275 Point eventPoint = mouseEvent.getPoint(); 276 currentMouseComponent = (Component) (mouseEvent.getSource()); 277 currentMousePosition = currentMouseComponent.getLocationOnScreen(); 278 currentMousePosition.translate(eventPoint.x,eventPoint.y); 279 } catch (Exception e) { 280 currentMousePosition = oldMousePos; 281 } 282 } 283 284 /** 285 * Process the event. This maintains the event cache in addition 286 * to calling all the registered listeners. NOTE: The events that 287 * come through here are from peered Components. 288 * 289 * @param theEvent the AWTEvent 290 */ 291 static void processEvent(AWTEvent theEvent) { 292 switch (theEvent.getID()) { 293 case MouseEvent.MOUSE_MOVED: 294 case MouseEvent.MOUSE_DRAGGED: 295 case FocusEvent.FOCUS_GAINED: 296 case WindowEvent.WINDOW_DEACTIVATED: 297 queueComponentEvent((ComponentEvent) theEvent); 298 break; 299 300 case WindowEvent.WINDOW_ACTIVATED: 301 // Dialogs fire WINDOW_ACTIVATED and FOCUS_GAINED events 302 // before WINDOW_OPENED so we need to add topLevelListeners 303 // for the dialog when it is first activated to get a 304 // focus gained event for the focus component in the dialog. 305 if (theEvent instanceof ComponentEvent) { 306 ComponentEvent ce = (ComponentEvent)theEvent; 307 if (ce.getComponent() instanceof Window) { 308 EventQueueMonitor.addTopLevelWindow(ce.getComponent()); 309 EventQueueMonitor.maybeNotifyAssistiveTechnologies(); 310 } else { 311 EventQueueMonitor.maybeNotifyAssistiveTechnologies(); 312 EventQueueMonitor.addTopLevelWindow(ce.getComponent()); 313 } 314 } 315 queueComponentEvent((ComponentEvent) theEvent); 316 break; 317 318 // handle WINDOW_OPENED and WINDOW_CLOSED events synchronously 319 case WindowEvent.WINDOW_OPENED: 320 if (theEvent instanceof ComponentEvent) { 321 ComponentEvent ce = (ComponentEvent)theEvent; 322 if (ce.getComponent() instanceof Window) { 323 EventQueueMonitor.addTopLevelWindow(ce.getComponent()); 324 EventQueueMonitor.maybeNotifyAssistiveTechnologies(); 325 } else { 326 EventQueueMonitor.maybeNotifyAssistiveTechnologies(); 327 EventQueueMonitor.addTopLevelWindow(ce.getComponent()); 328 } 329 } 330 break; 331 case WindowEvent.WINDOW_CLOSED: 332 if (theEvent instanceof ComponentEvent) { 333 ComponentEvent ce = (ComponentEvent)theEvent; 334 EventQueueMonitor.removeTopLevelWindow((Window) (ce.getComponent())); 335 } 336 break; 337 338 default: 339 break; 340 } 341 } 342 343 /** 344 * Internal test 345 */ 346 static synchronized Component getShowingComponentAt(Container c, int x, int y) { 347 if (!c.contains(x, y)) { 348 return null; 349 } 350 int ncomponents = c.getComponentCount(); 351 for (int i = 0 ; i < ncomponents ; i++) { 352 Component comp = c.getComponent(i); 353 if (comp != null && comp.isShowing()) { 354 Point location = comp.getLocation(); 355 if (comp.contains(x - location.x, y - location.y)) { 356 return comp; 357 } 358 } 359 } 360 return c; 361 } 362 363 /** 364 * Return the Component at the given Point on the screen in the 365 * given Container. 366 * 367 * @param c the Container to search 368 * @param p the Point in screen coordinates 369 * @return the Component at the given Point on the screen in the 370 * given Container -- can be null if no Component is at that Point 371 */ 372 static synchronized Component getComponentAt(Container c, Point p) { 373 if (!c.isShowing()) { 374 return null; 375 } 376 377 Component comp; 378 Point containerLoc = c.getLocationOnScreen(); 379 Point containerPoint = new Point(p.x - containerLoc.x, 380 p.y - containerLoc.y); 381 382 comp = getShowingComponentAt(c, containerPoint.x, containerPoint.y); 383 384 if ((comp != c) && (comp instanceof Container)) { 385 return getComponentAt((Container)comp,p); 386 } else { 387 return comp; 388 } 389 } 390 391 /** 392 * Obtain the {@link javax.accessibility.Accessible Accessible} object at the given point on the Screen. 393 * The return value may be null if an {@code Accessible} object cannot be 394 * found at the particular point. 395 * 396 * @param p the point to be accessed 397 * @return the {@code Accessible} at the specified point 398 */ 399 static public Accessible getAccessibleAt(Point p) { 400 Window w = getTopLevelWindowWithFocus(); 401 Window[] wins = getTopLevelWindows(); 402 Component c = null; 403 404 // See if the point we're being asked about is the 405 // currentMousePosition. If so, start with the component 406 // that we know the currentMousePostion is over 407 // 408 if (currentMousePosition == null) { 409 return null; 410 } 411 if (currentMousePosition.equals(p)) { 412 if (currentMouseComponent instanceof Container) { 413 c = getComponentAt((Container) currentMouseComponent, p); 414 } 415 } 416 417 // Try the window with focus next 418 // 419 if (c == null && w != null) { 420 c = getComponentAt(w,p); 421 } 422 423 // Try the other windows next. [[[WDW: Stacking order???]]] 424 if (c == null) { 425 for (int i = 0; i < wins.length; i++) { 426 c = getComponentAt(wins[i],p); 427 if (c != null) { 428 break; 429 } 430 } 431 } 432 433 if (c instanceof Accessible) { 434 AccessibleContext ac = ((Accessible) c).getAccessibleContext(); 435 if (ac != null) { 436 AccessibleComponent acmp = ac.getAccessibleComponent(); 437 if ((acmp != null) && (ac.getAccessibleChildrenCount() != 0)) { 438 Point location = acmp.getLocationOnScreen(); 439 location.move(p.x - location.x, p.y - location.y); 440 return acmp.getAccessibleAt(location); 441 } 442 } 443 return (Accessible) c; 444 } else { 445 return Translator.getAccessible(c); 446 } 447 } 448 449 /********************************************************************/ 450 /* */ 451 /* Public Methods */ 452 /* */ 453 /********************************************************************/ 454 455 /** 456 * Says whether the GUI subsystem has been initialized or not. 457 * If this returns true, the assistive technology can freely 458 * create GUI component instances. If the return value is false, 459 * the assistive technology should register a {@link GUIInitializedListener} 460 * and wait to create GUI component instances until the listener is 461 * called. 462 * 463 * @return true if the GUI subsystem has been initialized 464 * @see #addGUIInitializedListener 465 */ 466 static public boolean isGUIInitialized() { 467 maybeInitialize(); 468 return guiInitialized; 469 } 470 471 /** 472 * Adds the specified listener to be notified when the GUI subsystem 473 * is initialized. Assistive technologies should get the results of 474 * {@link #isGUIInitialized} before calling this method. 475 * 476 * @param l the listener to add 477 * @see #isGUIInitialized 478 * @see #removeTopLevelWindowListener 479 */ 480 static public void addGUIInitializedListener(GUIInitializedListener l) { 481 maybeInitialize(); 482 guiInitializedListener = 483 GUIInitializedMulticaster.add(guiInitializedListener,l); 484 } 485 486 /** 487 * Removes the specified listener to be notified when the GUI subsystem 488 * is initialized. 489 * 490 * @param l the listener to remove 491 * @see #addGUIInitializedListener 492 */ 493 static public void removeGUIInitializedListener(GUIInitializedListener l) { 494 guiInitializedListener = 495 GUIInitializedMulticaster.remove(guiInitializedListener,l); 496 } 497 498 /** 499 * Adds the specified listener to be notified when a top level window 500 * is created or destroyed. 501 * 502 * @param l the listener to add 503 * @see #removeTopLevelWindowListener 504 */ 505 static public void addTopLevelWindowListener(TopLevelWindowListener l) { 506 topLevelWindowListener = 507 TopLevelWindowMulticaster.add(topLevelWindowListener,l); 508 } 509 510 /** 511 * Removes the specified listener to be notified when a top level window 512 * is created or destroyed. 513 * 514 * @param l the listener to remove 515 * @see #addTopLevelWindowListener 516 */ 517 static public void removeTopLevelWindowListener(TopLevelWindowListener l) { 518 topLevelWindowListener = 519 TopLevelWindowMulticaster.remove(topLevelWindowListener,l); 520 } 521 522 /** 523 * Return the last recorded position of the mouse in screen coordinates. 524 * 525 * @return the last recorded position of the mouse in screen coordinates 526 */ 527 static public Point getCurrentMousePosition() { 528 return currentMousePosition; 529 } 530 531 /** 532 * Return the list of top level Windows in use in the Java Virtual Machine. 533 * 534 * @return an array of top level {@code Window}s in use in the Java Virtual Machine 535 */ 536 static public Window[] getTopLevelWindows() { 537 538 // Because this method is static, do not make it synchronized because 539 // it can lock the whole class. Instead, just lock what needs to be 540 // locked. 541 // 542 synchronized (topLevelWindows) { 543 int count = topLevelWindows.size(); 544 if (count > 0) { 545 Window[] w = new Window[count]; 546 for (int i = 0; i < count; i++) { 547 w[i] = (Window)topLevelWindows.elementAt(i); 548 } 549 return w; 550 } else { 551 return new Window[0]; 552 } 553 } 554 } 555 556 /** 557 * Return the top level {@code Window} that currently has keyboard focus. 558 * 559 * @return the top level {@code Window} that currently has keyboard focus 560 */ 561 static public Window getTopLevelWindowWithFocus() { 562 return topLevelWindowWithFocus; 563 } 564} 565 566/** 567 * Handle all Component events in a separate thread. The reason for this is 568 * that WindowEvents tend to be used to do lots of processing on the Window 569 * hierarchy. As a result, it can frequently result in deadlock situations. 570 */ 571class ComponentEvtDispatchThread extends Thread { 572 public ComponentEvtDispatchThread(String name) { 573 super(name); 574 } 575 public void run() { 576 ComponentEvent ce = null; 577 while (true) { 578 synchronized(EventQueueMonitor.componentEventQueueLock) { 579 while (EventQueueMonitor.componentEventQueue == null) { 580 try { 581 EventQueueMonitor.componentEventQueueLock.wait(); 582 } catch (InterruptedException e) { 583 } 584 } 585 ce = (ComponentEvent)EventQueueMonitor.componentEventQueue.event; 586 EventQueueMonitor.componentEventQueue = 587 EventQueueMonitor.componentEventQueue.next; 588 } 589 switch (ce.getID()) { 590 case MouseEvent.MOUSE_MOVED: 591 case MouseEvent.MOUSE_DRAGGED: 592 EventQueueMonitor.updateCurrentMousePosition((MouseEvent) ce); 593 break; 594 case WindowEvent.WINDOW_ACTIVATED: 595 EventQueueMonitor.maybeNotifyAssistiveTechnologies(); 596 EventQueueMonitor.topLevelWindowWithFocus = ((WindowEvent) ce).getWindow(); 597 break; 598 599 default: 600 break; 601 } 602 } 603 } 604} 605 606/** 607 * EventQueueMonitorItem is the basic type that handles the 608 * queue for queueComponentEvent and the ComponentEvtDispatchThread. 609 */ 610class EventQueueMonitorItem { 611 AWTEvent event; 612 EventQueueMonitorItem next; 613 614 EventQueueMonitorItem(AWTEvent evt) { 615 event = evt; 616 next = null; 617 } 618} 619