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 1151 JNF_COCOA_ENTER(env); 1152 [ThreadUtilities performOnMainThread:@selector(postFocusChanged:) onObject:[JavaComponentAccessibility class] withObject:nil waitUntilDone:NO awtMode:NO]; 1153 JNF_COCOA_EXIT(env); 1154 } 1155 1156 1157 1158 /* 1159 * Class: sun_lwawt_macosx_CAccessible 1160 * Method: valueChanged 1161 * Signature: (I)V 1162 */ 1163 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_valueChanged 1164 (JNIEnv *env, jclass jklass, jlong element) 1165 { 1166 JNF_COCOA_ENTER(env); 1167 [ThreadUtilities performOnMainThread:@selector(postValueChanged) onObject:(JavaComponentAccessibility *)jlong_to_ptr(element) withObject:nil waitUntilDone:NO awtMode:NO]; 1168 JNF_COCOA_EXIT(env); 1169 } 1170 1171 /* 1172 * Class: sun_lwawt_macosx_CAccessible 1173 * Method: selectionChanged 1174 * Signature: (I)V 1175 */ 1176 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_selectionChanged 1177 (JNIEnv *env, jclass jklass, jlong element) 1178 { 1179 JNF_COCOA_ENTER(env); 1180 [ThreadUtilities performOnMainThread:@selector(postSelectionChanged) onObject:(JavaComponentAccessibility *)jlong_to_ptr(element) withObject:nil waitUntilDone:NO awtMode:NO]; 1181 JNF_COCOA_EXIT(env); 1182 } 1183 1184 1185 /* 1186 * Class: sun_lwawt_macosx_CAccessible 1187 * Method: unregisterFromCocoaAXSystem 1188 * Signature: (I)V 1189 */ 1190 JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CAccessible_unregisterFromCocoaAXSystem 1191 (JNIEnv *env, jclass jklass, jlong element) 1192 { 1193 JNF_COCOA_ENTER(env); 1194 [ThreadUtilities performOnMainThread:@selector(unregisterFromCocoaAXSystem) onObject:(JavaComponentAccessibility *)jlong_to_ptr(element) withObject:nil waitUntilDone:NO awtMode:NO]; 1195 JNF_COCOA_EXIT(env); 1196 } 1197 1198 @implementation TabGroupAccessibility 1199 1200 - (id)initWithParent:(NSObject *)parent withEnv:(JNIEnv *)env withAccessible:(jobject)accessible withIndex:(jint)index withView:(NSView *)view withJavaRole:(NSString *)javaRole 1201 { 1202 self = [super initWithParent:parent withEnv:env withAccessible:accessible withIndex:index withView:view withJavaRole:javaRole]; 1203 if (self) { 1204 _numTabs = -1; //flag for uninitialized numTabs 1205 } 1206 return self; 1207 } 1208 1209 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env 1210 { 1211 NSMutableArray *names = (NSMutableArray *)[super initializeAttributeNamesWithEnv:env]; 1212 1213 [names addObject:NSAccessibilityTabsAttribute]; 1214 [names addObject:NSAccessibilityContentsAttribute]; 1215 [names addObject:NSAccessibilityValueAttribute]; 1216 1217 return names; 1218 } 1219 1220 - (id)currentTabWithEnv:(JNIEnv *)env withAxContext:(jobject)axContext 1221 { 1222 NSArray *tabs = [self tabControlsWithEnv:env withTabGroupAxContext:axContext withTabCode:JAVA_AX_ALL_CHILDREN allowIgnored:NO]; 1223 1224 // Looking at the JTabbedPane sources, there is always one AccessibleSelection. 1225 jobject selAccessible = getAxContextSelection(env, axContext, 0, fComponent); 1226 if (selAccessible == NULL) return nil; 1227 1228 // Go through the tabs and find selAccessible 1229 _numTabs = [tabs count]; 1230 JavaComponentAccessibility *aTab; 1231 NSUInteger i; 1232 for (i = 0; i < _numTabs; i++) { 1233 aTab = (JavaComponentAccessibility *)[tabs objectAtIndex:i]; 1234 if ([aTab isAccessibleWithEnv:env forAccessible:selAccessible]) { 1235 return aTab; 1236 } 1237 } 1238 1239 return nil; 1240 } 1241 1242 - (NSArray *)tabControlsWithEnv:(JNIEnv *)env withTabGroupAxContext:(jobject)axContext withTabCode:(NSInteger)whichTabs allowIgnored:(BOOL)allowIgnored 1243 { 1244 jobjectArray jtabsAndRoles = JNFCallStaticObjectMethod(env, jm_getChildrenAndRoles, fAccessible, fComponent, whichTabs, allowIgnored); // AWT_THREADING Safe (AWTRunLoop) 1245 if(jtabsAndRoles == NULL) return nil; 1246 1247 jsize arrayLen = (*env)->GetArrayLength(env, jtabsAndRoles); 1248 if (arrayLen == 0) return nil; 1249 1250 NSMutableArray *tabs = [NSMutableArray arrayWithCapacity:(arrayLen/2)]; 1251 1252 // 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 1253 jobject jtabJavaRole = (*env)->GetObjectArrayElement(env, jtabsAndRoles, 1); // the array entries alternate between tab/role, starting with tab. so the first role is entry 1. 1254 if (jtabJavaRole == NULL) return nil; 1255 1256 NSString *tabJavaRole = JNFJavaToNSString(env, JNFGetObjectField(env, jtabJavaRole, sjf_key)); 1257 1258 NSUInteger i; 1259 NSUInteger tabIndex = (whichTabs >= 0) ? whichTabs : 0; // if we're getting one particular child, make sure to set its index correctly 1260 for(i = 0; i < arrayLen; i+=2) { 1261 jobject jtab = (*env)->GetObjectArrayElement(env, jtabsAndRoles, i); 1262 JavaComponentAccessibility *tab = [[[TabGroupControlAccessibility alloc] initWithParent:self withEnv:env withAccessible:jtab withIndex:tabIndex withTabGroup:axContext withView:[self view] withJavaRole:tabJavaRole] autorelease]; 1263 [tabs addObject:tab]; 1264 tabIndex++; 1265 } 1266 1267 return tabs; 1268 } 1269 1270 - (NSArray *)contentsWithEnv:(JNIEnv *)env withTabGroupAxContext:(jobject)axContext withTabCode:(NSInteger)whichTabs allowIgnored:(BOOL)allowIgnored 1271 { 1272 // Contents are the children of the selected tab. 1273 id currentTab = [self currentTabWithEnv:env withAxContext:axContext]; 1274 if (currentTab == nil) return nil; 1275 1276 NSArray *contents = [JavaComponentAccessibility childrenOfParent:currentTab withEnv:env withChildrenCode:whichTabs allowIgnored:allowIgnored]; 1277 if ([contents count] <= 0) return nil; 1278 return contents; 1279 } 1280 1281 - (id) accessibilityTabsAttribute 1282 { 1283 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1284 jobject axContext = [self axContextWithEnv:env]; 1285 return [self tabControlsWithEnv:env withTabGroupAxContext:axContext withTabCode:JAVA_AX_ALL_CHILDREN allowIgnored:NO]; 1286 } 1287 1288 - (BOOL)accessibilityIsTabsAttributeSettable 1289 { 1290 return NO; //cmcnote: not sure. 1291 } 1292 1293 - (NSInteger)numTabs 1294 { 1295 if (_numTabs == -1) { 1296 _numTabs = [[self accessibilityTabsAttribute] count]; 1297 } 1298 return _numTabs; 1299 } 1300 1301 - (NSArray *) accessibilityContentsAttribute 1302 { 1303 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1304 jobject axContext = [self axContextWithEnv:env]; 1305 return [self contentsWithEnv:env withTabGroupAxContext:axContext withTabCode:JAVA_AX_ALL_CHILDREN allowIgnored:NO]; 1306 } 1307 1308 - (BOOL)accessibilityIsContentsAttributeSettable 1309 { 1310 return NO; 1311 } 1312 1313 // axValue is the currently selected tab 1314 -(id) accessibilityValueAttribute 1315 { 1316 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1317 jobject axContext = [self axContextWithEnv:env]; 1318 return [self currentTabWithEnv:env withAxContext:axContext]; 1319 } 1320 1321 - (BOOL)accessibilityIsValueAttributeSettable 1322 { 1323 return YES; 1324 } 1325 1326 - (void)accessibilitySetValueAttribute:(id)value //cmcnote: not certain this is ever actually called. investigate. 1327 { 1328 // set the current tab 1329 NSNumber *number = (NSNumber *)value; 1330 if (![number boolValue]) return; 1331 1332 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1333 jobject axContext = [self axContextWithEnv:env]; 1334 setAxContextSelection(env, axContext, fIndex, fComponent); 1335 } 1336 1337 - (NSArray *)accessibilityChildrenAttribute 1338 { 1339 //children = AXTabs + AXContents 1340 NSArray *tabs = [self accessibilityTabsAttribute]; 1341 NSArray *contents = [self accessibilityContentsAttribute]; 1342 1343 NSMutableArray *children = [NSMutableArray arrayWithCapacity:[tabs count] + [contents count]]; 1344 [children addObjectsFromArray:tabs]; 1345 [children addObjectsFromArray:contents]; 1346 1347 return (NSArray *)children; 1348 } 1349 1350 // Without this optimization accessibilityChildrenAttribute is called in order to get the entire array of children. 1351 // See similar optimization in JavaComponentAccessibility. We have to extend the base implementation here, since 1352 // children of tabs are AXTabs + AXContents 1353 - (NSArray *)accessibilityArrayAttributeValues:(NSString *)attribute index:(NSUInteger)index maxCount:(NSUInteger)maxCount { 1354 NSArray *result = nil; 1355 if ( (maxCount == 1) && [attribute isEqualToString:NSAccessibilityChildrenAttribute]) { 1356 // Children codes for ALL, SELECTED, VISIBLE are <0. If the code is >=0, we treat it as an index to a single child 1357 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1358 jobject axContext = [self axContextWithEnv:env]; 1359 1360 //children = AXTabs + AXContents 1361 NSArray *children = [self tabControlsWithEnv:env withTabGroupAxContext:axContext withTabCode:index allowIgnored:NO]; // first look at the tabs 1362 if ([children count] > 0) { 1363 result = children; 1364 } else { 1365 children= [self contentsWithEnv:env withTabGroupAxContext:axContext withTabCode:(index-[self numTabs]) allowIgnored:NO]; 1366 if ([children count] > 0) { 1367 result = children; 1368 } 1369 } 1370 } else { 1371 result = [super accessibilityArrayAttributeValues:attribute index:index maxCount:maxCount]; 1372 } 1373 return result; 1374 } 1375 1376 @end 1377 1378 1379 static BOOL ObjectEquals(JNIEnv *env, jobject a, jobject b, jobject component); 1380 1381 @implementation TabGroupControlAccessibility 1382 1383 - (id)initWithParent:(NSObject *)parent withEnv:(JNIEnv *)env withAccessible:(jobject)accessible withIndex:(jint)index withTabGroup:(jobject)tabGroup withView:(NSView *)view withJavaRole:(NSString *)javaRole 1384 { 1385 self = [super initWithParent:parent withEnv:env withAccessible:accessible withIndex:index withView:view withJavaRole:javaRole]; 1386 if (self) { 1387 if (tabGroup != NULL) { 1388 fTabGroupAxContext = JNFNewGlobalRef(env, tabGroup); 1389 } else { 1390 fTabGroupAxContext = NULL; 1391 } 1392 } 1393 return self; 1394 } 1395 1396 - (void)dealloc 1397 { 1398 JNIEnv *env = [ThreadUtilities getJNIEnvUncached]; 1399 1400 if (fTabGroupAxContext != NULL) { 1401 JNFDeleteGlobalRef(env, fTabGroupAxContext); 1402 fTabGroupAxContext = NULL; 1403 } 1404 1405 [super dealloc]; 1406 } 1407 1408 - (void)finalize 1409 { 1410 JNIEnv *env = [ThreadUtilities getJNIEnvUncached]; 1411 1412 if (fTabGroupAxContext != NULL) { 1413 JNFDeleteGlobalRef(env, fTabGroupAxContext); 1414 fTabGroupAxContext = NULL; 1415 } 1416 1417 [super finalize]; 1418 } 1419 1420 - (id)accessibilityValueAttribute 1421 { 1422 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1423 jobject axContext = [self axContextWithEnv:env]; 1424 1425 // Returns the current selection of the page tab list 1426 return [NSNumber numberWithBool:ObjectEquals(env, axContext, getAxContextSelection(env, [self tabGroup], fIndex, fComponent), fComponent)]; 1427 } 1428 1429 - (void)getActionsWithEnv:(JNIEnv *)env 1430 { 1431 TabGroupAction *action = [[TabGroupAction alloc] initWithEnv:env withTabGroup:[self tabGroup] withIndex:fIndex withComponent:fComponent]; 1432 [fActions setObject:action forKey:NSAccessibilityPressAction]; 1433 [action release]; 1434 } 1435 1436 - (jobject)tabGroup 1437 { 1438 if (fTabGroupAxContext == NULL) { 1439 JNIEnv* env = [ThreadUtilities getJNIEnv]; 1440 jobject tabGroupAxContext = [(JavaComponentAccessibility *)[self parent] axContextWithEnv:env]; 1441 fTabGroupAxContext = JNFNewGlobalRef(env, tabGroupAxContext); 1442 } 1443 return fTabGroupAxContext; 1444 } 1445 1446 @end 1447 1448 1449 @implementation ScrollAreaAccessibility 1450 1451 - (NSArray *)initializeAttributeNamesWithEnv:(JNIEnv *)env 1452 { 1453 NSMutableArray *names = (NSMutableArray *)[super initializeAttributeNamesWithEnv:env]; 1454 1455 [names addObject:NSAccessibilityHorizontalScrollBarAttribute]; 1456 [names addObject:NSAccessibilityVerticalScrollBarAttribute]; 1457 [names addObject:NSAccessibilityContentsAttribute]; 1458 1459 return names; 1460 } 1461 1462 - (id)accessibilityHorizontalScrollBarAttribute 1463 { 1464 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1465 1466 NSArray *children = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_ALL_CHILDREN allowIgnored:YES]; 1467 if ([children count] <= 0) return nil; 1468 1469 // The scroll bars are in the children. 1470 JavaComponentAccessibility *aElement; 1471 NSEnumerator *enumerator = [children objectEnumerator]; 1472 while ((aElement = (JavaComponentAccessibility *)[enumerator nextObject])) { 1473 if ([[aElement accessibilityRoleAttribute] isEqualToString:NSAccessibilityScrollBarRole]) { 1474 jobject elementAxContext = [aElement axContextWithEnv:env]; 1475 if (isHorizontal(env, elementAxContext, fComponent)) { 1476 return aElement; 1477 } 1478 } 1479 } 1480 1481 return nil; 1482 } 1483 1484 - (BOOL)accessibilityIsHorizontalScrollBarAttributeSettable 1485 { 1486 return NO; 1487 } 1488 1489 - (id)accessibilityVerticalScrollBarAttribute 1490 { 1491 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1492 1493 NSArray *children = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_ALL_CHILDREN allowIgnored:YES]; 1494 if ([children count] <= 0) return nil; 1495 1496 // The scroll bars are in the children. 1497 NSEnumerator *enumerator = [children objectEnumerator]; 1498 JavaComponentAccessibility *aElement; 1499 while ((aElement = (JavaComponentAccessibility *)[enumerator nextObject])) { 1500 if ([[aElement accessibilityRoleAttribute] isEqualToString:NSAccessibilityScrollBarRole]) { 1501 jobject elementAxContext = [aElement axContextWithEnv:env]; 1502 if (isVertical(env, elementAxContext, fComponent)) { 1503 return aElement; 1504 } 1505 } 1506 } 1507 1508 return nil; 1509 } 1510 1511 - (BOOL)accessibilityIsVerticalScrollBarAttributeSettable 1512 { 1513 return NO; 1514 } 1515 1516 - (NSArray *)accessibilityContentsAttribute 1517 { 1518 JNIEnv *env = [ThreadUtilities getJNIEnv]; 1519 NSArray *children = [JavaComponentAccessibility childrenOfParent:self withEnv:env withChildrenCode:JAVA_AX_ALL_CHILDREN allowIgnored:YES]; 1520 1521 if ([children count] <= 0) return nil; 1522 NSArray *contents = [NSMutableArray arrayWithCapacity:[children count]]; 1523 1524 // The scroll bars are in the children. children less the scroll bars is the contents 1525 NSEnumerator *enumerator = [children objectEnumerator]; 1526 JavaComponentAccessibility *aElement; 1527 while ((aElement = (JavaComponentAccessibility *)[enumerator nextObject])) { 1528 if (![[aElement accessibilityRoleAttribute] isEqualToString:NSAccessibilityScrollBarRole]) { 1529 // no scroll bars in contents 1530 [(NSMutableArray *)contents addObject:aElement]; 1531 } 1532 } 1533 1534 return contents; 1535 } 1536 1537 - (BOOL)accessibilityIsContentsAttributeSettable 1538 { 1539 return NO; 1540 } 1541 1542 @end 1543 1544 /* 1545 * Returns Object.equals for the two items 1546 * This may use LWCToolkit.invokeAndWait(); don't call while holding fLock 1547 * and try to pass a component so the event happens on the correct thread. 1548 */ 1549 static JNF_CLASS_CACHE(sjc_Object, "java/lang/Object"); 1550 static BOOL ObjectEquals(JNIEnv *env, jobject a, jobject b, jobject component) 1551 { 1552 static JNF_MEMBER_CACHE(jm_equals, sjc_Object, "equals", "(Ljava/lang/Object;)Z"); 1553 1554 if ((a == NULL) && (b == NULL)) return YES; 1555 if ((a == NULL) || (b == NULL)) return NO; 1556 1557 if (pthread_main_np() != 0) { 1558 // If we are on the AppKit thread 1559 static JNF_CLASS_CACHE(sjc_LWCToolkit, "sun/lwawt/macosx/LWCToolkit"); 1560 static JNF_STATIC_MEMBER_CACHE(jm_doEquals, sjc_LWCToolkit, "doEquals", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/awt/Component;)Z"); 1561 return JNFCallStaticBooleanMethod(env, jm_doEquals, a, b, component); // AWT_THREADING Safe (AWTRunLoopMode) 1562 } 1563 1564 return JNFCallBooleanMethod(env, a, jm_equals, b); // AWT_THREADING Safe (!appKit) 1565 }