--- /dev/null 2015-03-30 22:01:16.000000000 -0500 +++ new/jdk/src/windows/classes/com/sun/java/accessibility/AccessBridge.java 2015-03-30 22:01:15.662439200 -0500 @@ -0,0 +1,7272 @@ +/* + * Copyright (c) 2005, 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; + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import java.lang.*; +import java.lang.reflect.*; + +import java.beans.*; +import javax.swing.*; +import javax.swing.event.*; +import javax.swing.text.*; +import javax.swing.tree.*; +import javax.swing.table.*; +import javax.swing.plaf.TreeUI; + +import javax.accessibility.*; +import com.sun.java.accessibility.util.*; +import sun.awt.AWTAccessor; +import sun.awt.AppContext; +import sun.awt.SunToolkit; + +import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; + +/* + * Note: This class has to be public. It's loaded from the VM like this: + * Class.forName(atName).newInstance(); + */ +@jdk.Exported(false) +final public class AccessBridge extends AccessBridgeLoader { + + private final String AccessBridgeVersion = + "AccessBridge 2.0.4"; + + private static AccessBridge theAccessBridge; + private ObjectReferences references; + private EventHandler eventHandler; + private boolean runningOnJDK1_4 = false; + private boolean runningOnJDK1_5 = false; + + // Maps AccessibleRoles strings to AccessibleRoles. + private ConcurrentHashMap accessibleRoleMap = new ConcurrentHashMap<>(); + + /** + If the object's role is in the following array getVirtualAccessibleName + will use the extended search algorithm. + */ + private ArrayList extendedVirtualNameSearchRoles = new ArrayList<>(); + /** + If the role of the object's parent is in the following array + getVirtualAccessibleName will NOT use the extended search + algorithm even if the object's role is in the + extendedVirtualNameSearchRoles array. + */ + private ArrayList noExtendedVirtualNameSearchParentRoles = new ArrayList<>(); + + /** + * AccessBridge constructor + * + * Note: This constructor has to be public. It's called from the VM like this: + * Class.forName(atName).newInstance(); + */ + public AccessBridge() { + super(); + theAccessBridge = this; + references = new ObjectReferences(); + + // initialize shutdown hook + Runtime runTime = Runtime.getRuntime(); + shutdownHook hook = new shutdownHook(); + runTime.addShutdownHook(new Thread(hook)); + + // initialize AccessibleRole map + initAccessibleRoleMap(); + + // determine which version of the JDK is running + String version = getJavaVersionProperty(); + debugString("JDK version = "+version); + runningOnJDK1_4 = (version.compareTo("1.4") >= 0); + runningOnJDK1_5 = (version.compareTo("1.5") >= 0); + + // initialize the methods that map HWNDs and Java top-level + // windows + if (initHWNDcalls() == true) { + + // is this a JVM we can use? + // install JDK 1.2 and later Swing ToolKit listener + EventQueueMonitor.isGUIInitialized(); + + // start the Java event handler + eventHandler = new EventHandler(this); + + // register for menu selection events + if (runningOnJDK1_4) { + MenuSelectionManager.defaultManager().addChangeListener(eventHandler); + } + + // register as a NativeWindowHandler + addNativeWindowHandler(new DefaultNativeWindowHandler()); + + // start in a new thread + Thread abthread = new Thread(new dllRunner()); + abthread.setDaemon(true); + abthread.start(); + debugString("AccessBridge started"); + } + } + + /* + * adaptor to run the AccessBridge DLL + */ + private class dllRunner implements Runnable { + public void run() { + runDLL(); + } + } + + /* + * shutdown hook + */ + private class shutdownHook implements Runnable { + + public void run() { + debugString("***** shutdownHook: shutting down..."); + javaShutdown(); + } + } + + + /* + * Initialize the hashtable that maps Strings to AccessibleRoles. + */ + private void initAccessibleRoleMap() { + /* + * Initialize the AccessibleRoles map. This code uses methods in + * java.lang.reflect.* to build the map. + */ + try { + Class clAccessibleRole = Class.forName ("javax.accessibility.AccessibleRole"); + if (null != clAccessibleRole) { + AccessibleRole roleUnknown = AccessibleRole.UNKNOWN; + Field [] fields = clAccessibleRole.getFields (); + int i = 0; + for (i = 0; i < fields.length; i ++) { + Field f = fields [i]; + if (javax.accessibility.AccessibleRole.class == f.getType ()) { + AccessibleRole nextRole = (AccessibleRole) (f.get (roleUnknown)); + String nextRoleString = nextRole.toDisplayString (Locale.US); + accessibleRoleMap.put (nextRoleString, nextRole); + } + } + } + } catch (Exception e) {} + + /* + Build the extendedVirtualNameSearchRoles array list. I chose this method + because some of the Accessible Roles that need to be added to it are not + available in all versions of the J2SE that we want to support. + */ + extendedVirtualNameSearchRoles.add (AccessibleRole.COMBO_BOX); + try { + /* + Added in J2SE 1.4 + */ + extendedVirtualNameSearchRoles.add (AccessibleRole.DATE_EDITOR); + } catch (NoSuchFieldError e) {} + extendedVirtualNameSearchRoles.add (AccessibleRole.LIST); + extendedVirtualNameSearchRoles.add (AccessibleRole.PASSWORD_TEXT); + extendedVirtualNameSearchRoles.add (AccessibleRole.SLIDER); + try { + /* + Added in J2SE 1.3 + */ + extendedVirtualNameSearchRoles.add (AccessibleRole.SPIN_BOX); + } catch (NoSuchFieldError e) {} + extendedVirtualNameSearchRoles.add (AccessibleRole.TABLE); + extendedVirtualNameSearchRoles.add (AccessibleRole.TEXT); + extendedVirtualNameSearchRoles.add (AccessibleRole.UNKNOWN); + + noExtendedVirtualNameSearchParentRoles.add (AccessibleRole.TABLE); + noExtendedVirtualNameSearchParentRoles.add (AccessibleRole.TOOL_BAR); + } + + /** + * start the AccessBridge DLL running in its own thread + */ + private native void runDLL(); + + /** + * debugging output (goes to OutputDebugStr()) + */ + private native void sendDebugString(String debugStr); + + /** + * debugging output (goes to OutputDebugStr()) + */ + private void debugString(String debugStr) { + sendDebugString(debugStr); + } + + /* ===== utility methods ===== */ + + /** + * decrement the reference to the object (called by native code) + */ + private void decrementReference(Object o) { + references.decrement(o); + } + + /** + * get the java.version property from the JVM + */ + private String getJavaVersionProperty() { + String s = System.getProperty("java.version"); + if (s != null) { + references.increment(s); + return s; + } + return null; + } + + /** + * get the java.version property from the JVM + */ + private String getAccessBridgeVersion() { + String s = new String(AccessBridgeVersion); + references.increment(s); + return s; + } + + /* ===== HWND/Java window mapping methods ===== */ + + // Java toolkit methods for mapping HWNDs to Java components + private Method javaGetComponentFromNativeWindowHandleMethod; + private Method javaGetNativeWindowHandleFromComponentMethod; + + // native jawt methods for mapping HWNDs to Java components + private native int isJAWTInstalled(); + + private native int jawtGetNativeWindowHandleFromComponent(Component comp); + + private native Component jawtGetComponentFromNativeWindowHandle(int handle); + + Toolkit toolkit; + + /** + * map an HWND to an AWT Component + */ + private boolean initHWNDcalls() { + Class integerParemter[] = new Class[1]; + integerParemter[0] = Integer.TYPE; + Class componentParemter[] = new Class[1]; + try { + componentParemter[0] = Class.forName("java.awt.Component"); + } catch (ClassNotFoundException e) { + debugString("Exception: " + e.toString()); + } + Object[] args = new Object[1]; + Component c; + boolean returnVal = false; + + toolkit = Toolkit.getDefaultToolkit(); + + if (useJAWT_DLL) { + returnVal = true; + } else { + // verify javaGetComponentFromNativeWindowHandle() method + // is present if JAWT.DLL is not installed + try { + javaGetComponentFromNativeWindowHandleMethod = + toolkit.getClass().getMethod( + "getComponentFromNativeWindowHandle", integerParemter); + if (javaGetComponentFromNativeWindowHandleMethod != null) { + try { + args[0] = new Integer(1); + c = (Component) javaGetComponentFromNativeWindowHandleMethod.invoke(toolkit, args); + returnVal = true; + } catch (InvocationTargetException e) { + debugString("Exception: " + e.toString()); + } catch (IllegalAccessException e) { + debugString("Exception: " + e.toString()); + } + } + } catch (NoSuchMethodException e) { + debugString("Exception: " + e.toString()); + } catch (SecurityException e) { + debugString("Exception: " + e.toString()); + } + + // verify getComponentFromNativeWindowHandle() method + // is present if JAWT.DLL is not installed + try { + javaGetNativeWindowHandleFromComponentMethod = + toolkit.getClass().getMethod( + "getNativeWindowHandleFromComponent", componentParemter); + if (javaGetNativeWindowHandleFromComponentMethod != null) { + try { + args[0] = new Button("OK"); // need some Component... + Integer i = (Integer) javaGetNativeWindowHandleFromComponentMethod.invoke(toolkit, args); + returnVal = true; + } catch (InvocationTargetException e) { + debugString("Exception: " + e.toString()); + } catch (IllegalAccessException e) { + debugString("Exception: " + e.toString()); + } catch (Exception e) { + debugString("Exception: " + e.toString()); + } + } + } catch (NoSuchMethodException e) { + debugString("Exception: " + e.toString()); + } catch (SecurityException e) { + debugString("Exception: " + e.toString()); + } + } + return returnVal; + } + + // native window handler interface + private interface NativeWindowHandler { + public Accessible getAccessibleFromNativeWindowHandle(int nativeHandle); + } + + // hash table of native window handle to AccessibleContext mappings + static private ConcurrentHashMap windowHandleToContextMap = new ConcurrentHashMap<>(); + + // hash table of AccessibleContext to native window handle mappings + static private ConcurrentHashMap contextToWindowHandleMap = new ConcurrentHashMap<>(); + + /* + * adds a virtual window handler to our hash tables + */ + static private void registerVirtualFrame(final Accessible a, + Integer nativeWindowHandle ) { + if (a != null) { + AccessibleContext ac = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return a.getAccessibleContext(); + } + }, a); + windowHandleToContextMap.put(nativeWindowHandle, ac); + contextToWindowHandleMap.put(ac, nativeWindowHandle); + } + } + + /* + * removes a virtual window handler to our hash tables + */ + static private void revokeVirtualFrame(final Accessible a, + Integer nativeWindowHandle ) { + AccessibleContext ac = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return a.getAccessibleContext(); + } + }, a); + windowHandleToContextMap.remove(nativeWindowHandle); + contextToWindowHandleMap.remove(ac); + } + + // vector of native window handlers + private static Vector nativeWindowHandlers = new Vector<>(); + + /* + * adds a native window handler to our list + */ + private static void addNativeWindowHandler(NativeWindowHandler handler) { + if (handler == null) { + throw new IllegalArgumentException(); + } + nativeWindowHandlers.addElement(handler); + } + + /* + * removes a native window handler to our list + */ + private static boolean removeNativeWindowHandler(NativeWindowHandler handler) { + if (handler == null) { + throw new IllegalArgumentException(); + } + return nativeWindowHandlers.removeElement(handler); + } + + /** + * verifies that a native window handle is a Java window + */ + private boolean isJavaWindow(int nativeHandle) { + AccessibleContext ac = getContextFromNativeWindowHandle(nativeHandle); + if (ac != null) { + saveContextToWindowHandleMapping(ac, nativeHandle); + return true; + } + return false; + } + + /* + * saves the mapping between an AccessibleContext and a window handle + */ + private void saveContextToWindowHandleMapping(AccessibleContext ac, + int nativeHandle) { + debugString("saveContextToWindowHandleMapping..."); + if (ac == null) { + return; + } + if (! contextToWindowHandleMap.containsKey(ac)) { + debugString("saveContextToWindowHandleMapping: ac = "+ac+"; handle = "+nativeHandle); + contextToWindowHandleMap.put(ac, nativeHandle); + } + } + + /** + * maps a native window handle to an Accessible Context + */ + private AccessibleContext getContextFromNativeWindowHandle(int nativeHandle) { + // First, look for the Accessible in our hash table of + // virtual window handles. + AccessibleContext ac = windowHandleToContextMap.get(nativeHandle); + if(ac!=null) { + saveContextToWindowHandleMapping(ac, nativeHandle); + return ac; + } + + // Next, look for the native window handle in our vector + // of native window handles. + int numHandlers = nativeWindowHandlers.size(); + for (int i = 0; i < numHandlers; i++) { + NativeWindowHandler nextHandler = nativeWindowHandlers.elementAt(i); + final Accessible a = nextHandler.getAccessibleFromNativeWindowHandle(nativeHandle); + if (a != null) { + ac = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return a.getAccessibleContext(); + } + }, a); + saveContextToWindowHandleMapping(ac, nativeHandle); + return ac; + } + } + // Not found. + return null; + } + + /** + * maps an AccessibleContext to a native window handle + * returns 0 on error + */ + private int getNativeWindowHandleFromContext(AccessibleContext ac) { + debugString("getNativeWindowHandleFromContext: ac = "+ac); + try { + return contextToWindowHandleMap.get(ac); + } catch (Exception ex) { + return 0; + } + } + + private class DefaultNativeWindowHandler implements NativeWindowHandler { + /* + * returns the Accessible associated with a native window + */ + public Accessible getAccessibleFromNativeWindowHandle(int nativeHandle) { + final Component c = getComponentFromNativeWindowHandle(nativeHandle); + if (c instanceof Accessible) { + AccessibleContext ac = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return c.getAccessibleContext(); + } + }, c); + saveContextToWindowHandleMapping(ac, nativeHandle); + return (Accessible)c; + } else { + return null; + } + } + + /** + * map an HWND to an AWT Component + */ + private Component getComponentFromNativeWindowHandle(int nativeHandle) { + if (useJAWT_DLL) { + debugString("*** calling jawtGetComponentFromNativeWindowHandle"); + return jawtGetComponentFromNativeWindowHandle(nativeHandle); + } else { + debugString("*** calling javaGetComponentFromNativeWindowHandle"); + Object[] args = new Object[1]; + if (javaGetComponentFromNativeWindowHandleMethod != null) { + try { + args[0] = nativeHandle; + Object o = javaGetComponentFromNativeWindowHandleMethod.invoke(toolkit, args); + if (o instanceof Accessible) { + final Accessible acc=(Accessible)o; + AccessibleContext ac = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return acc.getAccessibleContext(); + } + }, (Component)o); + saveContextToWindowHandleMapping(ac,nativeHandle); + } + return (Component)o; + } catch (InvocationTargetException | IllegalAccessException e) { + debugString("Exception: " + e.toString()); + } + } + } + return null; + } + } + + /** + * map an AWT Component to an HWND + */ + private int getNativeWindowHandleFromComponent(final Component target) { + if (useJAWT_DLL) { + debugString("*** calling jawtGetNativeWindowHandleFromComponent"); + return jawtGetNativeWindowHandleFromComponent(target); + } else { + Object[] args = new Object[1]; + debugString("*** calling javaGetNativeWindowHandleFromComponent"); + if (javaGetNativeWindowHandleFromComponentMethod != null) { + try { + args[0] = target; + Integer i = (Integer) javaGetNativeWindowHandleFromComponentMethod.invoke(toolkit, args); + // cache the mapping + AccessibleContext ac = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return target.getAccessibleContext(); + } + }, target); + contextToWindowHandleMap.put(ac, i); + return i.intValue(); + } catch (InvocationTargetException e) { + debugString("Exception: " + e.toString()); + } catch (IllegalAccessException e) { + debugString("Exception: " + e.toString()); + } + } + } + return -1; + } + + /* ===== AccessibleContext methods =====*/ + + /* + * returns the inner-most AccessibleContext in parent at Point(x, y) + */ + private AccessibleContext getAccessibleContextAt(int x, int y, + AccessibleContext parent) { + if (parent == null) { + return null; + } + if (windowHandleToContextMap != null && + windowHandleToContextMap.containsValue(getRootAccessibleContext(parent))) { + // Path for applications that register their top-level + // windows with the AccessBridge (e.g., StarOffice 6.1) + return getAccessibleContextAt_1(x, y, parent); + } else { + // Path for applications that do not register + // their top-level windows with the AccessBridge + // (e.g., Swing/AWT applications) + return getAccessibleContextAt_2(x, y, parent); + } + } + + /* + * returns the root accessible context + */ + private AccessibleContext getRootAccessibleContext(final AccessibleContext ac) { + if (ac == null) { + return null; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + Accessible parent = ac.getAccessibleParent(); + if (parent == null) { + return ac; + } + Accessible tmp = parent.getAccessibleContext().getAccessibleParent(); + while (tmp != null) { + parent = tmp; + tmp = parent.getAccessibleContext().getAccessibleParent(); + } + return parent.getAccessibleContext(); + } + }, ac); + } + + /* + * StarOffice version that does not use the EventQueueMonitor + */ + private AccessibleContext getAccessibleContextAt_1(final int x, final int y, + final AccessibleContext parent) { + debugString(" : getAccessibleContextAt_1 called"); + debugString(" -> x = " + x + " y = " + y + " parent = " + parent); + + if (parent == null) return null; + final AccessibleComponent acmp = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleComponent call() throws Exception { + return parent.getAccessibleComponent(); + } + }, parent); + if (acmp!=null) { + final Point loc = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Point call() throws Exception { + return acmp.getLocation(); + } + }, parent); + final Accessible a = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Accessible call() throws Exception { + return acmp.getAccessibleAt(new Point(x - loc.x, y - loc.y)); + } + }, parent); + if (a != null) { + AccessibleContext foundAC = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return a.getAccessibleContext(); + } + }, parent); + if (foundAC != null) { + if (foundAC != parent) { + // recurse down into the child + return getAccessibleContextAt_1(x - loc.x, y - loc.y, + foundAC); + } else + return foundAC; + } + } + } + return parent; + } + + /* + * AWT/Swing version + */ + private AccessibleContext getAccessibleContextAt_2(final int x, final int y, + AccessibleContext parent) { + debugString("getAccessibleContextAt_2 called"); + debugString(" -> x = " + x + " y = " + y + " parent = " + parent); + + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + Accessible a = EventQueueMonitor.getAccessibleAt(new Point(x, y)); + if (a != null) { + AccessibleContext childAC = a.getAccessibleContext(); + if (childAC != null) { + debugString(" returning childAC = " + childAC); + return childAC; + } + } + return null; + } + }, parent); + } + + /** + * returns the Accessible that has focus + */ + private AccessibleContext getAccessibleContextWithFocus() { + Component c = AWTEventMonitor.getComponentWithFocus(); + if (c != null) { + final Accessible a = Translator.getAccessible(c); + if (a != null) { + AccessibleContext ac = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return a.getAccessibleContext(); + } + }, c); + if (ac != null) { + return ac; + } + } + } + return null; + } + + /** + * returns the AccessibleName from an AccessibleContext + */ + private String getAccessibleNameFromContext(final AccessibleContext ac) { + debugString("***** ac = "+ac.getClass()); + if (ac != null) { + String s = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + return ac.getAccessibleName(); + } + }, ac); + if (s != null) { + references.increment(s); + debugString("Returning AccessibleName from Context: " + s); + return s; + } else { + return null; + } + } else { + debugString("getAccessibleNameFromContext; ac = null!"); + return null; + } + } + + /** + * Returns an AccessibleName for a component using an algorithm optimized + * for the JAWS screen reader. This method is only intended for JAWS. All + * other uses are entirely optional. + */ + private String getVirtualAccessibleNameFromContext(final AccessibleContext ac) { + if (null != ac) { + /* + Step 1: + ======= + Determine if we can obtain the Virtual Accessible Name from the + Accessible Name or Accessible Description of the object. + */ + String nameString = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + return ac.getAccessibleName(); + } + }, ac); + if ( ( null != nameString ) && ( 0 != nameString.length () ) ) { + debugString ("bk -- The Virtual Accessible Name was obtained from AccessibleContext::getAccessibleName."); + references.increment (nameString); + return nameString; + } + String descriptionString = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + return ac.getAccessibleDescription(); + } + }, ac); + if ( ( null != descriptionString ) && ( 0 != descriptionString.length () ) ) { + debugString ("bk -- The Virtual Accessible Name was obtained from AccessibleContext::getAccessibleDescription."); + references.increment (descriptionString); + return descriptionString; + } + + debugString ("The Virtual Accessible Name was not found using AccessibleContext::getAccessibleDescription. or getAccessibleName"); + /* + Step 2: + ======= + Decide whether the extended name search algorithm should be + used for this object. + */ + boolean bExtendedSearch = false; + AccessibleRole role = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleRole call() throws Exception { + return ac.getAccessibleRole(); + } + }, ac); + AccessibleContext parentContext = null; + AccessibleRole parentRole = AccessibleRole.UNKNOWN; + + if ( extendedVirtualNameSearchRoles.contains (role) ) { + parentContext = getAccessibleParentFromContext (ac); + if ( null != parentContext ) { + final AccessibleContext parentContextInnerTemp = parentContext; + parentRole = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleRole call() throws Exception { + return parentContextInnerTemp.getAccessibleRole(); + } + }, ac); + if ( AccessibleRole.UNKNOWN != parentRole ) { + bExtendedSearch = true; + if ( noExtendedVirtualNameSearchParentRoles.contains (parentRole) ) { + bExtendedSearch = false; + } + } + } + } + + if (false == bExtendedSearch) { + debugString ("bk -- getVirtualAccessibleNameFromContext will not use the extended name search algorithm. role = " + role.toDisplayString (Locale.US) ); + /* + Step 3: + ======= + We have determined that we should not use the extended name + search algorithm for this object (we must obtain the name of + the object from the object itself and not from neighboring + objects). However the object name cannot be obtained from + the Accessible Name or Accessible Description of the object. + + Handle several special cases here that might yield a value for + the Virtual Accessible Name. Return null if the object does + not match the criteria for any of these special cases. + */ + if (AccessibleRole.LABEL == role) { + /* + Does the label support the Accessible Text Interface? + */ + final AccessibleText at = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleText call() throws Exception { + return ac.getAccessibleText(); + } + }, ac); + if (null != at) { + int charCount = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return at.getCharCount(); + } + }, ac); + String text = getAccessibleTextRangeFromContext (ac, 0, charCount); + if (null != text) { + debugString ("bk -- The Virtual Accessible Name was obtained from the Accessible Text of the LABEL object."); + references.increment (text); + return text; + } + } + /* + Does the label support the Accessible Icon Interface? + */ + debugString ("bk -- Attempting to obtain the Virtual Accessible Name from the Accessible Icon information."); + final AccessibleIcon [] ai = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleIcon[] call() throws Exception { + return ac.getAccessibleIcon(); + } + }, ac); + if ( (null != ai) && (ai.length > 0) ) { + String iconDescription = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + return ai[0].getAccessibleIconDescription(); + } + }, ac); + if (iconDescription != null){ + debugString ("bk -- The Virtual Accessible Name was obtained from the description of the first Accessible Icon found in the LABEL object."); + references.increment (iconDescription); + return iconDescription; + } + } else { + parentContext = getAccessibleParentFromContext (ac); + if ( null != parentContext ) { + final AccessibleContext parentContextInnerTemp = parentContext; + parentRole = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleRole call() throws Exception { + return parentContextInnerTemp.getAccessibleRole(); + } + }, ac); + if ( AccessibleRole.TABLE == parentRole ) { + int indexInParent = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return ac.getAccessibleIndexInParent(); + } + }, ac); + final AccessibleContext acTableCell = getAccessibleChildFromContext (parentContext, indexInParent); + debugString ("bk -- Making a second attempt to obtain the Virtual Accessible Name from the Accessible Icon information for the Table Cell."); + if (acTableCell != null) { + final AccessibleIcon [] aiRet =InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleIcon[] call() throws Exception { + return acTableCell.getAccessibleIcon(); + } + }, ac); + if ( (null != aiRet) && (aiRet.length > 0) ) { + String iconDescription = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return aiRet[0].getAccessibleIconDescription (); + } + }, ac); + if (iconDescription != null){ + debugString ("bk -- The Virtual Accessible Name was obtained from the description of the first Accessible Icon found in the Table Cell object."); + references.increment (iconDescription); + return iconDescription; + } + } + } + } + } + } + } else if ( (AccessibleRole.TOGGLE_BUTTON == role) || + (AccessibleRole.PUSH_BUTTON == role) ) { + /* + Does the button support the Accessible Icon Interface? + */ + debugString ("bk -- Attempting to obtain the Virtual Accessible Name from the Accessible Icon information."); + final AccessibleIcon [] ai = InvocationUtils.invokeAndWait(new Callable() { + public AccessibleIcon [] call() { + return ac.getAccessibleIcon (); + } + }, ac); + if ( (null != ai) && (ai.length > 0) ) { + String iconDescription = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return ai[0].getAccessibleIconDescription (); + } + }, ac); + if (iconDescription != null){ + debugString ("bk -- The Virtual Accessible Name was obtained from the description of the first Accessible Icon found in the TOGGLE_BUTTON or PUSH_BUTTON object."); + references.increment (iconDescription); + return iconDescription; + } + } + } else if ( AccessibleRole.CHECK_BOX == role ) { + /* + NOTE: The only case I know of in which a check box does not + have a name is when that check box is contained in a table. + + In this case it would be appropriate to use the display string + of the check box object as the name (in US English the display + string is typically either "true" or "false"). + + I am using the AccessibleValue interface to obtain the display + string of the check box. If the Accessible Value is 1, I am + returning Boolean.TRUE.toString (), If the Accessible Value is + 0, I am returning Boolean.FALSE.toString (). If the Accessible + Value is some other number, I will return the display string of + the current numerical value of the check box. + */ + final AccessibleValue av = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleValue call() throws Exception { + return ac.getAccessibleValue(); + } + }, ac); + if ( null != av ) { + nameString = null; + Number value = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Number call() throws Exception { + return av.getCurrentAccessibleValue(); + } + }, ac); + if ( null != value ) { + if ( 1 == value.intValue () ) { + nameString = Boolean.TRUE.toString (); + } else if ( 0 == value.intValue () ) { + nameString = Boolean.FALSE.toString (); + } else { + nameString = value.toString (); + } + if ( null != nameString ) { + references.increment (nameString); + return nameString; + } + } + } + } + return null; + } + + /* + + + Beginning of the extended name search + + + */ + final AccessibleContext parentContextOuterTemp = parentContext; + String parentName = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + return parentContextOuterTemp.getAccessibleName(); + } + }, ac); + String parentDescription = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + return parentContextOuterTemp.getAccessibleDescription(); + } + }, ac); + + /* + Step 4: + ======= + Special case for Slider Bar objects. + */ + if ( (AccessibleRole.SLIDER == role) && + (AccessibleRole.PANEL == parentRole) && + (null != parentName) ) { + debugString ("bk -- The Virtual Accessible Name was obtained from the Accessible Name of the SLIDER object's parent object."); + references.increment (parentName); + return parentName; + } + + boolean bIsEditCombo = false; + + AccessibleContext testContext = ac; + /* + Step 5: + ======= + Special case for Edit Combo Boxes + */ + if ( (AccessibleRole.TEXT == role) && + (AccessibleRole.COMBO_BOX == parentRole) ) { + bIsEditCombo = true; + if (null != parentName) { + debugString ("bk -- The Virtual Accessible Name for this Edit Combo box was obtained from the Accessible Name of the object's parent object."); + references.increment (parentName); + return parentName; + } else if (null != parentDescription) { + debugString ("bk -- The Virtual Accessible Name for this Edit Combo box was obtained from the Accessible Description of the object's parent object."); + references.increment (parentDescription); + return parentDescription; + } + testContext = parentContext; + parentRole = AccessibleRole.UNKNOWN; + parentContext = getAccessibleParentFromContext (testContext); + if ( null != parentContext ) { + final AccessibleContext parentContextInnerTemp = parentContext; + parentRole = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleRole call() throws Exception { + return parentContextInnerTemp.getAccessibleRole(); + } + }, ac); + } + } + + /* + Step 6: + ======= + Attempt to get the Virtual Accessible Name of the object using the + Accessible Relation Set Info (the LABELED_BY Accessible Relation). + */ + String version = getJavaVersionProperty (); + if ( (null != version) && (version.compareTo ("1.3") >= 0) ) { + final AccessibleContext parentContextTempInner = parentContext; + AccessibleRelationSet ars = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleRelationSet call() throws Exception { + return parentContextTempInner.getAccessibleRelationSet(); + } + }, ac); + if ( ars != null && (ars.size () > 0) && (ars.contains (AccessibleRelation.LABELED_BY)) ) { + AccessibleRelation labeledByRelation = ars.get (AccessibleRelation.LABELED_BY); + if (labeledByRelation != null) { + Object [] targets = labeledByRelation.getTarget (); + Object o = targets [0]; + if (o instanceof Accessible) { + AccessibleContext labelContext = ((Accessible)o).getAccessibleContext (); + if (labelContext != null) { + String labelName = labelContext.getAccessibleName (); + String labelDescription = labelContext.getAccessibleDescription (); + if (null != labelName) { + debugString ("bk -- The Virtual Accessible Name was obtained using the LABELED_BY AccessibleRelation -- Name Case."); + references.increment (labelName); + return labelName; + } else if (null != labelDescription) { + debugString ("bk -- The Virtual Accessible Name was obtained using the LABELED_BY AccessibleRelation -- Description Case."); + references.increment (labelDescription); + return labelDescription; + } + } + } + } + } + } else { + debugString ("bk -- This version of Java does not support AccessibleContext::getAccessibleRelationSet."); + } + + //Note: add AccessibleContext to use InvocationUtils.invokeAndWait + /* + Step 7: + ======= + Search for a label object that is positioned either just to the left + or just above the object and get the Accessible Name of the Label + object. + */ + int testIndexMax = 0; + int testX = 0; + int testY = 0; + int testWidth = 0; + int testHeight = 0; + int targetX = 0; + int targetY = 0; + final AccessibleContext tempContext = testContext; + int testIndex = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return tempContext.getAccessibleIndexInParent(); + } + }, ac); + if ( null != parentContext ) { + final AccessibleContext parentContextInnerTemp = parentContext; + testIndexMax = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return parentContextInnerTemp.getAccessibleChildrenCount() - 1; + } + }, ac); + } + testX = getAccessibleXcoordFromContext (testContext); + testY = getAccessibleYcoordFromContext (testContext); + testWidth = getAccessibleWidthFromContext (testContext); + testHeight = getAccessibleHeightFromContext (testContext); + targetX = testX + 2; + targetY = testY + 2; + + int childIndex = testIndex - 1; + /*Accessible child = null; + AccessibleContext childContext = null; + AccessibleRole childRole = AccessibleRole.UNKNOWN;*/ + int childX = 0; + int childY = 0; + int childWidth = 0; + int childHeight = 0; + String childName = null; + String childDescription = null; + while (childIndex >= 0) { + final int childIndexTemp = childIndex; + final AccessibleContext parentContextInnerTemp = parentContext; + final Accessible child = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Accessible call() throws Exception { + return parentContextInnerTemp.getAccessibleChild(childIndexTemp); + } + }, ac); + if ( null != child ) { + final AccessibleContext childContext = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return child.getAccessibleContext(); + } + }, ac); + if ( null != childContext ) { + AccessibleRole childRole = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleRole call() throws Exception { + return childContext.getAccessibleRole(); + } + }, ac); + if ( AccessibleRole.LABEL == childRole ) { + childX = getAccessibleXcoordFromContext (childContext); + childY = getAccessibleYcoordFromContext (childContext); + childWidth = getAccessibleWidthFromContext (childContext); + childHeight = getAccessibleHeightFromContext (childContext); + if ( (childX < testX) && + ((childY <= targetY) && (targetY <= (childY + childHeight))) ) { + childName = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleName (); + } + }, ac); + if ( null != childName ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Name of a LABEL object positioned to the left of the object."); + references.increment (childName); + return childName; + } + childDescription = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleDescription (); + } + }, ac); + if ( null != childDescription ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Description of a LABEL object positioned to the left of the object."); + references.increment (childDescription); + return childDescription; + } + } else if ( (childY < targetY) && + ((childX <= targetX) && (targetX <= (childX + childWidth))) ) { + childName = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleName (); + } + }, ac); + if ( null != childName ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Name of a LABEL object positioned above the object."); + references.increment (childName); + return childName; + } + childDescription = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleDescription (); + } + }, ac); + if ( null != childDescription ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Description of a LABEL object positioned above the object."); + references.increment (childDescription); + return childDescription; + } + } + } + } + } + childIndex --; + } + childIndex = testIndex + 1; + while (childIndex <= testIndexMax) { + final int childIndexTemp = childIndex; + final AccessibleContext parentContextInnerTemp = parentContext; + final Accessible child = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Accessible call() throws Exception { + return parentContextInnerTemp.getAccessibleChild(childIndexTemp); + } + }, ac); + if ( null != child ) { + final AccessibleContext childContext = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return child.getAccessibleContext(); + } + }, ac); + if ( null != childContext ) { + AccessibleRole childRole = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleRole call() throws Exception { + return childContext.getAccessibleRole(); + } + }, ac); + if ( AccessibleRole.LABEL == childRole ) { + childX = getAccessibleXcoordFromContext (childContext); + childY = getAccessibleYcoordFromContext (childContext); + childWidth = getAccessibleWidthFromContext (childContext); + childHeight = getAccessibleHeightFromContext (childContext); + if ( (childX < testX) && + ((childY <= targetY) && (targetY <= (childY + childHeight))) ) { + childName = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleName (); + } + }, ac); + if ( null != childName ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Name of a LABEL object positioned to the left of the object."); + references.increment (childName); + return childName; + } + childDescription = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleDescription (); + } + }, ac); + if ( null != childDescription ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Description of a LABEL object positioned to the left of the object."); + references.increment (childDescription); + return childDescription; + } + } else if ( (childY < targetY) && + ((childX <= targetX) && (targetX <= (childX + childWidth))) ) { + childName = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleName (); + } + }, ac); + if ( null != childName ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Name of a LABEL object positioned above the object."); + references.increment (childName); + return childName; + } + childDescription = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleDescription (); + } + }, ac); + if ( null != childDescription ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Description of a LABEL object positioned above the object."); + references.increment (childDescription); + return childDescription; + } + } + } + } + } + childIndex ++; + } + /* + Step 8: + ======= + Special case for combo boxes and text objects, based on a + similar special case I found in some of our internal JAWS code. + + Search for a button object that is positioned either just to the left + or just above the object and get the Accessible Name of the button + object. + */ + if ( (AccessibleRole.TEXT == role) || + (AccessibleRole.COMBO_BOX == role) || + (bIsEditCombo) ) { + childIndex = testIndex - 1; + while (childIndex >= 0) { + final int childIndexTemp = childIndex; + final AccessibleContext parentContextInnerTemp = parentContext; + final Accessible child = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Accessible call() throws Exception { + return parentContextInnerTemp.getAccessibleChild(childIndexTemp); + } + }, ac); + if ( null != child ) { + final AccessibleContext childContext = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return child.getAccessibleContext(); + } + }, ac); + if ( null != childContext ) { + AccessibleRole childRole = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleRole call() throws Exception { + return childContext.getAccessibleRole(); + } + }, ac); + if ( ( AccessibleRole.PUSH_BUTTON == childRole ) || + ( AccessibleRole.TOGGLE_BUTTON == childRole )) { + childX = getAccessibleXcoordFromContext (childContext); + childY = getAccessibleYcoordFromContext (childContext); + childWidth = getAccessibleWidthFromContext (childContext); + childHeight = getAccessibleHeightFromContext (childContext); + if ( (childX < testX) && + ((childY <= targetY) && (targetY <= (childY + childHeight))) ) { + childName = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleName (); + } + }, ac); + if ( null != childName ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Name of a PUSH_BUTTON or TOGGLE_BUTTON object positioned to the left of the object."); + references.increment (childName); + return childName; + } + childDescription = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleDescription (); + } + }, ac); + if ( null != childDescription ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Description of a PUSH_BUTTON or TOGGLE_BUTTON object positioned to the left of the object."); + references.increment (childDescription); + return childDescription; + } + } + } + } + } + childIndex --; + } + childIndex = testIndex + 1; + while (childIndex <= testIndexMax) { + final int childIndexTemp = childIndex; + final AccessibleContext parentContextInnerTemp = parentContext; + final Accessible child = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Accessible call() throws Exception { + return parentContextInnerTemp.getAccessibleChild(childIndexTemp); + } + }, ac); + if ( null != child ) { + final AccessibleContext childContext = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return child.getAccessibleContext(); + } + }, ac); + if ( null != childContext ) { + AccessibleRole childRole = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleRole call() throws Exception { + return childContext.getAccessibleRole(); + } + }, ac); + if ( ( AccessibleRole.PUSH_BUTTON == childRole ) || + ( AccessibleRole.TOGGLE_BUTTON == childRole ) ) { + childX = getAccessibleXcoordFromContext (childContext); + childY = getAccessibleYcoordFromContext (childContext); + childWidth = getAccessibleWidthFromContext (childContext); + childHeight = getAccessibleHeightFromContext (childContext); + if ( (childX < testX) && + ((childY <= targetY) && (targetY <= (childY + childHeight))) ) { + childName = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleName(); + } + }, ac); + if ( null != childName ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Name of a PUSH_BUTTON or TOGGLE_BUTTON object positioned to the left of the object."); + references.increment (childName); + return childName; + } + childDescription = InvocationUtils.invokeAndWait(new Callable() { + public String call() { + return childContext.getAccessibleDescription (); + } + }, ac); + if ( null != childDescription ) { + debugString ("bk -- The Virtual Accessible Name was obtained from Accessible Description of a PUSH_BUTTON or TOGGLE_BUTTON object positioned to the left of the object."); + references.increment (childDescription); + return childDescription; + } + } + } + } + } + childIndex ++; + } + } + return null; + } else { + debugString ("AccessBridge::getVirtualAccessibleNameFromContext error - ac == null."); + return null; + } + } + + /** + * returns the AccessibleDescription from an AccessibleContext + */ + private String getAccessibleDescriptionFromContext(final AccessibleContext ac) { + if (ac != null) { + String s = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + return ac.getAccessibleDescription(); + } + }, ac); + if (s != null) { + references.increment(s); + debugString("Returning AccessibleDescription from Context: " + s); + return s; + } + } else { + debugString("getAccessibleDescriptionFromContext; ac = null"); + } + return null; + } + + /** + * returns the AccessibleRole from an AccessibleContext + */ + private String getAccessibleRoleStringFromContext(final AccessibleContext ac) { + if (ac != null) { + AccessibleRole role = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleRole call() throws Exception { + return ac.getAccessibleRole(); + } + }, ac); + if (role != null) { + String s = role.toDisplayString(Locale.US); + if (s != null) { + references.increment(s); + debugString("Returning AccessibleRole from Context: " + s); + return s; + } + } + } else { + debugString("getAccessibleRoleStringFromContext; ac = null"); + } + return null; + } + + /** + * return the AccessibleRole from an AccessibleContext in the en_US locale + */ + private String getAccessibleRoleStringFromContext_en_US(final AccessibleContext ac) { + return getAccessibleRoleStringFromContext(ac); + } + + /** + * return the AccessibleStates from an AccessibleContext + */ + private String getAccessibleStatesStringFromContext(final AccessibleContext ac) { + if (ac != null) { + AccessibleStateSet stateSet = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleStateSet call() throws Exception { + return ac.getAccessibleStateSet(); + } + }, ac); + if (stateSet != null) { + String s = stateSet.toString(); + if (s != null && + s.indexOf(AccessibleState.MANAGES_DESCENDANTS.toDisplayString(Locale.US)) == -1) { + // Indicate whether this component manages its own + // children + AccessibleRole role = ac.getAccessibleRole(); + if (role == AccessibleRole.LIST || + role == AccessibleRole.TABLE || + role == AccessibleRole.TREE) { + s += ","; + s += AccessibleState.MANAGES_DESCENDANTS.toDisplayString(Locale.US); + } + references.increment(s); + debugString("Returning AccessibleStateSet from Context: " + s); + return s; + } + } + } else { + debugString("getAccessibleStatesStringFromContext; ac = null"); + } + return null; + } + + /** + * returns the AccessibleStates from an AccessibleContext in the en_US locale + */ + private String getAccessibleStatesStringFromContext_en_US(final AccessibleContext ac) { + if (ac != null) { + AccessibleStateSet stateSet = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleStateSet call() throws Exception { + return ac.getAccessibleStateSet(); + } + }, ac); + if (stateSet != null) { + String s = ""; + AccessibleState[] states = stateSet.toArray(); + if (states != null && states.length > 0) { + s = states[0].toDisplayString(Locale.US); + for (int i = 1; i < states.length; i++) { + s = s + "," + states[i].toDisplayString(Locale.US); + } + } + references.increment(s); + debugString("Returning AccessibleStateSet en_US from Context: " + s); + return s; + } + } + debugString("getAccessibleStatesStringFromContext; ac = null"); + return null; + } + + /** + * returns the AccessibleParent from an AccessibleContext + */ + private AccessibleContext getAccessibleParentFromContext(final AccessibleContext ac) { + if (ac==null) + return null; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + Accessible a = ac.getAccessibleParent(); + if (a != null) { + AccessibleContext apc = a.getAccessibleContext(); + if (apc != null) { + return apc; + } + } + return null; + } + }, ac); + } + + /** + * returns the AccessibleIndexInParent from an AccessibleContext + */ + private int getAccessibleIndexInParentFromContext(final AccessibleContext ac) { + if (ac==null) + return -1; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return ac.getAccessibleIndexInParent(); + } + }, ac); + } + + /** + * returns the AccessibleChild count from an AccessibleContext + */ + private int getAccessibleChildrenCountFromContext(final AccessibleContext ac) { + if (ac==null) + return -1; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return ac.getAccessibleChildrenCount(); + } + }, ac); + } + + /** + * returns the AccessibleChild Context from an AccessibleContext + */ + private AccessibleContext getAccessibleChildFromContext(final AccessibleContext ac, final int index) { + + if (ac == null) { + return null; + } + + final JTable table = InvocationUtils.invokeAndWait(new Callable() { + @Override + public JTable call() throws Exception { + // work-around for AccessibleJTable.getCurrentAccessibleContext returning + // wrong renderer component when cell contains more than one component + Accessible parent = ac.getAccessibleParent(); + if (parent != null) { + int indexInParent = ac.getAccessibleIndexInParent(); + Accessible child = + parent.getAccessibleContext().getAccessibleChild(indexInParent); + if (child instanceof JTable) { + return (JTable) child; + } + } + return null; + } + }, ac); + + if (table == null) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + Accessible a = ac.getAccessibleChild(index); + if (a != null) { + return a.getAccessibleContext(); + } + return null; + } + }, ac); + } + + final AccessibleTable at = getAccessibleTableFromContext(ac); + + final int row = getAccessibleTableRow(at, index); + final int column = getAccessibleTableColumn(at, index); + + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + TableCellRenderer renderer = table.getCellRenderer(row, column); + if (renderer == null) { + Class columnClass = table.getColumnClass(column); + renderer = table.getDefaultRenderer(columnClass); + } + Component component = + renderer.getTableCellRendererComponent(table, table.getValueAt(row, column), + false, false, row, column); + if (component instanceof Accessible) { + return component.getAccessibleContext(); + } + return null; + } + }, ac); + } + + /** + * returns the AccessibleComponent bounds on screen from an AccessibleContext + */ + private Rectangle getAccessibleBoundsOnScreenFromContext(final AccessibleContext ac) { + if(ac==null) + return null; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Rectangle call() throws Exception { + AccessibleComponent acmp = ac.getAccessibleComponent(); + if (acmp != null) { + Rectangle r = acmp.getBounds(); + if (r != null) { + try { + Point p = acmp.getLocationOnScreen(); + if (p != null) { + r.x = p.x; + r.y = p.y; + return r; + } + } catch (Exception e) { + return null; + } + } + } + return null; + } + }, ac); + } + + /** + * returns the AccessibleComponent x-coord from an AccessibleContext + */ + private int getAccessibleXcoordFromContext(AccessibleContext ac) { + if (ac != null) { + Rectangle r = getAccessibleBoundsOnScreenFromContext(ac); + if (r != null) { + debugString(" - Returning Accessible x coord from Context: " + r.x); + return r.x; + } + } else { + debugString("getAccessibleXcoordFromContext ac = null"); + } + return -1; + } + + /** + * returns the AccessibleComponent y-coord from an AccessibleContext + */ + private int getAccessibleYcoordFromContext(AccessibleContext ac) { + debugString("getAccessibleYcoordFromContext() called"); + if (ac != null) { + Rectangle r = getAccessibleBoundsOnScreenFromContext(ac); + if (r != null) { + return r.y; + } + } else { + debugString("getAccessibleYcoordFromContext; ac = null"); + } + return -1; + } + + /** + * returns the AccessibleComponent height from an AccessibleContext + */ + private int getAccessibleHeightFromContext(AccessibleContext ac) { + if (ac != null) { + Rectangle r = getAccessibleBoundsOnScreenFromContext(ac); + if (r != null) { + return r.height; + } + } else { + debugString("getAccessibleHeightFromContext; ac = null"); + } + return -1; + } + + /** + * returns the AccessibleComponent width from an AccessibleContext + */ + private int getAccessibleWidthFromContext(AccessibleContext ac) { + if (ac != null) { + Rectangle r = getAccessibleBoundsOnScreenFromContext(ac); + if (r != null) { + return r.width; + } + } else { + debugString("getAccessibleWidthFromContext; ac = null"); + } + return -1; + } + + + /** + * returns the AccessibleComponent from an AccessibleContext + */ + private AccessibleComponent getAccessibleComponentFromContext(AccessibleContext ac) { + if (ac != null) { + AccessibleComponent acmp = ac.getAccessibleComponent(); + if (acmp != null) { + debugString("Returning AccessibleComponent Context"); + return acmp; + } + } else { + debugString("getAccessibleComponentFromContext; ac = null"); + } + return null; + } + + /** + * returns the AccessibleAction from an AccessibleContext + */ + private AccessibleAction getAccessibleActionFromContext(final AccessibleContext ac) { + debugString("Returning AccessibleAction Context"); + return ac == null ? null : InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleAction call() throws Exception { + return ac.getAccessibleAction(); + } + }, ac); + } + + /** + * returns the AccessibleSelection from an AccessibleContext + */ + private AccessibleSelection getAccessibleSelectionFromContext(final AccessibleContext ac) { + return ac == null ? null : InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleSelection call() throws Exception { + return ac.getAccessibleSelection(); + } + }, ac); + } + + /** + * return the AccessibleText from an AccessibleContext + */ + private AccessibleText getAccessibleTextFromContext(final AccessibleContext ac) { + return ac == null ? null : InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleText call() throws Exception { + return ac.getAccessibleText(); + } + }, ac); + } + + /** + * return the AccessibleComponent from an AccessibleContext + */ + private AccessibleValue getAccessibleValueFromContext(final AccessibleContext ac) { + return ac == null ? null : InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleValue call() throws Exception { + return ac.getAccessibleValue(); + } + }, ac); + } + + /* ===== AccessibleText methods ===== */ + + /** + * returns the bounding rectangle for the text cursor + * XXX + */ + private Rectangle getCaretLocation(final AccessibleContext ac) { + debugString("getCaretLocation"); + if (ac==null) + return null; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Rectangle call() throws Exception { + // workaround for JAAPI not returning cursor bounding rectangle + Rectangle r = null; + Accessible parent = ac.getAccessibleParent(); + if (parent instanceof Accessible) { + int indexInParent = ac.getAccessibleIndexInParent(); + Accessible child = + parent.getAccessibleContext().getAccessibleChild(indexInParent); + + if (child instanceof JTextComponent) { + JTextComponent text = (JTextComponent) child; + try { + r = text.modelToView(text.getCaretPosition()); + if (r != null) { + Point p = text.getLocationOnScreen(); + r.translate(p.x, p.y); + } + } catch (BadLocationException ble) { + } + } + } + return r; + } + }, ac); + } + + /** + * returns the x-coordinate for the text cursor rectangle + */ + private int getCaretLocationX(AccessibleContext ac) { + Rectangle r = getCaretLocation(ac); + if (r != null) { + return r.x; + } else { + return -1; + } + } + + /** + * returns the y-coordinate for the text cursor rectangle + */ + private int getCaretLocationY(AccessibleContext ac) { + Rectangle r = getCaretLocation(ac); + if (r != null) { + return r.y; + } else { + return -1; + } + } + + /** + * returns the height for the text cursor rectangle + */ + private int getCaretLocationHeight(AccessibleContext ac) { + Rectangle r = getCaretLocation(ac); + if (r != null) { + return r.height; + } else { + return -1; + } + } + + /** + * returns the width for the text cursor rectangle + */ + private int getCaretLocationWidth(AccessibleContext ac) { + Rectangle r = getCaretLocation(ac); + if (r != null) { + return r.width; + } else { + return -1; + } + } + + /** + * returns the character count from an AccessibleContext + */ + private int getAccessibleCharCountFromContext(final AccessibleContext ac) { + if (ac==null) + return -1; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (at != null) { + return at.getCharCount(); + } + return -1; + } + }, ac); + } + + /** + * returns the caret position from an AccessibleContext + */ + private int getAccessibleCaretPositionFromContext(final AccessibleContext ac) { + if (ac==null) + return -1; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (at != null) { + return at.getCaretPosition(); + } + return -1; + } + }, ac); + } + + /** + * Return the index at a specific point from an AccessibleContext + * Point(x, y) is in screen coordinates. + */ + private int getAccessibleIndexAtPointFromContext(final AccessibleContext ac, + final int x, final int y) { + debugString("getAccessibleIndexAtPointFromContext: x = "+x+"; y = "+y); + if (ac==null) + return -1; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + AccessibleComponent acomp = ac.getAccessibleComponent(); + if (at != null && acomp != null) { + // Convert x and y from screen coordinates to + // local coordinates. + try { + Point p = acomp.getLocationOnScreen(); + int x1, y1; + if (p != null) { + x1 = x - p.x; + if (x1 < 0) { + x1 = 0; + } + y1 = y - p.y; + if (y1 < 0) { + y1 = 0; + } + + Point newPoint = new Point(x1, y1); + int indexAtPoint = at.getIndexAtPoint(new Point(x1, y1)); + return indexAtPoint; + } + } catch (Exception e) { + } + } + return -1; + } + }, ac); + } + + /** + * return the letter at a specific point from an AccessibleContext + */ + private String getAccessibleLetterAtIndexFromContext(final AccessibleContext ac, final int index) { + if (ac != null) { + String s = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (at == null) return null; + return at.getAtIndex(AccessibleText.CHARACTER, index); + } + }, ac); + if (s != null) { + references.increment(s); + return s; + } + } else { + debugString("getAccessibleLetterAtIndexFromContext; ac = null"); + } + return null; + } + + /** + * return the word at a specific point from an AccessibleContext + */ + private String getAccessibleWordAtIndexFromContext(final AccessibleContext ac, final int index) { + if (ac != null) { + String s = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (at == null) return null; + return at.getAtIndex(AccessibleText.WORD, index); + } + }, ac); + if (s != null) { + references.increment(s); + return s; + } + } else { + debugString("getAccessibleWordAtIndexFromContext; ac = null"); + } + return null; + } + + /** + * return the sentence at a specific point from an AccessibleContext + */ + private String getAccessibleSentenceAtIndexFromContext(final AccessibleContext ac, final int index) { + if (ac != null) { + String s = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (at == null) return null; + return at.getAtIndex(AccessibleText.SENTENCE, index); + } + }, ac); + if (s != null) { + references.increment(s); + return s; + } + } else { + debugString("getAccessibleSentenceAtIndexFromContext; ac = null"); + } + return null; + } + + /** + * return the text selection start from an AccessibleContext + */ + private int getAccessibleTextSelectionStartFromContext(final AccessibleContext ac) { + if (ac == null) return -1; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (at != null) { + return at.getSelectionStart(); + } + return -1; + } + }, ac); + } + + /** + * return the text selection end from an AccessibleContext + */ + private int getAccessibleTextSelectionEndFromContext(final AccessibleContext ac) { + if (ac == null) + return -1; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (at != null) { + return at.getSelectionEnd(); + } + return -1; + } + }, ac); + } + + /** + * return the selected text from an AccessibleContext + */ + private String getAccessibleTextSelectedTextFromContext(final AccessibleContext ac) { + if (ac != null) { + String s = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (at == null) return null; + return at.getSelectedText(); + } + }, ac); + if (s != null) { + references.increment(s); + return s; + } + } else { + debugString("getAccessibleTextSelectedTextFromContext; ac = null"); + } + return null; + } + + /** + * return the attribute string at a given index from an AccessibleContext + */ + private String getAccessibleAttributesAtIndexFromContext(final AccessibleContext ac, + final int index) { + if (ac == null) + return null; + AttributeSet as = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AttributeSet call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (at != null) { + return at.getCharacterAttribute(index); + } + return null; + } + }, ac); + String s = expandStyleConstants(as); + if (s != null) { + references.increment(s); + return s; + } + return null; + } + + /** + * Get line info: left index of line + * + * algorithm: cast back, doubling each time, + * 'till find line boundaries + * + * return -1 if we can't get the info (e.g. index or at passed in + * is bogus; etc.) + */ + private int getAccessibleTextLineLeftBoundsFromContext(final AccessibleContext ac, + final int index) { + if (ac == null) + return -1; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (at != null) { + int lineStart; + int offset; + Rectangle charRect; + Rectangle indexRect = at.getCharacterBounds(index); + int textLen = at.getCharCount(); + if (indexRect == null) { + return -1; + } + // find the start of the line + // + offset = 1; + lineStart = index - offset < 0 ? 0 : index - offset; + charRect = at.getCharacterBounds(lineStart); + // slouch behind beginning of line + while (charRect != null + && charRect.y >= indexRect.y + && lineStart > 0) { + offset = offset << 1; + lineStart = index - offset < 0 ? 0 : index - offset; + charRect = at.getCharacterBounds(lineStart); + } + if (lineStart == 0) { // special case: we're on the first line! + // we found it! + } else { + offset = offset >> 1; // know boundary within last expansion + // ground forward to beginning of line + while (offset > 0) { + charRect = at.getCharacterBounds(lineStart + offset); + if (charRect.y < indexRect.y) { // still before line + lineStart += offset; + } else { + // leave lineStart alone, it's close! + } + offset = offset >> 1; + } + // subtract one 'cause we're already too far... + lineStart += 1; + } + return lineStart; + } + return -1; + } + }, ac); + } + + /** + * Get line info: right index of line + * + * algorithm: cast back, doubling each time, + * 'till find line boundaries + * + * return -1 if we can't get the info (e.g. index or at passed in + * is bogus; etc.) + */ + private int getAccessibleTextLineRightBoundsFromContext(final AccessibleContext ac, final int index) { + if(ac == null) + return -1; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (at != null) { + int lineEnd; + int offset; + Rectangle charRect; + Rectangle indexRect = at.getCharacterBounds(index); + int textLen = at.getCharCount(); + if (indexRect == null) { + return -1; + } + // find the end of the line + // + offset = 1; + lineEnd = index + offset > textLen - 1 + ? textLen - 1 : index + offset; + charRect = at.getCharacterBounds(lineEnd); + // push past end of line + while (charRect != null && + charRect.y <= indexRect.y && + lineEnd < textLen - 1) { + offset = offset << 1; + lineEnd = index + offset > textLen - 1 + ? textLen - 1 : index + offset; + charRect = at.getCharacterBounds(lineEnd); + } + if (lineEnd == textLen - 1) { // special case: on the last line! + // we found it! + } else { + offset = offset >> 1; // know boundary within last expansion + // pull back to end of line + while (offset > 0) { + charRect = at.getCharacterBounds(lineEnd - offset); + if (charRect.y > indexRect.y) { // still beyond line + lineEnd -= offset; + } else { + // leave lineEnd alone, it's close! + } + offset = offset >> 1; + } + // subtract one 'cause we're already too far... + lineEnd -= 1; + } + return lineEnd; + } + return -1; + } + }, ac); + } + + /** + * Get a range of text; null if indicies are bogus + */ + private String getAccessibleTextRangeFromContext(final AccessibleContext ac, + final int start, final int end) { + String s = InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + if (ac != null) { + AccessibleText at = ac.getAccessibleText(); + if (at != null) { + // start - end is inclusive + if (start > end) { + return null; + } + if (end >= at.getCharCount()) { + return null; + } + StringBuffer buf = new StringBuffer(end - start + 1); + for (int i = start; i <= end; i++) { + buf.append(at.getAtIndex(AccessibleText.CHARACTER, i)); + } + return buf.toString(); + } + } + return null; + } + }, ac); + if (s != null) { + references.increment(s); + return s; + } else { + return null; + } + } + + /** + * return the AttributeSet object at a given index from an AccessibleContext + */ + private AttributeSet getAccessibleAttributeSetAtIndexFromContext(final AccessibleContext ac, + final int index) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AttributeSet call() throws Exception { + if (ac != null) { + AccessibleText at = ac.getAccessibleText(); + if (at != null) { + AttributeSet as = at.getCharacterAttribute(index); + if (as != null) { + AccessBridge.this.references.increment(as); + return as; + } + } + } + return null; + } + }, ac); + } + + + /** + * return the bounding rectangle at index from an AccessibleContext + */ + private Rectangle getAccessibleTextRectAtIndexFromContext(final AccessibleContext ac, + final int index) { + // want to do this in global coords, so need to combine w/ac global coords + Rectangle r = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Rectangle call() throws Exception { + // want to do this in global coords, so need to combine w/ac global coords + if (ac != null) { + AccessibleText at = ac.getAccessibleText(); + if (at != null) { + Rectangle rect = at.getCharacterBounds(index); + if (rect != null) { + String s = at.getAtIndex(AccessibleText.CHARACTER, index); + if (s != null && s.equals("\n")) { + rect.width = 0; + } + return rect; + } + } + } + return null; + } + }, ac); + Rectangle acRect = getAccessibleBoundsOnScreenFromContext(ac); + if (r != null && acRect != null) { + r.translate(acRect.x, acRect.y); + return r; + } + return null; + } + + /** + * return the AccessibleText character x-coord at index from an AccessibleContext + */ + private int getAccessibleXcoordTextRectAtIndexFromContext(AccessibleContext ac, int index) { + if (ac != null) { + Rectangle r = getAccessibleTextRectAtIndexFromContext(ac, index); + if (r != null) { + return r.x; + } + } else { + debugString("getAccessibleXcoordTextRectAtIndexFromContext; ac = null"); + } + return -1; + } + + /** + * return the AccessibleText character y-coord at index from an AccessibleContext + */ + private int getAccessibleYcoordTextRectAtIndexFromContext(AccessibleContext ac, int index) { + if (ac != null) { + Rectangle r = getAccessibleTextRectAtIndexFromContext(ac, index); + if (r != null) { + return r.y; + } + } else { + debugString("getAccessibleYcoordTextRectAtIndexFromContext; ac = null"); + } + return -1; + } + + /** + * return the AccessibleText character height at index from an AccessibleContext + */ + private int getAccessibleHeightTextRectAtIndexFromContext(AccessibleContext ac, int index) { + if (ac != null) { + Rectangle r = getAccessibleTextRectAtIndexFromContext(ac, index); + if (r != null) { + return r.height; + } + } else { + debugString("getAccessibleHeightTextRectAtIndexFromContext; ac = null"); + } + return -1; + } + + /** + * return the AccessibleText character width at index from an AccessibleContext + */ + private int getAccessibleWidthTextRectAtIndexFromContext(AccessibleContext ac, int index) { + if (ac != null) { + Rectangle r = getAccessibleTextRectAtIndexFromContext(ac, index); + if (r != null) { + return r.width; + } + } else { + debugString("getAccessibleWidthTextRectAtIndexFromContext; ac = null"); + } + return -1; + } + + /* ===== AttributeSet methods for AccessibleText ===== */ + + /** + * return the bold setting from an AttributeSet + */ + private boolean getBoldFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.isBold(as); + } else { + debugString("getBoldFromAttributeSet; as = null"); + } + return false; + } + + /** + * return the italic setting from an AttributeSet + */ + private boolean getItalicFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.isItalic(as); + } else { + debugString("getItalicFromAttributeSet; as = null"); + } + return false; + } + + /** + * return the underline setting from an AttributeSet + */ + private boolean getUnderlineFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.isUnderline(as); + } else { + debugString("getUnderlineFromAttributeSet; as = null"); + } + return false; + } + + /** + * return the strikethrough setting from an AttributeSet + */ + private boolean getStrikethroughFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.isStrikeThrough(as); + } else { + debugString("getStrikethroughFromAttributeSet; as = null"); + } + return false; + } + + /** + * return the superscript setting from an AttributeSet + */ + private boolean getSuperscriptFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.isSuperscript(as); + } else { + debugString("getSuperscriptFromAttributeSet; as = null"); + } + return false; + } + + /** + * return the subscript setting from an AttributeSet + */ + private boolean getSubscriptFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.isSubscript(as); + } else { + debugString("getSubscriptFromAttributeSet; as = null"); + } + return false; + } + + /** + * return the background color from an AttributeSet + */ + private String getBackgroundColorFromAttributeSet(AttributeSet as) { + if (as != null) { + String s = StyleConstants.getBackground(as).toString(); + if (s != null) { + references.increment(s); + return s; + } + } else { + debugString("getBackgroundColorFromAttributeSet; as = null"); + } + return null; + } + + /** + * return the foreground color from an AttributeSet + */ + private String getForegroundColorFromAttributeSet(AttributeSet as) { + if (as != null) { + String s = StyleConstants.getForeground(as).toString(); + if (s != null) { + references.increment(s); + return s; + } + } else { + debugString("getForegroundColorFromAttributeSet; as = null"); + } + return null; + } + + /** + * return the font family from an AttributeSet + */ + private String getFontFamilyFromAttributeSet(AttributeSet as) { + if (as != null) { + String s = StyleConstants.getFontFamily(as).toString(); + if (s != null) { + references.increment(s); + return s; + } + } else { + debugString("getFontFamilyFromAttributeSet; as = null"); + } + return null; + } + + /** + * return the font size from an AttributeSet + */ + private int getFontSizeFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.getFontSize(as); + } else { + debugString("getFontSizeFromAttributeSet; as = null"); + } + return -1; + } + + /** + * return the alignment from an AttributeSet + */ + private int getAlignmentFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.getAlignment(as); + } else { + debugString("getAlignmentFromAttributeSet; as = null"); + } + return -1; + } + + /** + * return the BiDi level from an AttributeSet + */ + private int getBidiLevelFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.getBidiLevel(as); + } else { + debugString("getBidiLevelFromAttributeSet; as = null"); + } + return -1; + } + + + /** + * return the first line indent from an AttributeSet + */ + private float getFirstLineIndentFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.getFirstLineIndent(as); + } else { + debugString("getFirstLineIndentFromAttributeSet; as = null"); + } + return -1; + } + + /** + * return the left indent from an AttributeSet + */ + private float getLeftIndentFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.getLeftIndent(as); + } else { + debugString("getLeftIndentFromAttributeSet; as = null"); + } + return -1; + } + + /** + * return the right indent from an AttributeSet + */ + private float getRightIndentFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.getRightIndent(as); + } else { + debugString("getRightIndentFromAttributeSet; as = null"); + } + return -1; + } + + /** + * return the line spacing from an AttributeSet + */ + private float getLineSpacingFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.getLineSpacing(as); + } else { + debugString("getLineSpacingFromAttributeSet; as = null"); + } + return -1; + } + + /** + * return the space above from an AttributeSet + */ + private float getSpaceAboveFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.getSpaceAbove(as); + } else { + debugString("getSpaceAboveFromAttributeSet; as = null"); + } + return -1; + } + + /** + * return the space below from an AttributeSet + */ + private float getSpaceBelowFromAttributeSet(AttributeSet as) { + if (as != null) { + return StyleConstants.getSpaceBelow(as); + } else { + debugString("getSpaceBelowFromAttributeSet; as = null"); + } + return -1; + } + + /** + * Enumerate all StyleConstants in the AttributeSet + * + * We need to check explicitly, 'cause of the HTML package conversion + * mechanism (they may not be stored as StyleConstants, just translated + * to them when asked). + * + * (Use convenience methods where they are defined...) + * + * Not checking the following (which the IBM SNS guidelines says + * should be defined): + * - ComponentElementName + * - IconElementName + * - NameAttribute + * - ResolveAttribute + */ + private String expandStyleConstants(AttributeSet as) { + Color c; + Object o; + String attrString = ""; + + // ---------- check for various Character Constants + + attrString += "BidiLevel = " + StyleConstants.getBidiLevel(as); + + final Component comp = StyleConstants.getComponent(as); + if (comp != null) { + if (comp instanceof Accessible) { + final AccessibleContext ac = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return comp.getAccessibleContext(); + } + }, comp); + if (ac != null) { + attrString += "; Accessible Component = " + InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + return ac.getAccessibleName(); + } + }, ac); + } else { + attrString += "; Innaccessible Component = " + comp; + } + } else { + attrString += "; Innaccessible Component = " + comp; + } + } + + Icon i = StyleConstants.getIcon(as); + if (i != null) { + if (i instanceof ImageIcon) { + attrString += "; ImageIcon = " + ((ImageIcon) i).getDescription(); + } else { + attrString += "; Icon = " + i; + } + } + + attrString += "; FontFamily = " + StyleConstants.getFontFamily(as); + + attrString += "; FontSize = " + StyleConstants.getFontSize(as); + + if (StyleConstants.isBold(as)) { + attrString += "; bold"; + } + + if (StyleConstants.isItalic(as)) { + attrString += "; italic"; + } + + if (StyleConstants.isUnderline(as)) { + attrString += "; underline"; + } + + if (StyleConstants.isStrikeThrough(as)) { + attrString += "; strikethrough"; + } + + if (StyleConstants.isSuperscript(as)) { + attrString += "; superscript"; + } + + if (StyleConstants.isSubscript(as)) { + attrString += "; subscript"; + } + + c = StyleConstants.getForeground(as); + if (c != null) { + attrString += "; Foreground = " + c; + } + + c = StyleConstants.getBackground(as); + if (c != null) { + attrString += "; Background = " + c; + } + + attrString += "; FirstLineIndent = " + StyleConstants.getFirstLineIndent(as); + + attrString += "; RightIndent = " + StyleConstants.getRightIndent(as); + + attrString += "; LeftIndent = " + StyleConstants.getLeftIndent(as); + + attrString += "; LineSpacing = " + StyleConstants.getLineSpacing(as); + + attrString += "; SpaceAbove = " + StyleConstants.getSpaceAbove(as); + + attrString += "; SpaceBelow = " + StyleConstants.getSpaceBelow(as); + + attrString += "; Alignment = " + StyleConstants.getAlignment(as); + + TabSet ts = StyleConstants.getTabSet(as); + if (ts != null) { + attrString += "; TabSet = " + ts; + } + + return attrString; + } + + + /* ===== AccessibleValue methods ===== */ + + /** + * return the AccessibleValue current value from an AccessibleContext + * returned using a String 'cause the value is a java Number + * + */ + private String getCurrentAccessibleValueFromContext(final AccessibleContext ac) { + if (ac != null) { + final Number value = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Number call() throws Exception { + AccessibleValue av = ac.getAccessibleValue(); + if (av == null) return null; + return av.getCurrentAccessibleValue(); + } + }, ac); + if (value != null) { + String s = value.toString(); + if (s != null) { + references.increment(s); + return s; + } + } + } else { + debugString("getCurrentAccessibleValueFromContext; ac = null"); + } + return null; + } + + /** + * return the AccessibleValue maximum value from an AccessibleContext + * returned using a String 'cause the value is a java Number + * + */ + private String getMaximumAccessibleValueFromContext(final AccessibleContext ac) { + if (ac != null) { + final Number value = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Number call() throws Exception { + AccessibleValue av = ac.getAccessibleValue(); + if (av == null) return null; + return av.getMaximumAccessibleValue(); + } + }, ac); + if (value != null) { + String s = value.toString(); + if (s != null) { + references.increment(s); + return s; + } + } + } else { + debugString("getMaximumAccessibleValueFromContext; ac = null"); + } + return null; + } + + /** + * return the AccessibleValue minimum value from an AccessibleContext + * returned using a String 'cause the value is a java Number + * + */ + private String getMinimumAccessibleValueFromContext(final AccessibleContext ac) { + if (ac != null) { + final Number value = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Number call() throws Exception { + AccessibleValue av = ac.getAccessibleValue(); + if (av == null) return null; + return av.getMinimumAccessibleValue(); + } + }, ac); + if (value != null) { + String s = value.toString(); + if (s != null) { + references.increment(s); + return s; + } + } + } else { + debugString("getMinimumAccessibleValueFromContext; ac = null"); + } + return null; + } + + + /* ===== AccessibleSelection methods ===== */ + + /** + * add to the AccessibleSelection of an AccessibleContext child i + * + */ + private void addAccessibleSelectionFromContext(final AccessibleContext ac, final int i) { + try { + InvocationUtils.invokeAndWait(new Callable() { + @Override + public Object call() throws Exception { + if (ac != null) { + AccessibleSelection as = ac.getAccessibleSelection(); + if (as != null) { + as.addAccessibleSelection(i); + } + } + return null; + } + }, ac); + } catch(Exception e){} + } + + /** + * clear all of the AccessibleSelection of an AccessibleContex + * + */ + private void clearAccessibleSelectionFromContext(final AccessibleContext ac) { + try { + InvocationUtils.invokeAndWait(new Callable() { + @Override + public Object call() throws Exception { + AccessibleSelection as = ac.getAccessibleSelection(); + if (as != null) { + as.clearAccessibleSelection(); + } + return null; + } + }, ac); + } catch(Exception e){} + + } + + /** + * get the AccessibleContext of the i-th AccessibleSelection of an AccessibleContext + * + */ + private AccessibleContext getAccessibleSelectionFromContext(final AccessibleContext ac, final int i) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + if (ac != null) { + AccessibleSelection as = ac.getAccessibleSelection(); + if (as != null) { + Accessible a = as.getAccessibleSelection(i); + if (a == null) + return null; + else + return a.getAccessibleContext(); + } + } + return null; + } + }, ac); + } + + /** + * get number of things selected in the AccessibleSelection of an AccessibleContext + * + */ + private int getAccessibleSelectionCountFromContext(final AccessibleContext ac) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + if (ac != null) { + AccessibleSelection as = ac.getAccessibleSelection(); + if (as != null) { + return as.getAccessibleSelectionCount(); + } + } + return -1; + } + }, ac); + } + + /** + * return true if the i-th child of the AccessibleSelection of an AccessibleContext is selected + * + */ + private boolean isAccessibleChildSelectedFromContext(final AccessibleContext ac, final int i) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + if (ac != null) { + AccessibleSelection as = ac.getAccessibleSelection(); + if (as != null) { + return as.isAccessibleChildSelected(i); + } + } + return false; + } + }, ac); + } + + /** + * remove the i-th child from the AccessibleSelection of an AccessibleContext + * + */ + private void removeAccessibleSelectionFromContext(final AccessibleContext ac, final int i) { + InvocationUtils.invokeAndWait(new Callable() { + @Override + public Object call() throws Exception { + if (ac != null) { + AccessibleSelection as = ac.getAccessibleSelection(); + if (as != null) { + as.removeAccessibleSelection(i); + } + } + return null; + } + }, ac); + } + + /** + * select all (if possible) of the children of the AccessibleSelection of an AccessibleContext + * + */ + private void selectAllAccessibleSelectionFromContext(final AccessibleContext ac) { + InvocationUtils.invokeAndWait(new Callable() { + @Override + public Object call() throws Exception { + if (ac != null) { + AccessibleSelection as = ac.getAccessibleSelection(); + if (as != null) { + as.selectAllAccessibleSelection(); + } + } + return null; + } + }, ac); + } + + // ======== AccessibleTable ======== + + ConcurrentHashMap hashtab = new ConcurrentHashMap<>(); + + /** + * returns the AccessibleTable for an AccessibleContext + */ + private AccessibleTable getAccessibleTableFromContext(final AccessibleContext ac) { + String version = getJavaVersionProperty(); + if ((version != null && version.compareTo("1.3") >= 0)) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleTable call() throws Exception { + if (ac != null) { + AccessibleTable at = ac.getAccessibleTable(); + if (at != null) { + AccessBridge.this.hashtab.put(at, ac); + return at; + } + } + return null; + } + }, ac); + } + return null; + } + + + /* + * returns the AccessibleContext that contains an AccessibleTable + */ + private AccessibleContext getContextFromAccessibleTable(AccessibleTable at) { + return hashtab.get(at); + } + + /* + * returns the row count for an AccessibleTable + */ + private int getAccessibleTableRowCount(final AccessibleContext ac) { + debugString("##### getAccessibleTableRowCount"); + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + if (ac != null) { + AccessibleTable at = ac.getAccessibleTable(); + if (at != null) { + return at.getAccessibleRowCount(); + } + } + return -1; + } + }, ac); + } + + /* + * returns the column count for an AccessibleTable + */ + private int getAccessibleTableColumnCount(final AccessibleContext ac) { + debugString("##### getAccessibleTableColumnCount"); + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + if (ac != null) { + AccessibleTable at = ac.getAccessibleTable(); + if (at != null) { + return at.getAccessibleColumnCount(); + } + } + return -1; + } + }, ac); + } + + /* + * returns the AccessibleContext for an AccessibleTable cell + */ + private AccessibleContext getAccessibleTableCellAccessibleContext(final AccessibleTable at, + final int row, final int column) { + debugString("getAccessibleTableCellAccessibleContext: at = "+at.getClass()); + if (at == null) return null; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + if (!(at instanceof AccessibleContext)) { + Accessible a = at.getAccessibleAt(row, column); + if (a != null) { + return a.getAccessibleContext(); + } + } else { + // work-around for AccessibleJTable.getCurrentAccessibleContext returning + // wrong renderer component when cell contains more than one component + AccessibleContext ac = (AccessibleContext) at; + Accessible parent = ac.getAccessibleParent(); + if (parent != null) { + int indexInParent = ac.getAccessibleIndexInParent(); + Accessible child = + parent.getAccessibleContext().getAccessibleChild(indexInParent); + if (child instanceof JTable) { + JTable table = (JTable) child; + + TableCellRenderer renderer = table.getCellRenderer(row, column); + if (renderer == null) { + Class columnClass = table.getColumnClass(column); + renderer = table.getDefaultRenderer(columnClass); + } + Component component = + renderer.getTableCellRendererComponent(table, table.getValueAt(row, column), + false, false, row, column); + if (component instanceof Accessible) { + return component.getAccessibleContext(); + } + } + } + } + return null; + } + }, getContextFromAccessibleTable(at)); + } + + /* + * returns the index of a cell at a given row and column in an AccessibleTable + */ + private int getAccessibleTableCellIndex(final AccessibleTable at, int row, int column) { + debugString("##### getAccessibleTableCellIndex: at="+at); + if (at != null) { + int cellIndex = row * + InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return at.getAccessibleColumnCount(); + } + }, getContextFromAccessibleTable(at)) + + column; + debugString(" ##### getAccessibleTableCellIndex="+cellIndex); + return cellIndex; + } + debugString(" ##### getAccessibleTableCellIndex FAILED"); + return -1; + } + + /* + * returns the row extent of a cell at a given row and column in an AccessibleTable + */ + private int getAccessibleTableCellRowExtent(final AccessibleTable at, final int row, final int column) { + debugString("##### getAccessibleTableCellRowExtent"); + if (at != null) { + int rowExtent = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return at.getAccessibleRowExtentAt(row, column); + } + }, + getContextFromAccessibleTable(at)); + debugString(" ##### getAccessibleTableCellRowExtent="+rowExtent); + return rowExtent; + } + debugString(" ##### getAccessibleTableCellRowExtent FAILED"); + return -1; + } + + /* + * returns the column extent of a cell at a given row and column in an AccessibleTable + */ + private int getAccessibleTableCellColumnExtent(final AccessibleTable at, final int row, final int column) { + debugString("##### getAccessibleTableCellColumnExtent"); + if (at != null) { + int columnExtent = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return at.getAccessibleColumnExtentAt(row, column); + } + }, + getContextFromAccessibleTable(at)); + debugString(" ##### getAccessibleTableCellColumnExtent="+columnExtent); + return columnExtent; + } + debugString(" ##### getAccessibleTableCellColumnExtent FAILED"); + return -1; + } + + /* + * returns whether a cell is selected at a given row and column in an AccessibleTable + */ + private boolean isAccessibleTableCellSelected(final AccessibleTable at, final int row, + final int column) { + debugString("##### isAccessibleTableCellSelected: ["+row+"]["+column+"]"); + if (at == null) + return false; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + boolean isSelected = false; + Accessible a = at.getAccessibleAt(row, column); + if (a != null) { + AccessibleContext ac = a.getAccessibleContext(); + if (ac == null) + return false; + AccessibleStateSet as = ac.getAccessibleStateSet(); + if (as != null) { + isSelected = as.contains(AccessibleState.SELECTED); + } + } + return isSelected; + } + }, getContextFromAccessibleTable(at)); + } + + /* + * returns an AccessibleTable that represents the row header in an + * AccessibleTable + */ + private AccessibleTable getAccessibleTableRowHeader(final AccessibleContext ac) { + debugString(" ##### getAccessibleTableRowHeader called"); + AccessibleTable at = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleTable call() throws Exception { + if (ac != null) { + AccessibleTable at = ac.getAccessibleTable(); + if (at != null) { + return at.getAccessibleRowHeader(); + } + } + return null; + } + }, ac); + if (at != null) { + hashtab.put(at, ac); + } + return at; + } + + /* + * returns an AccessibleTable that represents the column header in an + * AccessibleTable + */ + private AccessibleTable getAccessibleTableColumnHeader(final AccessibleContext ac) { + debugString("##### getAccessibleTableColumnHeader"); + if (ac == null) + return null; + AccessibleTable at = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleTable call() throws Exception { + // workaround for getAccessibleColumnHeader NPE + // when the table header is null + Accessible parent = ac.getAccessibleParent(); + if (parent != null) { + int indexInParent = ac.getAccessibleIndexInParent(); + Accessible child = + parent.getAccessibleContext().getAccessibleChild(indexInParent); + if (child instanceof JTable) { + JTable table = (JTable) child; + if (table.getTableHeader() == null) { + return null; + } + } + } + AccessibleTable at = ac.getAccessibleTable(); + if (at != null) { + return at.getAccessibleColumnHeader(); + } + return null; + } + }, ac); + if (at != null) { + hashtab.put(at, ac); + } + return at; + } + + /* + * returns the number of row headers in an AccessibleTable that represents + * the row header in an AccessibleTable + */ + private int getAccessibleTableRowHeaderRowCount(AccessibleContext ac) { + + debugString(" ##### getAccessibleTableRowHeaderRowCount called"); + if (ac != null) { + final AccessibleTable atRowHeader = getAccessibleTableRowHeader(ac); + if (atRowHeader != null) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + if (atRowHeader != null) { + return atRowHeader.getAccessibleRowCount(); + } + return -1; + } + }, ac); + } + } + return -1; + } + + /* + * returns the number of column headers in an AccessibleTable that represents + * the row header in an AccessibleTable + */ + private int getAccessibleTableRowHeaderColumnCount(AccessibleContext ac) { + debugString(" ##### getAccessibleTableRowHeaderColumnCount called"); + if (ac != null) { + final AccessibleTable atRowHeader = getAccessibleTableRowHeader(ac); + if (atRowHeader != null) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + if (atRowHeader != null) { + return atRowHeader.getAccessibleColumnCount(); + } + return -1; + } + }, ac); + } + } + debugString(" ##### getAccessibleTableRowHeaderColumnCount FAILED"); + return -1; + } + + /* + * returns the number of row headers in an AccessibleTable that represents + * the column header in an AccessibleTable + */ + private int getAccessibleTableColumnHeaderRowCount(AccessibleContext ac) { + + debugString("##### getAccessibleTableColumnHeaderRowCount"); + if (ac != null) { + final AccessibleTable atColumnHeader = getAccessibleTableColumnHeader(ac); + if (atColumnHeader != null) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + if (atColumnHeader != null) { + return atColumnHeader.getAccessibleRowCount(); + } + return -1; + } + }, ac); + } + } + debugString(" ##### getAccessibleTableColumnHeaderRowCount FAILED"); + return -1; + } + + /* + * returns the number of column headers in an AccessibleTable that represents + * the column header in an AccessibleTable + */ + private int getAccessibleTableColumnHeaderColumnCount(AccessibleContext ac) { + + debugString("##### getAccessibleTableColumnHeaderColumnCount"); + if (ac != null) { + final AccessibleTable atColumnHeader = getAccessibleTableColumnHeader(ac); + if (atColumnHeader != null) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + if (atColumnHeader != null) { + return atColumnHeader.getAccessibleColumnCount(); + } + return -1; + } + }, ac); + } + } + debugString(" ##### getAccessibleTableColumnHeaderColumnCount FAILED"); + return -1; + } + + /* + * returns the description of a row header in an AccessibleTable + */ + private AccessibleContext getAccessibleTableRowDescription(final AccessibleTable table, + final int row) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + if (table != null) { + Accessible a = table.getAccessibleRowDescription(row); + if (a != null) { + return a.getAccessibleContext(); + } + } + return null; + } + }, getContextFromAccessibleTable(table)); + } + + /* + * returns the description of a column header in an AccessibleTable + */ + private AccessibleContext getAccessibleTableColumnDescription(final AccessibleTable at, + final int column) { + if (at == null) + return null; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + Accessible a = at.getAccessibleColumnDescription(column); + if (a != null) { + return a.getAccessibleContext(); + } + return null; + } + }, getContextFromAccessibleTable(at)); + } + + /* + * returns the number of rows selected in an AccessibleTable + */ + private int getAccessibleTableRowSelectionCount(final AccessibleTable at) { + if (at != null) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + int[] selections = at.getSelectedAccessibleRows(); + if (selections != null) + return selections.length; + else + return -1; + } + }, getContextFromAccessibleTable(at)); + } + return -1; + } + + /* + * returns the row number of the i-th selected row in an AccessibleTable + */ + private int getAccessibleTableRowSelections(final AccessibleTable at, final int i) { + if (at != null) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + int[] selections = at.getSelectedAccessibleRows(); + if (selections.length > i) { + return selections[i]; + } + return -1; + } + }, getContextFromAccessibleTable(at)); + } + return -1; + } + + /* + * returns whether a row is selected in an AccessibleTable + */ + private boolean isAccessibleTableRowSelected(final AccessibleTable at, + final int row) { + if (at == null) + return false; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + return at.isAccessibleRowSelected(row); + } + }, getContextFromAccessibleTable(at)); + } + + /* + * returns whether a column is selected in an AccessibleTable + */ + private boolean isAccessibleTableColumnSelected(final AccessibleTable at, + final int column) { + if (at == null) + return false; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + return at.isAccessibleColumnSelected(column); + } + }, getContextFromAccessibleTable(at)); + } + + /* + * returns the number of columns selected in an AccessibleTable + */ + private int getAccessibleTableColumnSelectionCount(final AccessibleTable at) { + if (at == null) + return -1; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + int[] selections = at.getSelectedAccessibleColumns(); + if (selections != null) + return selections.length; + else + return -1; + } + }, getContextFromAccessibleTable(at)); + } + + /* + * returns the row number of the i-th selected row in an AccessibleTable + */ + private int getAccessibleTableColumnSelections(final AccessibleTable at, final int i) { + if (at == null) + return -1; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + int[] selections = at.getSelectedAccessibleColumns(); + if (selections != null && selections.length > i) { + return selections[i]; + } + return -1; + } + }, getContextFromAccessibleTable(at)); + } + + /* ===== AccessibleExtendedTable (since 1.4) ===== */ + + /* + * returns the row number for a cell at a given index in an AccessibleTable + */ + private int getAccessibleTableRow(final AccessibleTable at, int index) { + if (at == null) + return -1; + int colCount=InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return at.getAccessibleColumnCount(); + } + }, getContextFromAccessibleTable(at)); + return index / colCount; + } + + /* + * returns the column number for a cell at a given index in an AccessibleTable + */ + private int getAccessibleTableColumn(final AccessibleTable at, int index) { + if (at == null) + return -1; + int colCount=InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return at.getAccessibleColumnCount(); + } + }, getContextFromAccessibleTable(at)); + return index % colCount; + } + + /* + * returns the index for a cell at a given row and column in an + * AccessibleTable + */ + private int getAccessibleTableIndex(final AccessibleTable at, int row, int column) { + if (at == null) + return -1; + int colCount = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return at.getAccessibleColumnCount(); + } + }, getContextFromAccessibleTable(at)); + return row * colCount + column; + } + + // ===== AccessibleRelationSet ===== + + /* + * returns the number of relations in the AccessibleContext's + * AccessibleRelationSet + */ + private int getAccessibleRelationCount(final AccessibleContext ac) { + String version = getJavaVersionProperty(); + if ((version != null && version.compareTo("1.3") >= 0)) { + if (ac != null) { + AccessibleRelationSet ars = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleRelationSet call() throws Exception { + return ac.getAccessibleRelationSet(); + } + }, ac); + if (ars != null) + return ars.size(); + } + } + return 0; + } + + /* + * returns the ith relation key in the AccessibleContext's + * AccessibleRelationSet + */ + private String getAccessibleRelationKey(final AccessibleContext ac, final int i) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + if (ac != null) { + AccessibleRelationSet ars = ac.getAccessibleRelationSet(); + if (ars != null) { + AccessibleRelation[] relations = ars.toArray(); + if (relations != null && i >= 0 && i < relations.length) { + return relations[i].getKey(); + } + } + } + return null; + } + }, ac); + } + + /* + * returns the number of targets in a relation in the AccessibleContext's + * AccessibleRelationSet + */ + private int getAccessibleRelationTargetCount(final AccessibleContext ac, final int i) { + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + if (ac != null) { + AccessibleRelationSet ars = ac.getAccessibleRelationSet(); + if (ars != null) { + AccessibleRelation[] relations = ars.toArray(); + if (relations != null && i >= 0 && i < relations.length) { + Object[] targets = relations[i].getTarget(); + return targets.length; + } + } + } + return -1; + } + }, ac); + } + + /* + * returns the jth target in the ith relation in the AccessibleContext's + * AccessibleRelationSet + */ + private AccessibleContext getAccessibleRelationTarget(final AccessibleContext ac, + final int i, final int j) { + debugString("***** getAccessibleRelationTarget"); + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + if (ac != null) { + AccessibleRelationSet ars = ac.getAccessibleRelationSet(); + if (ars != null) { + AccessibleRelation[] relations = ars.toArray(); + if (relations != null && i >= 0 && i < relations.length) { + Object[] targets = relations[i].getTarget(); + if (targets != null && j >= 0 & j < targets.length) { + Object o = targets[j]; + if (o instanceof Accessible) { + return ((Accessible) o).getAccessibleContext(); + } + } + } + } + } + return null; + } + }, ac); + } + + // ========= AccessibleHypertext ========= + + private Map hyperTextContextMap = new WeakHashMap<>(); + private Map hyperLinkContextMap = new WeakHashMap<>(); + + /* + * Returns the AccessibleHypertext + */ + private AccessibleHypertext getAccessibleHypertext(final AccessibleContext ac) { + debugString("getAccessibleHyperlink"); + if (ac==null) + return null; + AccessibleHypertext hypertext = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleHypertext call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (!(at instanceof AccessibleHypertext)) { + return null; + } + return ((AccessibleHypertext) at); + } + }, ac); + hyperTextContextMap.put(hypertext, ac); + return hypertext; + } + + /* + * Returns the number of AccessibleHyperlinks + */ + private int getAccessibleHyperlinkCount(AccessibleContext ac) { + debugString("getAccessibleHyperlinkCount"); + if (ac == null) { + return 0; + } + final AccessibleHypertext hypertext = getAccessibleHypertext(ac); + if (hypertext == null) { + return 0; + } + //return hypertext.getLinkCount(); + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return hypertext.getLinkCount(); + } + }, ac); + } + + /* + * Returns the hyperlink at the specified index + */ + private AccessibleHyperlink getAccessibleHyperlink(final AccessibleHypertext hypertext, final int i) { + debugString("getAccessibleHyperlink"); + if (hypertext == null) { + return null; + } + AccessibleContext ac = hyperTextContextMap.get(hypertext); + if ( i < 0 || i >= + InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return hypertext.getLinkCount(); + } + }, ac) ) { + return null; + } + AccessibleHyperlink acLink = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleHyperlink call() throws Exception { + AccessibleHyperlink link = hypertext.getLink(i); + if (link == null || (!link.isValid())) { + return null; + } + return link; + } + }, ac); + hyperLinkContextMap.put(acLink, ac); + return acLink; + } + + /* + * Returns the hyperlink object description + */ + private String getAccessibleHyperlinkText(final AccessibleHyperlink link) { + debugString("getAccessibleHyperlinkText"); + if (link == null) { + return null; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + Object o = link.getAccessibleActionDescription(0); + if (o != null) { + return o.toString(); + } + return null; + } + }, hyperLinkContextMap.get(link)); + } + + /* + * Returns the hyperlink URL + */ + private String getAccessibleHyperlinkURL(final AccessibleHyperlink link) { + debugString("getAccessibleHyperlinkURL"); + if (link == null) { + return null; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + Object o = link.getAccessibleActionObject(0); + if (o != null) { + return o.toString(); + } else { + return null; + } + } + }, hyperLinkContextMap.get(link)); + } + + /* + * Returns the start index of the hyperlink text + */ + private int getAccessibleHyperlinkStartIndex(final AccessibleHyperlink link) { + debugString("getAccessibleHyperlinkStartIndex"); + if (link == null) { + return -1; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return link.getStartIndex(); + } + }, hyperLinkContextMap.get(link)); + } + + /* + * Returns the end index of the hyperlink text + */ + private int getAccessibleHyperlinkEndIndex(final AccessibleHyperlink link) { + debugString("getAccessibleHyperlinkEndIndex"); + if (link == null) { + return -1; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return link.getEndIndex(); + } + }, hyperLinkContextMap.get(link)); + } + + /* + * Returns the index into an array of hyperlinks that + * is associated with this character index, or -1 if there + * is no hyperlink associated with this index. + */ + private int getAccessibleHypertextLinkIndex(final AccessibleHypertext hypertext, final int charIndex) { + debugString("getAccessibleHypertextLinkIndex: charIndex = "+charIndex); + if (hypertext == null) { + return -1; + } + int linkIndex = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return hypertext.getLinkIndex(charIndex); + } + }, hyperTextContextMap.get(hypertext)); + debugString("getAccessibleHypertextLinkIndex returning "+linkIndex); + return linkIndex; + } + + /* + * Actives the hyperlink + */ + private boolean activateAccessibleHyperlink(final AccessibleContext ac, + final AccessibleHyperlink link) { + //debugString("activateAccessibleHyperlink: link = "+link.getClass()); + if (link == null) { + return false; + } + boolean retval = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + return link.doAccessibleAction(0); + } + }, ac); + debugString("activateAccessibleHyperlink: returning = "+retval); + return retval; + } + + + // ============ AccessibleKeyBinding ============= + + /* + * returns the component mnemonic + */ + private KeyStroke getMnemonic(final AccessibleContext ac) { + if (ac == null) + return null; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public KeyStroke call() throws Exception { + AccessibleComponent comp = ac.getAccessibleComponent(); + if (!(comp instanceof AccessibleExtendedComponent)) { + return null; + } + AccessibleExtendedComponent aec = (AccessibleExtendedComponent) comp; + if (aec != null) { + AccessibleKeyBinding akb = aec.getAccessibleKeyBinding(); + if (akb != null) { + Object o = akb.getAccessibleKeyBinding(0); + if (o instanceof KeyStroke) { + return (KeyStroke) o; + } + } + } + return null; + } + }, ac); + } + + /* + * returns the JMenuItem accelerator + */ + private KeyStroke getAccelerator(final AccessibleContext ac) { + // workaround for getAccessibleKeyBinding not returning the + // JMenuItem accelerator + if (ac == null) + return null; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public KeyStroke call() throws Exception { + Accessible parent = ac.getAccessibleParent(); + if (parent instanceof Accessible) { + int indexInParent = ac.getAccessibleIndexInParent(); + Accessible child = + parent.getAccessibleContext().getAccessibleChild(indexInParent); + if (child instanceof JMenuItem) { + JMenuItem menuItem = (JMenuItem) child; + if (menuItem == null) + return null; + KeyStroke keyStroke = menuItem.getAccelerator(); + return keyStroke; + } + } + return null; + } + }, ac); + } + + /* + * returns 1-24 to indicate which F key is being used for a shortcut or 0 otherwise + */ + private int fKeyNumber(KeyStroke keyStroke) { + if (keyStroke == null) + return 0; + int fKey = 0; + String keyText = KeyEvent.getKeyText(keyStroke.getKeyCode()); + if (keyText != null && (keyText.length() == 2 || keyText.length() == 3)) { + String prefix = keyText.substring(0, 1); + if (prefix.equals("F")) { + try { + int suffix = Integer.parseInt(keyText.substring(1)); + if (suffix >= 1 && suffix <= 24) { + fKey = suffix; + } + } catch (Exception e) { // ignore NumberFormatException + } + } + } + return fKey; + } + + /* + * returns one of several important control characters or 0 otherwise + */ + private int controlCode(KeyStroke keyStroke) { + if (keyStroke == null) + return 0; + int code = keyStroke.getKeyCode(); + switch (code) { + case KeyEvent.VK_BACK_SPACE: + case KeyEvent.VK_DELETE: + case KeyEvent.VK_DOWN: + case KeyEvent.VK_END: + case KeyEvent.VK_HOME: + case KeyEvent.VK_INSERT: + case KeyEvent.VK_KP_DOWN: + case KeyEvent.VK_KP_LEFT: + case KeyEvent.VK_KP_RIGHT: + case KeyEvent.VK_KP_UP: + case KeyEvent.VK_LEFT: + case KeyEvent.VK_PAGE_DOWN: + case KeyEvent.VK_PAGE_UP: + case KeyEvent.VK_RIGHT: + case KeyEvent.VK_UP: + break; + default: + code = 0; + break; + } + return code; + } + + /* + * returns the KeyStoke character + */ + private char getKeyChar(KeyStroke keyStroke) { + // If the shortcut is an FKey return 1-24 + if (keyStroke == null) + return 0; + int fKey = fKeyNumber(keyStroke); + if (fKey != 0) { + // return 0x00000001 through 0x00000018 + debugString(" Shortcut is: F" + fKey); + return (char)fKey; + } + // If the accelerator is a control character, return it + int keyCode = controlCode(keyStroke); + if (keyCode != 0) { + debugString(" Shortcut is control character: " + Integer.toHexString(keyCode)); + return (char)keyCode; + } + String keyText = KeyEvent.getKeyText(keyStroke.getKeyCode()); + debugString(" Shortcut is: " + keyText); + if (keyText != null || keyText.length() > 0) { + CharSequence seq = keyText.subSequence(0, 1); + if (seq != null || seq.length() > 0) { + return seq.charAt(0); + } + } + return 0; + } + + /* + * returns the KeyStroke modifiers as an int + */ + private int getModifiers(KeyStroke keyStroke) { + if (keyStroke == null) + return 0; + debugString("In AccessBridge.getModifiers"); + // modifiers is a bit strip where bits 0-7 indicate a traditional modifier + // such as Ctrl/Alt/Shift, bit 8 indicates an F key shortcut, and bit 9 indicates + // a control code shortcut such as the delete key. + + int modifiers = 0; + // Is the shortcut an FKey? + if (fKeyNumber(keyStroke) != 0) { + modifiers |= 1 << 8; + } + // Is the shortcut a control code? + if (controlCode(keyStroke) != 0) { + modifiers |= 1 << 9; + } + // The following is needed in order to handle translated modifiers. + // getKeyModifiersText doesn't work because for example in German Strg is + // returned for Ctrl. + + // There can be more than one modifier, e.g. if the modifier is ctrl + shift + B + // the toString text is "shift ctrl pressed B". Need to parse through that. + StringTokenizer st = new StringTokenizer(keyStroke.toString()); + while (st.hasMoreTokens()) { + String text = st.nextToken(); + // Meta+Ctrl+Alt+Shift + // 0-3 are shift, ctrl, meta, alt + // 4-7 are for Solaris workstations (though not being used) + if (text.startsWith("met")) { + debugString(" found meta"); + modifiers |= ActionEvent.META_MASK; + } + if (text.startsWith("ctr")) { + debugString(" found ctrl"); + modifiers |= ActionEvent.CTRL_MASK; + } + if (text.startsWith("alt")) { + debugString(" found alt"); + modifiers |= ActionEvent.ALT_MASK; + } + if (text.startsWith("shi")) { + debugString(" found shift"); + modifiers |= ActionEvent.SHIFT_MASK; + } + } + debugString(" returning modifiers: 0x" + Integer.toHexString(modifiers)); + return modifiers; + } + + /* + * returns the number of key bindings associated with this context + */ + private int getAccessibleKeyBindingsCount(AccessibleContext ac) { + if (ac == null || (! runningOnJDK1_4) ) + return 0; + int count = 0; + + if (getMnemonic(ac) != null) { + count++; + } + if (getAccelerator(ac) != null) { + count++; + } + return count; + } + + /* + * returns the key binding character at the specified index + */ + private char getAccessibleKeyBindingChar(AccessibleContext ac, int index) { + if (ac == null || (! runningOnJDK1_4) ) + return 0; + if((index == 0) && getMnemonic(ac)==null) {// special case when there is no mnemonic + KeyStroke keyStroke = getAccelerator(ac); + if (keyStroke != null) { + return getKeyChar(keyStroke); + } + } + if (index == 0) { // mnemonic + KeyStroke keyStroke = getMnemonic(ac); + if (keyStroke != null) { + return getKeyChar(keyStroke); + } + } else if (index == 1) { // accelerator + KeyStroke keyStroke = getAccelerator(ac); + if (keyStroke != null) { + return getKeyChar(keyStroke); + } + } + return 0; + } + + /* + * returns the key binding modifiers at the specified index + */ + private int getAccessibleKeyBindingModifiers(AccessibleContext ac, int index) { + if (ac == null || (! runningOnJDK1_4) ) + return 0; + if((index == 0) && getMnemonic(ac)==null) {// special case when there is no mnemonic + KeyStroke keyStroke = getAccelerator(ac); + if (keyStroke != null) { + return getModifiers(keyStroke); + } + } + if (index == 0) { // mnemonic + KeyStroke keyStroke = getMnemonic(ac); + if (keyStroke != null) { + return getModifiers(keyStroke); + } + } else if (index == 1) { // accelerator + KeyStroke keyStroke = getAccelerator(ac); + if (keyStroke != null) { + return getModifiers(keyStroke); + } + } + return 0; + } + + // ========== AccessibleIcon ============ + + /* + * return the number of icons associated with this context + */ + private int getAccessibleIconsCount(final AccessibleContext ac) { + debugString("getAccessibleIconsCount"); + if (ac == null) { + return 0; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + AccessibleIcon[] ai = ac.getAccessibleIcon(); + if (ai == null) { + return 0; + } + return ai.length; + } + }, ac); + } + + /* + * return icon description at the specified index + */ + private String getAccessibleIconDescription(final AccessibleContext ac, final int index) { + debugString("getAccessibleIconDescription: index = "+index); + if (ac == null) { + return null; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + AccessibleIcon[] ai = ac.getAccessibleIcon(); + if (ai == null || index < 0 || index >= ai.length) { + return null; + } + return ai[index].getAccessibleIconDescription(); + } + }, ac); + } + + /* + * return icon height at the specified index + */ + private int getAccessibleIconHeight(final AccessibleContext ac, final int index) { + debugString("getAccessibleIconHeight: index = "+index); + if (ac == null) { + return 0; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + AccessibleIcon[] ai = ac.getAccessibleIcon(); + if (ai == null || index < 0 || index >= ai.length) { + return 0; + } + return ai[index].getAccessibleIconHeight(); + } + }, ac); + } + + /* + * return icon width at the specified index + */ + private int getAccessibleIconWidth(final AccessibleContext ac, final int index) { + debugString("getAccessibleIconWidth: index = "+index); + if (ac == null) { + return 0; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + AccessibleIcon[] ai = ac.getAccessibleIcon(); + if (ai == null || index < 0 || index >= ai.length) { + return 0; + } + return ai[index].getAccessibleIconWidth(); + } + }, ac); + } + + // ========= AccessibleAction =========== + + /* + * return the number of icons associated with this context + */ + private int getAccessibleActionsCount(final AccessibleContext ac) { + debugString("getAccessibleActionsCount"); + if (ac == null) { + return 0; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + AccessibleAction aa = ac.getAccessibleAction(); + if (aa == null) + return 0; + return aa.getAccessibleActionCount(); + } + }, ac); + } + + /* + * return icon description at the specified index + */ + private String getAccessibleActionName(final AccessibleContext ac, final int index) { + debugString("getAccessibleActionName: index = "+index); + if (ac == null) { + return null; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + AccessibleAction aa = ac.getAccessibleAction(); + if (aa == null) { + return null; + } + return aa.getAccessibleActionDescription(index); + } + }, ac); + } + /* + * return icon description at the specified index + */ + private boolean doAccessibleActions(final AccessibleContext ac, final String name) { + debugString("doAccessibleActions: action name = "+name); + if (ac == null || name == null) { + return false; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + AccessibleAction aa = ac.getAccessibleAction(); + if (aa == null) { + return false; + } + int index = -1; + int numActions = aa.getAccessibleActionCount(); + for (int i = 0; i < numActions; i++) { + String actionName = aa.getAccessibleActionDescription(i); + if (name.equals(actionName)) { + index = i; + break; + } + } + if (index == -1) { + return false; + } + boolean retval = aa.doAccessibleAction(index); + return retval; + } + }, ac); + } + + /* ===== AT utility methods ===== */ + + /** + * Sets the contents of an AccessibleContext that + * implements AccessibleEditableText with the + * specified text string. + * Returns whether successful. + */ + private boolean setTextContents(final AccessibleContext ac, final String text) { + debugString("setTextContents: ac = "+ac+"; text = "+text); + + if (! (ac instanceof AccessibleEditableText)) { + debugString(" ac not instanceof AccessibleEditableText: "+ac); + return false; + } + if (text == null) { + debugString(" text is null"); + return false; + } + + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + // check whether the text field is editable + AccessibleStateSet ass = ac.getAccessibleStateSet(); + if (!ass.contains(AccessibleState.ENABLED)) { + return false; + } + ((AccessibleEditableText) ac).setTextContents(text); + return true; + } + }, ac); + } + + /** + * Returns the Accessible Context of an Internal Frame object that is + * the ancestor of a given object. If the object is an Internal Frame + * object or an Internal Frame ancestor object was found, returns the + * object's AccessibleContext. + * If there is no ancestor object that has an Accessible Role of + * Internal Frame, returns (AccessibleContext)0. + */ + private AccessibleContext getInternalFrame (AccessibleContext ac) { + return getParentWithRole(ac, AccessibleRole.INTERNAL_FRAME.toString()); + } + + /** + * Returns the Accessible Context for the top level object in + * a Java Window. This is same Accessible Context that is obtained + * from GetAccessibleContextFromHWND for that window. Returns + * (AccessibleContext)0 on error. + */ + private AccessibleContext getTopLevelObject (final AccessibleContext ac) { + debugString("getTopLevelObject; ac = "+ac); + if (ac == null) { + return null; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + if (ac.getAccessibleRole() == AccessibleRole.DIALOG) { + // return the dialog, not the parent window + return ac; + } + + Accessible parent = ac.getAccessibleParent(); + if (parent == null) { + return ac; + } + Accessible tmp = parent; + while (tmp != null && tmp.getAccessibleContext() != null) { + AccessibleContext ac2 = tmp.getAccessibleContext(); + if (ac2 != null && ac2.getAccessibleRole() == AccessibleRole.DIALOG) { + // return the dialog, not the parent window + return ac2; + } + parent = tmp; + tmp = parent.getAccessibleContext().getAccessibleParent(); + } + return parent.getAccessibleContext(); + } + }, ac); + } + + /** + * Returns the parent AccessibleContext that has the specified AccessibleRole. + * Returns null on error or if the AccessibleContext does not exist. + */ + private AccessibleContext getParentWithRole (final AccessibleContext ac, + final String roleName) { + debugString("getParentWithRole; ac = "+ac); + debugString("role = "+roleName); + if (ac == null || roleName == null) { + return null; + } + + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + AccessibleRole role = AccessBridge.this.accessibleRoleMap.get(roleName); + if (role == null) { + return ac; + } + + Accessible parent = ac.getAccessibleParent(); + if (parent == null && ac.getAccessibleRole() == role) { + return ac; + } + + Accessible tmp = parent; + AccessibleContext tmp_ac = null; + + while (tmp != null && (tmp_ac = tmp.getAccessibleContext()) != null) { + AccessibleRole ar = tmp_ac.getAccessibleRole(); + if (ar == role) { + // found + return tmp_ac; + } + parent = tmp; + tmp = parent.getAccessibleContext().getAccessibleParent(); + } + // not found + return null; + } + }, ac); + } + + /** + * Returns the parent AccessibleContext that has the specified AccessibleRole. + * Otherwise, returns the top level object for the Java Window. + * Returns (AccessibleContext)0 on error. + */ + private AccessibleContext getParentWithRoleElseRoot (AccessibleContext ac, + String roleName) { + AccessibleContext retval = getParentWithRole(ac, roleName); + if (retval == null) { + retval = getTopLevelObject(ac); + } + return retval; + } + + /** + * Returns how deep in the object hierarchy a given object is. + * The top most object in the object hierarchy has an object depth of 0. + * Returns -1 on error. + */ + private int getObjectDepth(final AccessibleContext ac) { + debugString("getObjectDepth: ac = "+ac); + + if (ac == null) { + return -1; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + int count = 0; + Accessible parent = ac.getAccessibleParent(); + if (parent == null) { + return count; + } + Accessible tmp = parent; + while (tmp != null && tmp.getAccessibleContext() != null) { + parent = tmp; + tmp = parent.getAccessibleContext().getAccessibleParent(); + count++; + } + return count; + } + }, ac); + } + + /** + * Returns the Accessible Context of the current ActiveDescendent of an object. + * Returns (AccessibleContext)0 on error. + */ + private AccessibleContext getActiveDescendent (final AccessibleContext ac) { + debugString("getActiveDescendent: ac = "+ac); + if (ac == null) { + return null; + } + // workaround for JTree bug where the only possible active + // descendent is the JTree root + final Accessible parent = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Accessible call() throws Exception { + return ac.getAccessibleParent(); + } + }, ac); + + if (parent != null) { + Accessible child = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Accessible call() throws Exception { + int indexInParent = ac.getAccessibleIndexInParent(); + return parent.getAccessibleContext().getAccessibleChild(indexInParent); + } + }, ac); + + if (child instanceof JTree) { + // return the selected node + final JTree tree = (JTree)child; + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + return new AccessibleJTreeNode(tree, + tree.getSelectionPath(), + null); + } + }, child); + } + } + + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + AccessibleSelection as = ac.getAccessibleSelection(); + if (as == null) { + return null; + } + // assume single selection + if (as.getAccessibleSelectionCount() != 1) { + return null; + } + Accessible a = as.getAccessibleSelection(0); + if (a == null) { + return null; + } + return a.getAccessibleContext(); + } + }, ac); + } + + + /** + * Additional methods for Teton + */ + + /** + * Gets the AccessibleName for a component based upon the JAWS algorithm. + * Returns whether successful. + * + * Bug ID 4916682 - Implement JAWS AccessibleName policy + */ + private String getJAWSAccessibleName(final AccessibleContext ac) { + debugString("getJAWSAccessibleName"); + if (ac == null) { + return null; + } + // placeholder + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + return ac.getAccessibleName(); + } + }, ac); + } + + /** + * Request focus for a component. Returns whether successful; + * + * Bug ID 4944757 - requestFocus method needed + */ + private boolean requestFocus(final AccessibleContext ac) { + debugString("requestFocus"); + if (ac == null) { + return false; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + AccessibleComponent acomp = ac.getAccessibleComponent(); + if (acomp == null) { + return false; + } + acomp.requestFocus(); + return ac.getAccessibleStateSet().contains(AccessibleState.FOCUSED); + } + }, ac); + } + + /** + * Selects text between two indices. Selection includes the + * text at the start index and the text at the end index. Returns + * whether successful; + * + * Bug ID 4944758 - selectTextRange method needed + */ + private boolean selectTextRange(final AccessibleContext ac, final int startIndex, final int endIndex) { + debugString("selectTextRange: start = "+startIndex+"; end = "+endIndex); + if (ac == null) { + return false; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (!(at instanceof AccessibleEditableText)) { + return false; + } + ((AccessibleEditableText) at).selectText(startIndex, endIndex); + + boolean result = at.getSelectionStart() == startIndex && + at.getSelectionEnd() == endIndex; + return result; + } + }, ac); + } + + /** + * Set the caret to a text position. Returns whether successful; + * + * Bug ID 4944770 - setCaretPosition method needed + */ + private boolean setCaretPosition(final AccessibleContext ac, final int position) { + debugString("setCaretPosition: position = "+position); + if (ac == null) { + return false; + } + return InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + AccessibleText at = ac.getAccessibleText(); + if (!(at instanceof AccessibleEditableText)) { + return false; + } + ((AccessibleEditableText) at).selectText(position, position); + return at.getCaretPosition() == position; + } + }, ac); + } + + /** + * Gets the number of visible children of an AccessibleContext. + * + * Bug ID 4944762- getVisibleChildren for list-like components needed + */ + private int _visibleChildrenCount; + private AccessibleContext _visibleChild; + private int _currentVisibleIndex; + private boolean _foundVisibleChild; + + private int getVisibleChildrenCount(AccessibleContext ac) { + debugString("getVisibleChildrenCount"); + if (ac == null) { + return -1; + } + _visibleChildrenCount = 0; + _getVisibleChildrenCount(ac); + debugString(" _visibleChildrenCount = "+_visibleChildrenCount); + return _visibleChildrenCount; + } + + /* + * Recursively descends AccessibleContext and gets the number + * of visible children + */ + private void _getVisibleChildrenCount(final AccessibleContext ac) { + if (ac == null) + return; + int numChildren = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return ac.getAccessibleChildrenCount(); + } + }, ac); + for (int i = 0; i < numChildren; i++) { + final int idx = i; + final AccessibleContext ac2 = InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + Accessible a = ac.getAccessibleChild(idx); + if (a != null) + return a.getAccessibleContext(); + else + return null; + } + }, ac); + if ( ac2 == null || + (!InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + return ac2.getAccessibleStateSet().contains(AccessibleState.SHOWING); + } + }, ac)) + ) { + continue; + } + _visibleChildrenCount++; + + if (InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return ac2.getAccessibleChildrenCount(); + } + }, ac) > 0 ) { + _getVisibleChildrenCount(ac2); + } + } + } + + /** + * Gets the visible child of an AccessibleContext at the + * specified index + * + * Bug ID 4944762- getVisibleChildren for list-like components needed + */ + private AccessibleContext getVisibleChild(AccessibleContext ac, int index) { + debugString("getVisibleChild: index = "+index); + if (ac == null) { + return null; + } + _visibleChild = null; + _currentVisibleIndex = 0; + _foundVisibleChild = false; + _getVisibleChild(ac, index); + + if (_visibleChild != null) { + debugString( " getVisibleChild: found child = " + + InvocationUtils.invokeAndWait(new Callable() { + @Override + public String call() throws Exception { + return AccessBridge.this._visibleChild.getAccessibleName(); + } + }, ac) ); + } + return _visibleChild; + } + + /* + * Recursively searchs AccessibleContext and finds the visible component + * at the specified index + */ + private void _getVisibleChild(final AccessibleContext ac, final int index) { + if (_visibleChild != null) { + return; + } + + int numChildren = InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return ac.getAccessibleChildrenCount(); + } + }, ac); + for (int i = 0; i < numChildren; i++) { + final int idx=i; + final AccessibleContext ac2=InvocationUtils.invokeAndWait(new Callable() { + @Override + public AccessibleContext call() throws Exception { + Accessible a = ac.getAccessibleChild(idx); + if (a == null) + return null; + else + return a.getAccessibleContext(); + } + }, ac); + if (ac2 == null || + (!InvocationUtils.invokeAndWait(new Callable() { + @Override + public Boolean call() throws Exception { + return ac2.getAccessibleStateSet().contains(AccessibleState.SHOWING); + } + }, ac))) { + continue; + } + if (!_foundVisibleChild && _currentVisibleIndex == index) { + _visibleChild = ac2; + _foundVisibleChild = true; + return; + } + _currentVisibleIndex++; + + if ( InvocationUtils.invokeAndWait(new Callable() { + @Override + public Integer call() throws Exception { + return ac2.getAccessibleChildrenCount(); + } + }, ac) > 0 ) { + _getVisibleChild(ac2, index); + } + } + } + + + /* ===== Java object memory management code ===== */ + + /** + * Class to track object references to ensure the + * Java VM doesn't garbage collect them + */ + private class ObjectReferences { + + private class Reference { + private int value; + + Reference(int i) { + value = i; + } + + public String toString() { + return ("refCount: " + value); + } + } + + /** + * table object references, to keep 'em from being garbage collected + */ + private ConcurrentHashMap refs; + + /** + * Constructor + */ + ObjectReferences() { + refs = new ConcurrentHashMap<>(4); + } + + /** + * Debugging: dump the contents of ObjectReferences' refs Hashtable + */ + String dump() { + return refs.toString(); + } + + /** + * Increment ref count; set to 1 if we have no references for it + */ + void increment(Object o) { + if (o == null){ + debugString("ObjectReferences::increment - Passed in object is null"); + return; + } + + if (refs.containsKey(o)) { + (refs.get(o)).value++; + } else { + refs.put(o, new Reference(1)); + } + } + + /** + * Decrement ref count; remove if count drops to 0 + */ + void decrement(Object o) { + Reference aRef = refs.get(o); + if (aRef != null) { + aRef.value--; + if (aRef.value == 0) { + refs.remove(o); + } else if (aRef.value < 0) { + debugString("ERROR: decrementing reference count below 0"); + } + } else { + debugString("ERROR: object to decrement not in ObjectReferences table"); + } + } + + } + + /* ===== event handling code ===== */ + + /** + * native method for handling property change events + */ + private native void propertyCaretChange(PropertyChangeEvent e, + AccessibleContext src, + int oldValue, int newValue); + private native void propertyDescriptionChange(PropertyChangeEvent e, + AccessibleContext src, + String oldValue, String newValue); + private native void propertyNameChange(PropertyChangeEvent e, + AccessibleContext src, + String oldValue, String newValue); + private native void propertySelectionChange(PropertyChangeEvent e, + AccessibleContext src); + private native void propertyStateChange(PropertyChangeEvent e, + AccessibleContext src, + String oldValue, String newValue); + private native void propertyTextChange(PropertyChangeEvent e, + AccessibleContext src); + private native void propertyValueChange(PropertyChangeEvent e, + AccessibleContext src, + String oldValue, String newValue); + private native void propertyVisibleDataChange(PropertyChangeEvent e, + AccessibleContext src); + private native void propertyChildChange(PropertyChangeEvent e, + AccessibleContext src, + AccessibleContext oldValue, + AccessibleContext newValue); + private native void propertyActiveDescendentChange(PropertyChangeEvent e, + AccessibleContext src, + AccessibleContext oldValue, + AccessibleContext newValue); + + private native void javaShutdown(); + + /** + * native methods for handling focus events + */ + private native void focusGained(FocusEvent e, AccessibleContext src); + private native void focusLost(FocusEvent e, AccessibleContext src); + + /** + * native method for handling caret events + */ + private native void caretUpdate(CaretEvent e, AccessibleContext src); + + /** + * native methods for handling mouse events + */ + private native void mouseClicked(MouseEvent e, AccessibleContext src); + private native void mouseEntered(MouseEvent e, AccessibleContext src); + private native void mouseExited(MouseEvent e, AccessibleContext src); + private native void mousePressed(MouseEvent e, AccessibleContext src); + private native void mouseReleased(MouseEvent e, AccessibleContext src); + + /** + * native methods for handling menu & popupMenu events + */ + private native void menuCanceled(MenuEvent e, AccessibleContext src); + private native void menuDeselected(MenuEvent e, AccessibleContext src); + private native void menuSelected(MenuEvent e, AccessibleContext src); + private native void popupMenuCanceled(PopupMenuEvent e, AccessibleContext src); + private native void popupMenuWillBecomeInvisible(PopupMenuEvent e, + AccessibleContext src); + private native void popupMenuWillBecomeVisible(PopupMenuEvent e, + AccessibleContext src); + + /* ===== event definitions ===== */ + + private static final long PROPERTY_CHANGE_EVENTS = 1; + private static final long FOCUS_GAINED_EVENTS = 2; + private static final long FOCUS_LOST_EVENTS = 4; + private static final long FOCUS_EVENTS = (FOCUS_GAINED_EVENTS | FOCUS_LOST_EVENTS); + + private static final long CARET_UPATE_EVENTS = 8; + private static final long CARET_EVENTS = CARET_UPATE_EVENTS; + + private static final long MOUSE_CLICKED_EVENTS = 16; + private static final long MOUSE_ENTERED_EVENTS = 32; + private static final long MOUSE_EXITED_EVENTS = 64; + private static final long MOUSE_PRESSED_EVENTS = 128; + private static final long MOUSE_RELEASED_EVENTS = 256; + private static final long MOUSE_EVENTS = (MOUSE_CLICKED_EVENTS | MOUSE_ENTERED_EVENTS | + MOUSE_EXITED_EVENTS | MOUSE_PRESSED_EVENTS | + MOUSE_RELEASED_EVENTS); + + private static final long MENU_CANCELED_EVENTS = 512; + private static final long MENU_DESELECTED_EVENTS = 1024; + private static final long MENU_SELECTED_EVENTS = 2048; + private static final long MENU_EVENTS = (MENU_CANCELED_EVENTS | MENU_DESELECTED_EVENTS | + MENU_SELECTED_EVENTS); + + private static final long POPUPMENU_CANCELED_EVENTS = 4096; + private static final long POPUPMENU_WILL_BECOME_INVISIBLE_EVENTS = 8192; + private static final long POPUPMENU_WILL_BECOME_VISIBLE_EVENTS = 16384; + private static final long POPUPMENU_EVENTS = (POPUPMENU_CANCELED_EVENTS | + POPUPMENU_WILL_BECOME_INVISIBLE_EVENTS | + POPUPMENU_WILL_BECOME_VISIBLE_EVENTS); + + /* These use their own numbering scheme, to ensure sufficient expansion room */ + private static final long PROPERTY_NAME_CHANGE_EVENTS = 1; + private static final long PROPERTY_DESCRIPTION_CHANGE_EVENTS = 2; + private static final long PROPERTY_STATE_CHANGE_EVENTS = 4; + private static final long PROPERTY_VALUE_CHANGE_EVENTS = 8; + private static final long PROPERTY_SELECTION_CHANGE_EVENTS = 16; + private static final long PROPERTY_TEXT_CHANGE_EVENTS = 32; + private static final long PROPERTY_CARET_CHANGE_EVENTS = 64; + private static final long PROPERTY_VISIBLEDATA_CHANGE_EVENTS = 128; + private static final long PROPERTY_CHILD_CHANGE_EVENTS = 256; + private static final long PROPERTY_ACTIVEDESCENDENT_CHANGE_EVENTS = 512; + + + private static final long PROPERTY_EVENTS = (PROPERTY_NAME_CHANGE_EVENTS | + PROPERTY_DESCRIPTION_CHANGE_EVENTS | + PROPERTY_STATE_CHANGE_EVENTS | + PROPERTY_VALUE_CHANGE_EVENTS | + PROPERTY_SELECTION_CHANGE_EVENTS | + PROPERTY_TEXT_CHANGE_EVENTS | + PROPERTY_CARET_CHANGE_EVENTS | + PROPERTY_VISIBLEDATA_CHANGE_EVENTS | + PROPERTY_CHILD_CHANGE_EVENTS | + PROPERTY_ACTIVEDESCENDENT_CHANGE_EVENTS); + + /** + * The EventHandler class listens for Java events and + * forwards them to the AT + */ + private class EventHandler implements PropertyChangeListener, + FocusListener, CaretListener, + MenuListener, PopupMenuListener, + MouseListener, WindowListener, + ChangeListener { + + private AccessBridge accessBridge; + private long javaEventMask = 0; + private long accessibilityEventMask = 0; + + EventHandler(AccessBridge bridge) { + accessBridge = bridge; + + // Register to receive WINDOW_OPENED and WINDOW_CLOSED + // events. Add the event source as a native window + // handler is it implements NativeWindowHandler. + // SwingEventMonitor.addWindowListener(this); + } + + // --------- Event Notification Registration methods + + /** + * Invoked the first time a window is made visible. + */ + public void windowOpened(WindowEvent e) { + // If the window is a NativeWindowHandler, add it. + Object o = null; + if (e != null) + o = e.getSource(); + if (o instanceof NativeWindowHandler) { + addNativeWindowHandler((NativeWindowHandler)o); + } + } + + /** + * Invoked when the user attempts to close the window + * from the window's system menu. If the program does not + * explicitly hide or dispose the window while processing + * this event, the window close operation will be canceled. + */ + public void windowClosing(WindowEvent e) {} + + /** + * Invoked when a window has been closed as the result + * of calling dispose on the window. + */ + public void windowClosed(WindowEvent e) { + // If the window is a NativeWindowHandler, remove it. + Object o = null; + if (e != null) + o = e.getSource(); + if (o instanceof NativeWindowHandler) { + removeNativeWindowHandler((NativeWindowHandler)o); + } + } + + /** + * Invoked when a window is changed from a normal to a + * minimized state. For many platforms, a minimized window + * is displayed as the icon specified in the window's + * iconImage property. + * @see java.awt.Frame#setIconImage + */ + public void windowIconified(WindowEvent e) {} + + /** + * Invoked when a window is changed from a minimized + * to a normal state. + */ + public void windowDeiconified(WindowEvent e) {} + + /** + * Invoked when the Window is set to be the active Window. Only a Frame or + * a Dialog can be the active Window. The native windowing system may + * denote the active Window or its children with special decorations, such + * as a highlighted title bar. The active Window is always either the + * focused Window, or the first Frame or Dialog that is an owner of the + * focused Window. + */ + public void windowActivated(WindowEvent e) {} + + /** + * Invoked when a Window is no longer the active Window. Only a Frame or a + * Dialog can be the active Window. The native windowing system may denote + * the active Window or its children with special decorations, such as a + * highlighted title bar. The active Window is always either the focused + * Window, or the first Frame or Dialog that is an owner of the focused + * Window. + */ + public void windowDeactivated(WindowEvent e) {} + + /** + * Turn on event monitoring for the event type passed in + * If necessary, add the appropriate event listener (if + * no other event of that type is being listened for) + */ + void addJavaEventNotification(long type) { + long newEventMask = javaEventMask | type; + /* + if ( ((javaEventMask & PROPERTY_EVENTS) == 0) && + ((newEventMask & PROPERTY_EVENTS) != 0) ) { + AccessibilityEventMonitor.addPropertyChangeListener(this); + } + */ + if ( ((javaEventMask & FOCUS_EVENTS) == 0) && + ((newEventMask & FOCUS_EVENTS) != 0) ) { + SwingEventMonitor.addFocusListener(this); + } + if ( ((javaEventMask & CARET_EVENTS) == 0) && + ((newEventMask & CARET_EVENTS) != 0) ) { + SwingEventMonitor.addCaretListener(this); + } + if ( ((javaEventMask & MOUSE_EVENTS) == 0) && + ((newEventMask & MOUSE_EVENTS) != 0) ) { + SwingEventMonitor.addMouseListener(this); + } + if ( ((javaEventMask & MENU_EVENTS) == 0) && + ((newEventMask & MENU_EVENTS) != 0) ) { + SwingEventMonitor.addMenuListener(this); + SwingEventMonitor.addPopupMenuListener(this); + } + if ( ((javaEventMask & POPUPMENU_EVENTS) == 0) && + ((newEventMask & POPUPMENU_EVENTS) != 0) ) { + SwingEventMonitor.addPopupMenuListener(this); + } + + javaEventMask = newEventMask; + } + + /** + * Turn off event monitoring for the event type passed in + * If necessary, remove the appropriate event listener (if + * no other event of that type is being listened for) + */ + void removeJavaEventNotification(long type) { + long newEventMask = javaEventMask & (~type); + /* + if ( ((javaEventMask & PROPERTY_EVENTS) != 0) && + ((newEventMask & PROPERTY_EVENTS) == 0) ) { + AccessibilityEventMonitor.removePropertyChangeListener(this); + } + */ + if (((javaEventMask & FOCUS_EVENTS) != 0) && + ((newEventMask & FOCUS_EVENTS) == 0)) { + SwingEventMonitor.removeFocusListener(this); + } + if (((javaEventMask & CARET_EVENTS) != 0) && + ((newEventMask & CARET_EVENTS) == 0)) { + SwingEventMonitor.removeCaretListener(this); + } + if (((javaEventMask & MOUSE_EVENTS) == 0) && + ((newEventMask & MOUSE_EVENTS) != 0)) { + SwingEventMonitor.removeMouseListener(this); + } + if (((javaEventMask & MENU_EVENTS) == 0) && + ((newEventMask & MENU_EVENTS) != 0)) { + SwingEventMonitor.removeMenuListener(this); + } + if (((javaEventMask & POPUPMENU_EVENTS) == 0) && + ((newEventMask & POPUPMENU_EVENTS) != 0)) { + SwingEventMonitor.removePopupMenuListener(this); + } + + javaEventMask = newEventMask; + } + + /** + * Turn on event monitoring for the event type passed in + * If necessary, add the appropriate event listener (if + * no other event of that type is being listened for) + */ + void addAccessibilityEventNotification(long type) { + long newEventMask = accessibilityEventMask | type; + if ( ((accessibilityEventMask & PROPERTY_EVENTS) == 0) && + ((newEventMask & PROPERTY_EVENTS) != 0) ) { + AccessibilityEventMonitor.addPropertyChangeListener(this); + } + accessibilityEventMask = newEventMask; + } + + /** + * Turn off event monitoring for the event type passed in + * If necessary, remove the appropriate event listener (if + * no other event of that type is being listened for) + */ + void removeAccessibilityEventNotification(long type) { + long newEventMask = accessibilityEventMask & (~type); + if ( ((accessibilityEventMask & PROPERTY_EVENTS) != 0) && + ((newEventMask & PROPERTY_EVENTS) == 0) ) { + AccessibilityEventMonitor.removePropertyChangeListener(this); + } + accessibilityEventMask = newEventMask; + } + + /** + * ------- property change event glue + */ + // This is invoked on the EDT , as + public void propertyChange(PropertyChangeEvent e) { + + accessBridge.debugString("propertyChange(" + e.toString() + ") called"); + + if (e != null && (accessibilityEventMask & PROPERTY_EVENTS) != 0) { + Object o = e.getSource(); + AccessibleContext ac; + + if (o instanceof AccessibleContext) { + ac = (AccessibleContext) o; + } else { + Accessible a = Translator.getAccessible(e.getSource()); + if (a == null) + return; + else + ac = a.getAccessibleContext(); + } + if (ac != null) { + InvocationUtils.registerAccessibleContext(ac, AppContext.getAppContext()); + + accessBridge.debugString("AccessibleContext: " + ac); + String propertyName = e.getPropertyName(); + + if (propertyName.compareTo(AccessibleContext.ACCESSIBLE_CARET_PROPERTY) == 0) { + int oldValue = 0; + int newValue = 0; + + if (e.getOldValue() instanceof Integer) { + oldValue = ((Integer) e.getOldValue()).intValue(); + } + if (e.getNewValue() instanceof Integer) { + newValue = ((Integer) e.getNewValue()).intValue(); + } + accessBridge.debugString(" - about to call propertyCaretChange()"); + accessBridge.debugString(" old value: " + oldValue + "new value: " + newValue); + accessBridge.propertyCaretChange(e, ac, oldValue, newValue); + + } else if (propertyName.compareTo(AccessibleContext.ACCESSIBLE_DESCRIPTION_PROPERTY) == 0) { + String oldValue = null; + String newValue = null; + + if (e.getOldValue() != null) { + oldValue = e.getOldValue().toString(); + } + if (e.getNewValue() != null) { + newValue = e.getNewValue().toString(); + } + accessBridge.debugString(" - about to call propertyDescriptionChange()"); + accessBridge.debugString(" old value: " + oldValue + "new value: " + newValue); + accessBridge.propertyDescriptionChange(e, ac, oldValue, newValue); + + } else if (propertyName.compareTo(AccessibleContext.ACCESSIBLE_NAME_PROPERTY) == 0) { + String oldValue = null; + String newValue = null; + + if (e.getOldValue() != null) { + oldValue = e.getOldValue().toString(); + } + if (e.getNewValue() != null) { + newValue = e.getNewValue().toString(); + } + accessBridge.debugString(" - about to call propertyNameChange()"); + accessBridge.debugString(" old value: " + oldValue + " new value: " + newValue); + accessBridge.propertyNameChange(e, ac, oldValue, newValue); + + } else if (propertyName.compareTo(AccessibleContext.ACCESSIBLE_SELECTION_PROPERTY) == 0) { + accessBridge.debugString(" - about to call propertySelectionChange() " + ac + " " + Thread.currentThread() + " " + e.getSource()); + + accessBridge.propertySelectionChange(e, ac); + + } else if (propertyName.compareTo(AccessibleContext.ACCESSIBLE_STATE_PROPERTY) == 0) { + String oldValue = null; + String newValue = null; + + // Localization fix requested by Oliver for EA-1 + if (e.getOldValue() != null) { + AccessibleState oldState = (AccessibleState) e.getOldValue(); + oldValue = oldState.toDisplayString(Locale.US); + } + if (e.getNewValue() != null) { + AccessibleState newState = (AccessibleState) e.getNewValue(); + newValue = newState.toDisplayString(Locale.US); + } + + accessBridge.debugString(" - about to call propertyStateChange()"); + accessBridge.propertyStateChange(e, ac, oldValue, newValue); + + } else if (propertyName.compareTo(AccessibleContext.ACCESSIBLE_TEXT_PROPERTY) == 0) { + accessBridge.debugString(" - about to call propertyTextChange()"); + accessBridge.propertyTextChange(e, ac); + + } else if (propertyName.compareTo(AccessibleContext.ACCESSIBLE_VALUE_PROPERTY) == 0) { // strings 'cause of floating point, etc. + String oldValue = null; + String newValue = null; + + if (e.getOldValue() != null) { + oldValue = e.getOldValue().toString(); + } + if (e.getNewValue() != null) { + newValue = e.getNewValue().toString(); + } + accessBridge.debugString(" - about to call propertyDescriptionChange()"); + accessBridge.propertyValueChange(e, ac, oldValue, newValue); + + } else if (propertyName.compareTo(AccessibleContext.ACCESSIBLE_VISIBLE_DATA_PROPERTY) == 0) { + accessBridge.propertyVisibleDataChange(e, ac); + + } else if (propertyName.compareTo(AccessibleContext.ACCESSIBLE_CHILD_PROPERTY) == 0) { + AccessibleContext oldAC = null; + AccessibleContext newAC = null; + Accessible a; + + if (e.getOldValue() instanceof AccessibleContext) { + oldAC = (AccessibleContext) e.getOldValue(); + InvocationUtils.registerAccessibleContext(oldAC, AppContext.getAppContext()); + } + if (e.getNewValue() instanceof AccessibleContext) { + newAC = (AccessibleContext) e.getNewValue(); + InvocationUtils.registerAccessibleContext(newAC, AppContext.getAppContext()); + } + accessBridge.debugString(" - about to call propertyChildChange()"); + accessBridge.debugString(" old AC: " + oldAC + "new AC: " + newAC); + accessBridge.propertyChildChange(e, ac, oldAC, newAC); + + } else if (propertyName.compareTo(AccessibleContext.ACCESSIBLE_ACTIVE_DESCENDANT_PROPERTY) == 0) { + handleActiveDescendentEvent(e, ac); + } + } + } + } + + /* + * Handle an ActiveDescendent PropertyChangeEvent. This + * method works around a JTree bug where ActiveDescendent + * PropertyChangeEvents have the wrong parent. + */ + private AccessibleContext prevAC = null; // previous AccessibleContext + + private void handleActiveDescendentEvent(PropertyChangeEvent e, + AccessibleContext ac) { + if (e == null || ac == null) + return; + AccessibleContext oldAC = null; + AccessibleContext newAC = null; + Accessible a; + + // get the old active descendent + if (e.getOldValue() instanceof Accessible) { + oldAC = ((Accessible) e.getOldValue()).getAccessibleContext(); + } else if (e.getOldValue() instanceof Component) { + a = Translator.getAccessible(e.getOldValue()); + if (a != null) { + oldAC = a.getAccessibleContext(); + } + } + if (oldAC != null) { + Accessible parent = oldAC.getAccessibleParent(); + if (parent instanceof JTree) { + // use the previous AccessibleJTreeNode + oldAC = prevAC; + } + } + + // get the new active descendent + if (e.getNewValue() instanceof Accessible) { + newAC = ((Accessible) e.getNewValue()).getAccessibleContext(); + } else if (e.getNewValue() instanceof Component) { + a = Translator.getAccessible(e.getNewValue()); + if (a != null) { + newAC = a.getAccessibleContext(); + } + } + if (newAC != null) { + Accessible parent = newAC.getAccessibleParent(); + if (parent instanceof JTree) { + // use a new AccessibleJTreeNode with the right parent + JTree tree = (JTree)parent; + newAC = new AccessibleJTreeNode(tree, + tree.getSelectionPath(), + null); + } + } + prevAC = newAC; + + accessBridge.debugString(" - about to call propertyActiveDescendentChange()"); + accessBridge.debugString(" AC: " + ac); + accessBridge.debugString(" old AC: " + oldAC + "new AC: " + newAC); + + InvocationUtils.registerAccessibleContext(oldAC, AppContext.getAppContext()); + InvocationUtils.registerAccessibleContext(newAC, AppContext.getAppContext()); + accessBridge.propertyActiveDescendentChange(e, ac, oldAC, newAC); + } + + /** + * ------- focus event glue + */ + private boolean stateChangeListenerAdded = false; + + public void focusGained(FocusEvent e) { + if (runningOnJDK1_4) { + processFocusGained(); + } else { + if ((javaEventMask & FOCUS_GAINED_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, SunToolkit.targetToAppContext(e.getSource())); + accessBridge.focusGained(e, context); + } + } + } + } + + public void stateChanged(ChangeEvent e) { + processFocusGained(); + } + + private void processFocusGained() { + Component focusOwner = KeyboardFocusManager. + getCurrentKeyboardFocusManager().getFocusOwner(); + if (focusOwner == null) { + return; + } + + // Only menus and popup selections are handled by the JRootPane. + if (focusOwner instanceof JRootPane) { + MenuElement [] path = + MenuSelectionManager.defaultManager().getSelectedPath(); + if (path.length > 1) { + Component penult = path[path.length-2].getComponent(); + Component last = path[path.length-1].getComponent(); + + if (last instanceof JPopupMenu) { + // This is a popup with nothing in the popup + // selected. The menu itself is selected. + FocusEvent e = new FocusEvent(penult, FocusEvent.FOCUS_GAINED); + AccessibleContext context = penult.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, SunToolkit.targetToAppContext(penult)); + accessBridge.focusGained(e, context); + } else if (penult instanceof JPopupMenu) { + // This is a popup with an item selected + FocusEvent e = + new FocusEvent(last, FocusEvent.FOCUS_GAINED); + accessBridge.debugString(" - about to call focusGained()"); + AccessibleContext focusedAC = last.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(focusedAC, SunToolkit.targetToAppContext(last)); + accessBridge.debugString(" AC: " + focusedAC); + accessBridge.focusGained(e, focusedAC); + } + } + } else { + // The focus owner has the selection. + if (focusOwner instanceof Accessible) { + FocusEvent e = new FocusEvent(focusOwner, + FocusEvent.FOCUS_GAINED); + accessBridge.debugString(" - about to call focusGained()"); + AccessibleContext focusedAC = focusOwner.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(focusedAC, SunToolkit.targetToAppContext(focusOwner)); + accessBridge.debugString(" AC: " + focusedAC); + accessBridge.focusGained(e, focusedAC); + } + } + } + + public void focusLost(FocusEvent e) { + if (e != null && (javaEventMask & FOCUS_LOST_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + accessBridge.debugString(" - about to call focusLost()"); + accessBridge.debugString(" AC: " + a.getAccessibleContext()); + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.focusLost(e, context); + } + } + } + + /** + * ------- caret event glue + */ + public void caretUpdate(CaretEvent e) { + if (e != null && (javaEventMask & CARET_UPATE_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.caretUpdate(e, context); + } + } + } + + /** + * ------- mouse event glue + */ + + public void mouseClicked(MouseEvent e) { + if (e != null && (javaEventMask & MOUSE_CLICKED_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.mouseClicked(e, context); + } + } + } + + public void mouseEntered(MouseEvent e) { + if (e != null && (javaEventMask & MOUSE_ENTERED_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.mouseEntered(e, context); + } + } + } + + public void mouseExited(MouseEvent e) { + if (e != null && (javaEventMask & MOUSE_EXITED_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.mouseExited(e, context); + } + } + } + + public void mousePressed(MouseEvent e) { + if (e != null && (javaEventMask & MOUSE_PRESSED_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.mousePressed(e, context); + } + } + } + + public void mouseReleased(MouseEvent e) { + if (e != null && (javaEventMask & MOUSE_RELEASED_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.mouseReleased(e, context); + } + } + } + + /** + * ------- menu event glue + */ + public void menuCanceled(MenuEvent e) { + if (e != null && (javaEventMask & MENU_CANCELED_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.menuCanceled(e, context); + } + } + } + + public void menuDeselected(MenuEvent e) { + if (e != null && (javaEventMask & MENU_DESELECTED_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.menuDeselected(e, context); + } + } + } + + public void menuSelected(MenuEvent e) { + if (e != null && (javaEventMask & MENU_SELECTED_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.menuSelected(e, context); + } + } + } + + public void popupMenuCanceled(PopupMenuEvent e) { + if (e != null && (javaEventMask & POPUPMENU_CANCELED_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.popupMenuCanceled(e, context); + } + } + } + + public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { + if (e != null && (javaEventMask & POPUPMENU_WILL_BECOME_INVISIBLE_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.popupMenuWillBecomeInvisible(e, context); + } + } + } + + public void popupMenuWillBecomeVisible(PopupMenuEvent e) { + if (e != null && (javaEventMask & POPUPMENU_WILL_BECOME_VISIBLE_EVENTS) != 0) { + Accessible a = Translator.getAccessible(e.getSource()); + if (a != null) { + AccessibleContext context = a.getAccessibleContext(); + InvocationUtils.registerAccessibleContext(context, AppContext.getAppContext()); + accessBridge.popupMenuWillBecomeVisible(e, context); + } + } + } + + } // End of EventHandler Class + + // --------- Event Notification Registration methods + + /** + * Wrapper method around eventHandler.addJavaEventNotification() + */ + private void addJavaEventNotification(final long type) { + EventQueue.invokeLater(new Runnable() { + public void run(){ + eventHandler.addJavaEventNotification(type); + } + }); + } + + /** + * Wrapper method around eventHandler.removeJavaEventNotification() + */ + private void removeJavaEventNotification(final long type) { + EventQueue.invokeLater(new Runnable() { + public void run(){ + eventHandler.removeJavaEventNotification(type); + } + }); + } + + + /** + * Wrapper method around eventHandler.addAccessibilityEventNotification() + */ + private void addAccessibilityEventNotification(final long type) { + EventQueue.invokeLater(new Runnable() { + public void run(){ + eventHandler.addAccessibilityEventNotification(type); + } + }); + } + + /** + * Wrapper method around eventHandler.removeAccessibilityEventNotification() + */ + private void removeAccessibilityEventNotification(final long type) { + EventQueue.invokeLater(new Runnable() { + public void run(){ + eventHandler.removeAccessibilityEventNotification(type); + } + }); + } + + /** + ****************************************************** + * All AccessibleRoles + * + * We shouldn't have to do this since it requires us + * to synchronize the allAccessibleRoles array when + * the AccessibleRoles class interface changes. However, + * there is no Accessibility API method to get all + * AccessibleRoles + ****************************************************** + */ + private AccessibleRole [] allAccessibleRoles = { + /** + * Object is used to alert the user about something. + */ + AccessibleRole.ALERT, + + /** + * The header for a column of data. + */ + AccessibleRole.COLUMN_HEADER, + + /** + * Object that can be drawn into and is used to trap + * events. + * @see #FRAME + * @see #GLASS_PANE + * @see #LAYERED_PANE + */ + AccessibleRole.CANVAS, + + /** + * A list of choices the user can select from. Also optionally + * allows the user to enter a choice of their own. + */ + AccessibleRole.COMBO_BOX, + + /** + * An iconified internal frame in a DESKTOP_PANE. + * @see #DESKTOP_PANE + * @see #INTERNAL_FRAME + */ + AccessibleRole.DESKTOP_ICON, + + /** + * A frame-like object that is clipped by a desktop pane. The + * desktop pane, internal frame, and desktop icon objects are + * often used to create multiple document interfaces within an + * application. + * @see #DESKTOP_ICON + * @see #DESKTOP_PANE + * @see #FRAME + */ + AccessibleRole.INTERNAL_FRAME, + + /** + * A pane that supports internal frames and + * iconified versions of those internal frames. + * @see #DESKTOP_ICON + * @see #INTERNAL_FRAME + */ + AccessibleRole.DESKTOP_PANE, + + /** + * A specialized pane whose primary use is inside a DIALOG + * @see #DIALOG + */ + AccessibleRole.OPTION_PANE, + + /** + * A top level window with no title or border. + * @see #FRAME + * @see #DIALOG + */ + AccessibleRole.WINDOW, + + /** + * A top level window with a title bar, border, menu bar, etc. It is + * often used as the primary window for an application. + * @see #DIALOG + * @see #CANVAS + * @see #WINDOW + */ + AccessibleRole.FRAME, + + /** + * A top level window with title bar and a border. A dialog is similar + * to a frame, but it has fewer properties and is often used as a + * secondary window for an application. + * @see #FRAME + * @see #WINDOW + */ + AccessibleRole.DIALOG, + + /** + * A specialized dialog that lets the user choose a color. + */ + AccessibleRole.COLOR_CHOOSER, + + + /** + * A pane that allows the user to navigate through + * and select the contents of a directory. May be used + * by a file chooser. + * @see #FILE_CHOOSER + */ + AccessibleRole.DIRECTORY_PANE, + + /** + * A specialized dialog that displays the files in the directory + * and lets the user select a file, browse a different directory, + * or specify a filename. May use the directory pane to show the + * contents of a directory. + * @see #DIRECTORY_PANE + */ + AccessibleRole.FILE_CHOOSER, + + /** + * An object that fills up space in a user interface. It is often + * used in interfaces to tweak the spacing between components, + * but serves no other purpose. + */ + AccessibleRole.FILLER, + + /** + * A hypertext anchor + */ + // AccessibleRole.HYPERLINK, + + /** + * A small fixed size picture, typically used to decorate components. + */ + AccessibleRole.ICON, + + /** + * An object used to present an icon or short string in an interface. + */ + AccessibleRole.LABEL, + + /** + * A specialized pane that has a glass pane and a layered pane as its + * children. + * @see #GLASS_PANE + * @see #LAYERED_PANE + */ + AccessibleRole.ROOT_PANE, + + /** + * A pane that is guaranteed to be painted on top + * of all panes beneath it. + * @see #ROOT_PANE + * @see #CANVAS + */ + AccessibleRole.GLASS_PANE, + + /** + * A specialized pane that allows its children to be drawn in layers, + * providing a form of stacking order. This is usually the pane that + * holds the menu bar as well as the pane that contains most of the + * visual components in a window. + * @see #GLASS_PANE + * @see #ROOT_PANE + */ + AccessibleRole.LAYERED_PANE, + + /** + * An object that presents a list of objects to the user and allows the + * user to select one or more of them. A list is usually contained + * within a scroll pane. + * @see #SCROLL_PANE + * @see #LIST_ITEM + */ + AccessibleRole.LIST, + + /** + * An object that presents an element in a list. A list is usually + * contained within a scroll pane. + * @see #SCROLL_PANE + * @see #LIST + */ + AccessibleRole.LIST_ITEM, + + /** + * An object usually drawn at the top of the primary dialog box of + * an application that contains a list of menus the user can choose + * from. For example, a menu bar might contain menus for "File," + * "Edit," and "Help." + * @see #MENU + * @see #POPUP_MENU + * @see #LAYERED_PANE + */ + AccessibleRole.MENU_BAR, + + /** + * A temporary window that is usually used to offer the user a + * list of choices, and then hides when the user selects one of + * those choices. + * @see #MENU + * @see #MENU_ITEM + */ + AccessibleRole.POPUP_MENU, + + /** + * An object usually found inside a menu bar that contains a list + * of actions the user can choose from. A menu can have any object + * as its children, but most often they are menu items, other menus, + * or rudimentary objects such as radio buttons, check boxes, or + * separators. For example, an application may have an "Edit" menu + * that contains menu items for "Cut" and "Paste." + * @see #MENU_BAR + * @see #MENU_ITEM + * @see #SEPARATOR + * @see #RADIO_BUTTON + * @see #CHECK_BOX + * @see #POPUP_MENU + */ + AccessibleRole.MENU, + + /** + * An object usually contained in a menu that presents an action + * the user can choose. For example, the "Cut" menu item in an + * "Edit" menu would be an action the user can select to cut the + * selected area of text in a document. + * @see #MENU_BAR + * @see #SEPARATOR + * @see #POPUP_MENU + */ + AccessibleRole.MENU_ITEM, + + /** + * An object usually contained in a menu to provide a visual + * and logical separation of the contents in a menu. For example, + * the "File" menu of an application might contain menu items for + * "Open," "Close," and "Exit," and will place a separator between + * "Close" and "Exit" menu items. + * @see #MENU + * @see #MENU_ITEM + */ + AccessibleRole.SEPARATOR, + + /** + * An object that presents a series of panels (or page tabs), one at a + * time, through some mechanism provided by the object. The most common + * mechanism is a list of tabs at the top of the panel. The children of + * a page tab list are all page tabs. + * @see #PAGE_TAB + */ + AccessibleRole.PAGE_TAB_LIST, + + /** + * An object that is a child of a page tab list. Its sole child is + * the panel that is to be presented to the user when the user + * selects the page tab from the list of tabs in the page tab list. + * @see #PAGE_TAB_LIST + */ + AccessibleRole.PAGE_TAB, + + /** + * A generic container that is often used to group objects. + */ + AccessibleRole.PANEL, + + /** + * An object used to indicate how much of a task has been completed. + */ + AccessibleRole.PROGRESS_BAR, + + /** + * A text object used for passwords, or other places where the + * text contents is not shown visibly to the user + */ + AccessibleRole.PASSWORD_TEXT, + + /** + * An object the user can manipulate to tell the application to do + * something. + * @see #CHECK_BOX + * @see #TOGGLE_BUTTON + * @see #RADIO_BUTTON + */ + AccessibleRole.PUSH_BUTTON, + + /** + * A specialized push button that can be checked or unchecked, but + * does not provide a separate indicator for the current state. + * @see #PUSH_BUTTON + * @see #CHECK_BOX + * @see #RADIO_BUTTON + */ + AccessibleRole.TOGGLE_BUTTON, + + /** + * A choice that can be checked or unchecked and provides a + * separate indicator for the current state. + * @see #PUSH_BUTTON + * @see #TOGGLE_BUTTON + * @see #RADIO_BUTTON + */ + AccessibleRole.CHECK_BOX, + + /** + * A specialized check box that will cause other radio buttons in the + * same group to become unchecked when this one is checked. + * @see #PUSH_BUTTON + * @see #TOGGLE_BUTTON + * @see #CHECK_BOX + */ + AccessibleRole.RADIO_BUTTON, + + /** + * The header for a row of data. + */ + AccessibleRole.ROW_HEADER, + + /** + * An object that allows a user to incrementally view a large amount + * of information. Its children can include scroll bars and a viewport. + * @see #SCROLL_BAR + * @see #VIEWPORT + */ + AccessibleRole.SCROLL_PANE, + + /** + * An object usually used to allow a user to incrementally view a + * large amount of data. Usually used only by a scroll pane. + * @see #SCROLL_PANE + */ + AccessibleRole.SCROLL_BAR, + + /** + * An object usually used in a scroll pane. It represents the portion + * of the entire data that the user can see. As the user manipulates + * the scroll bars, the contents of the viewport can change. + * @see #SCROLL_PANE + */ + AccessibleRole.VIEWPORT, + + /** + * An object that allows the user to select from a bounded range. For + * example, a slider might be used to select a number between 0 and 100. + */ + AccessibleRole.SLIDER, + + /** + * A specialized panel that presents two other panels at the same time. + * Between the two panels is a divider the user can manipulate to make + * one panel larger and the other panel smaller. + */ + AccessibleRole.SPLIT_PANE, + + /** + * An object used to present information in terms of rows and columns. + * An example might include a spreadsheet application. + */ + AccessibleRole.TABLE, + + /** + * An object that presents text to the user. The text is usually + * editable by the user as opposed to a label. + * @see #LABEL + */ + AccessibleRole.TEXT, + + /** + * An object used to present hierarchical information to the user. + * The individual nodes in the tree can be collapsed and expanded + * to provide selective disclosure of the tree's contents. + */ + AccessibleRole.TREE, + + /** + * A bar or palette usually composed of push buttons or toggle buttons. + * It is often used to provide the most frequently used functions for an + * application. + */ + AccessibleRole.TOOL_BAR, + + /** + * An object that provides information about another object. The + * accessibleDescription property of the tool tip is often displayed + * to the user in a small "help bubble" when the user causes the + * mouse to hover over the object associated with the tool tip. + */ + AccessibleRole.TOOL_TIP, + + /** + * An AWT component, but nothing else is known about it. + * @see #SWING_COMPONENT + * @see #UNKNOWN + */ + AccessibleRole.AWT_COMPONENT, + + /** + * A Swing component, but nothing else is known about it. + * @see #AWT_COMPONENT + * @see #UNKNOWN + */ + AccessibleRole.SWING_COMPONENT, + + /** + * The object contains some Accessible information, but its role is + * not known. + * @see #AWT_COMPONENT + * @see #SWING_COMPONENT + */ + AccessibleRole.UNKNOWN, + + // These roles are only available in JDK 1.4 + + /** + * A STATUS_BAR is an simple component that can contain + * multiple labels of status information to the user. + AccessibleRole.STATUS_BAR, + + /** + * A DATE_EDITOR is a component that allows users to edit + * java.util.Date and java.util.Time objects + AccessibleRole.DATE_EDITOR, + + /** + * A SPIN_BOX is a simple spinner component and its main use + * is for simple numbers. + AccessibleRole.SPIN_BOX, + + /** + * A FONT_CHOOSER is a component that lets the user pick various + * attributes for fonts. + AccessibleRole.FONT_CHOOSER, + + /** + * A GROUP_BOX is a simple container that contains a border + * around it and contains components inside it. + AccessibleRole.GROUP_BOX + + /** + * Since JDK 1.5 + * + * A text header + + AccessibleRole.HEADER, + + /** + * A text footer + + AccessibleRole.FOOTER, + + /** + * A text paragraph + + AccessibleRole.PARAGRAPH, + + /** + * A ruler is an object used to measure distance + + AccessibleRole.RULER, + + /** + * A role indicating the object acts as a formula for + * calculating a value. An example is a formula in + * a spreadsheet cell. + AccessibleRole.EDITBAR + */ + }; + + /** + * This class implements accessibility support for the + * JTree child. It provides an implementation of the + * Java Accessibility API appropriate to tree nodes. + * + * Copied from JTree.java to work around a JTree bug where + * ActiveDescendent PropertyChangeEvents contain the wrong + * parent. + */ + /** + * This class in invoked on the EDT as its part of ActiveDescendant, + * hence the calls do not need to be specifically made on the EDT + */ + private class AccessibleJTreeNode extends AccessibleContext + implements Accessible, AccessibleComponent, AccessibleSelection, + AccessibleAction { + + private JTree tree = null; + private TreeModel treeModel = null; + private Object obj = null; + private TreePath path = null; + private Accessible accessibleParent = null; + private int index = 0; + private boolean isLeaf = false; + + /** + * Constructs an AccessibleJTreeNode + */ + AccessibleJTreeNode(JTree t, TreePath p, Accessible ap) { + tree = t; + path = p; + accessibleParent = ap; + if (t != null) + treeModel = t.getModel(); + if (p != null) { + obj = p.getLastPathComponent(); + if (treeModel != null && obj != null) { + isLeaf = treeModel.isLeaf(obj); + } + } + debugString("AccessibleJTreeNode: name = "+getAccessibleName()+"; TreePath = "+p+"; parent = "+ap); + } + + private TreePath getChildTreePath(int i) { + // Tree nodes can't be so complex that they have + // two sets of children -> we're ignoring that case + if (i < 0 || i >= getAccessibleChildrenCount() || path == null || treeModel == null) { + return null; + } else { + Object childObj = treeModel.getChild(obj, i); + Object[] objPath = path.getPath(); + Object[] objChildPath = new Object[objPath.length+1]; + java.lang.System.arraycopy(objPath, 0, objChildPath, 0, objPath.length); + objChildPath[objChildPath.length-1] = childObj; + return new TreePath(objChildPath); + } + } + + /** + * Get the AccessibleContext associated with this tree node. + * In the implementation of the Java Accessibility API for + * this class, return this object, which is its own + * AccessibleContext. + * + * @return this object + */ + public AccessibleContext getAccessibleContext() { + return this; + } + + private AccessibleContext getCurrentAccessibleContext() { + Component c = getCurrentComponent(); + if (c instanceof Accessible) { + return (c.getAccessibleContext()); + } else { + return null; + } + } + + private Component getCurrentComponent() { + debugString("AccessibleJTreeNode: getCurrentComponent"); + // is the object visible? + // if so, get row, selected, focus & leaf state, + // and then get the renderer component and return it + if (tree != null && tree.isVisible(path)) { + TreeCellRenderer r = tree.getCellRenderer(); + if (r == null) { + debugString(" returning null 1"); + return null; + } + TreeUI ui = tree.getUI(); + if (ui != null) { + int row = ui.getRowForPath(tree, path); + boolean selected = tree.isPathSelected(path); + boolean expanded = tree.isExpanded(path); + boolean hasFocus = false; // how to tell?? -PK + Component retval = r.getTreeCellRendererComponent(tree, obj, + selected, expanded, + isLeaf, row, hasFocus); + debugString(" returning = "+retval.getClass()); + return retval; + } + } + debugString(" returning null 2"); + return null; + } + + // AccessibleContext methods + + /** + * Get the accessible name of this object. + * + * @return the localized name of the object; null if this + * object does not have a name + */ + public String getAccessibleName() { + debugString("AccessibleJTreeNode: getAccessibleName"); + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null) { + String name = ac.getAccessibleName(); + if ((name != null) && (!name.isEmpty())) { + String retval = ac.getAccessibleName(); + debugString(" returning "+retval); + return retval; + } else { + return null; + } + } + if ((accessibleName != null) && (accessibleName.isEmpty())) { + return accessibleName; + } else { + return null; + } + } + + /** + * Set the localized accessible name of this object. + * + * @param s the new localized name of the object. + */ + public void setAccessibleName(String s) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null) { + ac.setAccessibleName(s); + } else { + super.setAccessibleName(s); + } + } + + // + // *** should check tooltip text for desc. (needs MouseEvent) + // + /** + * Get the accessible description of this object. + * + * @return the localized description of the object; null if + * this object does not have a description + */ + public String getAccessibleDescription() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null) { + return ac.getAccessibleDescription(); + } else { + return super.getAccessibleDescription(); + } + } + + /** + * Set the accessible description of this object. + * + * @param s the new localized description of the object + */ + public void setAccessibleDescription(String s) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null) { + ac.setAccessibleDescription(s); + } else { + super.setAccessibleDescription(s); + } + } + + /** + * Get the role of this object. + * + * @return an instance of AccessibleRole describing the role of the object + * @see AccessibleRole + */ + public AccessibleRole getAccessibleRole() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null) { + return ac.getAccessibleRole(); + } else { + return AccessibleRole.UNKNOWN; + } + } + + /** + * Get the state set of this object. + * + * @return an instance of AccessibleStateSet containing the + * current state set of the object + * @see AccessibleState + */ + public AccessibleStateSet getAccessibleStateSet() { + if (tree == null) + return null; + AccessibleContext ac = getCurrentAccessibleContext(); + AccessibleStateSet states; + int row = tree.getUI().getRowForPath(tree,path); + int lsr = tree.getLeadSelectionRow(); + if (ac != null) { + states = ac.getAccessibleStateSet(); + } else { + states = new AccessibleStateSet(); + } + // need to test here, 'cause the underlying component + // is a cellRenderer, which is never showing... + if (isShowing()) { + states.add(AccessibleState.SHOWING); + } else if (states.contains(AccessibleState.SHOWING)) { + states.remove(AccessibleState.SHOWING); + } + if (isVisible()) { + states.add(AccessibleState.VISIBLE); + } else if (states.contains(AccessibleState.VISIBLE)) { + states.remove(AccessibleState.VISIBLE); + } + if (tree.isPathSelected(path)){ + states.add(AccessibleState.SELECTED); + } + if (lsr == row) { + states.add(AccessibleState.ACTIVE); + } + if (!isLeaf) { + states.add(AccessibleState.EXPANDABLE); + } + if (tree.isExpanded(path)) { + states.add(AccessibleState.EXPANDED); + } else { + states.add(AccessibleState.COLLAPSED); + } + if (tree.isEditable()) { + states.add(AccessibleState.EDITABLE); + } + return states; + } + + /** + * Get the Accessible parent of this object. + * + * @return the Accessible parent of this object; null if this + * object does not have an Accessible parent + */ + public Accessible getAccessibleParent() { + // someone wants to know, so we need to create our parent + // if we don't have one (hey, we're a talented kid!) + if (accessibleParent == null && path != null) { + Object[] objPath = path.getPath(); + if (objPath.length > 1) { + Object objParent = objPath[objPath.length-2]; + if (treeModel != null) { + index = treeModel.getIndexOfChild(objParent, obj); + } + Object[] objParentPath = new Object[objPath.length-1]; + java.lang.System.arraycopy(objPath, 0, objParentPath, + 0, objPath.length-1); + TreePath parentPath = new TreePath(objParentPath); + accessibleParent = new AccessibleJTreeNode(tree, + parentPath, + null); + this.setAccessibleParent(accessibleParent); + } else if (treeModel != null) { + accessibleParent = tree; // we're the top! + index = 0; // we're an only child! + this.setAccessibleParent(accessibleParent); + } + } + return accessibleParent; + } + + /** + * Get the index of this object in its accessible parent. + * + * @return the index of this object in its parent; -1 if this + * object does not have an accessible parent. + * @see #getAccessibleParent + */ + public int getAccessibleIndexInParent() { + // index is invalid 'till we have an accessibleParent... + if (accessibleParent == null) { + getAccessibleParent(); + } + if (path != null) { + Object[] objPath = path.getPath(); + if (objPath.length > 1) { + Object objParent = objPath[objPath.length-2]; + if (treeModel != null) { + index = treeModel.getIndexOfChild(objParent, obj); + } + } + } + return index; + } + + /** + * Returns the number of accessible children in the object. + * + * @return the number of accessible children in the object. + */ + public int getAccessibleChildrenCount() { + // Tree nodes can't be so complex that they have + // two sets of children -> we're ignoring that case + if (obj != null && treeModel != null) { + return treeModel.getChildCount(obj); + } + return 0; + } + + /** + * Return the specified Accessible child of the object. + * + * @param i zero-based index of child + * @return the Accessible child of the object + */ + public Accessible getAccessibleChild(int i) { + // Tree nodes can't be so complex that they have + // two sets of children -> we're ignoring that case + if (i < 0 || i >= getAccessibleChildrenCount() || path == null || treeModel == null) { + return null; + } else { + Object childObj = treeModel.getChild(obj, i); + Object[] objPath = path.getPath(); + Object[] objChildPath = new Object[objPath.length+1]; + java.lang.System.arraycopy(objPath, 0, objChildPath, 0, objPath.length); + objChildPath[objChildPath.length-1] = childObj; + TreePath childPath = new TreePath(objChildPath); + return new AccessibleJTreeNode(tree, childPath, this); + } + } + + /** + * Gets the locale of the component. If the component does not have + * a locale, then the locale of its parent is returned. + * + * @return This component's locale. If this component does not have + * a locale, the locale of its parent is returned. + * @exception IllegalComponentStateException + * If the Component does not have its own locale and has not yet + * been added to a containment hierarchy such that the locale can be + * determined from the containing parent. + * @see #setLocale + */ + public Locale getLocale() { + if (tree == null) + return null; + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null) { + return ac.getLocale(); + } else { + return tree.getLocale(); + } + } + + /** + * Add a PropertyChangeListener to the listener list. + * The listener is registered for all properties. + * + * @param l The PropertyChangeListener to be added + */ + public void addPropertyChangeListener(PropertyChangeListener l) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null) { + ac.addPropertyChangeListener(l); + } else { + super.addPropertyChangeListener(l); + } + } + + /** + * Remove a PropertyChangeListener from the listener list. + * This removes a PropertyChangeListener that was registered + * for all properties. + * + * @param l The PropertyChangeListener to be removed + */ + public void removePropertyChangeListener(PropertyChangeListener l) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null) { + ac.removePropertyChangeListener(l); + } else { + super.removePropertyChangeListener(l); + } + } + + /** + * Get the AccessibleAction associated with this object. In the + * implementation of the Java Accessibility API for this class, + * return this object, which is responsible for implementing the + * AccessibleAction interface on behalf of itself. + * + * @return this object + */ + public AccessibleAction getAccessibleAction() { + return this; + } + + /** + * Get the AccessibleComponent associated with this object. In the + * implementation of the Java Accessibility API for this class, + * return this object, which is responsible for implementing the + * AccessibleComponent interface on behalf of itself. + * + * @return this object + */ + public AccessibleComponent getAccessibleComponent() { + return this; // to override getBounds() + } + + /** + * Get the AccessibleSelection associated with this object if one + * exists. Otherwise return null. + * + * @return the AccessibleSelection, or null + */ + public AccessibleSelection getAccessibleSelection() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null && isLeaf) { + return getCurrentAccessibleContext().getAccessibleSelection(); + } else { + return this; + } + } + + /** + * Get the AccessibleText associated with this object if one + * exists. Otherwise return null. + * + * @return the AccessibleText, or null + */ + public AccessibleText getAccessibleText() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null) { + return getCurrentAccessibleContext().getAccessibleText(); + } else { + return null; + } + } + + /** + * Get the AccessibleValue associated with this object if one + * exists. Otherwise return null. + * + * @return the AccessibleValue, or null + */ + public AccessibleValue getAccessibleValue() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null) { + return getCurrentAccessibleContext().getAccessibleValue(); + } else { + return null; + } + } + + + // AccessibleComponent methods + + /** + * Get the background color of this object. + * + * @return the background color, if supported, of the object; + * otherwise, null + */ + public Color getBackground() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + return ((AccessibleComponent) ac).getBackground(); + } else { + Component c = getCurrentComponent(); + if (c != null) { + return c.getBackground(); + } else { + return null; + } + } + } + + /** + * Set the background color of this object. + * + * @param c the new Color for the background + */ + public void setBackground(Color c) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + ((AccessibleComponent) ac).setBackground(c); + } else { + Component cp = getCurrentComponent(); + if ( cp != null) { + cp.setBackground(c); + } + } + } + + + /** + * Get the foreground color of this object. + * + * @return the foreground color, if supported, of the object; + * otherwise, null + */ + public Color getForeground() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + return ((AccessibleComponent) ac).getForeground(); + } else { + Component c = getCurrentComponent(); + if (c != null) { + return c.getForeground(); + } else { + return null; + } + } + } + + public void setForeground(Color c) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + ((AccessibleComponent) ac).setForeground(c); + } else { + Component cp = getCurrentComponent(); + if (cp != null) { + cp.setForeground(c); + } + } + } + + public Cursor getCursor() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + return ((AccessibleComponent) ac).getCursor(); + } else { + Component c = getCurrentComponent(); + if (c != null) { + return c.getCursor(); + } else { + Accessible ap = getAccessibleParent(); + if (ap instanceof AccessibleComponent) { + return ((AccessibleComponent) ap).getCursor(); + } else { + return null; + } + } + } + } + + public void setCursor(Cursor c) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + ((AccessibleComponent) ac).setCursor(c); + } else { + Component cp = getCurrentComponent(); + if (cp != null) { + cp.setCursor(c); + } + } + } + + public Font getFont() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + return ((AccessibleComponent) ac).getFont(); + } else { + Component c = getCurrentComponent(); + if (c != null) { + return c.getFont(); + } else { + return null; + } + } + } + + public void setFont(Font f) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + ((AccessibleComponent) ac).setFont(f); + } else { + Component c = getCurrentComponent(); + if (c != null) { + c.setFont(f); + } + } + } + + public FontMetrics getFontMetrics(Font f) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + return ((AccessibleComponent) ac).getFontMetrics(f); + } else { + Component c = getCurrentComponent(); + if (c != null) { + return c.getFontMetrics(f); + } else { + return null; + } + } + } + + public boolean isEnabled() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + return ((AccessibleComponent) ac).isEnabled(); + } else { + Component c = getCurrentComponent(); + if (c != null) { + return c.isEnabled(); + } else { + return false; + } + } + } + + public void setEnabled(boolean b) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + ((AccessibleComponent) ac).setEnabled(b); + } else { + Component c = getCurrentComponent(); + if (c != null) { + c.setEnabled(b); + } + } + } + + public boolean isVisible() { + if (tree == null) + return false; + Rectangle pathBounds = tree.getPathBounds(path); + Rectangle parentBounds = tree.getVisibleRect(); + if ( pathBounds != null && parentBounds != null && + parentBounds.intersects(pathBounds) ) { + return true; + } else { + return false; + } + } + + public void setVisible(boolean b) { + } + + public boolean isShowing() { + return (tree.isShowing() && isVisible()); + } + + public boolean contains(Point p) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + Rectangle r = ((AccessibleComponent) ac).getBounds(); + return r.contains(p); + } else { + Component c = getCurrentComponent(); + if (c != null) { + Rectangle r = c.getBounds(); + return r.contains(p); + } else { + return getBounds().contains(p); + } + } + } + + public Point getLocationOnScreen() { + if (tree != null) { + Point treeLocation = tree.getLocationOnScreen(); + Rectangle pathBounds = tree.getPathBounds(path); + if (treeLocation != null && pathBounds != null) { + Point nodeLocation = new Point(pathBounds.x, + pathBounds.y); + nodeLocation.translate(treeLocation.x, treeLocation.y); + return nodeLocation; + } else { + return null; + } + } else { + return null; + } + } + + private Point getLocationInJTree() { + Rectangle r = tree.getPathBounds(path); + if (r != null) { + return r.getLocation(); + } else { + return null; + } + } + + public Point getLocation() { + Rectangle r = getBounds(); + if (r != null) { + return r.getLocation(); + } else { + return null; + } + } + + public void setLocation(Point p) { + } + + public Rectangle getBounds() { + if (tree == null) + return null; + Rectangle r = tree.getPathBounds(path); + Accessible parent = getAccessibleParent(); + if (parent instanceof AccessibleJTreeNode) { + Point parentLoc = ((AccessibleJTreeNode) parent).getLocationInJTree(); + if (parentLoc != null && r != null) { + r.translate(-parentLoc.x, -parentLoc.y); + } else { + return null; // not visible! + } + } + return r; + } + + public void setBounds(Rectangle r) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + ((AccessibleComponent) ac).setBounds(r); + } else { + Component c = getCurrentComponent(); + if (c != null) { + c.setBounds(r); + } + } + } + + public Dimension getSize() { + return getBounds().getSize(); + } + + public void setSize (Dimension d) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + ((AccessibleComponent) ac).setSize(d); + } else { + Component c = getCurrentComponent(); + if (c != null) { + c.setSize(d); + } + } + } + + /** + * Returns the Accessible child, if one exists, + * contained at the local coordinate Point. + * Otherwise returns null. + * + * @param p point in local coordinates of this + * Accessible + * @return the Accessible, if it exists, + * at the specified location; else null + */ + public Accessible getAccessibleAt(Point p) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + return ((AccessibleComponent) ac).getAccessibleAt(p); + } else { + return null; + } + } + + public boolean isFocusTraversable() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + return ((AccessibleComponent) ac).isFocusTraversable(); + } else { + Component c = getCurrentComponent(); + if (c != null) { + return c.isFocusable(); + } else { + return false; + } + } + } + + public void requestFocus() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + ((AccessibleComponent) ac).requestFocus(); + } else { + Component c = getCurrentComponent(); + if (c != null) { + c.requestFocus(); + } + } + } + + public void addFocusListener(FocusListener l) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + ((AccessibleComponent) ac).addFocusListener(l); + } else { + Component c = getCurrentComponent(); + if (c != null) { + c.addFocusListener(l); + } + } + } + + public void removeFocusListener(FocusListener l) { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac instanceof AccessibleComponent) { + ((AccessibleComponent) ac).removeFocusListener(l); + } else { + Component c = getCurrentComponent(); + if (c != null) { + c.removeFocusListener(l); + } + } + } + + // AccessibleSelection methods + + /** + * Returns the number of items currently selected. + * If no items are selected, the return value will be 0. + * + * @return the number of items currently selected. + */ + public int getAccessibleSelectionCount() { + int count = 0; + int childCount = getAccessibleChildrenCount(); + for (int i = 0; i < childCount; i++) { + TreePath childPath = getChildTreePath(i); + if (tree.isPathSelected(childPath)) { + count++; + } + } + return count; + } + + /** + * Returns an Accessible representing the specified selected item + * in the object. If there isn't a selection, or there are + * fewer items selected than the integer passed in, the return + * value will be null. + * + * @param i the zero-based index of selected items + * @return an Accessible containing the selected item + */ + public Accessible getAccessibleSelection(int i) { + int childCount = getAccessibleChildrenCount(); + if (i < 0 || i >= childCount) { + return null; // out of range + } + int count = 0; + for (int j = 0; j < childCount && i >= count; j++) { + TreePath childPath = getChildTreePath(j); + if (tree.isPathSelected(childPath)) { + if (count == i) { + return new AccessibleJTreeNode(tree, childPath, this); + } else { + count++; + } + } + } + return null; + } + + /** + * Returns true if the current child of this object is selected. + * + * @param i the zero-based index of the child in this Accessible + * object. + * @see AccessibleContext#getAccessibleChild + */ + public boolean isAccessibleChildSelected(int i) { + int childCount = getAccessibleChildrenCount(); + if (i < 0 || i >= childCount) { + return false; // out of range + } else { + TreePath childPath = getChildTreePath(i); + return tree.isPathSelected(childPath); + } + } + + /** + * Adds the specified selected item in the object to the object's + * selection. If the object supports multiple selections, + * the specified item is added to any existing selection, otherwise + * it replaces any existing selection in the object. If the + * specified item is already selected, this method has no effect. + * + * @param i the zero-based index of selectable items + */ + public void addAccessibleSelection(int i) { + if (tree == null) + return; + TreeModel model = tree.getModel(); + if (model != null) { + if (i >= 0 && i < getAccessibleChildrenCount()) { + TreePath path = getChildTreePath(i); + tree.addSelectionPath(path); + } + } + } + + /** + * Removes the specified selected item in the object from the + * object's + * selection. If the specified item isn't currently selected, this + * method has no effect. + * + * @param i the zero-based index of selectable items + */ + public void removeAccessibleSelection(int i) { + if (tree == null) + return; + TreeModel model = tree.getModel(); + if (model != null) { + if (i >= 0 && i < getAccessibleChildrenCount()) { + TreePath path = getChildTreePath(i); + tree.removeSelectionPath(path); + } + } + } + + /** + * Clears the selection in the object, so that nothing in the + * object is selected. + */ + public void clearAccessibleSelection() { + int childCount = getAccessibleChildrenCount(); + for (int i = 0; i < childCount; i++) { + removeAccessibleSelection(i); + } + } + + /** + * Causes every selected item in the object to be selected + * if the object supports multiple selections. + */ + public void selectAllAccessibleSelection() { + if (tree == null) + return; + TreeModel model = tree.getModel(); + if (model != null) { + int childCount = getAccessibleChildrenCount(); + TreePath path; + for (int i = 0; i < childCount; i++) { + path = getChildTreePath(i); + tree.addSelectionPath(path); + } + } + } + + // AccessibleAction methods + + /** + * Returns the number of accessible actions available in this + * tree node. If this node is not a leaf, there is at least + * one action (toggle expand), in addition to any available + * on the object behind the TreeCellRenderer. + * + * @return the number of Actions in this object + */ + public int getAccessibleActionCount() { + AccessibleContext ac = getCurrentAccessibleContext(); + if (ac != null) { + AccessibleAction aa = ac.getAccessibleAction(); + if (aa != null) { + return (aa.getAccessibleActionCount() + (isLeaf ? 0 : 1)); + } + } + return isLeaf ? 0 : 1; + } + + /** + * Return a description of the specified action of the tree node. + * If this node is not a leaf, there is at least one action + * description (toggle expand), in addition to any available + * on the object behind the TreeCellRenderer. + * + * @param i zero-based index of the actions + * @return a description of the action + */ + public String getAccessibleActionDescription(int i) { + if (i < 0 || i >= getAccessibleActionCount()) { + return null; + } + AccessibleContext ac = getCurrentAccessibleContext(); + if (i == 0) { + // TIGER - 4766636 + // return AccessibleAction.TOGGLE_EXPAND; + return "toggle expand"; + } else if (ac != null) { + AccessibleAction aa = ac.getAccessibleAction(); + if (aa != null) { + return aa.getAccessibleActionDescription(i - 1); + } + } + return null; + } + + /** + * Perform the specified Action on the tree node. If this node + * is not a leaf, there is at least one action which can be + * done (toggle expand), in addition to any available on the + * object behind the TreeCellRenderer. + * + * @param i zero-based index of actions + * @return true if the the action was performed; else false. + */ + public boolean doAccessibleAction(int i) { + if (i < 0 || i >= getAccessibleActionCount()) { + return false; + } + AccessibleContext ac = getCurrentAccessibleContext(); + if (i == 0) { + if (tree.isExpanded(path)) { + tree.collapsePath(path); + } else { + tree.expandPath(path); + } + return true; + } else if (ac != null) { + AccessibleAction aa = ac.getAccessibleAction(); + if (aa != null) { + return aa.doAccessibleAction(i - 1); + } + } + return false; + } + + } // inner class AccessibleJTreeNode + + /** + * A helper class to perform {@code Callable} objects on the event dispatch thread appropriate + * for the provided {@code AccessibleContext}. + */ + private static class InvocationUtils { + + /** + * Invokes a {@code Callable} in the {@code AppContext} of the given {@code Accessible} + * and waits for it to finish blocking the caller thread. + * + * @param callable the {@code Callable} to invoke + * @param accessible the {@code Accessible} which would be used to find the right context + * for the task execution + * @param type parameter for the result value + * + * @return the result of the {@code Callable} execution + */ + public static T invokeAndWait(final Callable callable, + final Accessible accessible) { + if (accessible instanceof Component) { + return invokeAndWait(callable, (Component)accessible); + } + if (accessible instanceof AccessibleContext) { + // This case also covers the Translator + return invokeAndWait(callable, (AccessibleContext)accessible); + } + throw new RuntimeException("Unmapped Accessible used to dispatch event: " + accessible); + } + + /** + * Invokes a {@code Callable} in the {@code AppContext} of the given {@code Component} + * and waits for it to finish blocking the caller thread. + * + * @param callable the {@code Callable} to invoke + * @param component the {@code Component} which would be used to find the right context + * for the task execution + * @param type parameter for the result value + * + * @return the result of the {@code Callable} execution + */ + public static T invokeAndWait(final Callable callable, + final Component component) { + return invokeAndWait(callable, SunToolkit.targetToAppContext(component)); + } + + /** + * Invokes a {@code Callable} in the {@code AppContext} mapped to the given {@code AccessibleContext} + * and waits for it to finish blocking the caller thread. + * + * @param callable the {@code Callable} to invoke + * @param accessibleContext the {@code AccessibleContext} which would be used to determine the right + * context for the task execution. + * @param type parameter for the result value + * + * @return the result of the {@code Callable} execution + */ + public static T invokeAndWait(final Callable callable, + final AccessibleContext accessibleContext) { + AppContext targetContext = AWTAccessor.getAccessibleContextAccessor() + .getAppContext(accessibleContext); + if (targetContext != null) { + return invokeAndWait(callable, targetContext); + } else { + // Normally this should not happen, unmapped context provided and + // the target AppContext is unknown. + + // Try to recover in case the context is a translator. + if (accessibleContext instanceof Translator) { + Object source = ((Translator)accessibleContext).getSource(); + if (source instanceof Component) { + return invokeAndWait(callable, (Component)source); + } + } + } + throw new RuntimeException("Unmapped AccessibleContext used to dispatch event: " + accessibleContext); + } + + private static T invokeAndWait(final Callable callable, + final AppContext targetAppContext) { + final CallableWrapper wrapper = new CallableWrapper(callable); + try { + invokeAndWait(wrapper, targetAppContext); + T result = wrapper.getResult(); + updateAppContextMap(result, targetAppContext); + return result; + } catch (final Exception e) { + throw new RuntimeException(e); + } + } + + private static void invokeAndWait(final Runnable runnable, + final AppContext appContext) + throws InterruptedException, InvocationTargetException { + + EventQueue eq = SunToolkit.getSystemEventQueueImplPP(appContext); + Object lock = new Object(); + Toolkit source = Toolkit.getDefaultToolkit(); + InvocationEvent event = + new InvocationEvent(source, runnable, lock, true); + synchronized (lock) { + eq.postEvent(event); + lock.wait(); + } + + Throwable eventThrowable = event.getThrowable(); + if (eventThrowable != null) { + throw new InvocationTargetException(eventThrowable); + } + } + + /** + * Maps the {@code AccessibleContext} to the {@code AppContext} which should be used + * to dispatch events related to the {@code AccessibleContext} + * @param accessibleContext the {@code AccessibleContext} for the mapping + * @param targetContext the {@code AppContext} for the mapping + */ + public static void registerAccessibleContext(final AccessibleContext accessibleContext, + final AppContext targetContext) { + if (accessibleContext != null) { + AWTAccessor.getAccessibleContextAccessor().setAppContext(accessibleContext, targetContext); + } + } + + private static void updateAppContextMap(final T accessibleContext, + final AppContext targetContext) { + if (accessibleContext instanceof AccessibleContext) { + registerAccessibleContext((AccessibleContext)accessibleContext, targetContext); + } + } + + private static class CallableWrapper implements Runnable { + private final Callable callable; + private volatile T object; + private Exception e; + + CallableWrapper(final Callable callable) { + this.callable = callable; + } + + public void run() { + try { + if (callable != null) { + object = callable.call(); + } + } catch (final Exception e) { + this.e = e; + } + } + + T getResult() throws Exception { + if (e != null) + throw e; + return object; + } + } + } +}