/******************************************************************************* * Copyright (c) 2012 Google, Inc. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Google, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.actf.accservice.core.win32.msaa; import java.awt.Point; import java.awt.Rectangle; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Set; import com.windowtester.runtime.swt.internal.os.IAccessibleComponent; import com.windowtester.runtime.swt.internal.os.InvalidComponentException; /** * implementation of <code>IAccessibleElement</code> for GUI controls that implement Microsoft Active Accessibility (MSAA) interfaces. * * <p>This class is a wrapper for an IAccessible pointer, a pointer that Provides * access to a native Windows object that provides assistive technologies (ATs) with properties of GUI components * that allow the AT to offer an alternative interface to the control. This class relies upon actf-msaa.dll * for most of its implementation. The documentation for the Microsoft COM * library and, in particular, for MSAA will be helpful. * * @see <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msaa/msaastart_9w2t.asp">The MSDN MSAA Documentation</a> * @author Mike Squillace, Barry Feigenbaum */ public class MsaaAccessible implements IAccessibleComponent { protected static final String SWT_CTRL_TYPENAME = "org.eclipse.swt.widgets.Control"; protected static final String SWT_WIDGET_TYPENAME = "org.eclipse.swt.widgets.Widget"; protected static final String SWT_ACC_TYPENAME = "org.eclipse.swt.accessibility.ACC"; public static int childId_self = -1; // ACC.CHILDID_SELF protected static boolean highLightEnabled =false; protected int accRef; // pointer to helper class that wraps an MSAA IAccessible protected int hwnd = -1; // native window handle protected int childId; protected Object element; protected int indexInParent = -1; /** * wrap the given object as an ACTF <code>IAccessibleElement</code>. The * ACTF engine will invoke this constructor using a registered adaptor factory. Clients do not * typically call this constructor. * * <p>Note: To create a MsaaAccessible from a handle for an SWT control, use a child id of * <code>ACC.CHILDID_SELF</code>. * * @param hwnd - * window handle for an SWT control * @param childID - * child ID (if any) */ public MsaaAccessible (int hwnd, int childID) { initFromHwnd(hwnd, childID); } /** * create an MsaaAccessible element by utilizing the MSAA function <code>AccessibleObjectFromPoint</code>. * * @param location - any location on the current display device */ public MsaaAccessible (Point location) { initFromPoint(location.x, location.y); } /** * create a MsaaAccessible from an IAccessible pointer. * * @param ref - pointer value */ public MsaaAccessible (int ref) { accRef = ref; try { MsaaAccessibilityService.internalCoInitialize(); this.hwnd = internalGetWindowHandle(); this.childId = internalGetChildId(); }catch (Throwable e) { hwnd = -1; accRef = 0; } } /** {@inheritDoc} */ public Object element () { return element; } /** * used by native code only. Clients should not call directly. * @return ptr address for native object */ protected int internalRef () { return accRef; } public int getAccessibleAddress () { return internalGetAddress(); } protected native int internalGetAddress(); public int getWindowHandle () { return hwnd ; } /** * get the handle value as a hex string * * @return handle value as a hex string */ public String getWindowHandleAsHex () { String id = Integer.toHexString(hwnd).toUpperCase(); return "00000000".substring(0, 8 - id.length()) + id; } /** * get the child ID for this MsaaAccessible, if any. Child IDs are associated * with "simple" (i.e. non-IAccessible) children starting at 0. The child ID * for a control (rather than any of its children) is * <code>ACC.CHILDID_SELF</code> * * @return child ID */ public int getChildId () { return childId; } /** * tests whether or not this MsaaAccessible is in a valid state. Validity * consists of: * <p><ul> * <li>the handle for the SWT control associated with this MsaaAccessible (if * any) is a valid window handle * <li>the native code, upon initialization, returned a valid pointer to * the underlying MsaaAccessible object * <li>the underlying element returned by <code>getElement()</code> (if any) is not disposed * </ul> * * @throws InvalidComponentException */ public void checkIsValid () throws InvalidComponentException { boolean disposed = element() != null && isDisposed(element()); if (accRef == 0 || disposed) { throw new InvalidComponentException("Invalid accessible element: hwnd=" + hwnd + ",ref=" + accRef + ",isDisposed:" + disposed); } } protected boolean isDisposed (Object control) { boolean isDisposed = true; Class widgetClass; try { widgetClass = Class.forName(SWT_WIDGET_TYPENAME); if (control != null && widgetClass != null) { Method meth = widgetClass.getMethod("isDisposed", (Class[]) null); isDisposed = widgetClass.isAssignableFrom(control.getClass()) && ((Boolean) meth.invoke(control, (Object[]) null)).booleanValue(); } } catch (Exception e) { e.printStackTrace(); } return isDisposed; } public boolean equals (Object other) { boolean result = other != null && other instanceof MsaaAccessible; if (result) { MsaaAccessible accOther = (MsaaAccessible) other; try { String accName = this.getAccessibleName(); String accRole = this.getAccessibleRole(); String oAccName = accOther.getAccessibleName(); String oAccRole = accOther.getAccessibleRole(); result = !((accName == null && oAccName != null) || (accRole == null && oAccRole != null)); if (result) { result = this.getWindowHandle() == accOther.getWindowHandle() && this.getChildId() == accOther.getChildId() && ((accName == null && oAccName == null) || accName.equals(oAccName)) && ((accRole == null && oAccRole == null) || accRole.equals(oAccRole)) && (this.getAccessibleLocation().equals(accOther.getAccessibleLocation())) && (this.internalGetAccessibleState() == accOther.internalGetAccessibleState()); } } catch (InvalidComponentException e) { result = false; } } return result; } public int hashCode () { return accRef % 10000; } protected void finalize () throws Throwable { dispose(); accRef = 0; hwnd = -1; MsaaAccessibilityService.internalCoUnInitialize(); } public String toString () { String str = null; try { str = getAccessibleRole() + ":" + getAccessibleName() + "[" + getAccessibleIndexInParent() + "]"; } catch (InvalidComponentException e) { str = "<Exception>"; } return str; } protected void initFromHwnd (int hwnd, int childID) { accRef = internalInitFromHwnd(hwnd, childID); if (accRef != 0) { this.hwnd = internalGetWindowHandle(); this.childId = internalGetChildId(); } } protected void initFromPoint (int x, int y) { accRef = internalInitFromPoint(x, y); if (accRef > 0) { this.hwnd = internalGetWindowHandle(); this.childId = internalGetChildId(); } } protected void initFromHtmlElement (int htmlElemRef) { accRef = internalInitFromHtmlElement(htmlElemRef); if (accRef > 0) { this.hwnd = internalGetWindowHandle(); this.childId = internalGetChildId(); } } protected native int internalGetWindowHandle (); protected native int internalGetChildId (); protected native static int internalInitFromHwnd (int hwnd, int childID); protected native static int internalInitFromHtmlElement (int htmlElemRef); protected native static int internalInitFromPoint (int x, int y); /** * dispose the native resources * */ protected void dispose () throws InvalidComponentException { checkIsValid(); internalDispose(); } protected native void internalDispose (); /** * gets the class name for the given handle * * @param hwnd - * window handle * @return name of class */ public static String getClassNameFromHwnd (int hwnd) { return internalGetClassNameFromHwnd(hwnd); } protected native static String internalGetClassNameFromHwnd (int hwnd); /** {@inheritDoc} */ public MsaaAccessible getAccessibleParent () throws InvalidComponentException { checkIsValid(); MsaaAccessible parent = null; int accRef = internalGetAccessibleParent(); if (accRef != 0) { parent = new MsaaAccessible(accRef); } if (parent != null) { //see if the parent is Ia2 and, if so, convert it try { parent = testAndConvertToIA2((MsaaAccessible) parent); } catch (Exception e) { e.printStackTrace(); } } return parent; } protected native int internalGetAccessibleParent (); /** * return the number of children. Note that children include either * MsaaAccessible objects that represent MsaaAccessible wrappers of IAccessible * pointers or MsaaAccessible objects that wrap "simple" children, children * that share their properties with their IAccessible parent. * * @return number of child MsaaAccessible objects * @throws InvalidComponentException */ public int getAccessibleChildCount () throws InvalidComponentException { int res = 0; checkIsValid(); res = internalGetAccessibleChildCount(); return res; } protected native int internalGetAccessibleChildCount (); /** * return the accessible that has the given index in its parent. Note that this is not the * same value as its childID, though children are typically numbered consecutively * starting with 0. * * @param index index of desired child in parent * @return child with the given index or <code>null</code> if * the index is invalid */ public MsaaAccessible getAccessibleChild (int index) throws InvalidComponentException { checkIsValid(); int accRef = internalGetAccessibleChild(index); MsaaAccessible acc = null; if (accRef != 0) { acc = new MsaaAccessible(accRef); } if (acc != null) { try { acc = testAndConvertToIA2(acc); } catch (Exception e) { } acc.indexInParent = index; } return acc; } protected native int internalGetAccessibleChild (int childID); /** {@inheritDoc} */ public int getAccessibleIndexInParent () throws InvalidComponentException { if (indexInParent == -1) { MsaaAccessible parent = getAccessibleParent(); String accName = parent != null ? parent.getAccessibleName() : ""; IAccessibleComponent[] elems = accName == null || !accName.equalsIgnoreCase("desktop") ? parent.getAccessibleChildren() : new MsaaAccessible[0]; for (int c = 0; indexInParent == -1 && c < elems.length; ++c) { if (elems[c].equals(this)) { indexInParent = c; } } } return indexInParent; } /** * return all of the children of this MsaaAccessible. Note that children * include either MsaaAccessible objects that represent MsaaAccessible wrappers of * IAccessible pointers or MsaaAccessible objects that wrap "simple" children, * children that share their properties with their IAccessible parent. * * @return children of this MsaaAccessible * @throws InvalidComponentException */ public IAccessibleComponent[] getAccessibleChildren () throws InvalidComponentException { checkIsValid(); ArrayList children = new ArrayList(); int childCount = getAccessibleChildCount(); if (childCount > 0) { int[] childRefs = internalGetAccessibleChildren(); if (childRefs != null && childRefs.length > 0) { for (int i = 0; i < childRefs.length; i++) { if (childRefs[i] != 0) { MsaaAccessible acc = new MsaaAccessible(childRefs[i]); if (acc != null) { try { acc = testAndConvertToIA2(acc); } catch (Exception e) { } acc.indexInParent = i; } children.add(acc); } } } } return (MsaaAccessible[]) children.toArray(new MsaaAccessible[children.size()]); } protected native int[] internalGetAccessibleChildren (); /** * returns whether or not this MsaaAccessible is a simple child. Simple * children obtain their properties from their parent IAccessible object. * The parent IAccessible object has a child ID of * <code>ACC.CHILDID_SELF</code>. * * @return <code>true</code> if this is a simple child, <code>false</code> otherwise */ protected boolean isSimpleChild () { return childId != childId_self; } /** * returns whether the accessible object has the keyboard focus * * @return whether or not this object has keyboard focus * @throws InvalidComponentException */ public boolean hasFocus () throws InvalidComponentException { boolean res = false; checkIsValid(); if (!isDisposed(element)) { res = internalHasFocus(); } return res; } protected native boolean internalHasFocus (); /** {@inheritDoc} */ public String getAccessibleName () throws InvalidComponentException { String res = null; checkIsValid(); res = internalGetAccessibleName(); return res; } protected native String internalGetAccessibleName (); /** {@inheritDoc} */ public Object getAccessibleValue () throws InvalidComponentException { String res = null; checkIsValid(); res = internalGetAccessibleValue(); return res; } protected native String internalGetAccessibleValue (); /** * return help info (usually a tool tip) * * @return help or "" if no help is provided * @throws InvalidComponentException */ public String getAccessibleHelp () throws InvalidComponentException { String res = null; checkIsValid(); res = internalGetAccessibleHelp(); return res; } protected native String internalGetAccessibleHelp (); /** {@inheritDoc} */ public String getAccessibleKeyboardShortcut () throws InvalidComponentException { String res = null; checkIsValid(); res = internalGetAccessibleKeyboardShortcut(); return res; } protected native String internalGetAccessibleKeyboardShortcut (); /** {@inheritDoc} */ public Object getAccessibleAction () throws InvalidComponentException { String res = null; checkIsValid(); res = internalGetAccessibleAction(); return res; } protected native String internalGetAccessibleAction (); /** {@inheritDoc} */ public String getAccessibleDescription () throws InvalidComponentException { String res = null; checkIsValid(); res = internalGetAccessibleDescription(); return res; } protected native String internalGetAccessibleDescription (); /** {@inheritDoc} */ public String getAccessibleRole () throws InvalidComponentException { checkIsValid(); String res = null; int role = internalGetAccessibleRoleAsInt(); res = Msaa.getMsaaActfRoleName(role); if (res == null || res.length() < 1) { try { res = (String) Class.forName("org.eclipse.actf.accservice.core.win32.ia2.IA2") .getMethod("getIA2ActfRoleName", new Class[] {long.class}) .invoke(null, new Object[] {new Integer(role)}); }catch (Exception e) { } } if(res==null || res.length() <1){ res = internalGetAccessibleRole(); } return res; } protected native int internalGetAccessibleRoleAsInt (); protected native String internalGetAccessibleRole(); /** {@inheritDoc} */ public Set getAccessibleState () throws InvalidComponentException { checkIsValid(); int state = internalGetAccessibleState(); return Msaa.getState(state); } protected native int internalGetAccessibleState (); /** * get the list of selected accessibles. Currently this method will only * return a single selected item, although MSAA supports multiple * selection in some controls. If a control supports multiple selection and * multiple selections are present, this method will return an empty array. * * @return selections or empty array if no selection * @throws InvalidComponentException */ public MsaaAccessible[] getAccessibleSelection () throws InvalidComponentException { // TODO support multiple selections in dll checkIsValid(); int[] accRefs = internalGetAccessibleSelection(); MsaaAccessible[] selections = new MsaaAccessible[accRefs != null ? accRefs.length : 0]; if ((accRefs != null) && (accRefs.length > 0)) { for (int i = 0; i < selections.length; i++) { selections[i] = new MsaaAccessible(accRefs[i]); } } return selections; } protected native int[] internalGetAccessibleSelection (); /** {@inheritDoc} */ public Rectangle getAccessibleLocation() throws InvalidComponentException { Rectangle pt = new Rectangle(); checkIsValid(); pt = internalGetAccessibleLocation(); return pt; } protected native Rectangle internalGetAccessibleLocation (); public boolean drawRectangle() { return highLightEnabled && internalDrawRectangle(); } protected native boolean internalDrawRectangle(); public boolean eraseRectangle(Rectangle drawRef) { if (drawRef == null) { return eraseDesktop(); } int left = drawRef.x; int top = drawRef.y; int right = drawRef.x + drawRef.width; int bottom = drawRef.y + drawRef.height; return internalEraseRectangle(left, top, right, bottom); } public static boolean eraseDesktop() { return internalEraseDesktop(); } protected native static boolean internalEraseDesktop(); protected native boolean internalEraseRectangle(int left, int top, int right, int bottom); public static boolean isHighlightEnabled(){ return highLightEnabled; } public static void setHighlightEnabled(boolean val){ highLightEnabled = val; } public MsaaAccessible testAndConvertToIA2 (MsaaAccessible acc) throws Exception { // boolean isIA2 = false; //TODO: [pq]: this is disabled to reduce dependencies... MsaaAccessible result = acc; // isIA2 = ((Boolean) Class.forName("org.eclipse.actf.accservice.core.win32.ia2.IA2Accessible") // .getMethod("isIA2Accessible", new Class[] {MsaaAccessible.class}) // .invoke(null, new Object[] {acc})).booleanValue(); // if (isIA2){ // if(acc.hwnd!=0) { // result = (MsaaAccessible) Class.forName("org.eclipse.actf.accservice.core.win32.ia2.IA2Accessible") // .getConstructor(new Class[] {int.class, int.class}) // .newInstance(new Object[] {new Integer(acc.hwnd), new Integer(acc.childId)}); // } // else{ // int ia2Acc =0; // ia2Acc = ((Integer) Class.forName("org.eclipse.actf.accservice.core.win32.ia2.IA2Accessible") // .getMethod("getIA2FromIAcc", new Class[] {MsaaAccessible.class}) // .invoke(null, new Object[] {acc})).intValue(); // result = (MsaaAccessible) Class.forName("org.eclipse.actf.accservice.core.win32.ia2.IA2Accessible") // .getConstructor(new Class[] {int.class}) // .newInstance(new Object[]{new Integer(ia2Acc)}); // } // } return result; } public boolean doDefaultAction() throws InvalidComponentException{ checkIsValid(); return internalDoDefaultAction(); } protected native boolean internalDoDefaultAction(); public String getAccessibleHelpTopic () throws InvalidComponentException { String res = null; checkIsValid(); res = internalGetAccessibleHelpTopic(); return res; } protected native String internalGetAccessibleHelpTopic (); public boolean select(int flag) throws InvalidComponentException{ checkIsValid(); return internalSelect(flag); } protected native boolean internalSelect(int flag); }