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