blob: 3c2a1633b592e323e98ea93f94c6b629a4afe821 [file] [log] [blame]
/*
* 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;
}
}