/* * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.java.accessibility.util; import java.util.*; import java.awt.*; import java.awt.event.*; import javax.accessibility.*; import java.security.AccessController; import java.security.PrivilegedAction; /** * The {@code EventQueueMonitor} class provides key core functionality for Assistive * Technologies (and other system-level technologies that need some of the same * things that Assistive Technology needs). * * @see AWTEventMonitor * @see SwingEventMonitor */ @jdk.Exported public class EventQueueMonitor implements AWTEventListener { // NOTE: All of the following properties are static. The reason // for this is that there may be multiple EventQueue instances // in use in the same VM. By making these properties static, // we can guarantee we get the information from all of the // EventQueue instances. // The stuff that is cached. // static Vector topLevelWindows = new Vector(); static Window topLevelWindowWithFocus = null; static Point currentMousePosition = null; static Component currentMouseComponent = null; // Low-level listener interfaces // static GUIInitializedListener guiInitializedListener = null; static TopLevelWindowListener topLevelWindowListener = null; static MouseMotionListener mouseMotionListener = null; /** * Class variable stating whether the assistive technologies have * been loaded yet or not. The assistive technologies won't be * loaded until the first event is posted to the EventQueue. This * gives the toolkit a chance to do all the necessary initialization * it needs to do. */ /** * Class variable stating whether the GUI subsystem has been initialized * or not. * * @see #isGUIInitialized */ static boolean guiInitialized = false; /** * Queue that holds events for later processing. */ static EventQueueMonitorItem componentEventQueue = null; /** * Class that tells us what the component event dispatch thread is. */ static private ComponentEvtDispatchThread cedt = null; /** * Handle the synchronization between the thing that populates the * component event dispatch thread ({@link #queueComponentEvent}) * and the thing that processes the events ({@link ComponentEvtDispatchThread}). */ static Object componentEventQueueLock = new Object(); /** * Create a new {@code EventQueueMonitor} instance. Normally, this will * be called only by the AWT Toolkit during initialization time. * Assistive technologies should not create instances of * EventQueueMonitor by themselves. Instead, they should either * refer to it directly via the static methods in this class, e.g., * {@link #getCurrentMousePosition} or obtain the instance by asking the * Toolkit, e.g., {@link java.awt.Toolkit#getSystemEventQueue}. */ public EventQueueMonitor() { if (cedt == null) { cedt = new ComponentEvtDispatchThread("EventQueueMonitor-ComponentEvtDispatch"); cedt.setDaemon(true); cedt.start(); } } /** * Queue up a {@link java.awt.event.ComponentEvent ComponentEvent} for later * processing by the {@link ComponentEvtDispatch} thread. * * @param e a {@code ComponentEvent} */ static void queueComponentEvent(ComponentEvent e) { synchronized(componentEventQueueLock) { EventQueueMonitorItem eqi = new EventQueueMonitorItem(e); if (componentEventQueue == null) { componentEventQueue = eqi; } else { EventQueueMonitorItem q = componentEventQueue; while (true) { if (q.next != null) { q = q.next; } else { break; } } q.next = eqi; } componentEventQueueLock.notifyAll(); } } /** * Tell the {@code EventQueueMonitor} to start listening for events. */ public static void maybeInitialize() { if (cedt == null) { java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { try { long eventMask = AWTEvent.WINDOW_EVENT_MASK | AWTEvent.FOCUS_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK; Toolkit.getDefaultToolkit().addAWTEventListener(new EventQueueMonitor(), eventMask); } catch (Exception e) { } return null; } } ); } } /** * Handle events as a result of registering a listener * on the {@link java.awt.EventQueue EventQueue} in {@link #maybeInitialize}. */ public void eventDispatched(AWTEvent theEvent) { processEvent(theEvent); } /** * Assisitive technologies that have * registered a {@link GUIInitializedListener} will be notified. * * @see #addGUIInitializedListener */ static void maybeNotifyAssistiveTechnologies() { if (!guiInitialized) { guiInitialized = true; if (guiInitializedListener != null) { guiInitializedListener.guiInitialized(); } } } /********************************************************************/ /* */ /* Package Private Methods */ /* */ /********************************************************************/ /** * Add a Container to the list of top-level containers * in the cache. This follows the object's hierarchy up the * tree until it finds the top most parent. If the parent is * not already in the list of Containers, it adds it to the list. * * @param c the Container */ static void addTopLevelWindow(Component c) { Container parent; if (c == null) { return; } if (!(c instanceof Window)) { addTopLevelWindow(c.getParent()); return; } if ((c instanceof Dialog) || (c instanceof Window)) { parent = (Container) c; } else { parent = c.getParent(); if (parent != null) { addTopLevelWindow(parent); return; } } if (parent == null) { parent = (Container) c; } // Because this method is static, do not make it synchronized because // it can lock the whole class. Instead, just lock what needs to be // locked. // synchronized (topLevelWindows) { if ((parent != null) && !topLevelWindows.contains(parent)) { topLevelWindows.addElement(parent); if (topLevelWindowListener != null) { topLevelWindowListener.topLevelWindowCreated((Window) parent); } } } } /** * Removes a container from the list of top level containers in the cache. * * @param c the top level container to remove */ static void removeTopLevelWindow(Window w) { // Because this method is static, do not make it synchronized because // it can lock the whole class. Instead, just lock what needs to be // locked. // synchronized (topLevelWindows) { if (topLevelWindows.contains(w)) { topLevelWindows.removeElement(w); if (topLevelWindowListener != null) { topLevelWindowListener.topLevelWindowDestroyed(w); } } } } /** * Update current mouse position. * * @param mouseEvent the MouseEvent that holds the new mouse position. */ static void updateCurrentMousePosition(MouseEvent mouseEvent) { Point oldMousePos = currentMousePosition; // Be careful here. The component in the event might be // hidden by the time we process the event. try { Point eventPoint = mouseEvent.getPoint(); currentMouseComponent = (Component) (mouseEvent.getSource()); currentMousePosition = currentMouseComponent.getLocationOnScreen(); currentMousePosition.translate(eventPoint.x,eventPoint.y); } catch (Exception e) { currentMousePosition = oldMousePos; } } /** * Process the event. This maintains the event cache in addition * to calling all the registered listeners. NOTE: The events that * come through here are from peered Components. * * @param theEvent the AWTEvent */ static void processEvent(AWTEvent theEvent) { switch (theEvent.getID()) { case MouseEvent.MOUSE_MOVED: case MouseEvent.MOUSE_DRAGGED: case FocusEvent.FOCUS_GAINED: case WindowEvent.WINDOW_DEACTIVATED: queueComponentEvent((ComponentEvent) theEvent); break; case WindowEvent.WINDOW_ACTIVATED: // Dialogs fire WINDOW_ACTIVATED and FOCUS_GAINED events // before WINDOW_OPENED so we need to add topLevelListeners // for the dialog when it is first activated to get a // focus gained event for the focus component in the dialog. if (theEvent instanceof ComponentEvent) { ComponentEvent ce = (ComponentEvent)theEvent; if (ce.getComponent() instanceof Window) { EventQueueMonitor.addTopLevelWindow(ce.getComponent()); EventQueueMonitor.maybeNotifyAssistiveTechnologies(); } else { EventQueueMonitor.maybeNotifyAssistiveTechnologies(); EventQueueMonitor.addTopLevelWindow(ce.getComponent()); } } queueComponentEvent((ComponentEvent) theEvent); break; // handle WINDOW_OPENED and WINDOW_CLOSED events synchronously case WindowEvent.WINDOW_OPENED: if (theEvent instanceof ComponentEvent) { ComponentEvent ce = (ComponentEvent)theEvent; if (ce.getComponent() instanceof Window) { EventQueueMonitor.addTopLevelWindow(ce.getComponent()); EventQueueMonitor.maybeNotifyAssistiveTechnologies(); } else { EventQueueMonitor.maybeNotifyAssistiveTechnologies(); EventQueueMonitor.addTopLevelWindow(ce.getComponent()); } } break; case WindowEvent.WINDOW_CLOSED: if (theEvent instanceof ComponentEvent) { ComponentEvent ce = (ComponentEvent)theEvent; EventQueueMonitor.removeTopLevelWindow((Window) (ce.getComponent())); } break; default: break; } } /** * Internal test */ static synchronized Component getShowingComponentAt(Container c, int x, int y) { if (!c.contains(x, y)) { return null; } int ncomponents = c.getComponentCount(); for (int i = 0 ; i < ncomponents ; i++) { Component comp = c.getComponent(i); if (comp != null && comp.isShowing()) { Point location = comp.getLocation(); if (comp.contains(x - location.x, y - location.y)) { return comp; } } } return c; } /** * Return the Component at the given Point on the screen in the * given Container. * * @param c the Container to search * @param p the Point in screen coordinates * @return the Component at the given Point on the screen in the * given Container -- can be null if no Component is at that Point */ static synchronized Component getComponentAt(Container c, Point p) { if (!c.isShowing()) { return null; } Component comp; Point containerLoc = c.getLocationOnScreen(); Point containerPoint = new Point(p.x - containerLoc.x, p.y - containerLoc.y); comp = getShowingComponentAt(c, containerPoint.x, containerPoint.y); if ((comp != c) && (comp instanceof Container)) { return getComponentAt((Container)comp,p); } else { return comp; } } /** * Obtain the {@link javax.accessibility.Accessible Accessible} object at the given point on the Screen. * The return value may be null if an {@code Accessible} object cannot be * found at the particular point. * * @param p the point to be accessed * @return the {@code Accessible} at the specified point */ static public Accessible getAccessibleAt(Point p) { Window w = getTopLevelWindowWithFocus(); Window[] wins = getTopLevelWindows(); Component c = null; // See if the point we're being asked about is the // currentMousePosition. If so, start with the component // that we know the currentMousePostion is over // if (currentMousePosition == null) { return null; } if (currentMousePosition.equals(p)) { if (currentMouseComponent instanceof Container) { c = getComponentAt((Container) currentMouseComponent, p); } } // Try the window with focus next // if (c == null && w != null) { c = getComponentAt(w,p); } // Try the other windows next. [[[WDW: Stacking order???]]] if (c == null) { for (int i = 0; i < wins.length; i++) { c = getComponentAt(wins[i],p); if (c != null) { break; } } } if (c instanceof Accessible) { AccessibleContext ac = ((Accessible) c).getAccessibleContext(); if (ac != null) { AccessibleComponent acmp = ac.getAccessibleComponent(); if ((acmp != null) && (ac.getAccessibleChildrenCount() != 0)) { Point location = acmp.getLocationOnScreen(); location.move(p.x - location.x, p.y - location.y); return acmp.getAccessibleAt(location); } } return (Accessible) c; } else { return Translator.getAccessible(c); } } /********************************************************************/ /* */ /* Public Methods */ /* */ /********************************************************************/ /** * Says whether the GUI subsystem has been initialized or not. * If this returns true, the assistive technology can freely * create GUI component instances. If the return value is false, * the assistive technology should register a {@link GUIInitializedListener} * and wait to create GUI component instances until the listener is * called. * * @return true if the GUI subsystem has been initialized * @see #addGUIInitializedListener */ static public boolean isGUIInitialized() { maybeInitialize(); return guiInitialized; } /** * Adds the specified listener to be notified when the GUI subsystem * is initialized. Assistive technologies should get the results of * {@link #isGUIInitialized} before calling this method. * * @param l the listener to add * @see #isGUIInitialized * @see #removeTopLevelWindowListener */ static public void addGUIInitializedListener(GUIInitializedListener l) { maybeInitialize(); guiInitializedListener = GUIInitializedMulticaster.add(guiInitializedListener,l); } /** * Removes the specified listener to be notified when the GUI subsystem * is initialized. * * @param l the listener to remove * @see #addGUIInitializedListener */ static public void removeGUIInitializedListener(GUIInitializedListener l) { guiInitializedListener = GUIInitializedMulticaster.remove(guiInitializedListener,l); } /** * Adds the specified listener to be notified when a top level window * is created or destroyed. * * @param l the listener to add * @see #removeTopLevelWindowListener */ static public void addTopLevelWindowListener(TopLevelWindowListener l) { topLevelWindowListener = TopLevelWindowMulticaster.add(topLevelWindowListener,l); } /** * Removes the specified listener to be notified when a top level window * is created or destroyed. * * @param l the listener to remove * @see #addTopLevelWindowListener */ static public void removeTopLevelWindowListener(TopLevelWindowListener l) { topLevelWindowListener = TopLevelWindowMulticaster.remove(topLevelWindowListener,l); } /** * Return the last recorded position of the mouse in screen coordinates. * * @return the last recorded position of the mouse in screen coordinates */ static public Point getCurrentMousePosition() { return currentMousePosition; } /** * Return the list of top level Windows in use in the Java Virtual Machine. * * @return an array of top level {@code Window}s in use in the Java Virtual Machine */ static public Window[] getTopLevelWindows() { // Because this method is static, do not make it synchronized because // it can lock the whole class. Instead, just lock what needs to be // locked. // synchronized (topLevelWindows) { int count = topLevelWindows.size(); if (count > 0) { Window[] w = new Window[count]; for (int i = 0; i < count; i++) { w[i] = (Window)topLevelWindows.elementAt(i); } return w; } else { return new Window[0]; } } } /** * Return the top level {@code Window} that currently has keyboard focus. * * @return the top level {@code Window} that currently has keyboard focus */ static public Window getTopLevelWindowWithFocus() { return topLevelWindowWithFocus; } } /** * Handle all Component events in a separate thread. The reason for this is * that WindowEvents tend to be used to do lots of processing on the Window * hierarchy. As a result, it can frequently result in deadlock situations. */ class ComponentEvtDispatchThread extends Thread { public ComponentEvtDispatchThread(String name) { super(name); } public void run() { ComponentEvent ce = null; while (true) { synchronized(EventQueueMonitor.componentEventQueueLock) { while (EventQueueMonitor.componentEventQueue == null) { try { EventQueueMonitor.componentEventQueueLock.wait(); } catch (InterruptedException e) { } } ce = (ComponentEvent)EventQueueMonitor.componentEventQueue.event; EventQueueMonitor.componentEventQueue = EventQueueMonitor.componentEventQueue.next; } switch (ce.getID()) { case MouseEvent.MOUSE_MOVED: case MouseEvent.MOUSE_DRAGGED: EventQueueMonitor.updateCurrentMousePosition((MouseEvent) ce); break; case WindowEvent.WINDOW_ACTIVATED: EventQueueMonitor.maybeNotifyAssistiveTechnologies(); EventQueueMonitor.topLevelWindowWithFocus = ((WindowEvent) ce).getWindow(); break; default: break; } } } } /** * EventQueueMonitorItem is the basic type that handles the * queue for queueComponentEvent and the ComponentEvtDispatchThread. */ class EventQueueMonitorItem { AWTEvent event; EventQueueMonitorItem next; EventQueueMonitorItem(AWTEvent evt) { event = evt; next = null; } }