1 /*
   2  * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #import "JavaAccessibilityUtilities.h"
  27 
  28 #import <AppKit/AppKit.h>
  29 #import <JavaNativeFoundation/JavaNativeFoundation.h>
  30 
  31 
  32 static BOOL JavaAccessibilityIsSupportedAttribute(id element, NSString *attribute);
  33 static void JavaAccessibilityLogError(NSString *message);
  34 static void _JavaAccessibilityRaiseException(NSString *reason, SInt32 errorCode);
  35 static NSString *AttributeWithoutAXPrefix(NSString *attribute);
  36 static SEL JavaAccessibilityAttributeGetter(NSString *attribute);
  37 static SEL JavaAccessibilityAttributeSettableTester(NSString *attribute);
  38 static SEL JavaAccessibilityAttributeSetter(NSString *attribute);
  39 
  40 NSString *const JavaAccessibilityIgnore = @"JavaAxIgnore";
  41 
  42 NSMutableDictionary *sRoles = nil;
  43 void initializeRoles();
  44 
  45 // Unique
  46 static JNF_CLASS_CACHE(sjc_AccessibleState, "javax/accessibility/AccessibleState");
  47 
  48 // Duplicate
  49 JNF_CLASS_CACHE(sjc_CAccessibility, "sun/lwawt/macosx/CAccessibility");
  50 JNF_CLASS_CACHE(sjc_AccessibleComponent, "javax/accessibility/AccessibleComponent");
  51 JNF_CLASS_CACHE(sjc_AccessibleContext, "javax/accessibility/AccessibleContext");
  52 JNF_CLASS_CACHE(sjc_Accessible, "javax/accessibility/Accessible");
  53 JNF_CLASS_CACHE(sjc_AccessibleRole, "javax/accessibility/AccessibleRole");
  54 JNF_CLASS_CACHE(sjc_Point, "java/awt/Point");
  55 JNF_CLASS_CACHE(sjc_AccessibleText, "javax/accessibility/AccessibleText");
  56 
  57 JNF_MEMBER_CACHE(sjf_key, sjc_AccessibleRole, "key", "Ljava/lang/String;");
  58 JNF_MEMBER_CACHE(sjf_X, sjc_Point, "x", "I");
  59 JNF_MEMBER_CACHE(sjf_Y, sjc_Point, "y", "I");
  60 
  61 NSSize getAxComponentSize(JNIEnv *env, jobject axComponent, jobject component)
  62 {
  63     static JNF_CLASS_CACHE(jc_Dimension, "java/awt/Dimension");
  64     static JNF_MEMBER_CACHE(jf_width, jc_Dimension, "width", "I");
  65     static JNF_MEMBER_CACHE(jf_height, jc_Dimension, "height", "I");
  66     static JNF_STATIC_MEMBER_CACHE(jm_getSize, sjc_CAccessibility, "getSize", "(Ljavax/accessibility/AccessibleComponent;Ljava/awt/Component;)Ljava/awt/Dimension;");
  67 
  68     jobject dimension = JNFCallStaticObjectMethod(env, jm_getSize, axComponent, component); // AWT_THREADING Safe (AWTRunLoopMode)
  69 
  70     if (dimension == NULL) return NSZeroSize;
  71     return NSMakeSize(JNFGetIntField(env, dimension, jf_width), JNFGetIntField(env, dimension, jf_height));
  72 }
  73 
  74 NSString *getJavaRole(JNIEnv *env, jobject axComponent, jobject component)
  75 {
  76     static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleRole, sjc_CAccessibility, "getAccessibleRole", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;");
  77     jobject axRole = JNFCallStaticObjectMethod(env, sjm_getAccessibleRole, axComponent, component); // AWT_THREADING Safe (AWTRunLoopMode)
  78     if (axRole == NULL) return @"unknown";
  79 
  80     return JNFJavaToNSString(env, axRole);
  81 }
  82 
  83 jobject getAxSelection(JNIEnv *env, jobject axContext, jobject component)
  84 {
  85     static JNF_STATIC_MEMBER_CACHE(jm_getAccessibleSelection, sjc_CAccessibility, "getAccessibleSelection", "(Ljavax/accessibility/AccessibleContext;Ljava/awt/Component;)Ljavax/accessibility/AccessibleSelection;");
  86     return JNFCallStaticObjectMethod(env, jm_getAccessibleSelection, axContext, component); // AWT_THREADING Safe (AWTRunLoopMode)
  87 }
  88 
  89 jobject getAxContextSelection(JNIEnv *env, jobject axContext, jint index, jobject component)
  90 {
  91     static JNF_STATIC_MEMBER_CACHE(jm_ax_getAccessibleSelection, sjc_CAccessibility, "ax_getAccessibleSelection", "(Ljavax/accessibility/AccessibleContext;ILjava/awt/Component;)Ljavax/accessibility/Accessible;");
  92     return JNFCallStaticObjectMethod(env, jm_ax_getAccessibleSelection, axContext, index, component); // AWT_THREADING Safe (AWTRunLoopMode)
  93 }
  94 
  95 void setAxContextSelection(JNIEnv *env, jobject axContext, jint index, jobject component)
  96 {
  97     static JNF_STATIC_MEMBER_CACHE(jm_addAccessibleSelection, sjc_CAccessibility, "addAccessibleSelection", "(Ljavax/accessibility/AccessibleContext;ILjava/awt/Component;)V");
  98     JNFCallStaticVoidMethod(env, jm_addAccessibleSelection, axContext, index, component); // AWT_THREADING Safe (AWTRunLoopMode)
  99 }
 100 
 101 jobject getAxContext(JNIEnv *env, jobject accessible, jobject component)
 102 {
 103     static JNF_STATIC_MEMBER_CACHE(jm_getAccessibleContext, sjc_CAccessibility, "getAccessibleContext", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/AccessibleContext;");
 104     return JNFCallStaticObjectMethod(env, jm_getAccessibleContext, accessible, component); // AWT_THREADING Safe (AWTRunLoopMode)
 105 }
 106 
 107 BOOL isChildSelected(JNIEnv *env, jobject accessible, jint index, jobject component)
 108 {
 109     static JNF_STATIC_MEMBER_CACHE(jm_isAccessibleChildSelected, sjc_CAccessibility, "isAccessibleChildSelected", "(Ljavax/accessibility/Accessible;ILjava/awt/Component;)Z");
 110     return JNFCallStaticBooleanMethod(env, jm_isAccessibleChildSelected, accessible, index, component); // AWT_THREADING Safe (AWTRunLoopMode)
 111 }
 112 
 113 jobject getAxStateSet(JNIEnv *env, jobject axContext, jobject component)
 114 {
 115     static JNF_STATIC_MEMBER_CACHE(jm_getAccessibleStateSet, sjc_CAccessibility, "getAccessibleStateSet", "(Ljavax/accessibility/AccessibleContext;Ljava/awt/Component;)Ljavax/accessibility/AccessibleStateSet;");
 116     return JNFCallStaticObjectMethod(env, jm_getAccessibleStateSet, axContext, component); // AWT_THREADING Safe (AWTRunLoopMode)
 117 }
 118 
 119 BOOL containsAxState(JNIEnv *env, jobject axContext, jobject axState, jobject component)
 120 {
 121     static JNF_STATIC_MEMBER_CACHE(jm_contains, sjc_CAccessibility, "contains", "(Ljavax/accessibility/AccessibleContext;Ljavax/accessibility/AccessibleState;Ljava/awt/Component;)Z");
 122     return JNFCallStaticBooleanMethod(env, jm_contains, axContext, axState, component); // AWT_THREADING Safe (AWTRunLoopMode)
 123 }
 124 
 125 BOOL isVertical(JNIEnv *env, jobject axContext, jobject component)
 126 {
 127     static JNF_STATIC_MEMBER_CACHE(jm_VERTICAL, sjc_AccessibleState, "VERTICAL", "Ljavax/accessibility/AccessibleState;");
 128     jobject axVertState = JNFGetStaticObjectField(env, jm_VERTICAL);
 129     return containsAxState(env, axContext, axVertState, component);
 130 }
 131 
 132 BOOL isHorizontal(JNIEnv *env, jobject axContext, jobject component)
 133 {
 134     static JNF_STATIC_MEMBER_CACHE(jm_HORIZONTAL, sjc_AccessibleState, "HORIZONTAL", "Ljavax/accessibility/AccessibleState;");
 135     jobject axHorizState = JNFGetStaticObjectField(env, jm_HORIZONTAL);
 136     return containsAxState(env, axContext, axHorizState, component);
 137 }
 138 
 139 BOOL isShowing(JNIEnv *env, jobject axContext, jobject component)
 140 {
 141     static JNF_STATIC_MEMBER_CACHE(jm_SHOWING, sjc_AccessibleState, "SHOWING", "Ljavax/accessibility/AccessibleState;");
 142     jobject axVisibleState = JNFGetStaticObjectField(env, jm_SHOWING);
 143     return containsAxState(env, axContext, axVisibleState, component);
 144 }
 145 
 146 NSPoint getAxComponentLocationOnScreen(JNIEnv *env, jobject axComponent, jobject component)
 147 {
 148     static JNF_STATIC_MEMBER_CACHE(jm_getLocationOnScreen, sjc_CAccessibility, "getLocationOnScreen", "(Ljavax/accessibility/AccessibleComponent;Ljava/awt/Component;)Ljava/awt/Point;");
 149     jobject jpoint = JNFCallStaticObjectMethod(env, jm_getLocationOnScreen, axComponent, component); // AWT_THREADING Safe (AWTRunLoopMode)
 150     if (jpoint == NULL) return NSZeroPoint;
 151     return NSMakePoint(JNFGetIntField(env, jpoint, sjf_X), JNFGetIntField(env, jpoint, sjf_Y));
 152 }
 153 
 154 jint getAxTextCharCount(JNIEnv *env, jobject axText, jobject component)
 155 {
 156     static JNF_STATIC_MEMBER_CACHE(jm_getCharCount, sjc_CAccessibility, "getCharCount", "(Ljavax/accessibility/AccessibleText;Ljava/awt/Component;)I");
 157     return JNFCallStaticIntMethod(env, jm_getCharCount, axText, component); // AWT_THREADING Safe (AWTRunLoopMode)
 158 }
 159 
 160 // The following JavaAccessibility methods are copied from the corresponding
 161 // NSAccessibility methods in NSAccessibility.m.
 162 //
 163 // They implement a key-value-like coding scheme to transform messages like
 164 //        [self accessibilityAttributeValue:NSAccessibilityEnabledAttribute]
 165 // into calls on to specific methods like
 166 //        [self accessibilityEnabledAttribute].
 167 
 168 static NSString *AttributeWithoutAXPrefix(NSString *attribute)
 169 {
 170     return [attribute hasPrefix:@"AX"] ? [attribute substringFromIndex:2] : attribute;
 171 }
 172 
 173 static SEL JavaAccessibilityAttributeGetter(NSString *attribute)
 174 {
 175     return NSSelectorFromString([NSString stringWithFormat:@"accessibility%@Attribute", AttributeWithoutAXPrefix(attribute)]);
 176 }
 177 
 178 static SEL JavaAccessibilityAttributeSettableTester(NSString *attribute)
 179 {
 180     return NSSelectorFromString([NSString stringWithFormat:@"accessibilityIs%@AttributeSettable", AttributeWithoutAXPrefix(attribute)]);
 181 }
 182 
 183 static SEL JavaAccessibilityAttributeSetter(NSString *attribute)
 184 {
 185     return NSSelectorFromString([NSString stringWithFormat:@"accessibilitySet%@Attribute:", AttributeWithoutAXPrefix(attribute)]);
 186 }
 187 
 188 id JavaAccessibilityAttributeValue(id element, NSString *attribute)
 189 {
 190     if (!JavaAccessibilityIsSupportedAttribute(element, attribute)) return nil;
 191 
 192     SEL getter = JavaAccessibilityAttributeGetter(attribute);
 193 #ifdef JAVA_AX_DEBUG_PARMS
 194     if (![element respondsToSelector:getter]) {
 195         JavaAccessibilityRaiseUnimplementedAttributeException(__FUNCTION__, element, attribute);
 196         return nil;
 197     }
 198 #endif
 199 
 200     return [element performSelector:getter];
 201 }
 202 
 203 BOOL JavaAccessibilityIsAttributeSettable(id element, NSString *attribute)
 204 {
 205     if (!JavaAccessibilityIsSupportedAttribute(element, attribute)) return NO;
 206 
 207     SEL tester = JavaAccessibilityAttributeSettableTester(attribute);
 208 #ifdef JAVA_AX_DEBUG_PARMS
 209     if (![element respondsToSelector:tester]) {
 210         JavaAccessibilityRaiseUnimplementedAttributeException(__FUNCTION__, element, attribute);
 211         return NO;
 212     }
 213 #endif
 214 
 215     return [element performSelector:tester] != nil;
 216 }
 217 
 218 void JavaAccessibilitySetAttributeValue(id element, NSString *attribute ,id value)
 219 {
 220     if (!JavaAccessibilityIsSupportedAttribute(element, attribute)) return;
 221 
 222     SEL setter = JavaAccessibilityAttributeSetter(attribute);
 223     if (![element accessibilityIsAttributeSettable:attribute]) return;
 224 
 225 #ifdef JAVA_AX_DEBUG_PARMS
 226     if (![element respondsToSelector:setter]) {
 227         JavaAccessibilityRaiseUnimplementedAttributeException(__FUNCTION__, element, attribute);
 228         return;
 229     }
 230 #endif
 231 
 232     [element performSelector:setter withObject:value];
 233 }
 234 
 235 static BOOL JavaAccessibilityIsSupportedAttribute(id element, NSString *attribute)
 236 {
 237     return [[element accessibilityAttributeNames] indexOfObject:attribute] != NSNotFound;
 238 }
 239 
 240 /*
 241  * Class:     sun_lwawt_macosx_CAccessibility
 242  * Method:    roleKey
 243  * Signature: (Ljavax/accessibility/AccessibleRole;)Ljava/lang/String;
 244  */
 245 JNIEXPORT jstring JNICALL Java_sun_lwawt_macosx_CAccessibility_roleKey
 246 (JNIEnv *env, jclass clz, jobject axRole)
 247 {
 248     return JNFGetObjectField(env, axRole, sjf_key);
 249 }
 250 
 251 
 252 // errors from NSAccessibilityErrors
 253 void JavaAccessibilityRaiseSetAttributeToIllegalTypeException(const char *functionName, id element, NSString *attribute, id value)
 254 {
 255     NSString *reason = [NSString stringWithFormat:@"%s: Attempt set \"%@\" attribute to illegal type of value (%@:%@) for element: %@", functionName, attribute, [value class], value, element];
 256     _JavaAccessibilityRaiseException(reason, kAXErrorIllegalArgument);
 257 }
 258 
 259 void JavaAccessibilityRaiseUnimplementedAttributeException(const char *functionName, id element, NSString *attribute)
 260 {
 261     NSString *reason = [NSString stringWithFormat:@"%s: \"%@\" attribute unimplemented by element: %@", functionName, attribute, element];
 262     _JavaAccessibilityRaiseException(reason, kAXErrorFailure);
 263 }
 264 
 265 void JavaAccessibilityRaiseIllegalParameterTypeException(const char *functionName, id element, NSString *attribute, id parameter)
 266 {
 267     NSString *reason = [NSString stringWithFormat:@"%s: \"%@\" parameterized attribute passed illegal type of parameter (%@:%@) for element: %@", functionName, attribute, [parameter class], parameter, element];
 268     _JavaAccessibilityRaiseException(reason, kAXErrorIllegalArgument);
 269 }
 270 
 271 static void _JavaAccessibilityRaiseException(NSString *reason, SInt32 errorCode)
 272 {
 273     JavaAccessibilityLogError(reason);
 274     [[NSException exceptionWithName:NSAccessibilityException reason:reason userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:errorCode], NSAccessibilityErrorCodeExceptionInfo, nil]] raise];
 275 }
 276 
 277 static void JavaAccessibilityLogError(NSString *message)
 278 {
 279     NSLog(@"!!! %@", message);
 280 }
 281 
 282 // end appKit copies
 283 
 284 /*
 285  To get the roles below, verify the perl has table below called macRoleCodes is correct.
 286  Then copy the perl code into a perl script called makeAxTables.pl (make
 287  sure to chmod +x makeAxTables.pl). Then run the perl script like this:
 288 
 289  ./makeAxTables.pl /Builds/jdk1_4_1/
 290 
 291  It will then write the void initializeRoles() method below to stdout.
 292 
 293  Any new AccessibleRole items that aren't in the perl hash table will be written out as follows:
 294  // Unknown AccessibleRole: <role>
 295 
 296  Add these unknowns to the perl hash table and re-run the script, and use the new generated table.
 297 */
 298 
 299 // NOTE: Don't modify this directly. It is machine generated. See below
 300 void initializeRoles()
 301 {
 302     sRoles = [[NSMutableDictionary alloc] initWithCapacity:56];
 303 
 304     [sRoles setObject:JavaAccessibilityIgnore forKey:@"alert"];
 305     [sRoles setObject:NSAccessibilityGroupRole forKey:@"awtcomponent"];
 306     [sRoles setObject:NSAccessibilityGroupRole forKey:@"canvas"];
 307     [sRoles setObject:NSAccessibilityCheckBoxRole forKey:@"checkbox"];
 308     [sRoles setObject:JavaAccessibilityIgnore forKey:@"colorchooser"];
 309     [sRoles setObject:NSAccessibilityColumnRole forKey:@"columnheader"];
 310     [sRoles setObject:NSAccessibilityComboBoxRole forKey:@"combobox"];
 311     [sRoles setObject:NSAccessibilityTextFieldRole forKey:@"dateeditor"];
 312     [sRoles setObject:NSAccessibilityImageRole forKey:@"desktopicon"];
 313     [sRoles setObject:JavaAccessibilityIgnore forKey:@"desktoppane"];
 314     [sRoles setObject:JavaAccessibilityIgnore forKey:@"dialog"];
 315     [sRoles setObject:JavaAccessibilityIgnore forKey:@"directorypane"];
 316     [sRoles setObject:JavaAccessibilityIgnore forKey:@"filechooser"];
 317     [sRoles setObject:JavaAccessibilityIgnore forKey:@"filler"];
 318     [sRoles setObject:JavaAccessibilityIgnore forKey:@"fontchooser"];
 319     [sRoles setObject:JavaAccessibilityIgnore forKey:@"frame"];
 320     [sRoles setObject:JavaAccessibilityIgnore forKey:@"glasspane"];
 321     [sRoles setObject:NSAccessibilityGroupRole forKey:@"groupbox"];
 322     [sRoles setObject:NSAccessibilityStaticTextRole forKey:@"hyperlink"]; //maybe a group?
 323     [sRoles setObject:NSAccessibilityImageRole forKey:@"icon"];
 324     [sRoles setObject:NSAccessibilityGroupRole forKey:@"internalframe"];
 325     [sRoles setObject:NSAccessibilityStaticTextRole forKey:@"label"];
 326     [sRoles setObject:JavaAccessibilityIgnore forKey:@"layeredpane"];
 327     [sRoles setObject:NSAccessibilityListRole forKey:@"list"]; // maybe a group? AccessibleRole.java says a list is: "An object that presents a list of objects to the user and allows the user to select one or more of them."
 328     [sRoles setObject:NSAccessibilityListRole forKey:@"listitem"];
 329     [sRoles setObject:NSAccessibilityMenuRole forKey:@"menu"];
 330     [sRoles setObject:NSAccessibilityMenuBarRole forKey:@"menubar"];
 331     [sRoles setObject:NSAccessibilityMenuItemRole forKey:@"menuitem"];
 332     [sRoles setObject:JavaAccessibilityIgnore forKey:@"optionpane"];
 333     [sRoles setObject:NSAccessibilityRadioButtonRole forKey:@"pagetab"]; // cmcnote: cocoa tabs are radio buttons - one selected button out of a group of options
 334     [sRoles setObject:NSAccessibilityTabGroupRole forKey:@"pagetablist"];
 335     [sRoles setObject:JavaAccessibilityIgnore forKey:@"panel"];
 336     [sRoles setObject:NSAccessibilityTextFieldRole forKey:@"passwordtext"];
 337     [sRoles setObject:NSAccessibilityPopUpButtonRole forKey:@"popupmenu"];
 338     [sRoles setObject:NSAccessibilityProgressIndicatorRole forKey:@"progressbar"];
 339     [sRoles setObject:NSAccessibilityButtonRole forKey:@"pushbutton"];
 340     [sRoles setObject:NSAccessibilityRadioButtonRole forKey:@"radiobutton"];
 341     [sRoles setObject:JavaAccessibilityIgnore forKey:@"rootpane"];
 342     [sRoles setObject:NSAccessibilityRowRole forKey:@"rowheader"];
 343     [sRoles setObject:NSAccessibilityScrollBarRole forKey:@"scrollbar"];
 344     [sRoles setObject:NSAccessibilityScrollAreaRole forKey:@"scrollpane"];
 345     [sRoles setObject:JavaAccessibilityIgnore forKey:@"separator"];
 346     [sRoles setObject:NSAccessibilitySliderRole forKey:@"slider"];
 347     [sRoles setObject:NSAccessibilityIncrementorRole forKey:@"spinbox"];
 348     [sRoles setObject:NSAccessibilitySplitGroupRole forKey:@"splitpane"];
 349     [sRoles setObject:NSAccessibilityValueIndicatorRole forKey:@"statusbar"];
 350     [sRoles setObject:NSAccessibilityGroupRole forKey:@"swingcomponent"];
 351     [sRoles setObject:NSAccessibilityTableRole forKey:@"table"];
 352     [sRoles setObject:NSAccessibilityTextFieldRole forKey:@"text"];
 353     [sRoles setObject:NSAccessibilityTextAreaRole forKey:@"textarea"]; // supports top/bottom of document notifications: CAccessability.getAccessibleRole()
 354     [sRoles setObject:NSAccessibilityCheckBoxRole forKey:@"togglebutton"];
 355     [sRoles setObject:NSAccessibilityToolbarRole forKey:@"toolbar"];
 356     [sRoles setObject:JavaAccessibilityIgnore forKey:@"tooltip"];
 357     [sRoles setObject:NSAccessibilityBrowserRole forKey:@"tree"];
 358     [sRoles setObject:NSAccessibilityUnknownRole forKey:@"unknown"];
 359     [sRoles setObject:JavaAccessibilityIgnore forKey:@"viewport"];
 360     [sRoles setObject:JavaAccessibilityIgnore forKey:@"window"];
 361 }