8076554: [macosx] Custom Swing text components need to allow standard accessibility
authorptbrunet
Wed, 20 Apr 2016 12:23:38 -0500
changeset 37707 6cebe9608430
parent 37706 818b9c789c6c
child 37708 0d7d574e748e
8076554: [macosx] Custom Swing text components need to allow standard accessibility Summary: Remove JTextComponent listeners; use property change listeners for caret/text Reviewed-by: prr, alexsch Contributed-by: peter.brunet@oracle.com
jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/AccessibilityEventMonitor.java
jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessible.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/AccessibilityEventMonitor.java	Wed Apr 20 12:23:38 2016 -0500
@@ -0,0 +1,253 @@
+/*
+ * Copyright (c) 2002, 2016, 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 sun.lwawt.macosx;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import javax.accessibility.Accessible;
+import javax.accessibility.AccessibleContext;
+import javax.accessibility.AccessibleRole;
+import javax.accessibility.AccessibleState;
+import javax.accessibility.AccessibleStateSet;
+import javax.swing.event.EventListenerList;
+
+/**
+ * <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.
+ *
+ * This code is a subset of com.sun.java.accessibility.util.AccessibilityEventMonitor
+ * which resides in module jdk.accessibility.  Due to modularization the code in
+ * this package, java.desktop, can not be dependent on code in jdk.accessibility.
+ */
+
+class AccessibilityEventMonitor {
+
+    /**
+     * The current list of registered {@link java.beans.PropertyChangeListener
+     * PropertyChangeListener} classes.
+     *
+     * @see #addPropertyChangeListener
+     */
+    private static final EventListenerList listenerList =
+        new EventListenerList();
+
+
+    /**
+     * 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.
+     */
+    private static 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
+     * @param a  the Accessible object to add the PropertyChangeListener to
+     */
+
+    static void addPropertyChangeListener(PropertyChangeListener l, Accessible a) {
+        if (listenerList.getListenerCount(PropertyChangeListener.class) == 0) {
+            accessibilityListener.installListeners(a);
+        }
+        listenerList.add(PropertyChangeListener.class, l);
+    }
+
+    /**
+     * AccessibilityEventListener is the class that does all the work for
+     * AccessibilityEventMonitor.  It is not intended for use by any other
+     * class except AccessibilityEventMonitor.
+     */
+
+    private static class AccessibilityEventListener implements PropertyChangeListener {
+
+        /**
+         * Installs PropertyChange listeners to the Accessible object, and its
+         * children (so long as the object isn't of TRANSIENT state).
+         *
+         * @param a the Accessible object to add listeners to
+         */
+        private void installListeners(Accessible a) {
+            installListeners(a.getAccessibleContext());
+        }
+
+        /**
+         * Installs PropertyChange listeners to the AccessibleContext object,
+         * and its * children (so long as the object isn't of TRANSIENT state).
+         *
+         * @param ac the AccessibleContext 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 for the given Accessible object,
+         * its children (so long as the object isn't of TRANSIENT state).
+         *
+         * @param a the Accessible object to remove listeners from
+         */
+        private void removeListeners(Accessible a) {
+            removeListeners(a.getAccessibleContext());
+        }
+
+        /**
+         * Removes PropertyChange listeners for the given AccessibleContext
+         * object, its 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);
+                        }
+                    }
+                }
+            }
+        }
+
+        @Override
+        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());
+                }
+            }
+        }
+    }
+}
--- a/jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessible.java	Wed Apr 20 10:59:23 2016 +0530
+++ b/jdk/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessible.java	Wed Apr 20 12:23:38 2016 -0500
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2016, 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
@@ -26,20 +26,18 @@
 package sun.lwawt.macosx;
 
 import java.awt.Component;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.lang.reflect.Field;
 
 import javax.accessibility.Accessible;
 import javax.accessibility.AccessibleContext;
 import javax.swing.JProgressBar;
 import javax.swing.JSlider;
-import javax.swing.event.CaretEvent;
-import javax.swing.event.CaretListener;
 import javax.swing.event.ChangeEvent;
 import javax.swing.event.ChangeListener;
-import javax.swing.event.DocumentEvent;
-import javax.swing.event.DocumentListener;
-import javax.swing.text.JTextComponent;
 
+import sun.lwawt.macosx.CFRetainedResource;
 
 class CAccessible extends CFRetainedResource implements Accessible {
     static Field getNativeAXResourceField() {
@@ -99,13 +97,10 @@
         return accessible.getAccessibleContext();
     }
 
-    // currently only supports text components
     public void addNotificationListeners(Component c) {
-        if (c instanceof JTextComponent) {
-            JTextComponent tc = (JTextComponent) c;
-            AXTextChangeNotifier listener = new AXTextChangeNotifier();
-            tc.getDocument().addDocumentListener(listener);
-            tc.addCaretListener(listener);
+        AXTextChangeNotifier listener = new AXTextChangeNotifier();
+        if (c instanceof Accessible) {
+            AccessibilityEventMonitor.addPropertyChangeListener(listener, (Accessible)c);
         }
         if (c instanceof JProgressBar) {
             JProgressBar pb = (JProgressBar) c;
@@ -117,29 +112,23 @@
     }
 
 
-    private class AXTextChangeNotifier implements DocumentListener, CaretListener {
-        @Override
-        public void changedUpdate(DocumentEvent e) {
-            if (ptr != 0) valueChanged(ptr);
-        }
+    private class AXTextChangeNotifier implements PropertyChangeListener {
 
         @Override
-        public void insertUpdate(DocumentEvent e) {
-            if (ptr != 0) valueChanged(ptr);
-        }
-
-        @Override
-        public void removeUpdate(DocumentEvent e) {
-            if (ptr != 0) valueChanged(ptr);
-        }
-
-        @Override
-        public void caretUpdate(CaretEvent e) {
-            if (ptr != 0) selectionChanged(ptr);
+        public void propertyChange(PropertyChangeEvent e) {
+            String name = e.getPropertyName();
+            if ( ptr != 0 ) {
+                if (name.compareTo(AccessibleContext.ACCESSIBLE_CARET_PROPERTY) == 0) {
+                    selectionChanged(ptr);
+                } else if (name.compareTo(AccessibleContext.ACCESSIBLE_TEXT_PROPERTY) == 0 ) {
+                    valueChanged(ptr);
+                }
+            }
         }
     }
 
     private class AXProgressChangeNotifier implements ChangeListener {
+        @Override
         public void stateChanged(ChangeEvent e) {
             if (ptr != 0) valueChanged(ptr);
         }