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