10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 #import "JavaTextAccessibility.h"
27 #import "JavaAccessibilityAction.h"
28 #import "JavaAccessibilityUtilities.h"
29 #import "ThreadUtilities.h"
30
31
32 static JNF_CLASS_CACHE(sjc_CAccessibleText, "sun/lwawt/macosx/CAccessibleText");
33 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleText, sjc_CAccessibility, "getAccessibleText", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/AccessibleText;");
34 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleEditableText, sjc_CAccessibleText, "getAccessibleEditableText", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/AccessibleEditableText;");
35 static JNF_STATIC_MEMBER_CACHE(sjm_getAccessibleName, sjc_CAccessibility, "getAccessibleName", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;");
36
37 /*
38 * Converts an int array to an NSRange wrapped inside an NSValue
39 * takes [start, end] values and returns [start, end - start]
40 */
41 NSValue *javaIntArrayToNSRangeValue(JNIEnv* env, jintArray array) {
42 jint *values = (*env)->GetIntArrayElements(env, array, 0);
43 if (values == NULL) {
44 // Note: Java will not be on the stack here so a java exception can't happen and no need to call ExceptionCheck.
45 NSLog(@"%s failed calling GetIntArrayElements", __FUNCTION__);
46 return nil;
47 };
48 NSValue *value = [NSValue valueWithRange:NSMakeRange(values[0], values[1] - values[0])];
49 (*env)->ReleaseIntArrayElements(env, array, values, 0);
50 return value;
51 }
52
53 @implementation JavaTextAccessibility
54
55 // based strongly upon NSTextViewAccessibility:accessibilityAttributeNames
91 attributes = [[NSArray alloc] initWithObjects:
92 NSAccessibilityLineForIndexParameterizedAttribute,
93 NSAccessibilityRangeForLineParameterizedAttribute,
94 NSAccessibilityStringForRangeParameterizedAttribute,
95 NSAccessibilityRangeForPositionParameterizedAttribute,
96 NSAccessibilityRangeForIndexParameterizedAttribute,
97 NSAccessibilityBoundsForRangeParameterizedAttribute,
98 //NSAccessibilityRTFForRangeParameterizedAttribute, // cmcnote: not sure when/how these three are used. Investigate. radr://3960026
99 //NSAccessibilityStyleRangeForIndexParameterizedAttribute,
100 //NSAccessibilityAttributedStringForRangeParameterizedAttribute,
101 nil];
102 }
103 //APPKIT_UNLOCK;
104 }
105 return attributes;
106 }
107
108 - (NSString *)accessibilityValueAttribute
109 {
110 JNIEnv *env = [ThreadUtilities getJNIEnv];
111 if ([[self accessibilityRoleAttribute] isEqualToString:NSAccessibilityStaticTextRole]) {
112 // if it's static text, the AppKit AXValue is the java accessibleName
113 jobject axName = JNFCallStaticObjectMethod(env, sjm_getAccessibleName, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
114 if (axName != NULL) {
115 NSString* str = JNFJavaToNSString(env, axName);
116 (*env)->DeleteLocalRef(env, axName);
117 return str;
118 }
119 // value is still nil if no accessibleName for static text. Below, try to get the accessibleText.
120 }
121
122 // cmcnote: inefficient to make three distinct JNI calls. Coalesce. radr://3951923
123 jobject axText = JNFCallStaticObjectMethod(env, sjm_getAccessibleText, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
124 if (axText == NULL) return nil;
125 (*env)->DeleteLocalRef(env, axText);
126
127 jobject axEditableText = JNFCallStaticObjectMethod(env, sjm_getAccessibleEditableText, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
128 if (axEditableText == NULL) return nil;
129
130 static JNF_STATIC_MEMBER_CACHE(jm_getTextRange, sjc_CAccessibleText, "getTextRange", "(Ljavax/accessibility/AccessibleEditableText;IILjava/awt/Component;)Ljava/lang/String;");
131 jobject jrange = JNFCallStaticObjectMethod(env, jm_getTextRange, axEditableText, 0, getAxTextCharCount(env, axEditableText, fComponent), fComponent);
132 NSString *string = JNFJavaToNSString(env, jrange); // AWT_THREADING Safe (AWTRunLoop)
133
134 (*env)->DeleteLocalRef(env, jrange);
135 (*env)->DeleteLocalRef(env, axEditableText);
136
137 if (string == nil) string = @"";
138 return string;
139 }
140
141 - (BOOL)accessibilityIsValueAttributeSettable
142 {
143 // if text is enabled and editable, it's settable (according to NSCellTextAttributesAccessibility)
144 BOOL isEnabled = [(NSNumber *)[self accessibilityEnabledAttribute] boolValue];
145 if (!isEnabled) return NO;
146
147 JNIEnv* env = [ThreadUtilities getJNIEnv];
148 jobject axEditableText = JNFCallStaticObjectMethod(env, sjm_getAccessibleEditableText, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
149 if (axEditableText == NULL) return NO;
150 (*env)->DeleteLocalRef(env, axEditableText);
151 return YES;
152 }
153
154 - (void)accessibilitySetValueAttribute:(id)value
155 {
156 // cmcnote: should set the accessibleEditableText to the stringValue of value - AccessibleEditableText.setTextContents(String s)
157 #ifdef JAVA_AX_DEBUG
158 NSLog(@"Not yet implemented: %s\n", __FUNCTION__); // radr://3954018
159 #endif
160 }
161
162 // Currently selected text (NSString)
163 - (NSString *)accessibilitySelectedTextAttribute
164 {
165 JNIEnv* env = [ThreadUtilities getJNIEnv];
166 static JNF_STATIC_MEMBER_CACHE(jm_getSelectedText, sjc_CAccessibleText, "getSelectedText", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;");
167 jobject axText = JNFCallStaticObjectMethod(env, jm_getSelectedText, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
168 if (axText == NULL) return @"";
169 NSString* str = JNFJavaToNSString(env, axText);
170 (*env)->DeleteLocalRef(env, axText);
171 return str;
172 }
173
174 - (BOOL)accessibilityIsSelectedTextAttributeSettable
175 {
176 return YES; //cmcnote: for AXTextField that's selectable, it's settable. Investigate further.
177 }
178
179 - (void)accessibilitySetSelectedTextAttribute:(id)value
180 {
181 #ifdef JAVA_AX_DEBUG_PARMS
182 if (![value isKindOfClass:[NSString class]]) {
183 JavaAccessibilityRaiseSetAttributeToIllegalTypeException(__FUNCTION__, self, NSAccessibilitySelectedTextAttribute, value);
184 return;
185 }
186 #endif
187
188 JNIEnv *env = [ThreadUtilities getJNIEnv];
189 jstring jstringValue = JNFNSToJavaString(env, (NSString *)value);
190 static JNF_STATIC_MEMBER_CACHE(jm_setSelectedText, sjc_CAccessibleText, "setSelectedText", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;Ljava/lang/String;)V");
191 JNFCallStaticVoidMethod(env, jm_setSelectedText, fAccessible, fComponent, jstringValue); // AWT_THREADING Safe (AWTRunLoop)
192 }
193
194 // Range of selected text (NSValue)
195 - (NSValue *)accessibilitySelectedTextRangeAttribute
196 {
197 JNIEnv *env = [ThreadUtilities getJNIEnv];
198 static JNF_STATIC_MEMBER_CACHE(jm_getSelectedTextRange, sjc_CAccessibleText, "getSelectedTextRange", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)[I");
199 jintArray axTextRange = JNFCallStaticObjectMethod(env, jm_getSelectedTextRange, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
200 if (axTextRange == NULL) return nil;
201
202 return javaIntArrayToNSRangeValue(env, axTextRange);
203 }
204
205 - (BOOL)accessibilityIsSelectedTextRangeAttributeSettable
206 {
207 return [(NSNumber *)[self accessibilityEnabledAttribute] boolValue]; // cmcnote: also may want to find out if isSelectable. Investigate.
208 }
209
210 - (void)accessibilitySetSelectedTextRangeAttribute:(id)value
211 {
212 #ifdef JAVA_AX_DEBUG_PARMS
213 if (!([value isKindOfClass:[NSValue class]] && strcmp([(NSValue *)value objCType], @encode(NSRange)) == 0)) {
214 JavaAccessibilityRaiseSetAttributeToIllegalTypeException(__FUNCTION__, self, NSAccessibilitySelectedTextRangeAttribute, value);
215 return;
216 }
217 #endif
218
219 NSRange range = [(NSValue *)value rangeValue];
220 jint startIndex = range.location;
221 jint endIndex = startIndex + range.length;
222
223 JNIEnv *env = [ThreadUtilities getJNIEnv];
224 static JNF_STATIC_MEMBER_CACHE(jm_setSelectedTextRange, sjc_CAccessibleText, "setSelectedTextRange", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;II)V");
225 JNFCallStaticVoidMethod(env, jm_setSelectedTextRange, fAccessible, fComponent, startIndex, endIndex); // AWT_THREADING Safe (AWTRunLoop)
226 }
227
228 - (NSNumber *)accessibilityNumberOfCharactersAttribute
229 {
230 // cmcnote: should coalesce these two calls - radr://3951923
231 // also, static text doesn't always have accessibleText. if axText is null, should get the charcount of the accessibleName instead
232 JNIEnv *env = [ThreadUtilities getJNIEnv];
233 jobject axText = JNFCallStaticObjectMethod(env, sjm_getAccessibleText, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
234 NSNumber* num = [NSNumber numberWithInt:getAxTextCharCount(env, axText, fComponent)];
235 (*env)->DeleteLocalRef(env, axText);
236 return num;
237 }
238
239 - (BOOL)accessibilityIsNumberOfCharactersAttributeSettable
240 {
241 return NO; // according to NSTextViewAccessibility.m and NSCellTextAttributesAccessibility.m
242 }
243
244 - (NSValue *)accessibilityVisibleCharacterRangeAttribute
245 {
246 JNIEnv *env = [ThreadUtilities getJNIEnv];
247 static JNF_STATIC_MEMBER_CACHE(jm_getVisibleCharacterRange, sjc_CAccessibleText, "getVisibleCharacterRange", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)[I");
248 jintArray axTextRange = JNFCallStaticObjectMethod(env, jm_getVisibleCharacterRange, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
249 if (axTextRange == NULL) return nil;
250
251 return javaIntArrayToNSRangeValue(env, axTextRange);
252 }
253
254 - (BOOL)accessibilityIsVisibleCharacterRangeAttributeSettable
255 {
256 #ifdef JAVA_AX_DEBUG
257 NSLog(@"Not yet implemented: %s\n", __FUNCTION__);
258 #endif
259 return NO;
260 }
261
262 - (NSValue *)accessibilityInsertionPointLineNumberAttribute
263 {
264 JNIEnv *env = [ThreadUtilities getJNIEnv];
265 static JNF_STATIC_MEMBER_CACHE(jm_getLineNumberForInsertionPoint, sjc_CAccessibleText, "getLineNumberForInsertionPoint", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)I");
266 jint row = JNFCallStaticIntMethod(env, jm_getLineNumberForInsertionPoint, fAccessible, fComponent); // AWT_THREADING Safe (AWTRunLoop)
267 if (row < 0) return nil;
268 return [NSNumber numberWithInt:row];
269 }
270
271 - (BOOL)accessibilityIsInsertionPointLineNumberAttributeSettable
272 {
273 #ifdef JAVA_AX_DEBUG
274 NSLog(@"Not yet implemented: %s\n", __FUNCTION__);
275 #endif
276 return NO;
277 }
278
279 // parameterized attributes
280
281 //
282 // Usage of accessibilityBoundsForRangeAttributeForParameter:
283 // ---
284 // called by VoiceOver when interacting with text via ctrl-option-shift-downArrow.
285 // Need to know bounding box for the character / word / line of interest in
286 // order to draw VoiceOver cursor
287 //
288 - (NSValue *)accessibilityBoundsForRangeAttributeForParameter:(id)parameter
289 {
290 #ifdef JAVA_AX_DEBUG_PARMS
291 if (!([parameter isKindOfClass:[NSValue class]] && strcmp([(NSValue *)parameter objCType], @encode(NSRange)) == 0)) {
292 JavaAccessibilityRaiseIllegalParameterTypeException(__FUNCTION__, self, NSAccessibilityBoundsForRangeParameterizedAttribute, parameter);
293 return nil;
294 }
295 #endif
296
297 NSRange range = [(NSValue *)parameter rangeValue];
298
299 JNIEnv *env = [ThreadUtilities getJNIEnv];
300 static JNF_STATIC_MEMBER_CACHE(jm_getBoundsForRange, sjc_CAccessibleText, "getBoundsForRange", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;II)[D");
301 jdoubleArray axBounds = (jdoubleArray)JNFCallStaticObjectMethod(env, jm_getBoundsForRange, fAccessible, fComponent, range.location, range.length); // AWT_THREADING Safe (AWTRunLoop)
302 if (axBounds == NULL) return nil;
303
304 // We cheat because we know that the array is 4 elements long (x, y, width, height)
305 jdouble *values = (*env)->GetDoubleArrayElements(env, axBounds, 0);
306 if (values == NULL) {
307 // Note: Java will not be on the stack here so a java exception can't happen and no need to call ExceptionCheck.
308 NSLog(@"%s failed calling GetDoubleArrayElements", __FUNCTION__);
309 return nil;
310 };
311 NSRect bounds;
312 bounds.origin.x = values[0];
313 bounds.origin.y = [[[[self view] window] screen] frame].size.height - values[1] - values[3]; //values[1] is y-coord from top-left of screen. Flip. Account for the height (values[3]) when flipping
314 bounds.size.width = values[2];
315 bounds.size.height = values[3];
316 NSValue *result = [NSValue valueWithRect:bounds];
317 (*env)->ReleaseDoubleArrayElements(env, axBounds, values, 0);
318 return result;
319 }
320
321 - (NSNumber *)accessibilityLineForIndexAttributeForParameter:(id)parameter
322 {
323 NSNumber *line = (NSNumber *) parameter;
324 if (line == nil) return nil;
325
326 JNIEnv *env = [ThreadUtilities getJNIEnv];
327 static JNF_STATIC_MEMBER_CACHE(jm_getLineNumberForIndex, sjc_CAccessibleText, "getLineNumberForIndex", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;I)I");
328 jint row = JNFCallStaticIntMethod(env, jm_getLineNumberForIndex, fAccessible, fComponent, [line intValue]); // AWT_THREADING Safe (AWTRunLoop)
329 if (row < 0) return nil;
330 return [NSNumber numberWithInt:row];
331 }
332
333 - (NSValue *)accessibilityRangeForLineAttributeForParameter:(id)parameter
334 {
335 NSNumber *line = (NSNumber *) parameter;
336 if (line == nil) return nil;
337
338 JNIEnv *env = [ThreadUtilities getJNIEnv];
339 static JNF_STATIC_MEMBER_CACHE(jm_getRangeForLine, sjc_CAccessibleText, "getRangeForLine", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;I)[I");
340 jintArray axTextRange = (jintArray)JNFCallStaticObjectMethod(env, jm_getRangeForLine, fAccessible, fComponent, [line intValue]); // AWT_THREADING Safe (AWTRunLoop)
341 if (axTextRange == NULL) return nil;
342
343 return javaIntArrayToNSRangeValue(env,axTextRange);
344 }
345
346 //
347 // Usage of accessibilityStringForRangeAttributeForParameter:
348 // ---
349 // called by VoiceOver when interacting with text via ctrl-option-shift-downArrow.
350 // VO needs to know the particular string its currently dealing with so it can
351 // speak the string
352 //
353 - (NSString *)accessibilityStringForRangeAttributeForParameter:(id)parameter
354 {
355 #ifdef JAVA_AX_DEBUG_PARMS
356 if (!([parameter isKindOfClass:[NSValue class]] && strcmp([(NSValue *)parameter objCType], @encode(NSRange)) == 0)) {
357 JavaAccessibilityRaiseIllegalParameterTypeException(__FUNCTION__, self, NSAccessibilityBoundsForRangeParameterizedAttribute, parameter);
358 return nil;
359 }
360 #endif
361
362 NSRange range = [(NSValue *)parameter rangeValue];
363
364 JNIEnv *env = [ThreadUtilities getJNIEnv];
365 static JNF_STATIC_MEMBER_CACHE(jm_getStringForRange, sjc_CAccessibleText, "getStringForRange", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;II)Ljava/lang/String;");
366 jstring jstringForRange = (jstring)JNFCallStaticObjectMethod(env, jm_getStringForRange, fAccessible, fComponent, range.location, range.length); // AWT_THREADING Safe (AWTRunLoop)
367
368 if (jstringForRange == NULL) return @"";
369 NSString* str = JNFJavaToNSString(env, jstringForRange);
370 (*env)->DeleteLocalRef(env, jstringForRange);
371 return str;
372 }
373
374 //
375 // Usage of accessibilityRangeForPositionAttributeForParameter:
376 // ---
377 // cmcnote: I'm not sure when this is called / how it's used. Investigate.
378 // probably could be used in a special text-only accessibilityHitTest to
379 // find the index of the string under the mouse?
380 //
381 - (NSValue *)accessibilityRangeForPositionAttributeForParameter:(id)parameter
382 {
383 #ifdef JAVA_AX_DEBUG_PARMS
384 if (!([parameter isKindOfClass:[NSValue class]] && strcmp([(NSValue *)parameter objCType], @encode(NSPoint)) == 0)) {
385 JavaAccessibilityRaiseIllegalParameterTypeException(__FUNCTION__, self, NSAccessibilityRangeForPositionParameterizedAttribute, parameter);
386 return nil;
387 }
388 #endif
389
390 NSPoint point = [(NSValue *)parameter pointValue]; // point is in screen coords
391 point.y = [[[[self view] window] screen] frame].size.height - point.y; // flip into java screen coords (0 is at upper-left corner of screen)
392
393 JNIEnv *env = [ThreadUtilities getJNIEnv];
394 static JNF_STATIC_MEMBER_CACHE(jm_getCharacterIndexAtPosition, sjc_CAccessibleText, "getCharacterIndexAtPosition", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;II)I");
395 jint charIndex = JNFCallStaticIntMethod(env, jm_getCharacterIndexAtPosition, fAccessible, fComponent, point.x, point.y); // AWT_THREADING Safe (AWTRunLoop)
396 if (charIndex == -1) return nil;
397
398 // AccessibleText.getIndexAtPoint returns -1 for an invalid point
399 NSRange range = NSMakeRange(charIndex, 1); //range's length is 1 - one-character range
400 return [NSValue valueWithRange:range];
401 }
402
403 //
404 // Usage of accessibilityRangeForIndexAttributeForParameter:
405 // ---
406 // cmcnote: I'm not sure when this is called / how it's used. Investigate.
407 // AppKit version calls: [string rangeOfComposedCharacterSequenceAtIndex:index]
408 // We call: CAccessibility.getRangeForIndex, which calls AccessibleText.getAtIndex(AccessibleText.WORD, index)
409 // to determine the word closest to the given index. Then we find the length/location of this string.
410 //
411 - (NSValue *)accessibilityRangeForIndexAttributeForParameter:(id)parameter
412 {
413 #ifdef JAVA_AX_DEBUG_PARMS
414 if (![parameter isKindOfClass:[NSNumber class]]) {
415 JavaAccessibilityRaiseIllegalParameterTypeException(__FUNCTION__, self, NSAccessibilityRangeForIndexParameterizedAttribute, parameter);
416 return nil;
417 }
418 #endif
419
420 NSUInteger index = [(NSNumber *)parameter unsignedIntegerValue];
421
422 JNIEnv *env = [ThreadUtilities getJNIEnv];
423 static JNF_STATIC_MEMBER_CACHE(jm_getRangeForIndex, sjc_CAccessibleText, "getRangeForIndex", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;I)[I");
424 jintArray axTextRange = (jintArray)JNFCallStaticObjectMethod(env, jm_getRangeForIndex, fAccessible, fComponent, index); // AWT_THREADING Safe (AWTRunLoop)
425 if (axTextRange == NULL) return nil;
426
427 return javaIntArrayToNSRangeValue(env, axTextRange);
428 }
429
430 /*
431 * - (NSDictionary *)getActions:(JNIEnv *)env { ... }
432 *
433 * In the future, possibly add support: Editable text has AXShowMenu.
434 * Textfields have AXConfirm.
435 *
436 * Note: JLabels (static text) in JLists have a press/click selection action
437 * which is currently handled in superclass JavaComponentAccessibility.
438 * If function is added here be sure to use [super getActions:env] for JLabels.
439 */
440
441 @end
|
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 #import "JavaTextAccessibility.h"
27 #import "JavaAccessibilityAction.h"
28 #import "JavaAccessibilityUtilities.h"
29 #import "ThreadUtilities.h"
30 #import "JNIUtilities.h"
31
32 static jclass sjc_CAccessibility = NULL;
33 #define GET_CACCESSIBLITY_CLASS() \
34 GET_CLASS(sjc_CAccessibility, "sun/lwawt/macosx/CAccessibility");
35 #define GET_CACCESSIBLITY_CLASS_RETURN(ret) \
36 GET_CLASS_RETURN(sjc_CAccessibility, "sun/lwawt/macosx/CAccessibility", ret);
37
38 static jmethodID sjm_getAccessibleText = NULL;
39 #define GET_ACCESSIBLETEXT_METHOD_RETURN(ret) \
40 GET_CACCESSIBLITY_CLASS_RETURN(ret); \
41 GET_STATIC_METHOD_RETURN(sjm_getAccessibleText, sjc_CAccessibility, "getAccessibleText", \
42 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/AccessibleText;", ret);
43
44 static jclass sjc_CAccessibleText = NULL;
45 #define GET_CACCESSIBLETEXT_CLASS() \
46 GET_CLASS(sjc_CAccessibleText, "sun/lwawt/macosx/CAccessibleText");
47 #define GET_CACCESSIBLETEXT_CLASS_RETURN(ret) \
48 GET_CLASS_RETURN(sjc_CAccessibleText, "sun/lwawt/macosx/CAccessibleText", ret);
49
50 static jmethodID sjm_getAccessibleEditableText = NULL;
51 #define GET_ACCESSIBLEEDITABLETEXT_METHOD_RETURN(ret) \
52 GET_CACCESSIBLETEXT_CLASS_RETURN(ret); \
53 GET_STATIC_METHOD_RETURN(sjm_getAccessibleEditableText, sjc_CAccessibleText, "getAccessibleEditableText", \
54 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljavax/accessibility/AccessibleEditableText;", ret);
55
56
57 /*
58 * Converts an int array to an NSRange wrapped inside an NSValue
59 * takes [start, end] values and returns [start, end - start]
60 */
61 NSValue *javaIntArrayToNSRangeValue(JNIEnv* env, jintArray array) {
62 jint *values = (*env)->GetIntArrayElements(env, array, 0);
63 if (values == NULL) {
64 // Note: Java will not be on the stack here so a java exception can't happen and no need to call ExceptionCheck.
65 NSLog(@"%s failed calling GetIntArrayElements", __FUNCTION__);
66 return nil;
67 };
68 NSValue *value = [NSValue valueWithRange:NSMakeRange(values[0], values[1] - values[0])];
69 (*env)->ReleaseIntArrayElements(env, array, values, 0);
70 return value;
71 }
72
73 @implementation JavaTextAccessibility
74
75 // based strongly upon NSTextViewAccessibility:accessibilityAttributeNames
111 attributes = [[NSArray alloc] initWithObjects:
112 NSAccessibilityLineForIndexParameterizedAttribute,
113 NSAccessibilityRangeForLineParameterizedAttribute,
114 NSAccessibilityStringForRangeParameterizedAttribute,
115 NSAccessibilityRangeForPositionParameterizedAttribute,
116 NSAccessibilityRangeForIndexParameterizedAttribute,
117 NSAccessibilityBoundsForRangeParameterizedAttribute,
118 //NSAccessibilityRTFForRangeParameterizedAttribute, // cmcnote: not sure when/how these three are used. Investigate. radr://3960026
119 //NSAccessibilityStyleRangeForIndexParameterizedAttribute,
120 //NSAccessibilityAttributedStringForRangeParameterizedAttribute,
121 nil];
122 }
123 //APPKIT_UNLOCK;
124 }
125 return attributes;
126 }
127
128 - (NSString *)accessibilityValueAttribute
129 {
130 JNIEnv *env = [ThreadUtilities getJNIEnv];
131 GET_CACCESSIBLITY_CLASS_RETURN(nil);
132 DECLARE_STATIC_METHOD_RETURN(sjm_getAccessibleName, sjc_CAccessibility, "getAccessibleName",
133 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;", nil);
134 if ([[self accessibilityRoleAttribute] isEqualToString:NSAccessibilityStaticTextRole]) {
135 // if it's static text, the AppKit AXValue is the java accessibleName
136 jobject axName = (*env)->CallStaticObjectMethod(env, sjc_CAccessibility,
137 sjm_getAccessibleName, fAccessible, fComponent);
138 CHECK_EXCEPTION();
139 if (axName != NULL) {
140 NSString* str = JavaStringToNSString(env, axName);
141 (*env)->DeleteLocalRef(env, axName);
142 return str;
143 }
144 // value is still nil if no accessibleName for static text. Below, try to get the accessibleText.
145 }
146
147 // cmcnote: inefficient to make three distinct JNI calls. Coalesce. radr://3951923
148 GET_ACCESSIBLETEXT_METHOD_RETURN(@"");
149 jobject axText = (*env)->CallStaticObjectMethod(env, sjc_CAccessibility,
150 sjm_getAccessibleText, fAccessible, fComponent);
151 CHECK_EXCEPTION();
152 if (axText == NULL) return nil;
153 (*env)->DeleteLocalRef(env, axText);
154
155 GET_ACCESSIBLEEDITABLETEXT_METHOD_RETURN(nil);
156 jobject axEditableText = (*env)->CallStaticObjectMethod(env, sjc_CAccessibleText,
157 sjm_getAccessibleEditableText, fAccessible, fComponent);
158 CHECK_EXCEPTION();
159 if (axEditableText == NULL) return nil;
160
161 DECLARE_STATIC_METHOD_RETURN(jm_getTextRange, sjc_CAccessibleText, "getTextRange",
162 "(Ljavax/accessibility/AccessibleEditableText;IILjava/awt/Component;)Ljava/lang/String;", nil);
163 jobject jrange = (*env)->CallStaticObjectMethod(env, sjc_CAccessibleText, jm_getTextRange,
164 axEditableText, 0, getAxTextCharCount(env, axEditableText, fComponent), fComponent);
165 CHECK_EXCEPTION();
166 NSString *string = JavaStringToNSString(env, jrange);
167
168 (*env)->DeleteLocalRef(env, jrange);
169 (*env)->DeleteLocalRef(env, axEditableText);
170
171 if (string == nil) string = @"";
172 return string;
173 }
174
175 - (BOOL)accessibilityIsValueAttributeSettable
176 {
177 // if text is enabled and editable, it's settable (according to NSCellTextAttributesAccessibility)
178 BOOL isEnabled = [(NSNumber *)[self accessibilityEnabledAttribute] boolValue];
179 if (!isEnabled) return NO;
180
181 JNIEnv* env = [ThreadUtilities getJNIEnv];
182 GET_ACCESSIBLEEDITABLETEXT_METHOD_RETURN(NO);
183 jobject axEditableText = (*env)->CallStaticObjectMethod(env, sjc_CAccessibleText,
184 sjm_getAccessibleEditableText, fAccessible, fComponent);
185 CHECK_EXCEPTION();
186 if (axEditableText == NULL) return NO;
187 (*env)->DeleteLocalRef(env, axEditableText);
188 return YES;
189 }
190
191 - (void)accessibilitySetValueAttribute:(id)value
192 {
193 // cmcnote: should set the accessibleEditableText to the stringValue of value - AccessibleEditableText.setTextContents(String s)
194 #ifdef JAVA_AX_DEBUG
195 NSLog(@"Not yet implemented: %s\n", __FUNCTION__); // radr://3954018
196 #endif
197 }
198
199 // Currently selected text (NSString)
200 - (NSString *)accessibilitySelectedTextAttribute
201 {
202 JNIEnv* env = [ThreadUtilities getJNIEnv];
203 GET_CACCESSIBLETEXT_CLASS_RETURN(nil);
204 DECLARE_STATIC_METHOD_RETURN(jm_getSelectedText, sjc_CAccessibleText, "getSelectedText",
205 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)Ljava/lang/String;", nil);
206 jobject axText = (*env)->CallStaticObjectMethod(env, sjc_CAccessibleText, jm_getSelectedText,
207 fAccessible, fComponent);
208 CHECK_EXCEPTION();
209 if (axText == NULL) return @"";
210 NSString* str = JavaStringToNSString(env, axText);
211 (*env)->DeleteLocalRef(env, axText);
212 return str;
213 }
214
215 - (BOOL)accessibilityIsSelectedTextAttributeSettable
216 {
217 return YES; //cmcnote: for AXTextField that's selectable, it's settable. Investigate further.
218 }
219
220 - (void)accessibilitySetSelectedTextAttribute:(id)value
221 {
222 #ifdef JAVA_AX_DEBUG_PARMS
223 if (![value isKindOfClass:[NSString class]]) {
224 JavaAccessibilityRaiseSetAttributeToIllegalTypeException(__FUNCTION__, self, NSAccessibilitySelectedTextAttribute, value);
225 return;
226 }
227 #endif
228
229 JNIEnv *env = [ThreadUtilities getJNIEnv];
230 jstring jstringValue = NSStringToJavaString(env, (NSString *)value);
231 GET_CACCESSIBLETEXT_CLASS();
232 DECLARE_STATIC_METHOD(jm_setSelectedText, sjc_CAccessibleText, "setSelectedText",
233 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;Ljava/lang/String;)V");
234 (*env)->CallStaticVoidMethod(env, sjc_CAccessibleText, jm_setSelectedText,
235 fAccessible, fComponent, jstringValue);
236 CHECK_EXCEPTION();
237 }
238
239 // Range of selected text (NSValue)
240 - (NSValue *)accessibilitySelectedTextRangeAttribute
241 {
242 JNIEnv *env = [ThreadUtilities getJNIEnv];
243 GET_CACCESSIBLETEXT_CLASS_RETURN(nil);
244 DECLARE_STATIC_METHOD_RETURN(jm_getSelectedTextRange, sjc_CAccessibleText, "getSelectedTextRange",
245 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)[I", nil);
246 jintArray axTextRange = (*env)->CallStaticObjectMethod(env, sjc_CAccessibleText,
247 jm_getSelectedTextRange, fAccessible, fComponent);
248 CHECK_EXCEPTION();
249 if (axTextRange == NULL) return nil;
250
251 return javaIntArrayToNSRangeValue(env, axTextRange);
252 }
253
254 - (BOOL)accessibilityIsSelectedTextRangeAttributeSettable
255 {
256 return [(NSNumber *)[self accessibilityEnabledAttribute] boolValue]; // cmcnote: also may want to find out if isSelectable. Investigate.
257 }
258
259 - (void)accessibilitySetSelectedTextRangeAttribute:(id)value
260 {
261 #ifdef JAVA_AX_DEBUG_PARMS
262 if (!([value isKindOfClass:[NSValue class]] && strcmp([(NSValue *)value objCType], @encode(NSRange)) == 0)) {
263 JavaAccessibilityRaiseSetAttributeToIllegalTypeException(__FUNCTION__, self, NSAccessibilitySelectedTextRangeAttribute, value);
264 return;
265 }
266 #endif
267
268 NSRange range = [(NSValue *)value rangeValue];
269 jint startIndex = range.location;
270 jint endIndex = startIndex + range.length;
271
272 JNIEnv *env = [ThreadUtilities getJNIEnv];
273 GET_CACCESSIBLETEXT_CLASS();
274 DECLARE_STATIC_METHOD(jm_setSelectedTextRange, sjc_CAccessibleText, "setSelectedTextRange",
275 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;II)V");
276 (*env)->CallStaticVoidMethod(env, sjc_CAccessibleText, jm_setSelectedTextRange,
277 fAccessible, fComponent, startIndex, endIndex);
278 CHECK_EXCEPTION();
279 }
280
281 - (NSNumber *)accessibilityNumberOfCharactersAttribute
282 {
283 // cmcnote: should coalesce these two calls - radr://3951923
284 // also, static text doesn't always have accessibleText. if axText is null, should get the charcount of the accessibleName instead
285 JNIEnv *env = [ThreadUtilities getJNIEnv];
286 GET_ACCESSIBLETEXT_METHOD_RETURN(nil);
287 jobject axText = (*env)->CallStaticObjectMethod(env, sjc_CAccessibility,
288 sjm_getAccessibleText, fAccessible, fComponent);
289 CHECK_EXCEPTION();
290 NSNumber* num = [NSNumber numberWithInt:getAxTextCharCount(env, axText, fComponent)];
291 (*env)->DeleteLocalRef(env, axText);
292 return num;
293 }
294
295 - (BOOL)accessibilityIsNumberOfCharactersAttributeSettable
296 {
297 return NO; // according to NSTextViewAccessibility.m and NSCellTextAttributesAccessibility.m
298 }
299
300 - (NSValue *)accessibilityVisibleCharacterRangeAttribute
301 {
302 JNIEnv *env = [ThreadUtilities getJNIEnv];
303 GET_CACCESSIBLETEXT_CLASS_RETURN(nil);
304 DECLARE_STATIC_METHOD_RETURN(jm_getVisibleCharacterRange, sjc_CAccessibleText, "getVisibleCharacterRange",
305 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)[I", nil);
306 jintArray axTextRange = (*env)->CallStaticObjectMethod(env, sjc_CAccessibleText,
307 jm_getVisibleCharacterRange, fAccessible, fComponent);
308 CHECK_EXCEPTION();
309 if (axTextRange == NULL) return nil;
310
311 return javaIntArrayToNSRangeValue(env, axTextRange);
312 }
313
314 - (BOOL)accessibilityIsVisibleCharacterRangeAttributeSettable
315 {
316 #ifdef JAVA_AX_DEBUG
317 NSLog(@"Not yet implemented: %s\n", __FUNCTION__);
318 #endif
319 return NO;
320 }
321
322 - (NSValue *)accessibilityInsertionPointLineNumberAttribute
323 {
324 JNIEnv *env = [ThreadUtilities getJNIEnv];
325 GET_CACCESSIBLETEXT_CLASS_RETURN(nil);
326 DECLARE_STATIC_METHOD_RETURN(jm_getLineNumberForInsertionPoint, sjc_CAccessibleText,
327 "getLineNumberForInsertionPoint", "(Ljavax/accessibility/Accessible;Ljava/awt/Component;)I", nil);
328 jint row = (*env)->CallStaticIntMethod(env, sjc_CAccessibleText,
329 jm_getLineNumberForInsertionPoint, fAccessible, fComponent);
330 CHECK_EXCEPTION();
331 if (row < 0) return nil;
332 return [NSNumber numberWithInt:row];
333 }
334
335 - (BOOL)accessibilityIsInsertionPointLineNumberAttributeSettable
336 {
337 #ifdef JAVA_AX_DEBUG
338 NSLog(@"Not yet implemented: %s\n", __FUNCTION__);
339 #endif
340 return NO;
341 }
342
343 // parameterized attributes
344
345 //
346 // Usage of accessibilityBoundsForRangeAttributeForParameter:
347 // ---
348 // called by VoiceOver when interacting with text via ctrl-option-shift-downArrow.
349 // Need to know bounding box for the character / word / line of interest in
350 // order to draw VoiceOver cursor
351 //
352 - (NSValue *)accessibilityBoundsForRangeAttributeForParameter:(id)parameter
353 {
354 #ifdef JAVA_AX_DEBUG_PARMS
355 if (!([parameter isKindOfClass:[NSValue class]] && strcmp([(NSValue *)parameter objCType], @encode(NSRange)) == 0)) {
356 JavaAccessibilityRaiseIllegalParameterTypeException(__FUNCTION__, self, NSAccessibilityBoundsForRangeParameterizedAttribute, parameter);
357 return nil;
358 }
359 #endif
360
361 NSRange range = [(NSValue *)parameter rangeValue];
362
363 JNIEnv *env = [ThreadUtilities getJNIEnv];
364 GET_CACCESSIBLETEXT_CLASS_RETURN(nil);
365 DECLARE_STATIC_METHOD_RETURN(jm_getBoundsForRange, sjc_CAccessibleText, "getBoundsForRange",
366 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;II)[D", nil);
367 jdoubleArray axBounds = (jdoubleArray)(*env)->CallStaticObjectMethod(env, sjc_CAccessibleText, jm_getBoundsForRange,
368 fAccessible, fComponent, range.location, range.length);
369 CHECK_EXCEPTION();
370 if (axBounds == NULL) return nil;
371
372 // We cheat because we know that the array is 4 elements long (x, y, width, height)
373 jdouble *values = (*env)->GetDoubleArrayElements(env, axBounds, 0);
374 CHECK_EXCEPTION();
375 if (values == NULL) {
376 // Note: Java will not be on the stack here so a java exception can't happen and no need to call ExceptionCheck.
377 NSLog(@"%s failed calling GetDoubleArrayElements", __FUNCTION__);
378 return nil;
379 };
380 NSRect bounds;
381 bounds.origin.x = values[0];
382 bounds.origin.y = [[[[self view] window] screen] frame].size.height - values[1] - values[3]; //values[1] is y-coord from top-left of screen. Flip. Account for the height (values[3]) when flipping
383 bounds.size.width = values[2];
384 bounds.size.height = values[3];
385 NSValue *result = [NSValue valueWithRect:bounds];
386 (*env)->ReleaseDoubleArrayElements(env, axBounds, values, 0);
387 return result;
388 }
389
390 - (NSNumber *)accessibilityLineForIndexAttributeForParameter:(id)parameter
391 {
392 NSNumber *line = (NSNumber *) parameter;
393 if (line == nil) return nil;
394
395 JNIEnv *env = [ThreadUtilities getJNIEnv];
396 GET_CACCESSIBLETEXT_CLASS_RETURN(nil);
397 DECLARE_STATIC_METHOD_RETURN(jm_getLineNumberForIndex, sjc_CAccessibleText, "getLineNumberForIndex",
398 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;I)I", nil);
399 jint row = (*env)->CallStaticIntMethod(env, sjc_CAccessibleText, jm_getLineNumberForIndex,
400 fAccessible, fComponent, [line intValue]);
401 CHECK_EXCEPTION();
402 if (row < 0) return nil;
403 return [NSNumber numberWithInt:row];
404 }
405
406 - (NSValue *)accessibilityRangeForLineAttributeForParameter:(id)parameter
407 {
408 NSNumber *line = (NSNumber *) parameter;
409 if (line == nil) return nil;
410
411 JNIEnv *env = [ThreadUtilities getJNIEnv];
412 GET_CACCESSIBLETEXT_CLASS_RETURN(nil);
413 DECLARE_STATIC_METHOD_RETURN(jm_getRangeForLine, sjc_CAccessibleText, "getRangeForLine",
414 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;I)[I", nil);
415 jintArray axTextRange = (jintArray)(*env)->CallStaticObjectMethod(env, sjc_CAccessibleText,
416 jm_getRangeForLine, fAccessible, fComponent, [line intValue]);
417 CHECK_EXCEPTION();
418 if (axTextRange == NULL) return nil;
419
420 return javaIntArrayToNSRangeValue(env,axTextRange);
421 }
422
423 //
424 // Usage of accessibilityStringForRangeAttributeForParameter:
425 // ---
426 // called by VoiceOver when interacting with text via ctrl-option-shift-downArrow.
427 // VO needs to know the particular string its currently dealing with so it can
428 // speak the string
429 //
430 - (NSString *)accessibilityStringForRangeAttributeForParameter:(id)parameter
431 {
432 #ifdef JAVA_AX_DEBUG_PARMS
433 if (!([parameter isKindOfClass:[NSValue class]] && strcmp([(NSValue *)parameter objCType], @encode(NSRange)) == 0)) {
434 JavaAccessibilityRaiseIllegalParameterTypeException(__FUNCTION__, self, NSAccessibilityBoundsForRangeParameterizedAttribute, parameter);
435 return nil;
436 }
437 #endif
438
439 NSRange range = [(NSValue *)parameter rangeValue];
440
441 JNIEnv *env = [ThreadUtilities getJNIEnv];
442 GET_CACCESSIBLETEXT_CLASS_RETURN(nil);
443 DECLARE_STATIC_METHOD_RETURN(jm_getStringForRange, sjc_CAccessibleText, "getStringForRange",
444 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;II)Ljava/lang/String;", nil);
445 jstring jstringForRange = (jstring)(*env)->CallStaticObjectMethod(env, sjc_CAccessibleText, jm_getStringForRange,
446 fAccessible, fComponent, range.location, range.length);
447 CHECK_EXCEPTION();
448 if (jstringForRange == NULL) return @"";
449 NSString* str = JavaStringToNSString(env, jstringForRange);
450 (*env)->DeleteLocalRef(env, jstringForRange);
451 return str;
452 }
453
454 //
455 // Usage of accessibilityRangeForPositionAttributeForParameter:
456 // ---
457 // cmcnote: I'm not sure when this is called / how it's used. Investigate.
458 // probably could be used in a special text-only accessibilityHitTest to
459 // find the index of the string under the mouse?
460 //
461 - (NSValue *)accessibilityRangeForPositionAttributeForParameter:(id)parameter
462 {
463 #ifdef JAVA_AX_DEBUG_PARMS
464 if (!([parameter isKindOfClass:[NSValue class]] && strcmp([(NSValue *)parameter objCType], @encode(NSPoint)) == 0)) {
465 JavaAccessibilityRaiseIllegalParameterTypeException(__FUNCTION__, self, NSAccessibilityRangeForPositionParameterizedAttribute, parameter);
466 return nil;
467 }
468 #endif
469
470 NSPoint point = [(NSValue *)parameter pointValue]; // point is in screen coords
471 point.y = [[[[self view] window] screen] frame].size.height - point.y; // flip into java screen coords (0 is at upper-left corner of screen)
472
473 JNIEnv *env = [ThreadUtilities getJNIEnv];
474 GET_CACCESSIBLETEXT_CLASS_RETURN(nil);
475 DECLARE_STATIC_METHOD_RETURN(jm_getCharacterIndexAtPosition, sjc_CAccessibleText, "getCharacterIndexAtPosition",
476 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;II)I", nil);
477 jint charIndex = (*env)->CallStaticIntMethod(env, sjc_CAccessibleText, jm_getCharacterIndexAtPosition,
478 fAccessible, fComponent, point.x, point.y);
479 CHECK_EXCEPTION();
480 if (charIndex == -1) return nil;
481
482 // AccessibleText.getIndexAtPoint returns -1 for an invalid point
483 NSRange range = NSMakeRange(charIndex, 1); //range's length is 1 - one-character range
484 return [NSValue valueWithRange:range];
485 }
486
487 //
488 // Usage of accessibilityRangeForIndexAttributeForParameter:
489 // ---
490 // cmcnote: I'm not sure when this is called / how it's used. Investigate.
491 // AppKit version calls: [string rangeOfComposedCharacterSequenceAtIndex:index]
492 // We call: CAccessibility.getRangeForIndex, which calls AccessibleText.getAtIndex(AccessibleText.WORD, index)
493 // to determine the word closest to the given index. Then we find the length/location of this string.
494 //
495 - (NSValue *)accessibilityRangeForIndexAttributeForParameter:(id)parameter
496 {
497 #ifdef JAVA_AX_DEBUG_PARMS
498 if (![parameter isKindOfClass:[NSNumber class]]) {
499 JavaAccessibilityRaiseIllegalParameterTypeException(__FUNCTION__, self, NSAccessibilityRangeForIndexParameterizedAttribute, parameter);
500 return nil;
501 }
502 #endif
503
504 NSUInteger index = [(NSNumber *)parameter unsignedIntegerValue];
505
506 JNIEnv *env = [ThreadUtilities getJNIEnv];
507 GET_CACCESSIBLETEXT_CLASS_RETURN(nil);
508 DECLARE_STATIC_METHOD_RETURN(jm_getRangeForIndex, sjc_CAccessibleText, "getRangeForIndex",
509 "(Ljavax/accessibility/Accessible;Ljava/awt/Component;I)[I", nil);
510 jintArray axTextRange = (jintArray)(*env)->CallStaticObjectMethod(env, sjc_CAccessibleText, jm_getRangeForIndex,
511 fAccessible, fComponent, index);
512 CHECK_EXCEPTION();
513 if (axTextRange == NULL) return nil;
514
515 return javaIntArrayToNSRangeValue(env, axTextRange);
516 }
517
518 /*
519 * - (NSDictionary *)getActions:(JNIEnv *)env { ... }
520 *
521 * In the future, possibly add support: Editable text has AXShowMenu.
522 * Textfields have AXConfirm.
523 *
524 * Note: JLabels (static text) in JLists have a press/click selection action
525 * which is currently handled in superclass JavaComponentAccessibility.
526 * If function is added here be sure to use [super getActions:env] for JLabels.
527 */
528
529 @end
|