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