1 /* 2 * Copyright (c) 2011, 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 // External Java Accessibility links: 27 // 28 // <http://java.sun.com/j2se/1.4.2/docs/guide/access/index.html> 29 // <http://www-106.ibm.com/developerworks/library/j-access/?n-j-10172> 30 // <http://archives.java.sun.com/archives/java-access.html> (Sun's mailing list for Java accessibility) 31 32 #import "JavaComponentAccessibility.h" 33 34 #import "sun_lwawt_macosx_CAccessibility.h" 35 36 #import <AppKit/AppKit.h> 37 38 #import <JavaNativeFoundation/JavaNativeFoundation.h> 39 #import <JavaRuntimeSupport/JavaRuntimeSupport.h> 40 41 #import <dlfcn.h> 42 43 #import "JavaAccessibilityAction.h" 44 #import "JavaAccessibilityUtilities.h" 45 #import "JavaTextAccessibility.h" 46 #import "ThreadUtilities.h" 47 #import "AWTView.h" 48 49 50 // these constants are duplicated in CAccessibility.java 51 #define JAVA_AX_ALL_CHILDREN (-1) 52 #define JAVA_AX_SELECTED_CHILDREN (-2) 53 #define JAVA_AX_VISIBLE_CHILDREN (-3) 54 // If the value is >=0, it's an index 55 56 static JNF_STATIC_MEMBER_CACHE(jm_getChildrenAndRoles, sjc_CAccessibility, "getChildrenAndRoles", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;IZ)[Ljava/lang/Object;"); 57 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleComponent, sjc_CAccessibility, "getAccessibleComponent", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/AccessibleComponent;"); 58 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleValue, sjc_CAccessibility, "getAccessibleValue", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/AccessibleValue;"); 59 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleName, sjc_CAccessibility, "getAccessibleName", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;"); 60 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleDescription, sjc_CAccessibility, "getAccessibleDescription", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;"); 61 static JNF_STATIC_MEMBER_CACHE(sjm_isFocusTraversable, sjc_CAccessibility, "isFocusTraversable", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Z"); 62 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleIndexInParent, sjc_CAccessibility, "getAccessibleIndexInParent", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)I"); 63 64 static JNF_CLASS_CACHE(sjc_CAccessible, "sun/lwawt/macosx/CAccessible"); 65 66 static JNF_MEMBER_CACHE(jf_ptr, sjc_CAccessible, "ptr", "J"); 67 static JNF_STATIC_MEMBER_CACHE(sjm_getCAccessible, sjc_CAccessible, "getCAccessible", "(Ljavax/accessibility/Accessible;)Lsun/lwawt/macosx/CAccessible;"); 68 69 70 static jobject sAccessibilityClass = NULL; 71 72 // sAttributeNamesForRoleCache holds the names of the attributes to which each java 73 // AccessibleRole responds (see AccessibleRole.java). 74 // This cache is queried before attempting to access a given attribute for a particular role. 75 static NSMutableDictionary *sAttributeNamesForRoleCache = nil; 76 static NSObject *sAttributeNamesLOCK = nil; 77 78 79 @interface TabGroupAccessibility : JavaComponentAccessibility { 80 NSInteger _numTabs; 81 } 82 83 - (id)currentTabWithEnv:(JNIEnv *)env withAxContext:(jobject)axContext; 84 - (NSArray *)tabControlsWithEnv:(JNIEnv *)env withTabGroupAxContext:(jobject)axContext withTabCode:(NSInteger)whichTabs allowIgnored:(BOOL)allowIgnored; 85 - (NSArray *)contentsWithEnv:(JNIEnv *)env withTabGroupAxContext:(jobject)axContext withTabCode:(NSInteger)whichTabs allowIgnored:(BOOL)allowIgnored; 86 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env; 87 88 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount; 89 - (NSArray *)accessibilityChildrenAttribute; 90 - (id) accessibilityTabsAttribute; 91 - (BOOL)accessibilityIsTabsAttributeSettable; 92 - (NSArray *)accessibilityContentsAttribute; 93 - (BOOL)accessibilityIsContentsAttributeSettable; 94 - (id) accessibilityValueAttribute; 95 96 @end 97 98 99 @interface TabGroupControlAccessibility : JavaComponentAccessibility { 100 jobject fTabGroupAxContext; 101 } 102 - (id)initWithParent:(NSObject *)parent withEnv:(JNIEnv *)env withAccessible:(jobject)accessible withIndex:(jint)index withTabGroup:(jobject)tabGroup withView:(NSView *)view withJavaRole:(NSString *)javaRole; 103 - (jobject)tabGroup; 104 - (void)getActionsWithEnv:(JNIEnv *)env; 105 106 - (id)accessibilityValueAttribute; 107 @end 108 109 110 @interface ScrollAreaAccessibility : JavaComponentAccessibility { 111 112 } 113 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env; 114 - (NSArray *)accessibilityContentsAttribute; 115 - (BOOL)accessibilityIsContentsAttributeSettable; 116 - (id)accessibilityVerticalScrollBarAttribute; 117 - (BOOL)accessibilityIsVerticalScrollBarAttributeSettable; 118 - (id)accessibilityHorizontalScrollBarAttribute; 119 - (BOOL)accessibilityIsHorizontalScrollBarAttributeSettable; 120 @end 121 122 123 @implementation JavaComponentAccessibility 124 125 - (NSString *)description 126 { 127 return [NSString stringWithFormat:@"%@(title:'%@', desc:'%@', value:'%@')", [self accessibilityRoleAttribute], 128 [self accessibilityTitleAttribute], [self accessibilityRoleDescriptionAttribute], [self accessibilityValueAttribute]]; 129 } 130 131 - (id)initWithParent:(NSObject *)parent withEnv:(JNIEnv *)env withAccessible:(jobject)accessible withIndex:(jint)index withView:(NSView *)view withJavaRole:(NSString *)javaRole 132 { 133 self = [super init]; 134 if (self) 135 { 136 fParent = [parent retain]; 137 fView = [view retain]; 138 fJavaRole = [javaRole retain]; 139 140 fAccessible = JNFNewGlobalRef(env, accessible); 141 fComponent = JNFNewGlobalRef(env, [(AWTView *)fView awtComponent:env]); 142 143 fIndex = index; 144 145 fActions = nil; 146 fActionsLOCK = [[NSObject alloc] init]; 147 } 148 return self; 149 } 150 151 - (void)unregisterFromCocoaAXSystem 152 { 153 AWT_ASSERT_APPKIT_THREAD; 154 static dispatch_once_t initialize_unregisterUniqueId_once; 155 static void (*unregisterUniqueId)(id); 156 dispatch_once(&initialize_unregisterUniqueId_once, ^{ 157 void *jrsFwk = dlopen("/System/Library/Frameworks/JavaVM.framework/Frameworks/JavaRuntimeSupport.framework/JavaRuntimeSupport", RTLD_LAZY | RTLD_LOCAL); 158 unregisterUniqueId = dlsym(jrsFwk, "JRSAccessibilityUnregisterUniqueIdForUIElement"); 159 }); 160 if (unregisterUniqueId) unregisterUniqueId(self); 161 } 162 163 - (void)dealloc 164 { 165 [self unregisterFromCocoaAXSystem]; 166 167 JNIEnv *env = [ThreadUtilities getJNIEnvUncached]; 168 169 JNFDeleteGlobalRef(env, fAccessible); 170 fAccessible = NULL; 171 172 JNFDeleteGlobalRef(env, fComponent); 173 fComponent = NULL; 174 175 [fParent release]; 176 fParent = nil; 177 178 [fNSRole release]; 179 fNSRole = nil; 180 181 [fJavaRole release]; 182 fJavaRole = nil; 183 184 [fView release]; 185 fView = nil; 186 187 [fActions release]; 188 fActions = nil; 189 190 [fActionsLOCK release]; 191 fActionsLOCK = nil; 192 193 [super dealloc]; 194 } 195 - (void)finalize 196 { 197 [self unregisterFromCocoaAXSystem]; 198 199 JNIEnv *env = [ThreadUtilities getJNIEnvUncached]; 200 201 JNFDeleteGlobalRef(env, fAccessible); 202 fAccessible = NULL; 203 204 JNFDeleteGlobalRef(env, fComponent); 205 fComponent = NULL; 206 207 [super finalize]; 208 } 209 210 - (void)postValueChanged 211 { 212 AWT_ASSERT_APPKIT_THREAD; 213 NSAccessibilityPostNotification(self, NSAccessibilityValueChangedNotification); 214 } 215 216 - (void)postSelectionChanged 217 { 218 AWT_ASSERT_APPKIT_THREAD; 219 NSAccessibilityPostNotification(self, NSAccessibilitySelectedTextChangedNotification); 220 } 221 222 - (BOOL)isEqual:(id)anObject 223 { 224 if (![anObject isKindOfClass:[self class]]) return NO; 225 JavaComponentAccessibility *accessibility = (JavaComponentAccessibility *)anObject; 226 227 JNIEnv* env = [ThreadUtilities getJNIEnv]; 228 return (*env)->IsSameObject(env, accessibility->fAccessible, fAccessible); 229 } 230 231 - (BOOL)isAccessibleWithEnv:(JNIEnv *)env forAccessible:(jobject)accessible 232 { 233 return (*env)->IsSameObject(env, fAccessible, accessible); 234 } 235 236 + (void)initialize 237 { 238 if (sAttributeNamesForRoleCache == nil) { 239 sAttributeNamesLOCK = [[NSObject alloc] init]; 240 sAttributeNamesForRoleCache = [[NSMutableDictionary alloc] initWithCapacity:10]; 241 } 242 243 if (sRoles == nil) { 244 initializeRoles(); 245 } 246 247 if (sAccessibilityClass == NULL) { 248 JNF_STATIC_MEMBER_CACHE(jm_getAccessibility, sjc_CAccessibility, "getAccessibility", "([Ljava/lang/String;)Lsun/lwawt/macosx/CAccessibility;"); 249 250 #ifdef JAVA_AX_NO_IGNORES 251 NSArray *ignoredKeys = [NSArray array]; 252 #else 253 NSArray *ignoredKeys = [sRoles allKeysForObject:JavaAccessibilityIgnore]; 254 #endif 255 jobjectArray result = NULL; 256 jsize count = [ignoredKeys count]; 257 258 JNIEnv *env = [ThreadUtilities getJNIEnv]; 259 jclass clazz = (*env)->FindClass(env, "java/lang/String"); 260 result = (*env)->NewObjectArray(env, count, clazz, NULL); // AWT_THREADING Safe (known object) 261 (*env)->DeleteLocalRef(env, clazz); 262 263 NSUInteger i; 264 for (i = 0; i < count; i++) { 265 jstring jString = JNFNSToJavaString(env, [ignoredKeys objectAtIndex:i]); 266 (*env)->SetObjectArrayElement(env, result, i, jString); 267 (*env)->DeleteLocalRef(env, jString); 268 } 269 270 sAccessibilityClass = JNFCallStaticObjectMethod(env, jm_getAccessibility, result); // AWT_THREADING Safe (known object) 271 } 272 } 273 274 + (void)postFocusChanged:(id)message 275 { 276 AWT_ASSERT_APPKIT_THREAD; 277 NSAccessibilityPostNotification([NSApp accessibilityFocusedUIElement], NSAccessibilityFocusedUIElementChangedNotification); 278 } 279 280 + (jobject) getCAccessible:(jobject)jaccessible withEnv:(JNIEnv *)env { 281 if (JNFIsInstanceOf(env, jaccessible, &sjc_CAccessible)) { 282 return jaccessible; 283 } 284 else if (JNFIsInstanceOf(env, jaccessible, &sjc_Accessible)) { 285 return JNFCallStaticObjectMethod(env, sjm_getCAccessible, jaccessible); 286 } 287 return NULL; 288 } 289 290 + (NSArray *)childrenOfParent:(JavaComponentAccessibility *)parent withEnv:(JNIEnv *)env withChildrenCode:(NSInteger)whichChildren allowIgnored:(BOOL)allowIgnored 291 { 292 jobjectArray jchildrenAndRoles = JNFCallStaticObjectMethod(env, jm_getChildrenAndRoles, parent->fAccessible, parent->fComponent, whichChildren, allowIgnored); // AWT_THREADING Safe (AWTRunLoop) 293 if (jchildrenAndRoles == NULL) return nil; 294 295 jsize arrayLen = (*env)->GetArrayLength(env, jchildrenAndRoles); 296 NSMutableArray *children = [NSMutableArray arrayWithCapacity:arrayLen/2]; //childrenAndRoles array contains two elements (child, role) for each child 297 298 NSUInteger i; 299 NSUInteger childIndex = (whichChildren >= 0) ? whichChildren : 0; // if we're getting one particular child, make sure to set its index correctly 300 for(i = 0; i < arrayLen; i+=2) 301 { 302 jobject /* Accessible */ jchild = (*env)->GetObjectArrayElement(env, jchildrenAndRoles, i); 303 jobject /* String */ jchildJavaRole = (*env)->GetObjectArrayElement(env, jchildrenAndRoles, i+1); 304 305 NSString *childJavaRole = nil; 306 if (jchildJavaRole != NULL) { 307 childJavaRole = JNFJavaToNSString(env, JNFGetObjectField(env, jchildJavaRole, sjf_key)); 308 } 309 310 JavaComponentAccessibility *child = [self createWithParent:parent accessible:jchild role:childJavaRole index:childIndex withEnv:env withView:parent->fView]; 311 [children addObject:child]; 312 childIndex++; 313 } 314 315 return children; 316 } 317 318 + (JavaComponentAccessibility *)createWithAccessible:(jobject)jaccessible withEnv:(JNIEnv *)env withView:(NSView *)view 319 { 320 jobject jcomponent = [(AWTView *)view awtComponent:env]; 321 jint index = JNFCallStaticIntMethod(env, sjm_getAccessibleIndexInParent, jaccessible, jcomponent); 322 NSString *javaRole = getJavaRole(env, jaccessible, jcomponent); 323 324 return [self createWithAccessible:jaccessible role:javaRole index:index withEnv:env withView:view]; 325 } 326 327 + (JavaComponentAccessibility *) createWithAccessible:(jobject)jaccessible role:(NSString *)javaRole index:(jint)index withEnv:(JNIEnv *)env withView:(NSView *)view 328 { 329 return [self createWithParent:nil accessible:jaccessible role:javaRole index:index withEnv:env withView:view]; 330 } 331 332 + (JavaComponentAccessibility *) createWithParent:(JavaComponentAccessibility *)parent accessible:(jobject)jaccessible role:(NSString *)javaRole index:(jint)index withEnv:(JNIEnv *)env withView:(NSView *)view 333 { 334 // try to fetch the jCAX from Java, and return autoreleased 335 jobject jCAX = [JavaComponentAccessibility getCAccessible:jaccessible withEnv:env]; 336 if (jCAX == NULL) return nil; 337 JavaComponentAccessibility *value = (JavaComponentAccessibility *) jlong_to_ptr(JNFGetLongField(env, jCAX, jf_ptr)); 338 if (value != nil) return [[value retain] autorelease]; 339 340 // otherwise, create a new instance 341 JavaComponentAccessibility *newChild = nil; 342 if ([javaRole isEqualToString:@"pagetablist"]) { 343 newChild = [TabGroupAccessibility alloc]; 344 } else if ([javaRole isEqualToString:@"scrollpane"]) { 345 newChild = [ScrollAreaAccessibility alloc]; 346 } else { 347 NSString *nsRole = [sRoles objectForKey:javaRole]; 348 if ([nsRole isEqualToString:NSAccessibilityStaticTextRole] || [nsRole isEqualToString:NSAccessibilityTextAreaRole] || [nsRole isEqualToString:NSAccessibilityTextFieldRole]) { 349 newChild = [JavaTextAccessibility alloc]; 350 } else { 351 newChild = [JavaComponentAccessibility alloc]; 352 } 353 } 354 355 // must init freshly -alloc'd object 356 [newChild initWithParent:parent withEnv:env withAccessible:jCAX withIndex:index withView:view withJavaRole:javaRole]; // must init new instance 357 358 // must hard CFRetain() pointer poked into Java object 359 CFRetain(newChild); 360 JNFSetLongField(env, jCAX, jf_ptr, ptr_to_jlong(newChild)); 361 362 // return autoreleased instance 363 return [newChild autorelease]; 364 } 365 366 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env 367 { 368 static JNF_STATIC_MEMBER_CACHE(jm_getInitialAttributeStates, sjc_CAccessibility, "getInitialAttributeStates", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)[Z"); 369 370 NSMutableArray *attributeNames = [NSMutableArray arrayWithCapacity:10]; 371 [attributeNames retain]; 372 373 // all elements respond to parent, role, role description, window, topLevelUIElement, help 374 [attributeNames addObject:NSAccessibilityParentAttribute]; 375 [attributeNames addObject:NSAccessibilityRoleAttribute]; 376 [attributeNames addObject:NSAccessibilityRoleDescriptionAttribute]; 377 [attributeNames addObject:NSAccessibilityHelpAttribute]; 378 379 // cmcnote: AXMenu usually doesn't respond to window / topLevelUIElement. But menus within a Java app's window 380 // probably should. Should we use some role other than AXMenu / AXMenuBar for Java menus? 381 [attributeNames addObject:NSAccessibilityWindowAttribute]; 382 [attributeNames addObject:NSAccessibilityTopLevelUIElementAttribute]; 383 384 // set accessible subrole 385 NSString *javaRole = [self javaRole]; 386 if (javaRole != nil && [javaRole isEqualToString:@"passwordtext"]) { 387 //cmcnote: should turn this into a constant 388 [attributeNames addObject:NSAccessibilitySubroleAttribute]; 389 } 390 391 // Get all the other accessibility attributes states we need in one swell foop. 392 // javaRole isn't pulled in because we need protected access to AccessibleRole.key 393 jbooleanArray attributeStates = JNFCallStaticObjectMethod(env, jm_getInitialAttributeStates, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop) 394 if (attributeStates == NULL) return NULL; 395 jboolean *attributeStatesArray = (*env)->GetBooleanArrayElements(env, attributeStates, 0); 396 397 // if there's a component, it can be enabled and it has a size/position 398 if (attributeStatesArray[0]) { 399 [attributeNames addObject:NSAccessibilityEnabledAttribute]; 400 [attributeNames addObject:NSAccessibilitySizeAttribute]; 401 [attributeNames addObject:NSAccessibilityPositionAttribute]; 402 } 403 404 // According to javadoc, a component that is focusable will return true from isFocusTraversable, 405 // as well as having AccessibleState.FOCUSABLE in it's AccessibleStateSet. 406 // We use the former heuristic; if the component focus-traversable, add a focused attribute 407 // See also: accessibilityIsFocusedAttributeSettable 408 if (attributeStatesArray[1]) 409 { 410 [attributeNames addObject:NSAccessibilityFocusedAttribute]; 411 } 412 413 // if it's a pagetab / radiobutton, it has a value but no min/max value. 414 BOOL hasAxValue = attributeStatesArray[2]; 415 if ([javaRole isEqualToString:@"pagetab"] || [javaRole isEqualToString:@"radiobutton"]) { 416 [attributeNames addObject:NSAccessibilityValueAttribute]; 417 } else { 418 // if not a pagetab/radio button, and it has a value, it has a min/max/current value. 419 if (hasAxValue) { 420 // er, it has a min/max/current value if it's not a button. 421 // See AppKit/NSButtonCellAccessibility.m 422 if (![javaRole isEqualToString:@"pushbutton"]) { 423 //cmcnote: make this (and "passwordtext") constants instead of magic strings 424 [attributeNames addObject:NSAccessibilityMinValueAttribute]; 425 [attributeNames addObject:NSAccessibilityMaxValueAttribute]; 426 [attributeNames addObject:NSAccessibilityValueAttribute]; 427 } 428 } 429 } 430 431 // does it have an orientation? 432 if (attributeStatesArray[4]) { 433 [attributeNames addObject:NSAccessibilityOrientationAttribute]; 434 } 435 436 // name 437 if (attributeStatesArray[5]) { 438 [attributeNames addObject:NSAccessibilityTitleAttribute]; 439 } 440 441 // children 442 if (attributeStatesArray[6]) { 443 [attributeNames addObject:NSAccessibilityChildrenAttribute]; 444 // [attributeNames addObject:NSAccessibilitySelectedChildrenAttribute]; 445 // [attributeNames addObject:NSAccessibilityVisibleChildrenAttribute]; 446 //According to AXRoles.txt: 447 //VisibleChildren: radio group, list, row, table row subrole 448 //SelectedChildren: list 449 } 450 451 // Cleanup 452 (*env)->ReleaseBooleanArrayElements(env, attributeStates, attributeStatesArray, JNI_ABORT); 453 454 return attributeNames; 455 } 456 457 - (NSDictionary *)getActions:(JNIEnv *)env 458 { 459 @synchronized(fActionsLOCK) { 460 if (fActions == nil) { 461 fActions = [[NSMutableDictionary alloc] initWithCapacity:3]; 462 [self getActionsWithEnv:env]; 463 } 464 } 465 466 return fActions; 467 } 468 469 - (void)getActionsWithEnv:(JNIEnv *)env 470 { 471 static JNF_STATIC_MEMBER_CACHE(jm_getAccessibleAction, sjc_CAccessibility, "getAccessibleAction", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/AccessibleAction;"); 472 473 // On MacOSX, text doesn't have actions, in java it does. 474 // cmcnote: NOT TRUE - Editable text has AXShowMenu. Textfields have AXConfirm. Static text has no actions. 475 jobject axAction = JNFCallStaticObjectMethod(env, jm_getAccessibleAction, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop) 476 if (axAction != NULL) { 477 //+++gdb NOTE: In MacOSX, there is just a single Action, not multiple. In java, 478 // the first one seems to be the most basic, so this will be used. 479 // cmcnote: NOT TRUE - Sometimes there are multiple actions, eg sliders have AXDecrement AND AXIncrement (radr://3893192) 480 JavaAxAction *action = [[JavaAxAction alloc] initWithEnv:env withAccessibleAction:axAction withIndex:0 withComponent:fComponent]; 481 [fActions setObject:action forKey:[self isMenu] ? NSAccessibilityPickAction : NSAccessibilityPressAction]; 482 [action release]; 483 } 484 } 485 486 - (jobject)axContextWithEnv:(JNIEnv *)env 487 { 488 return getAxContext(env, fAccessible, fComponent); 489 } 490 491 - (id)parent 492 { 493 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleParent, sjc_CAccessibility, "getAccessibleParent", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/Accessible;"); 494 495 if(fParent == nil) { 496 JNIEnv* env = [ThreadUtilities getJNIEnv]; 497 498 jobject jparent = JNFCallStaticObjectMethod(env, sjm_getAccessibleParent, fAccessible, fComponent); 499 500 if (jparent == NULL) { 501 fParent = fView; 502 } else { 503 fParent = [JavaComponentAccessibility createWithAccessible:jparent withEnv:env withView:fView]; 504 if (fParent == nil) { 505 fParent = fView; 506 } 507 } 508 [fParent retain]; 509 } 510 return fParent; 511 } 512 513 - (NSView *)view 514 { 515 return fView; 516 } 517 518 - (NSWindow *)window 519 { 520 return [[self view] window]; 521 } 522 523 - (NSString *)javaRole 524 { 525 if(fJavaRole == nil) { 526 JNIEnv* env = [ThreadUtilities getJNIEnv]; 527 fJavaRole = getJavaRole(env, fAccessible, fComponent); 528 [fJavaRole retain]; 529 } 530 return fJavaRole; 531 } 532 533 - (BOOL)isMenu 534 { 535 id role = [self accessibilityRoleAttribute]; 536 return [role isEqualToString:NSAccessibilityMenuBarRole] || [role isEqualToString:NSAccessibilityMenuRole] || [role isEqualToString:NSAccessibilityMenuItemRole]; 537 } 538 539 - (BOOL)isSelected:(JNIEnv *)env 540 { 541 if (fIndex == -1) { 542 return NO; 543 } 544 545 return isChildSelected(env, ((JavaComponentAccessibility *)[self parent])->fAccessible, fIndex, fComponent); 546 } 547 548 - (BOOL)isVisible:(JNIEnv *)env 549 { 550 if (fIndex == -1) { 551 return NO; 552 } 553 554 return isShowing(env, [self axContextWithEnv:env], fComponent); 555 } 556 557 // the array of names for each role is cached in the sAttributeNamesForRoleCache 558 - (NSArray *)accessibilityAttributeNames 559 { 560 JNIEnv* env = [ThreadUtilities getJNIEnv]; 561 562 @synchronized(sAttributeNamesLOCK) { 563 NSString *javaRole = [self javaRole]; 564 NSArray *names = (NSArray *)[sAttributeNamesForRoleCache objectForKey:javaRole]; 565 if (names != nil) return names; 566 567 names = [self initializeAttributeNamesWithEnv:env]; 568 if (names != nil) { 569 #ifdef JAVA_AX_DEBUG 570 NSLog(@"Initializing: %s for %@: %@", __FUNCTION__, javaRole, names); 571 #endif 572 [sAttributeNamesForRoleCache setObject:names forKey:javaRole]; 573 return names; 574 } 575 } 576 577 #ifdef JAVA_AX_DEBUG 578 NSLog(@"Warning in %s: could not find attribute names for role: %@", __FUNCTION__, [self javaRole]); 579 #endif 580 581 return nil; 582 } 583 584 // -- accessibility attributes -- 585 586 - (BOOL)accessibilityShouldUseUniqueId { 587 return YES; 588 } 589 590 - (BOOL)accessibilitySupportsOverriddenAttributes { 591 return YES; 592 } 593 594 595 // generic getters & setters 596 // cmcnote: it would make more sense if these generic getters/setters were in JavaAccessibilityUtilities 597 - (id)accessibilityAttributeValue:(NSString *)attribute 598 { 599 AWT_ASSERT_APPKIT_THREAD; 600 601 // turns attribute "NSAccessibilityEnabledAttribute" into getter "accessibilityEnabledAttribute", 602 // calls getter on self 603 return JavaAccessibilityAttributeValue(self, attribute); 604 } 605 606 - (BOOL)accessibilityIsAttributeSettable:(NSString *)attribute 607 { 608 AWT_ASSERT_APPKIT_THREAD; 609 610 // turns attribute "NSAccessibilityParentAttribute" into selector "accessibilityIsParentAttributeSettable", 611 // calls selector on self 612 return JavaAccessibilityIsAttributeSettable(self, attribute); 613 } 614 615 - (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute 616 { 617 AWT_ASSERT_APPKIT_THREAD; 618 619 if ([self accessibilityIsAttributeSettable:attribute]) { 620 // turns attribute "NSAccessibilityFocusAttribute" into setter "accessibilitySetFocusAttribute", 621 // calls setter on self 622 JavaAccessibilitySetAttributeValue(self, attribute, value); 623 } 624 } 625 626 627 // specific attributes, in alphabetical order a la 628 // http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Protocols/NSAccessibility.html 629 630 // Elements that current element contains (NSArray) 631 - (NSArray *)accessibilityChildrenAttribute 632 { 633 JNIEnv* env = [ThreadUtilities getJNIEnv]; 634 NSArray *children = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_VISIBLE_CHILDREN allowIgnored:NO]; 635 636 NSArray *value = nil; 637 if ([children count] > 0) { 638 value = children; 639 } 640 641 return value; 642 } 643 - (BOOL)accessibilityIsChildrenAttributeSettable 644 { 645 return NO; 646 } 647 648 - (NSUInteger)accessibilityIndexOfChild:(id)child 649 { 650 // Only special-casing for Lists, for now. This allows lists to be accessible, fixing radr://3856139 "JLists are broken". 651 // Will probably want to special-case for Tables when we implement them (radr://3096643 "Accessibility: Table"). 652 // In AppKit, NSMatrixAccessibility (which uses NSAccessibilityListRole), NSTableRowAccessibility, and NSTableViewAccessibility are the 653 // only ones that override the default implementation in NSAccessibility 654 if (![[self accessibilityRoleAttribute] isEqualToString:NSAccessibilityListRole]) { 655 return [super accessibilityIndexOfChild:child]; 656 } 657 658 return JNFCallStaticIntMethod([ThreadUtilities getJNIEnv], sjm_getAccessibleIndexInParent, ((JavaComponentAccessibility *)child)->fAccessible, ((JavaComponentAccessibility *)child)->fComponent); 659 } 660 661 // Without this optimization accessibilityChildrenAttribute is called in order to get the entire array of children. 662 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount { 663 if ( (maxCount == 1) && [attribute isEqualToString:NSAccessibilityChildrenAttribute]) { 664 // Children codes for ALL, SELECTED, VISIBLE are <0. If the code is >=0, we treat it as an index to a single child 665 NSArray *child = [JavaComponentAccessibility childrenOfParent:self withEnv:[ThreadUtilities getJNIEnv] withChildrenCode:(NSInteger)index allowIgnored:NO]; 666 if ([child count] > 0) { 667 return child; 668 } 669 } 670 return [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount]; 671 } 672 673 // Flag indicating enabled state of element (NSNumber) 674 - (NSNumber *)accessibilityEnabledAttribute 675 { 676 static JNF_STATIC_MEMBER_CACHE(jm_isEnabled, sjc_CAccessibility, "isEnabled", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Z"); 677 678 JNIEnv* env = [ThreadUtilities getJNIEnv]; 679 NSNumber *value = [NSNumber numberWithBool:JNFCallStaticBooleanMethod(env, jm_isEnabled, fAccessible, fComponent)]; // AWT_THREADING Safe (AWTRunLoop) 680 if (value == nil) { 681 NSLog(@"WARNING: %s called on component that has no accessible component: %@", __FUNCTION__, self); 682 } 683 return value; 684 } 685 686 - (BOOL)accessibilityIsEnabledAttributeSettable 687 { 688 return NO; 689 } 690 691 // Flag indicating presence of keyboard focus (NSNumber) 692 - (NSNumber *)accessibilityFocusedAttribute 693 { 694 if ([self accessibilityIsFocusedAttributeSettable]) { 695 return [NSNumber numberWithBool:[self isEqual:[NSApp accessibilityFocusedUIElement]]]; 696 } 697 return [NSNumber numberWithBool:NO]; 698 } 699 700 - (BOOL)accessibilityIsFocusedAttributeSettable 701 { 702 JNIEnv* env = [ThreadUtilities getJNIEnv]; 703 // According to javadoc, a component that is focusable will return true from isFocusTraversable, 704 // as well as having AccessibleState.FOCUSABLE in its AccessibleStateSet. 705 // We use the former heuristic; if the component focus-traversable, add a focused attribute 706 // See also initializeAttributeNamesWithEnv: 707 if (JNFCallStaticBooleanMethod(env, sjm_isFocusTraversable, fAccessible, fComponent)) { // AWT_THREADING Safe (AWTRunLoop) 708 return YES; 709 } 710 711 return NO; 712 } 713 714 - (void)accessibilitySetFocusedAttribute:(id)value 715 { 716 static JNF_STATIC_MEMBER_CACHE(jm_requestFocus, sjc_CAccessibility, "requestFocus", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)V"); 717 718 if ([(NSNumber*)value boolValue]) 719 { 720 JNIEnv* env = [ThreadUtilities getJNIEnv]; 721 JNFCallStaticVoidMethod(env, jm_requestFocus, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop) 722 } 723 } 724 725 // Instance description, such as a help tag string (NSString) 726 - (NSString *)accessibilityHelpAttribute 727 { 728 JNIEnv* env = [ThreadUtilities getJNIEnv]; 729 730 jobject val = JNFCallStaticObjectMethod(env, sjm_getAccessibleDescription, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop) 731 return JNFJavaToNSString(env, val); 732 } 733 734 - (BOOL)accessibilityIsHelpAttributeSettable 735 { 736 return NO; 737 } 738 739 // Element's maximum value (id) 740 - (id)accessibilityMaxValueAttribute 741 { 742 static JNF_STATIC_MEMBER_CACHE(jm_getMaximumAccessibleValue, sjc_CAccessibility, "getMaximumAccessibleValue", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/Number;"); 743 744 JNIEnv* env = [ThreadUtilities getJNIEnv]; 745 746 jobject axValue = JNFCallStaticObjectMethod(env, jm_getMaximumAccessibleValue, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop) 747 return JNFJavaToNSNumber(env, axValue); 748 } 749 750 - (BOOL)accessibilityIsMaxValueAttributeSettable 751 { 752 return NO; 753 } 754 755 // Element's minimum value (id) 756 - (id)accessibilityMinValueAttribute 757 { 758 static JNF_STATIC_MEMBER_CACHE(jm_getMinimumAccessibleValue, sjc_CAccessibility, "getMinimumAccessibleValue", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/Number;"); 759 760 JNIEnv* env = [ThreadUtilities getJNIEnv]; 761 762 jobject axValue = JNFCallStaticObjectMethod(env, jm_getMinimumAccessibleValue, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop) 763 return JNFJavaToNSNumber(env, axValue); 764 } 765 766 - (BOOL)accessibilityIsMinValueAttributeSettable 767 { 768 return NO; 769 } 770 771 - (id)accessibilityOrientationAttribute 772 { 773 JNIEnv* env = [ThreadUtilities getJNIEnv]; 774 jobject axContext = [self axContextWithEnv:env]; 775 776 // cmcnote - should batch these two calls into one that returns an array of two bools, one for vertical and one for horiz 777 if (isVertical(env, axContext, fComponent)) { 778 return NSAccessibilityVerticalOrientationValue; 779 } 780 781 if (isHorizontal(env, axContext, fComponent)) { 782 return NSAccessibilityHorizontalOrientationValue; 783 } 784 785 return nil; 786 } 787 788 - (BOOL)accessibilityIsOrientationAttributeSettable 789 { 790 return NO; 791 } 792 793 // Element containing current element (id) 794 - (id)accessibilityParentAttribute 795 { 796 return NSAccessibilityUnignoredAncestor([self parent]); 797 } 798 799 - (BOOL)accessibilityIsParentAttributeSettable 800 { 801 return NO; 802 } 803 804 // Screen position of element's lower-left corner in lower-left relative screen coordinates (NSValue) 805 - (NSValue *)accessibilityPositionAttribute 806 { 807 JNIEnv* env = [ThreadUtilities getJNIEnv]; 808 jobject axComponent = JNFCallStaticObjectMethod(env, sjm_getAccessibleComponent, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop) 809 810 // NSAccessibility wants the bottom left point of the object in 811 // bottom left based screen coords 812 813 // Get the java screen coords, and make a NSPoint of the bottom left of the AxComponent. 814 NSSize size = getAxComponentSize(env, axComponent, fComponent); 815 NSPoint point = getAxComponentLocationOnScreen(env, axComponent, fComponent); 816 817 point.y += size.height; 818 819 // Now make it into Cocoa screen coords. 820 point.y = [[[[self view] window] screen] frame].size.height - point.y; 821 822 return [NSValue valueWithPoint:point]; 823 } 824 825 - (BOOL)accessibilityIsPositionAttributeSettable 826 { 827 // In AppKit, position is only settable for a window (NSAccessibilityWindowRole). Our windows are taken care of natively, so we don't need to deal with this here 828 // We *could* make use of Java's AccessibleComponent.setLocation() method. Investigate. radr://3953869 829 return NO; 830 } 831 832 // Element type, such as NSAccessibilityRadioButtonRole (NSString). See the role table 833 // at http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Protocols/NSAccessibility.html 834 - (NSString *)accessibilityRoleAttribute 835 { 836 if (fNSRole == nil) { 837 NSString *javaRole = [self javaRole]; 838 fNSRole = [sRoles objectForKey:javaRole]; 839 if (fNSRole == nil) { 840 // this component has assigned itself a custom AccessibleRole not in the sRoles array 841 fNSRole = javaRole; 842 } 843 [fNSRole retain]; 844 } 845 return fNSRole; 846 } 847 - (BOOL)accessibilityIsRoleAttributeSettable 848 { 849 return NO; 850 } 851 852 // Localized, user-readable description of role, such as radio button (NSString) 853 - (NSString *)accessibilityRoleDescriptionAttribute 854 { 855 // first ask AppKit for its accessible role description for a given AXRole 856 NSString *value = NSAccessibilityRoleDescription([self accessibilityRoleAttribute], nil); 857 858 if (value == nil) { 859 // query java if necessary 860 static JNF_STATIC_MEMBER_CACHE(jm_getAccessibleRoleDisplayString, sjc_CAccessibility, "getAccessibleRoleDisplayString", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;"); 861 862 JNIEnv* env = [ThreadUtilities getJNIEnv]; 863 864 jobject axRole = JNFCallStaticObjectMethod(env, jm_getAccessibleRoleDisplayString, fAccessible, fComponent); 865 if(axRole != NULL) { 866 value = JNFJavaToNSString(env, axRole); 867 } else { 868 value = @"unknown"; 869 } 870 } 871 872 return value; 873 } 874 875 - (BOOL)accessibilityIsRoleDescriptionAttributeSettable 876 { 877 return NO; 878 } 879 880 // Currently selected children (NSArray) 881 - (NSArray *)accessibilitySelectedChildrenAttribute 882 { 883 JNIEnv* env = [ThreadUtilities getJNIEnv]; 884 NSArray *selectedChildren = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_SELECTED_CHILDREN allowIgnored:NO]; 885 if ([selectedChildren count] > 0) { 886 return selectedChildren; 887 } 888 889 return nil; 890 } 891 892 - (BOOL)accessibilityIsSelectedChildrenAttributeSettable 893 { 894 return NO; // cmcnote: actually it should be. so need to write accessibilitySetSelectedChildrenAttribute also 895 } 896 897 // Element size (NSValue) 898 - (NSValue *)accessibilitySizeAttribute { 899 JNIEnv* env = [ThreadUtilities getJNIEnv]; 900 jobject axComponent = JNFCallStaticObjectMethod(env, sjm_getAccessibleComponent, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop) 901 return [NSValue valueWithSize:getAxComponentSize(env, axComponent, fComponent)]; 902 } 903 904 - (BOOL)accessibilityIsSizeAttributeSettable 905 { 906 // SIZE is settable in windows if [self styleMask] & NSResizableWindowMask - but windows are heavyweight so we're ok here 907 // SIZE is settable in columns if [[self tableValue] allowsColumnResizing - haven't dealt with columns yet 908 return NO; 909 } 910 911 // Element subrole type, such as NSAccessibilityTableRowSubrole (NSString). See the subrole attribute table at 912 // http://developer.apple.com/documentation/Cocoa/Reference/ApplicationKit/ObjC_classic/Protocols/NSAccessibility.html 913 - (NSString *)accessibilitySubroleAttribute 914 { 915 NSString *value = nil; 916 if ([[self javaRole] isEqualToString:@"passwordtext"]) 917 { 918 value = NSAccessibilitySecureTextFieldSubrole; 919 } 920 /* 921 // other subroles. TableRow and OutlineRow may be relevant to us 922 NSAccessibilityCloseButtonSubrole // no, heavyweight window takes care of this 923 NSAccessibilityMinimizeButtonSubrole // " 924 NSAccessibilityOutlineRowSubrole // maybe? 925 NSAccessibilitySecureTextFieldSubrole // currently used 926 NSAccessibilityTableRowSubrole // maybe? 927 NSAccessibilityToolbarButtonSubrole // maybe? 928 NSAccessibilityUnknownSubrole 929 NSAccessibilityZoomButtonSubrole // no, heavyweight window takes care of this 930 NSAccessibilityStandardWindowSubrole// no, heavyweight window takes care of this 931 NSAccessibilityDialogSubrole // maybe? 932 NSAccessibilitySystemDialogSubrole // no 933 NSAccessibilityFloatingWindowSubrole // in 1.5 if we implement these, heavyweight will take care of them anyway 934 NSAccessibilitySystemFloatingWindowSubrole 935 NSAccessibilityIncrementArrowSubrole // no 936 NSAccessibilityDecrementArrowSubrole // no 937 NSAccessibilityIncrementPageSubrole // no 938 NSAccessibilityDecrementPageSubrole // no 939 NSAccessibilitySearchFieldSubrole //no 940 */ 941 return value; 942 } 943 944 - (BOOL)accessibilityIsSubroleAttributeSettable 945 { 946 return NO; 947 } 948 949 // Title of element, such as button text (NSString) 950 - (NSString *)accessibilityTitleAttribute 951 { 952 // Return empty string for labels, since their value and tile end up being the same thing and this leads to repeated text. 953 if ([[self accessibilityRoleAttribute] isEqualToString:NSAccessibilityStaticTextRole]) { 954 return @""; 955 } 956 957 JNIEnv* env = [ThreadUtilities getJNIEnv]; 958 959 jobject val = JNFCallStaticObjectMethod(env, sjm_getAccessibleName, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop) 960 return JNFJavaToNSString(env, val); 961 } 962 963 - (BOOL)accessibilityIsTitleAttributeSettable 964 { 965 return NO; 966 } 967 968 - (NSWindow *)accessibilityTopLevelUIElementAttribute 969 { 970 return [self window]; 971 } 972 973 - (BOOL)accessibilityIsTopLevelUIElementAttributeSettable 974 { 975 return NO; 976 } 977 978 // Element's value (id) 979 // note that the appKit meaning of "accessibilityValue" is different from the java 980 // meaning of "accessibleValue", which is specific to numerical values 981 // (http://java.sun.com/j2se/1.3/docs/api/javax/accessibility/AccessibleValue.html#setCurrentAccessibleValue(java.lang.Number)) 982 - (id)accessibilityValueAttribute 983 { 984 static JNF_STATIC_MEMBER_CACHE(jm_getCurrentAccessibleValue, sjc_CAccessibility, "getCurrentAccessibleValue", "(Ljavax/accessibility/AccessibleValue;Ljava/awt/Component;)Ljava/lang/Number;"); 985 986 JNIEnv* env = [ThreadUtilities getJNIEnv]; 987 988 // ask Java for the component's accessibleValue. In java, the "accessibleValue" just means a numerical value 989 // a text value is taken care of in JavaTextAccessibility 990 991 // cmcnote should coalesce these calls into one java call 992 jobject axValue = JNFCallStaticObjectMethod(env, sjm_getAccessibleValue, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop) 993 return JNFJavaToNSNumber(env, JNFCallStaticObjectMethod(env, jm_getCurrentAccessibleValue, axValue, fComponent)); // AWT_THREADING Safe (AWTRunLoop) 994 } 995 996 - (BOOL)accessibilityIsValueAttributeSettable 997 { 998 // according ot AppKit sources, in general the value attribute is not settable, except in the cases 999 // of an NSScroller, an NSSplitView, and text that's both enabled & editable 1000 BOOL isSettable = NO; 1001 NSString *role = [self accessibilityRoleAttribute]; 1002 1003 if ([role isEqualToString:NSAccessibilityScrollBarRole] || // according to NSScrollerAccessibility 1004 [role isEqualToString:NSAccessibilitySplitGroupRole] ) // according to NSSplitViewAccessibility 1005 { 1006 isSettable = YES; 1007 } 1008 return isSettable; 1009 } 1010 1011 - (void)accessibilitySetValueAttribute:(id)value 1012 { 1013 #ifdef JAVA_AX_DEBUG 1014 NSLog(@"Not yet implemented: %s\n", __FUNCTION__); // radr://3954018 1015 #endif 1016 } 1017 1018 1019 // Child elements that are visible (NSArray) 1020 - (NSArray *)accessibilityVisibleChildrenAttribute 1021 { 1022 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1023 NSArray *visibleChildren = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_VISIBLE_CHILDREN allowIgnored:NO]; 1024 if ([visibleChildren count] <= 0) return nil; 1025 return visibleChildren; 1026 } 1027 1028 - (BOOL)accessibilityIsVisibleChildrenAttributeSettable 1029 { 1030 return NO; 1031 } 1032 1033 // Window containing current element (id) 1034 - (id)accessibilityWindowAttribute 1035 { 1036 return [self window]; 1037 } 1038 1039 - (BOOL)accessibilityIsWindowAttributeSettable 1040 { 1041 return NO; 1042 } 1043 1044 1045 // -- accessibility actions -- 1046 - (NSArray *)accessibilityActionNames 1047 { 1048 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1049 return [[self getActions:env] allKeys]; 1050 } 1051 1052 - (NSString *)accessibilityActionDescription:(NSString *)action 1053 { 1054 AWT_ASSERT_APPKIT_THREAD; 1055 1056 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1057 return [(id <JavaAccessibilityAction>)[[self getActions:env] objectForKey:action] getDescription]; 1058 } 1059 1060 - (void)accessibilityPerformAction:(NSString *)action 1061 { 1062 AWT_ASSERT_APPKIT_THREAD; 1063 1064 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1065 [(id <JavaAccessibilityAction>)[[self getActions:env] objectForKey:action] perform]; 1066 } 1067 1068 1069 // -- misc accessibility -- 1070 - (BOOL)accessibilityIsIgnored 1071 { 1072 #ifdef JAVA_AX_NO_IGNORES 1073 return NO; 1074 #else 1075 return [[self accessibilityRoleAttribute] isEqualToString:JavaAccessibilityIgnore]; 1076 #endif /* JAVA_AX_NO_IGNORES */ 1077 } 1078 1079 - (id)accessibilityHitTest:(NSPoint)point withEnv:(JNIEnv *)env 1080 { 1081 static JNF_CLASS_CACHE(jc_Container, "java/awt/Container"); 1082 static JNF_STATIC_MEMBER_CACHE(jm_accessibilityHitTest, sjc_CAccessibility, "accessibilityHitTest", "(Ljava/awt/Container;FF)Ljavax/accessibility/Accessible;"); 1083 1084 // Make it into java screen coords 1085 point.y = [[[[self view] window] screen] frame].size.height - point.y; 1086 1087 jobject jparent = fComponent; 1088 1089 id value = nil; 1090 if (JNFIsInstanceOf(env, jparent, &jc_Container)) { 1091 jobject jaccessible = JNFCallStaticObjectMethod(env, jm_accessibilityHitTest, jparent, (jfloat)point.x, (jfloat)point.y); // AWT_THREADING Safe (AWTRunLoop) 1092 value = [JavaComponentAccessibility createWithAccessible:jaccessible withEnv:env withView:fView]; 1093 } 1094 1095 if (value == nil) { 1096 value = self; 1097 } 1098 1099 if ([value accessibilityIsIgnored]) { 1100 value = NSAccessibilityUnignoredAncestor(value); 1101 } 1102 1103 #ifdef JAVA_AX_DEBUG 1104 NSLog(@"%s: %@", __FUNCTION__, value); 1105 #endif 1106 return value; 1107 } 1108 1109 - (id)accessibilityFocusedUIElement 1110 { 1111 static JNF_STATIC_MEMBER_CACHE(jm_getFocusOwner, sjc_CAccessibility, "getFocusOwner", "(Ljava/awt/Component;)Ljavax/accessibility/Accessible;"); 1112 1113 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1114 id value = nil; 1115 1116 // This code frequently gets called indirectly by Java when VoiceOver is active. 1117 // Basically, we just have to know when we going to be a bad state, and do something "special". 1118 // Note that while NSApplication isn't technically correct, we post a focus changed notification 1119 // (which will call this method, but with the correct codepath) shortly afterwards. See +postFocusChanged. 1120 if (sInPerformFromJava) { 1121 return [NSApplication sharedApplication]; 1122 } else { 1123 jobject focused = JNFCallStaticObjectMethod(env, jm_getFocusOwner, fComponent); // AWT_THREADING Safe (AWTRunLoop) 1124 if (focused != NULL) { 1125 if (JNFIsInstanceOf(env, focused, &sjc_Accessible)) { 1126 value = [JavaComponentAccessibility createWithAccessible:focused withEnv:env withView:fView]; 1127 } 1128 } 1129 } 1130 1131 if (value == nil) { 1132 value = self; 1133 } 1134 #ifdef JAVA_AX_DEBUG 1135 NSLog(@"%s: %@", __FUNCTION__, value); 1136 #endif 1137 return value; 1138 } 1139 1140 @end 1141 1142 /* 1143 * Class: sun_lwawt_macosx_CAccessibility 1144 * Method: focusChanged 1145 * Signature: ()V 1146 */ 1147 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessibility_focusChanged 1148 (JNIEnv *env, jobject jthis) 1149 { 1150 AWT_ASSERT_NOT_APPKIT_THREAD; 1151 1152 JNF_COCOA_ENTER(env); 1153 [ThreadUtilities performOnMainThread:@selector(postFocusChanged:) onObject:[JavaComponentAccessibility class] withObject:nil waitUntilDone:NO awtMode:NO]; 1154 JNF_COCOA_EXIT(env); 1155 } 1156 1157 1158 1159 /* 1160 * Class: sun_lwawt_macosx_CAccessible 1161 * Method: valueChanged 1162 * Signature: (I)V 1163 */ 1164 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_valueChanged 1165 (JNIEnv *env, jclass jklass, jlong element) 1166 { 1167 AWT_ASSERT_NOT_APPKIT_THREAD; 1168 JNF_COCOA_ENTER(env); 1169 [ThreadUtilities performOnMainThread:@selector(postValueChanged) onObject:(JavaComponentAccessibility *)jlong_to_ptr(element) withObject:nil waitUntilDone:NO awtMode:NO]; 1170 JNF_COCOA_EXIT(env); 1171 } 1172 1173 /* 1174 * Class: sun_lwawt_macosx_CAccessible 1175 * Method: selectionChanged 1176 * Signature: (I)V 1177 */ 1178 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_selectionChanged 1179 (JNIEnv *env, jclass jklass, jlong element) 1180 { 1181 AWT_ASSERT_NOT_APPKIT_THREAD; 1182 JNF_COCOA_ENTER(env); 1183 [ThreadUtilities performOnMainThread:@selector(postSelectionChanged) onObject:(JavaComponentAccessibility *)jlong_to_ptr(element) withObject:nil waitUntilDone:NO awtMode:NO]; 1184 JNF_COCOA_EXIT(env); 1185 } 1186 1187 1188 /* 1189 * Class: sun_lwawt_macosx_CAccessible 1190 * Method: unregisterFromCocoaAXSystem 1191 * Signature: (I)V 1192 */ 1193 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_unregisterFromCocoaAXSystem 1194 (JNIEnv *env, jclass jklass, jlong element) 1195 { 1196 AWT_ASSERT_NOT_APPKIT_THREAD; 1197 JNF_COCOA_ENTER(env); 1198 [ThreadUtilities performOnMainThread:@selector(unregisterFromCocoaAXSystem) onObject:(JavaComponentAccessibility *)jlong_to_ptr(element) withObject:nil waitUntilDone:NO awtMode:NO]; 1199 JNF_COCOA_EXIT(env); 1200 } 1201 1202 @implementation TabGroupAccessibility 1203 1204 - (id)initWithParent:(NSObject *)parent withEnv:(JNIEnv *)env withAccessible:(jobject)accessible withIndex:(jint)index withView:(NSView *)view withJavaRole:(NSString *)javaRole 1205 { 1206 self = [super initWithParent:parent withEnv:env withAccessible:accessible withIndex:index withView:view withJavaRole:javaRole]; 1207 if (self) { 1208 _numTabs = -1; //flag for uninitialized numTabs 1209 } 1210 return self; 1211 } 1212 1213 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env 1214 { 1215 NSMutableArray *names = (NSMutableArray *)[super initializeAttributeNamesWithEnv:env]; 1216 1217 [names addObject:NSAccessibilityTabsAttribute]; 1218 [names addObject:NSAccessibilityContentsAttribute]; 1219 [names addObject:NSAccessibilityValueAttribute]; 1220 1221 return names; 1222 } 1223 1224 - (id)currentTabWithEnv:(JNIEnv *)env withAxContext:(jobject)axContext 1225 { 1226 NSArray *tabs = [self tabControlsWithEnv:env withTabGroupAxContext:axContext withTabCode:JAVA_AX_ALL_CHILDREN allowIgnored:NO]; 1227 1228 // Looking at the JTabbedPane sources, there is always one AccessibleSelection. 1229 jobject selAccessible = getAxContextSelection(env, axContext, 0, fComponent); 1230 if (selAccessible == NULL) return nil; 1231 1232 // Go through the tabs and find selAccessible 1233 _numTabs = [tabs count]; 1234 JavaComponentAccessibility *aTab; 1235 NSUInteger i; 1236 for (i = 0; i < _numTabs; i++) { 1237 aTab = (JavaComponentAccessibility *)[tabs objectAtIndex:i]; 1238 if ([aTab isAccessibleWithEnv:env forAccessible:selAccessible]) { 1239 return aTab; 1240 } 1241 } 1242 1243 return nil; 1244 } 1245 1246 - (NSArray *)tabControlsWithEnv:(JNIEnv *)env withTabGroupAxContext:(jobject)axContext withTabCode:(NSInteger)whichTabs allowIgnored:(BOOL)allowIgnored 1247 { 1248 jobjectArray jtabsAndRoles = JNFCallStaticObjectMethod(env, jm_getChildrenAndRoles, fAccessible, fComponent, whichTabs, allowIgnored); // AWT_THREADING Safe (AWTRunLoop) 1249 if(jtabsAndRoles == NULL) return nil; 1250 1251 jsize arrayLen = (*env)->GetArrayLength(env, jtabsAndRoles); 1252 if (arrayLen == 0) return nil; 1253 1254 NSMutableArray *tabs = [NSMutableArray arrayWithCapacity:(arrayLen/2)]; 1255 1256 // all of the tabs have the same role, so we can just find out what that is here and use it for all the tabs 1257 jobject jtabJavaRole = (*env)->GetObjectArrayElement(env, jtabsAndRoles, 1); // the array entries alternate between tab/role, starting with tab. so the first role is entry 1. 1258 if (jtabJavaRole == NULL) return nil; 1259 1260 NSString *tabJavaRole = JNFJavaToNSString(env, JNFGetObjectField(env, jtabJavaRole, sjf_key)); 1261 1262 NSUInteger i; 1263 NSUInteger tabIndex = (whichTabs >= 0) ? whichTabs : 0; // if we're getting one particular child, make sure to set its index correctly 1264 for(i = 0; i < arrayLen; i+=2) { 1265 jobject jtab = (*env)->GetObjectArrayElement(env, jtabsAndRoles, i); 1266 JavaComponentAccessibility *tab = [[[TabGroupControlAccessibility alloc] initWithParent:self withEnv:env withAccessible:jtab withIndex:tabIndex withTabGroup:axContext withView:[self view] withJavaRole:tabJavaRole] autorelease]; 1267 [tabs addObject:tab]; 1268 tabIndex++; 1269 } 1270 1271 return tabs; 1272 } 1273 1274 - (NSArray *)contentsWithEnv:(JNIEnv *)env withTabGroupAxContext:(jobject)axContext withTabCode:(NSInteger)whichTabs allowIgnored:(BOOL)allowIgnored 1275 { 1276 // Contents are the children of the selected tab. 1277 id currentTab = [self currentTabWithEnv:env withAxContext:axContext]; 1278 if (currentTab == nil) return nil; 1279 1280 NSArray *contents = [JavaComponentAccessibility childrenOfParent:currentTab withEnv:env withChildrenCode:whichTabs allowIgnored:allowIgnored]; 1281 if ([contents count] <= 0) return nil; 1282 return contents; 1283 } 1284 1285 - (id) accessibilityTabsAttribute 1286 { 1287 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1288 jobject axContext = [self axContextWithEnv:env]; 1289 return [self tabControlsWithEnv:env withTabGroupAxContext:axContext withTabCode:JAVA_AX_ALL_CHILDREN allowIgnored:NO]; 1290 } 1291 1292 - (BOOL)accessibilityIsTabsAttributeSettable 1293 { 1294 return NO; //cmcnote: not sure. 1295 } 1296 1297 - (NSInteger)numTabs 1298 { 1299 if (_numTabs == -1) { 1300 _numTabs = [[self accessibilityTabsAttribute] count]; 1301 } 1302 return _numTabs; 1303 } 1304 1305 - (NSArray *) accessibilityContentsAttribute 1306 { 1307 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1308 jobject axContext = [self axContextWithEnv:env]; 1309 return [self contentsWithEnv:env withTabGroupAxContext:axContext withTabCode:JAVA_AX_ALL_CHILDREN allowIgnored:NO]; 1310 } 1311 1312 - (BOOL)accessibilityIsContentsAttributeSettable 1313 { 1314 return NO; 1315 } 1316 1317 // axValue is the currently selected tab 1318 -(id) accessibilityValueAttribute 1319 { 1320 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1321 jobject axContext = [self axContextWithEnv:env]; 1322 return [self currentTabWithEnv:env withAxContext:axContext]; 1323 } 1324 1325 - (BOOL)accessibilityIsValueAttributeSettable 1326 { 1327 return YES; 1328 } 1329 1330 - (void)accessibilitySetValueAttribute:(id)value //cmcnote: not certain this is ever actually called. investigate. 1331 { 1332 // set the current tab 1333 NSNumber *number = (NSNumber *)value; 1334 if (![number boolValue]) return; 1335 1336 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1337 jobject axContext = [self axContextWithEnv:env]; 1338 setAxContextSelection(env, axContext, fIndex, fComponent); 1339 } 1340 1341 - (NSArray *)accessibilityChildrenAttribute 1342 { 1343 //children = AXTabs + AXContents 1344 NSArray *tabs = [self accessibilityTabsAttribute]; 1345 NSArray *contents = [self accessibilityContentsAttribute]; 1346 1347 NSMutableArray *children = [NSMutableArray arrayWithCapacity:[tabs count] + [contents count]]; 1348 [children addObjectsFromArray:tabs]; 1349 [children addObjectsFromArray:contents]; 1350 1351 return (NSArray *)children; 1352 } 1353 1354 // Without this optimization accessibilityChildrenAttribute is called in order to get the entire array of children. 1355 // See similar optimization in JavaComponentAccessibility. We have to extend the base implementation here, since 1356 // children of tabs are AXTabs + AXContents 1357 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount { 1358 NSArray *result = nil; 1359 if ( (maxCount == 1) && [attribute isEqualToString:NSAccessibilityChildrenAttribute]) { 1360 // Children codes for ALL, SELECTED, VISIBLE are <0. If the code is >=0, we treat it as an index to a single child 1361 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1362 jobject axContext = [self axContextWithEnv:env]; 1363 1364 //children = AXTabs + AXContents 1365 NSArray *children = [self tabControlsWithEnv:env withTabGroupAxContext:axContext withTabCode:index allowIgnored:NO]; // first look at the tabs 1366 if ([children count] > 0) { 1367 result = children; 1368 } else { 1369 children= [self contentsWithEnv:env withTabGroupAxContext:axContext withTabCode:(index-[self numTabs]) allowIgnored:NO]; 1370 if ([children count] > 0) { 1371 result = children; 1372 } 1373 } 1374 } else { 1375 result = [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount]; 1376 } 1377 return result; 1378 } 1379 1380 @end 1381 1382 1383 static BOOL ObjectEquals(JNIEnv *env, jobject a, jobject b, jobject component); 1384 1385 @implementation TabGroupControlAccessibility 1386 1387 - (id)initWithParent:(NSObject *)parent withEnv:(JNIEnv *)env withAccessible:(jobject)accessible withIndex:(jint)index withTabGroup:(jobject)tabGroup withView:(NSView *)view withJavaRole:(NSString *)javaRole 1388 { 1389 self = [super initWithParent:parent withEnv:env withAccessible:accessible withIndex:index withView:view withJavaRole:javaRole]; 1390 if (self) { 1391 if (tabGroup != NULL) { 1392 fTabGroupAxContext = JNFNewGlobalRef(env, tabGroup); 1393 } else { 1394 fTabGroupAxContext = NULL; 1395 } 1396 } 1397 return self; 1398 } 1399 1400 - (void)dealloc 1401 { 1402 JNIEnv *env = [ThreadUtilities getJNIEnvUncached]; 1403 1404 if (fTabGroupAxContext != NULL) { 1405 JNFDeleteGlobalRef(env, fTabGroupAxContext); 1406 fTabGroupAxContext = NULL; 1407 } 1408 1409 [super dealloc]; 1410 } 1411 1412 - (void)finalize 1413 { 1414 JNIEnv *env = [ThreadUtilities getJNIEnvUncached]; 1415 1416 if (fTabGroupAxContext != NULL) { 1417 JNFDeleteGlobalRef(env, fTabGroupAxContext); 1418 fTabGroupAxContext = NULL; 1419 } 1420 1421 [super finalize]; 1422 } 1423 1424 - (id)accessibilityValueAttribute 1425 { 1426 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1427 jobject axContext = [self axContextWithEnv:env]; 1428 1429 // Returns the current selection of the page tab list 1430 return [NSNumber numberWithBool:ObjectEquals(env, axContext, getAxContextSelection(env, [self tabGroup], fIndex, fComponent), fComponent)]; 1431 } 1432 1433 - (void)getActionsWithEnv:(JNIEnv *)env 1434 { 1435 TabGroupAction *action = [[TabGroupAction alloc] initWithEnv:env withTabGroup:[self tabGroup] withIndex:fIndex withComponent:fComponent]; 1436 [fActions setObject:action forKey:NSAccessibilityPressAction]; 1437 [action release]; 1438 } 1439 1440 - (jobject)tabGroup 1441 { 1442 if (fTabGroupAxContext == NULL) { 1443 JNIEnv* env = [ThreadUtilities getJNIEnv]; 1444 jobject tabGroupAxContext = [(JavaComponentAccessibility *)[self parent] axContextWithEnv:env]; 1445 fTabGroupAxContext = JNFNewGlobalRef(env, tabGroupAxContext); 1446 } 1447 return fTabGroupAxContext; 1448 } 1449 1450 @end 1451 1452 1453 @implementation ScrollAreaAccessibility 1454 1455 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env 1456 { 1457 NSMutableArray *names = (NSMutableArray *)[super initializeAttributeNamesWithEnv:env]; 1458 1459 [names addObject:NSAccessibilityHorizontalScrollBarAttribute]; 1460 [names addObject:NSAccessibilityVerticalScrollBarAttribute]; 1461 [names addObject:NSAccessibilityContentsAttribute]; 1462 1463 return names; 1464 } 1465 1466 - (id)accessibilityHorizontalScrollBarAttribute 1467 { 1468 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1469 1470 NSArray *children = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_ALL_CHILDREN allowIgnored:YES]; 1471 if ([children count] <= 0) return nil; 1472 1473 // The scroll bars are in the children. 1474 JavaComponentAccessibility *aElement; 1475 NSEnumerator *enumerator = [children objectEnumerator]; 1476 while ((aElement = (JavaComponentAccessibility *)[enumerator nextObject])) { 1477 if ([[aElement accessibilityRoleAttribute] isEqualToString:NSAccessibilityScrollBarRole]) { 1478 jobject elementAxContext = [aElement axContextWithEnv:env]; 1479 if (isHorizontal(env, elementAxContext, fComponent)) { 1480 return aElement; 1481 } 1482 } 1483 } 1484 1485 return nil; 1486 } 1487 1488 - (BOOL)accessibilityIsHorizontalScrollBarAttributeSettable 1489 { 1490 return NO; 1491 } 1492 1493 - (id)accessibilityVerticalScrollBarAttribute 1494 { 1495 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1496 1497 NSArray *children = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_ALL_CHILDREN allowIgnored:YES]; 1498 if ([children count] <= 0) return nil; 1499 1500 // The scroll bars are in the children. 1501 NSEnumerator *enumerator = [children objectEnumerator]; 1502 JavaComponentAccessibility *aElement; 1503 while ((aElement = (JavaComponentAccessibility *)[enumerator nextObject])) { 1504 if ([[aElement accessibilityRoleAttribute] isEqualToString:NSAccessibilityScrollBarRole]) { 1505 jobject elementAxContext = [aElement axContextWithEnv:env]; 1506 if (isVertical(env, elementAxContext, fComponent)) { 1507 return aElement; 1508 } 1509 } 1510 } 1511 1512 return nil; 1513 } 1514 1515 - (BOOL)accessibilityIsVerticalScrollBarAttributeSettable 1516 { 1517 return NO; 1518 } 1519 1520 - (NSArray *)accessibilityContentsAttribute 1521 { 1522 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1523 NSArray *children = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_ALL_CHILDREN allowIgnored:YES]; 1524 1525 if ([children count] <= 0) return nil; 1526 NSArray *contents = [NSMutableArray arrayWithCapacity:[children count]]; 1527 1528 // The scroll bars are in the children. children less the scroll bars is the contents 1529 NSEnumerator *enumerator = [children objectEnumerator]; 1530 JavaComponentAccessibility *aElement; 1531 while ((aElement = (JavaComponentAccessibility *)[enumerator nextObject])) { 1532 if (![[aElement accessibilityRoleAttribute] isEqualToString:NSAccessibilityScrollBarRole]) { 1533 // no scroll bars in contents 1534 [(NSMutableArray *)contents addObject:aElement]; 1535 } 1536 } 1537 1538 return contents; 1539 } 1540 1541 - (BOOL)accessibilityIsContentsAttributeSettable 1542 { 1543 return NO; 1544 } 1545 1546 @end 1547 1548 /* 1549 * Returns Object.equals for the two items 1550 * This may use LWCToolkit.invokeAndWait(); don't call while holding fLock 1551 * and try to pass a component so the event happens on the correct thread. 1552 */ 1553 static JNF_CLASS_CACHE(sjc_Object, "java/lang/Object"); 1554 static BOOL ObjectEquals(JNIEnv *env, jobject a, jobject b, jobject component) 1555 { 1556 static JNF_MEMBER_CACHE(jm_equals, sjc_Object, "equals", "(Ljava/lang/Object;)Z"); 1557 1558 if ((a == NULL) && (b == NULL)) return YES; 1559 if ((a == NULL) || (b == NULL)) return NO; 1560 1561 if (pthread_main_np() != 0) { 1562 // If we are on the AppKit thread 1563 static JNF_CLASS_CACHE(sjc_LWCToolkit, "sun/lwawt/macosx/LWCToolkit"); 1564 static JNF_STATIC_MEMBER_CACHE(jm_doEquals, sjc_LWCToolkit, "doEquals", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/awt/Component;)Z"); 1565 return JNFCallStaticBooleanMethod(env, jm_doEquals, a, b, component); // AWT_THREADING Safe (AWTRunLoopMode) 1566 } 1567 1568 return JNFCallBooleanMethod(env, a, jm_equals, b); // AWT_THREADING Safe (!appKit) 1569 }