/* |
|
* 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.beans.*; |
|
import java.awt.*; |
|
import java.awt.event.*; |
|
import javax.accessibility.*; |
|
/** |
|
* <P>{@code AccessibilityEventMonitor} implements a PropertyChange listener |
|
* on every UI object that implements interface {@code Accessible} in the Java |
|
* Virtual Machine. The events captured by these listeners are made available |
|
* through listeners supported by {@code AccessibilityEventMonitor}. |
|
* With this, all the individual events on each of the UI object |
|
* instances are funneled into one set of PropertyChange listeners. |
|
* <p>This class depends upon {@link EventQueueMonitor}, which provides the base |
|
* level support for capturing the top-level containers as they are created. |
|
* |
|
*/ |
|
@jdk.Exported |
|
public class AccessibilityEventMonitor { |
|
// listeners |
|
/** |
|
* The current list of registered {@link java.beans.PropertyChangeListener |
|
* PropertyChangeListener} classes. |
|
* |
|
* @see #addPropertyChangeListener |
|
* @see #removePropertyChangeListener |
|
*/ |
|
static protected final AccessibilityListenerList listenerList = |
|
new AccessibilityListenerList(); |
|
/** |
|
* The actual listener that is installed on the component instances. |
|
* This listener calls the other registered listeners when an event |
|
* occurs. By doing things this way, the actual number of listeners |
|
* installed on a component instance is drastically reduced. |
|
*/ |
|
static protected final AccessibilityEventListener accessibilityListener = |
|
new AccessibilityEventListener(); |
|
/** |
|
* Adds the specified listener to receive all PropertyChange events on |
|
* each UI object instance in the Java Virtual Machine as they occur. |
|
* <P>Note: This listener is automatically added to all component |
|
* instances created after this method is called. In addition, it |
|
* is only added to UI object instances that support this listener type. |
|
* |
|
* @param l the listener to add |
|
* |
|
* @see #removePropertyChangeListener |
|
*/ |
|
static public void addPropertyChangeListener(PropertyChangeListener l) { |
|
if (listenerList.getListenerCount(PropertyChangeListener.class) == 0) { |
|
accessibilityListener.installListeners(); |
|
} |
|
listenerList.add(PropertyChangeListener.class, l); |
|
} |
|
/** |
|
* Removes the specified listener so it no longer receives PropertyChange |
|
* events when they occur. |
|
* @see #addPropertyChangeListener |
|
* @param l the listener to remove |
|
*/ |
|
static public void removePropertyChangeListener(PropertyChangeListener l) { |
|
listenerList.remove(PropertyChangeListener.class, l); |
|
if (listenerList.getListenerCount(PropertyChangeListener.class) == 0) { |
|
accessibilityListener.removeListeners(); |
|
} |
|
} |
|
/** |
|
* AccessibilityEventListener is the class that does all the work for |
|
* AccessibilityEventMonitor. It is not intended for use by any other |
|
* class except AccessibilityEventMonitor. |
|
* |
|
*/ |
|
static class AccessibilityEventListener implements TopLevelWindowListener, |
|
PropertyChangeListener { |
|
/** |
|
* Create a new instance of this class and install it on each component |
|
* instance in the virtual machine that supports any of the currently |
|
* registered listeners in AccessibilityEventMonitor. Also registers |
|
* itself as a TopLevelWindowListener with EventQueueMonitor so it can |
|
* automatically add new listeners to new components. |
|
* @see EventQueueMonitor |
|
* @see AccessibilityEventMonitor |
|
*/ |
|
public AccessibilityEventListener() { |
|
EventQueueMonitor.addTopLevelWindowListener(this); |
|
} |
|
/** |
|
* Installs PropertyChange listeners on all Accessible objects based |
|
* upon the current topLevelWindows cached by EventQueueMonitor. |
|
* @see EventQueueMonitor |
|
* @see AWTEventMonitor |
|
*/ |
|
protected void installListeners() { |
|
Window topLevelWindows[] = EventQueueMonitor.getTopLevelWindows(); |
|
if (topLevelWindows != null) { |
|
for (int i = 0; i < topLevelWindows.length; i++) { |
|
if (topLevelWindows[i] instanceof Accessible) { |
|
installListeners((Accessible) topLevelWindows[i]); |
|
} |
|
} |
|
} |
|
} |
|
/** |
|
* Installs PropertyChange listeners to the Accessible object, and it's |
|
* children (so long as the object isn't of TRANSIENT state). |
|
* @param a the Accessible object to add listeners to |
|
*/ |
|
protected void installListeners(Accessible a) { |
|
installListeners(a.getAccessibleContext()); |
|
} |
|
/** |
|
* Installs PropertyChange listeners to the AccessibleContext object, |
|
* and it's * children (so long as the object isn't of TRANSIENT state). |
|
* @param a the Accessible object to add listeners to |
|
*/ |
|
private void installListeners(AccessibleContext ac) { |
|
if (ac != null) { |
|
AccessibleStateSet states = ac.getAccessibleStateSet(); |
|
if (!states.contains(AccessibleState.TRANSIENT)) { |
|
ac.addPropertyChangeListener(this); |
|
/* |
|
* Don't add listeners to transient children. Components |
|
* with transient children should return an AccessibleStateSet |
|
* containing AccessibleState.MANAGES_DESCENDANTS. Components |
|
* may not explicitly return the MANAGES_DESCENDANTS state. |
|
* In this case, don't add listeners to the children of |
|
* lists, tables and trees. |
|
*/ |
|
AccessibleStateSet set = ac.getAccessibleStateSet(); |
|
if (set.contains(_AccessibleState.MANAGES_DESCENDANTS)) { |
|
return; |
|
} |
|
AccessibleRole role = ac.getAccessibleRole(); |
|
if (role == AccessibleRole.LIST || |
|
role == AccessibleRole.TREE) { |
|
return; |
|
} |
|
if (role == AccessibleRole.TABLE) { |
|
// handle Oracle tables containing tables |
|
Accessible child = ac.getAccessibleChild(0); |
|
if (child != null) { |
|
AccessibleContext ac2 = child.getAccessibleContext(); |
|
if (ac2 != null) { |
|
role = ac2.getAccessibleRole(); |
|
if (role != null && role != AccessibleRole.TABLE) { |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
int count = ac.getAccessibleChildrenCount(); |
|
for (int i = 0; i < count; i++) { |
|
Accessible child = ac.getAccessibleChild(i); |
|
if (child != null) { |
|
installListeners(child); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
/** |
|
* Removes PropertyChange listeners on all Accessible objects based |
|
* upon the topLevelWindows cached by EventQueueMonitor. |
|
* @param eventID the event ID |
|
* @see EventID |
|
*/ |
|
protected void removeListeners() { |
|
Window topLevelWindows[] = EventQueueMonitor.getTopLevelWindows(); |
|
if (topLevelWindows != null) { |
|
for (int i = 0; i < topLevelWindows.length; i++) { |
|
if (topLevelWindows[i] instanceof Accessible) { |
|
removeListeners((Accessible) topLevelWindows[i]); |
|
} |
|
} |
|
} |
|
} |
|
/** |
|
* Removes PropertyChange listeners for the given Accessible object, |
|
* it's children (so long as the object isn't of TRANSIENT state). |
|
* @param a the Accessible object to remove listeners from |
|
*/ |
|
protected void removeListeners(Accessible a) { |
|
removeListeners(a.getAccessibleContext()); |
|
} |
|
/** |
|
* Removes PropertyChange listeners for the given AccessibleContext |
|
* object, it's children (so long as the object isn't of TRANSIENT |
|
* state). |
|
* @param a the Accessible object to remove listeners from |
|
*/ |
|
private void removeListeners(AccessibleContext ac) { |
|
if (ac != null) { |
|
// Listeners are not added to transient components. |
|
AccessibleStateSet states = ac.getAccessibleStateSet(); |
|
if (!states.contains(AccessibleState.TRANSIENT)) { |
|
ac.removePropertyChangeListener(this); |
|
/* |
|
* Listeners are not added to transient children. Components |
|
* with transient children should return an AccessibleStateSet |
|
* containing AccessibleState.MANAGES_DESCENDANTS. Components |
|
* may not explicitly return the MANAGES_DESCENDANTS state. |
|
* In this case, don't remove listeners from the children of |
|
* lists, tables and trees. |
|
*/ |
|
if (states.contains(_AccessibleState.MANAGES_DESCENDANTS)) { |
|
return; |
|
} |
|
AccessibleRole role = ac.getAccessibleRole(); |
|
if (role == AccessibleRole.LIST || |
|
role == AccessibleRole.TABLE || |
|
role == AccessibleRole.TREE) { |
|
return; |
|
} |
|
int count = ac.getAccessibleChildrenCount(); |
|
for (int i = 0; i < count; i++) { |
|
Accessible child = ac.getAccessibleChild(i); |
|
if (child != null) { |
|
removeListeners(child); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
/********************************************************************/ |
|
/* */ |
|
/* Listener Interface Methods */ |
|
/* */ |
|
/********************************************************************/ |
|
/* TopLevelWindow Methods ***************************************/ |
|
/** |
|
* Called when top level window is created. |
|
* @see EventQueueMonitor |
|
* @see EventQueueMonitor#addTopLevelWindowListener |
|
*/ |
|
public void topLevelWindowCreated(Window w) { |
|
if (w instanceof Accessible) { |
|
installListeners((Accessible) w); |
|
} |
|
} |
|
/** |
|
* Called when top level window is destroyed. |
|
* @see EventQueueMonitor |
|
* @see EventQueueMonitor#addTopLevelWindowListener |
|
*/ |
|
public void topLevelWindowDestroyed(Window w) { |
|
if (w instanceof Accessible) { |
|
removeListeners((Accessible) w); |
|
} |
|
} |
|
/* PropertyChangeListener Methods **************************************/ |
|
public void propertyChange(PropertyChangeEvent e) { |
|
// propogate the event |
|
Object[] listeners = |
|
AccessibilityEventMonitor.listenerList.getListenerList(); |
|
for (int i = listeners.length-2; i>=0; i-=2) { |
|
if (listeners[i]==PropertyChangeListener.class) { |
|
((PropertyChangeListener)listeners[i+1]).propertyChange(e); |
|
} |
|
} |
|
// handle childbirth/death |
|
String name = e.getPropertyName(); |
|
if (name.compareTo(AccessibleContext.ACCESSIBLE_CHILD_PROPERTY) == 0) { |
|
Object oldValue = e.getOldValue(); |
|
Object newValue = e.getNewValue(); |
|
if ((oldValue == null) ^ (newValue == null)) { // one null, not both |
|
if (oldValue != null) { |
|
// this Accessible is a child that's going away |
|
if (oldValue instanceof Accessible) { |
|
Accessible a = (Accessible) oldValue; |
|
removeListeners(a.getAccessibleContext()); |
|
} else if (oldValue instanceof AccessibleContext) { |
|
removeListeners((AccessibleContext) oldValue); |
|
} |
|
} else if (newValue != null) { |
|
// this Accessible is a child was just born |
|
if (newValue instanceof Accessible) { |
|
Accessible a = (Accessible) newValue; |
|
installListeners(a.getAccessibleContext()); |
|
} else if (newValue instanceof AccessibleContext) { |
|
installListeners((AccessibleContext) newValue); |
|
} |
|
} |
|
} else { |
|
System.out.println("ERROR in usage of PropertyChangeEvents for: " + e.toString()); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
/* |
|
* workaround for no public AccessibleState constructor |
|
*/ |
|
class _AccessibleState extends AccessibleState { |
|
/** |
|
* Indicates this object is responsible for managing its |
|
* subcomponents. This is typically used for trees and tables |
|
* that have a large number of subcomponents and where the |
|
* objects are created only when needed and otherwise remain virtual. |
|
* The application should not manage the subcomponents directly. |
|
*/ |
|
public static final _AccessibleState MANAGES_DESCENDANTS |
|
= new _AccessibleState ("managesDescendants"); |
|
/** |
|
* Creates a new AccessibleState using the given locale independent key. |
|
* This should not be a public method. Instead, it is used to create |
|
* the constants in this file to make it a strongly typed enumeration. |
|
* Subclasses of this class should enforce similar policy. |
|
* <p> |
|
* The key String should be a locale independent key for the state. |
|
* It is not intended to be used as the actual String to display |
|
* to the user. To get the localized string, use toDisplayString. |
|
* |
|
* @param key the locale independent name of the state. |
|
* @see AccessibleBundle#toDisplayString |
|
*/ |
|
protected _AccessibleState(String key) { |
|
super(key); |
|
} |
|
} |