1 /*
   2  * Copyright (c) 2002, 2018, 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.beans.*;
  30 import java.awt.*;
  31 import java.awt.event.*;
  32 import javax.accessibility.*;
  33 
  34 /**
  35  * <P>{@code AccessibilityEventMonitor} implements a PropertyChange listener
  36  * on every UI object that implements interface {@code Accessible} in the Java
  37  * Virtual Machine.  The events captured by these listeners are made available
  38  * through listeners supported by {@code AccessibilityEventMonitor}.
  39  * With this, all the individual events on each of the UI object
  40  * instances are funneled into one set of PropertyChange listeners.
  41  * <p>This class depends upon {@link EventQueueMonitor}, which provides the base
  42  * level support for capturing the top-level containers as they are created.
  43  *
  44  */
  45 
  46 public class AccessibilityEventMonitor {
  47 
  48     // listeners
  49     /**
  50      * The current list of registered {@link java.beans.PropertyChangeListener
  51      * PropertyChangeListener} classes.
  52      *
  53      * @see #addPropertyChangeListener
  54      * @see #removePropertyChangeListener
  55      */
  56     static protected final AccessibilityListenerList listenerList =
  57         new AccessibilityListenerList();
  58 
  59 
  60     /**
  61      * The actual listener that is installed on the component instances.
  62      * This listener calls the other registered listeners when an event
  63      * occurs.  By doing things this way, the actual number of listeners
  64      * installed on a component instance is drastically reduced.
  65      */
  66     static private final AccessibilityEventListener accessibilityListener =
  67         new AccessibilityEventListener();
  68 
  69     /**
  70      * Adds the specified listener to receive all PropertyChange events on
  71      * each UI object instance in the Java Virtual Machine as they occur.
  72      * <P>Note: This listener is automatically added to all component
  73      * instances created after this method is called.  In addition, it
  74      * is only added to UI object instances that support this listener type.
  75      *
  76      * @param l the listener to add
  77      *
  78      * @see #removePropertyChangeListener
  79      */
  80     static public void addPropertyChangeListener(PropertyChangeListener l) {
  81         if (listenerList.getListenerCount(PropertyChangeListener.class) == 0) {
  82             accessibilityListener.installListeners();
  83         }
  84         listenerList.add(PropertyChangeListener.class, l);
  85     }
  86 
  87     /**
  88      * Removes the specified listener so it no longer receives PropertyChange
  89      * events when they occur.
  90      * @see #addPropertyChangeListener
  91      * @param l the listener to remove
  92      */
  93     static public void removePropertyChangeListener(PropertyChangeListener l) {
  94         listenerList.remove(PropertyChangeListener.class, l);
  95         if (listenerList.getListenerCount(PropertyChangeListener.class) == 0) {
  96             accessibilityListener.removeListeners();
  97         }
  98     }
  99 
 100 
 101     /**
 102      * AccessibilityEventListener is the class that does all the work for
 103      * AccessibilityEventMonitor.  It is not intended for use by any other
 104      * class except AccessibilityEventMonitor.
 105      *
 106      */
 107 
 108     static class AccessibilityEventListener implements TopLevelWindowListener,
 109                 PropertyChangeListener {
 110 
 111         /**
 112          * Create a new instance of this class and install it on each component
 113          * instance in the virtual machine that supports any of the currently
 114          * registered listeners in AccessibilityEventMonitor.  Also registers
 115          * itself as a TopLevelWindowListener with EventQueueMonitor so it can
 116          * automatically add new listeners to new components.
 117          * @see EventQueueMonitor
 118          * @see AccessibilityEventMonitor
 119          */
 120         public AccessibilityEventListener() {
 121             EventQueueMonitor.addTopLevelWindowListener(this);
 122         }
 123 
 124         /**
 125          * Installs PropertyChange listeners on all Accessible objects based
 126          * upon the current topLevelWindows cached by EventQueueMonitor.
 127          * @see EventQueueMonitor
 128          * @see AWTEventMonitor
 129          */
 130         protected void installListeners() {
 131             Window[] topLevelWindows = EventQueueMonitor.getTopLevelWindows();
 132             if (topLevelWindows != null) {
 133                 for (int i = 0; i < topLevelWindows.length; i++) {
 134                     if (topLevelWindows[i] instanceof Accessible) {
 135                         installListeners((Accessible) topLevelWindows[i]);
 136                     }
 137                 }
 138             }
 139         }
 140 
 141         /**
 142          * Installs PropertyChange listeners to the Accessible object, and it's
 143          * children (so long as the object isn't of TRANSIENT state).
 144          * @param a the Accessible object to add listeners to
 145          */
 146         protected void installListeners(Accessible a) {
 147             installListeners(a.getAccessibleContext());
 148         }
 149 
 150         /**
 151          * Installs PropertyChange listeners to the AccessibleContext object,
 152          * and it's * children (so long as the object isn't of TRANSIENT state).
 153          * @param a the Accessible object to add listeners to
 154          */
 155         private void installListeners(AccessibleContext ac) {
 156 
 157             if (ac != null) {
 158                 AccessibleStateSet states = ac.getAccessibleStateSet();
 159                 if (!states.contains(AccessibleState.TRANSIENT)) {
 160                     ac.addPropertyChangeListener(this);
 161                     /*
 162                      * Don't add listeners to transient children. Components
 163                      * with transient children should return an AccessibleStateSet
 164                      * containing AccessibleState.MANAGES_DESCENDANTS. Components
 165                      * may not explicitly return the MANAGES_DESCENDANTS state.
 166                      * In this case, don't add listeners to the children of
 167                      * lists, tables and trees.
 168                      */
 169                     AccessibleStateSet set = ac.getAccessibleStateSet();
 170                     if (set.contains(_AccessibleState.MANAGES_DESCENDANTS)) {
 171                         return;
 172                     }
 173                     AccessibleRole role = ac.getAccessibleRole();
 174                     if (role == AccessibleRole.LIST ||
 175                         role == AccessibleRole.TREE) {
 176                         return;
 177                     }
 178                     if (role == AccessibleRole.TABLE) {
 179                         // handle Oracle tables containing tables
 180                         Accessible child = ac.getAccessibleChild(0);
 181                         if (child != null) {
 182                             AccessibleContext ac2 = child.getAccessibleContext();
 183                             if (ac2 != null) {
 184                                 role = ac2.getAccessibleRole();
 185                                 if (role != null && role != AccessibleRole.TABLE) {
 186                                     return;
 187                                 }
 188                             }
 189                         }
 190                     }
 191                     int count = ac.getAccessibleChildrenCount();
 192                     for (int i = 0; i < count; i++) {
 193                         Accessible child = ac.getAccessibleChild(i);
 194                         if (child != null) {
 195                             installListeners(child);
 196                         }
 197                     }
 198                 }
 199             }
 200         }
 201 
 202         /**
 203          * Removes PropertyChange listeners on all Accessible objects based
 204          * upon the topLevelWindows cached by EventQueueMonitor.
 205          * @param eventID the event ID
 206          * @see EventID
 207          */
 208         protected void removeListeners() {
 209             Window[] topLevelWindows = EventQueueMonitor.getTopLevelWindows();
 210             if (topLevelWindows != null) {
 211                 for (int i = 0; i < topLevelWindows.length; i++) {
 212                     if (topLevelWindows[i] instanceof Accessible) {
 213                         removeListeners((Accessible) topLevelWindows[i]);
 214                     }
 215                 }
 216             }
 217         }
 218 
 219         /**
 220          * Removes PropertyChange listeners for the given Accessible object,
 221          * it's children (so long as the object isn't of TRANSIENT state).
 222          * @param a the Accessible object to remove listeners from
 223          */
 224         protected void removeListeners(Accessible a) {
 225             removeListeners(a.getAccessibleContext());
 226         }
 227 
 228         /**
 229          * Removes PropertyChange listeners for the given AccessibleContext
 230          * object, it's children (so long as the object isn't of TRANSIENT
 231          * state).
 232          * @param a the Accessible object to remove listeners from
 233          */
 234         private void removeListeners(AccessibleContext ac) {
 235 
 236 
 237             if (ac != null) {
 238                 // Listeners are not added to transient components.
 239                 AccessibleStateSet states = ac.getAccessibleStateSet();
 240                 if (!states.contains(AccessibleState.TRANSIENT)) {
 241                     ac.removePropertyChangeListener(this);
 242                     /*
 243                      * Listeners are not added to transient children. Components
 244                      * with transient children should return an AccessibleStateSet
 245                      * containing AccessibleState.MANAGES_DESCENDANTS. Components
 246                      * may not explicitly return the MANAGES_DESCENDANTS state.
 247                      * In this case, don't remove listeners from the children of
 248                      * lists, tables and trees.
 249                      */
 250                     if (states.contains(_AccessibleState.MANAGES_DESCENDANTS)) {
 251                         return;
 252                     }
 253                     AccessibleRole role = ac.getAccessibleRole();
 254                     if (role == AccessibleRole.LIST ||
 255                         role == AccessibleRole.TABLE ||
 256                         role == AccessibleRole.TREE) {
 257                         return;
 258                     }
 259                     int count = ac.getAccessibleChildrenCount();
 260                     for (int i = 0; i < count; i++) {
 261                         Accessible child = ac.getAccessibleChild(i);
 262                         if (child != null) {
 263                             removeListeners(child);
 264                         }
 265                     }
 266                 }
 267             }
 268         }
 269 
 270         /********************************************************************/
 271         /*                                                                  */
 272         /* Listener Interface Methods                                       */
 273         /*                                                                  */
 274         /********************************************************************/
 275 
 276         /* TopLevelWindow Methods ***************************************/
 277 
 278         /**
 279          * Called when top level window is created.
 280          * @see EventQueueMonitor
 281          * @see EventQueueMonitor#addTopLevelWindowListener
 282          */
 283         public void topLevelWindowCreated(Window w) {
 284             if (w instanceof Accessible) {
 285                 installListeners((Accessible) w);
 286             }
 287         }
 288 
 289         /**
 290          * Called when top level window is destroyed.
 291          * @see EventQueueMonitor
 292          * @see EventQueueMonitor#addTopLevelWindowListener
 293          */
 294         public void topLevelWindowDestroyed(Window w) {
 295             if (w instanceof Accessible) {
 296                 removeListeners((Accessible) w);
 297             }
 298         }
 299 
 300 
 301         /* PropertyChangeListener Methods **************************************/
 302 
 303         public void propertyChange(PropertyChangeEvent e) {
 304             // propogate the event
 305             Object[] listeners =
 306                     AccessibilityEventMonitor.listenerList.getListenerList();
 307             for (int i = listeners.length-2; i>=0; i-=2) {
 308                 if (listeners[i]==PropertyChangeListener.class) {
 309                     ((PropertyChangeListener)listeners[i+1]).propertyChange(e);
 310                 }
 311             }
 312 
 313             // handle childbirth/death
 314             String name = e.getPropertyName();
 315             if (name.compareTo(AccessibleContext.ACCESSIBLE_CHILD_PROPERTY) == 0) {
 316                 Object oldValue = e.getOldValue();
 317                 Object newValue = e.getNewValue();
 318 
 319                 if ((oldValue == null) ^ (newValue == null)) { // one null, not both
 320                     if (oldValue != null) {
 321                         // this Accessible is a child that's going away
 322                         if (oldValue instanceof Accessible) {
 323                             Accessible a = (Accessible) oldValue;
 324                             removeListeners(a.getAccessibleContext());
 325                         } else if (oldValue instanceof AccessibleContext) {
 326                             removeListeners((AccessibleContext) oldValue);
 327                         }
 328                     } else if (newValue != null) {
 329                         // this Accessible is a child was just born
 330                         if (newValue instanceof Accessible) {
 331                             Accessible a = (Accessible) newValue;
 332                             installListeners(a.getAccessibleContext());
 333                         } else if (newValue instanceof AccessibleContext) {
 334                             installListeners((AccessibleContext) newValue);
 335                         }
 336                     }
 337                 } else {
 338                     System.out.println("ERROR in usage of PropertyChangeEvents for: " + e.toString());
 339                 }
 340             }
 341         }
 342     }
 343 }
 344 
 345 /*
 346  * workaround for no public AccessibleState constructor
 347  */
 348 class _AccessibleState extends AccessibleState {
 349     /**
 350      * Indicates this object is responsible for managing its
 351      * subcomponents.  This is typically used for trees and tables
 352      * that have a large number of subcomponents and where the
 353      * objects are created only when needed and otherwise remain virtual.
 354      * The application should not manage the subcomponents directly.
 355      */
 356     public static final _AccessibleState MANAGES_DESCENDANTS
 357         = new _AccessibleState ("managesDescendants");
 358 
 359     /**
 360      * Creates a new AccessibleState using the given locale independent key.
 361      * This should not be a public method.  Instead, it is used to create
 362      * the constants in this file to make it a strongly typed enumeration.
 363      * Subclasses of this class should enforce similar policy.
 364      * <p>
 365      * The key String should be a locale independent key for the state.
 366      * It is not intended to be used as the actual String to display
 367      * to the user.  To get the localized string, use toDisplayString.
 368      *
 369      * @param key the locale independent name of the state.
 370      * @see AccessibleBundle#toDisplayString
 371      */
 372     protected _AccessibleState(String key) {
 373         super(key);
 374     }
 375 }