1/*
2 * Copyright (c) 2002, 2016, 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
26package com.sun.java.accessibility.util;
27
28import java.util.*;
29import java.beans.*;
30import java.awt.*;
31import java.awt.event.*;
32import 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
46public 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 */
348class _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}
376