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 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() {
 150                     public Object 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 }