|
1 /* -*- Mode: Objective-C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "Accessible-inl.h" |
|
7 #include "HyperTextAccessible-inl.h" |
|
8 #include "TextLeafAccessible.h" |
|
9 |
|
10 #include "nsCocoaUtils.h" |
|
11 #include "nsObjCExceptions.h" |
|
12 |
|
13 #import "mozTextAccessible.h" |
|
14 |
|
15 using namespace mozilla::a11y; |
|
16 |
|
17 inline bool |
|
18 ToNSRange(id aValue, NSRange* aRange) |
|
19 { |
|
20 NS_PRECONDITION(aRange, "aRange is nil"); |
|
21 |
|
22 if ([aValue isKindOfClass:[NSValue class]] && |
|
23 strcmp([(NSValue*)aValue objCType], @encode(NSRange)) == 0) { |
|
24 *aRange = [aValue rangeValue]; |
|
25 return true; |
|
26 } |
|
27 |
|
28 return false; |
|
29 } |
|
30 |
|
31 inline NSString* |
|
32 ToNSString(id aValue) |
|
33 { |
|
34 if ([aValue isKindOfClass:[NSString class]]) { |
|
35 return aValue; |
|
36 } |
|
37 |
|
38 return nil; |
|
39 } |
|
40 |
|
41 @interface mozTextAccessible () |
|
42 - (NSString*)subrole; |
|
43 - (NSString*)selectedText; |
|
44 - (NSValue*)selectedTextRange; |
|
45 - (NSValue*)visibleCharacterRange; |
|
46 - (long)textLength; |
|
47 - (BOOL)isReadOnly; |
|
48 - (NSNumber*)caretLineNumber; |
|
49 - (void)setText:(NSString*)newText; |
|
50 - (NSString*)text; |
|
51 - (NSString*)stringFromRange:(NSRange*)range; |
|
52 @end |
|
53 |
|
54 @implementation mozTextAccessible |
|
55 |
|
56 - (id)initWithAccessible:(AccessibleWrap*)accessible |
|
57 { |
|
58 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; |
|
59 |
|
60 if ((self = [super initWithAccessible:accessible])) { |
|
61 mGeckoTextAccessible = accessible->AsHyperText(); |
|
62 CallQueryInterface(accessible, &mGeckoEditableTextAccessible); |
|
63 } |
|
64 return self; |
|
65 |
|
66 NS_OBJC_END_TRY_ABORT_BLOCK_NIL; |
|
67 } |
|
68 |
|
69 - (BOOL)accessibilityIsIgnored |
|
70 { |
|
71 return !mGeckoAccessible; |
|
72 } |
|
73 |
|
74 - (NSArray*)accessibilityAttributeNames |
|
75 { |
|
76 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; |
|
77 |
|
78 static NSMutableArray* supportedAttributes = nil; |
|
79 if (!supportedAttributes) { |
|
80 // text-specific attributes to supplement the standard one |
|
81 supportedAttributes = [[NSMutableArray alloc] initWithObjects: |
|
82 NSAccessibilitySelectedTextAttribute, // required |
|
83 NSAccessibilitySelectedTextRangeAttribute, // required |
|
84 NSAccessibilityNumberOfCharactersAttribute, // required |
|
85 NSAccessibilityVisibleCharacterRangeAttribute, // required |
|
86 NSAccessibilityInsertionPointLineNumberAttribute, |
|
87 @"AXRequired", |
|
88 @"AXInvalid", |
|
89 nil |
|
90 ]; |
|
91 [supportedAttributes addObjectsFromArray:[super accessibilityAttributeNames]]; |
|
92 } |
|
93 return supportedAttributes; |
|
94 |
|
95 NS_OBJC_END_TRY_ABORT_BLOCK_NIL; |
|
96 } |
|
97 |
|
98 - (id)accessibilityAttributeValue:(NSString*)attribute |
|
99 { |
|
100 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; |
|
101 |
|
102 if ([attribute isEqualToString:NSAccessibilityNumberOfCharactersAttribute]) |
|
103 return [NSNumber numberWithInt:[self textLength]]; |
|
104 |
|
105 if ([attribute isEqualToString:NSAccessibilityInsertionPointLineNumberAttribute]) |
|
106 return [self caretLineNumber]; |
|
107 |
|
108 if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) |
|
109 return [self selectedTextRange]; |
|
110 |
|
111 if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) |
|
112 return [self selectedText]; |
|
113 |
|
114 if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) |
|
115 return @""; |
|
116 |
|
117 if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { |
|
118 // Apple's SpeechSynthesisServer expects AXValue to return an AXStaticText |
|
119 // object's AXSelectedText attribute. See bug 674612 for details. |
|
120 // Also if there is no selected text, we return the full text. |
|
121 // See bug 369710 for details. |
|
122 if ([[self role] isEqualToString:NSAccessibilityStaticTextRole]) { |
|
123 NSString* selectedText = [self selectedText]; |
|
124 return (selectedText && [selectedText length]) ? selectedText : [self text]; |
|
125 } |
|
126 |
|
127 return [self text]; |
|
128 } |
|
129 |
|
130 if ([attribute isEqualToString:@"AXRequired"]) |
|
131 return [NSNumber numberWithBool:!!(mGeckoAccessible->State() & states::REQUIRED)]; |
|
132 |
|
133 if ([attribute isEqualToString:@"AXInvalid"]) |
|
134 return [NSNumber numberWithBool:!!(mGeckoAccessible->State() & states::INVALID)]; |
|
135 |
|
136 if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) |
|
137 return [self visibleCharacterRange]; |
|
138 |
|
139 // let mozAccessible handle all other attributes |
|
140 return [super accessibilityAttributeValue:attribute]; |
|
141 |
|
142 NS_OBJC_END_TRY_ABORT_BLOCK_NIL; |
|
143 } |
|
144 |
|
145 - (NSArray*)accessibilityParameterizedAttributeNames |
|
146 { |
|
147 static NSArray* supportedParametrizedAttributes = nil; |
|
148 // text specific parametrized attributes |
|
149 if (!supportedParametrizedAttributes) { |
|
150 supportedParametrizedAttributes = [[NSArray alloc] initWithObjects: |
|
151 NSAccessibilityStringForRangeParameterizedAttribute, |
|
152 NSAccessibilityLineForIndexParameterizedAttribute, |
|
153 NSAccessibilityRangeForLineParameterizedAttribute, |
|
154 NSAccessibilityAttributedStringForRangeParameterizedAttribute, |
|
155 NSAccessibilityBoundsForRangeParameterizedAttribute, |
|
156 #if DEBUG |
|
157 NSAccessibilityRangeForPositionParameterizedAttribute, |
|
158 NSAccessibilityRangeForIndexParameterizedAttribute, |
|
159 NSAccessibilityRTFForRangeParameterizedAttribute, |
|
160 NSAccessibilityStyleRangeForIndexParameterizedAttribute, |
|
161 #endif |
|
162 nil |
|
163 ]; |
|
164 } |
|
165 return supportedParametrizedAttributes; |
|
166 } |
|
167 |
|
168 - (id)accessibilityAttributeValue:(NSString*)attribute forParameter:(id)parameter |
|
169 { |
|
170 if (!mGeckoTextAccessible) |
|
171 return nil; |
|
172 |
|
173 if ([attribute isEqualToString:NSAccessibilityStringForRangeParameterizedAttribute]) { |
|
174 NSRange range; |
|
175 if (!ToNSRange(parameter, &range)) { |
|
176 #if DEBUG |
|
177 NSLog(@"%@: range not set", attribute); |
|
178 #endif |
|
179 return @""; |
|
180 } |
|
181 |
|
182 return [self stringFromRange:&range]; |
|
183 } |
|
184 |
|
185 if ([attribute isEqualToString:NSAccessibilityRangeForLineParameterizedAttribute]) { |
|
186 // XXX: actually get the integer value for the line # |
|
187 return [NSValue valueWithRange:NSMakeRange(0, [self textLength])]; |
|
188 } |
|
189 |
|
190 if ([attribute isEqualToString:NSAccessibilityAttributedStringForRangeParameterizedAttribute]) { |
|
191 NSRange range; |
|
192 if (!ToNSRange(parameter, &range)) { |
|
193 #if DEBUG |
|
194 NSLog(@"%@: range not set", attribute); |
|
195 #endif |
|
196 return @""; |
|
197 } |
|
198 |
|
199 return [[[NSAttributedString alloc] initWithString:[self stringFromRange:&range]] autorelease]; |
|
200 } |
|
201 |
|
202 if ([attribute isEqualToString:NSAccessibilityLineForIndexParameterizedAttribute]) { |
|
203 // XXX: actually return the line # |
|
204 return [NSNumber numberWithInt:0]; |
|
205 } |
|
206 |
|
207 if ([attribute isEqualToString:NSAccessibilityBoundsForRangeParameterizedAttribute]) { |
|
208 NSRange range; |
|
209 if (!ToNSRange(parameter, &range)) { |
|
210 #if DEBUG |
|
211 NSLog(@"%@:no range", attribute); |
|
212 #endif |
|
213 return nil; |
|
214 } |
|
215 |
|
216 int32_t start = range.location; |
|
217 int32_t end = start + range.length; |
|
218 nsIntRect bounds = mGeckoTextAccessible->TextBounds(start, end); |
|
219 |
|
220 return [NSValue valueWithRect:nsCocoaUtils::GeckoRectToCocoaRect(bounds)]; |
|
221 } |
|
222 |
|
223 #if DEBUG |
|
224 NSLog(@"unhandled attribute:%@ forParameter:%@", attribute, parameter); |
|
225 #endif |
|
226 |
|
227 return nil; |
|
228 } |
|
229 |
|
230 - (BOOL)accessibilityIsAttributeSettable:(NSString*)attribute |
|
231 { |
|
232 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; |
|
233 |
|
234 if ([attribute isEqualToString:NSAccessibilityValueAttribute]) |
|
235 return ![self isReadOnly]; |
|
236 |
|
237 if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute] || |
|
238 [attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute] || |
|
239 [attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) |
|
240 return YES; |
|
241 |
|
242 return [super accessibilityIsAttributeSettable:attribute]; |
|
243 |
|
244 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); |
|
245 } |
|
246 |
|
247 - (void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute |
|
248 { |
|
249 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
250 |
|
251 if (!mGeckoTextAccessible) |
|
252 return; |
|
253 |
|
254 if ([attribute isEqualToString:NSAccessibilityValueAttribute]) { |
|
255 [self setText:ToNSString(value)]; |
|
256 |
|
257 return; |
|
258 } |
|
259 |
|
260 if ([attribute isEqualToString:NSAccessibilitySelectedTextAttribute]) { |
|
261 NSString* stringValue = ToNSString(value); |
|
262 if (!stringValue) |
|
263 return; |
|
264 |
|
265 int32_t start = 0, end = 0; |
|
266 mGeckoTextAccessible->SelectionBoundsAt(0, &start, &end); |
|
267 mGeckoTextAccessible->DeleteText(start, end - start); |
|
268 |
|
269 nsString text; |
|
270 nsCocoaUtils::GetStringForNSString(stringValue, text); |
|
271 mGeckoTextAccessible->InsertText(text, start); |
|
272 } |
|
273 |
|
274 if ([attribute isEqualToString:NSAccessibilitySelectedTextRangeAttribute]) { |
|
275 NSRange range; |
|
276 if (!ToNSRange(value, &range)) |
|
277 return; |
|
278 |
|
279 mGeckoTextAccessible->SetSelectionBoundsAt(0, range.location, |
|
280 range.location + range.length); |
|
281 return; |
|
282 } |
|
283 |
|
284 if ([attribute isEqualToString:NSAccessibilityVisibleCharacterRangeAttribute]) { |
|
285 NSRange range; |
|
286 if (!ToNSRange(value, &range)) |
|
287 return; |
|
288 |
|
289 mGeckoTextAccessible->ScrollSubstringTo(range.location, range.location + range.length, |
|
290 nsIAccessibleScrollType::SCROLL_TYPE_TOP_EDGE); |
|
291 return; |
|
292 } |
|
293 |
|
294 [super accessibilitySetValue:value forAttribute:attribute]; |
|
295 |
|
296 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
297 } |
|
298 |
|
299 - (NSString*)subrole |
|
300 { |
|
301 if(mRole == roles::PASSWORD_TEXT) |
|
302 return NSAccessibilitySecureTextFieldSubrole; |
|
303 |
|
304 return nil; |
|
305 } |
|
306 |
|
307 - (void)expire |
|
308 { |
|
309 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
310 |
|
311 mGeckoTextAccessible = nullptr; |
|
312 NS_IF_RELEASE(mGeckoEditableTextAccessible); |
|
313 [super expire]; |
|
314 |
|
315 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
316 } |
|
317 |
|
318 #pragma mark - |
|
319 |
|
320 - (BOOL)isReadOnly |
|
321 { |
|
322 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; |
|
323 |
|
324 if ([[self role] isEqualToString:NSAccessibilityStaticTextRole]) |
|
325 return YES; |
|
326 |
|
327 if (mGeckoEditableTextAccessible) |
|
328 return (mGeckoAccessible->State() & states::READONLY) == 0; |
|
329 |
|
330 return NO; |
|
331 |
|
332 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); |
|
333 } |
|
334 |
|
335 - (NSNumber*)caretLineNumber |
|
336 { |
|
337 int32_t lineNumber = mGeckoTextAccessible ? |
|
338 mGeckoTextAccessible->CaretLineNumber() - 1 : -1; |
|
339 |
|
340 return (lineNumber >= 0) ? [NSNumber numberWithInt:lineNumber] : nil; |
|
341 } |
|
342 |
|
343 - (void)setText:(NSString*)aNewString |
|
344 { |
|
345 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
346 |
|
347 if (mGeckoEditableTextAccessible) { |
|
348 nsString text; |
|
349 nsCocoaUtils::GetStringForNSString(aNewString, text); |
|
350 mGeckoEditableTextAccessible->SetTextContents(text); |
|
351 } |
|
352 |
|
353 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
354 } |
|
355 |
|
356 - (NSString*)text |
|
357 { |
|
358 if (!mGeckoAccessible || !mGeckoTextAccessible) |
|
359 return nil; |
|
360 |
|
361 // A password text field returns an empty value |
|
362 if (mRole == roles::PASSWORD_TEXT) |
|
363 return @""; |
|
364 |
|
365 nsAutoString text; |
|
366 mGeckoTextAccessible->TextSubstring(0, |
|
367 nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT, |
|
368 text); |
|
369 return nsCocoaUtils::ToNSString(text); |
|
370 } |
|
371 |
|
372 - (long)textLength |
|
373 { |
|
374 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; |
|
375 |
|
376 if (!mGeckoAccessible || !mGeckoTextAccessible) |
|
377 return 0; |
|
378 |
|
379 return mGeckoTextAccessible ? mGeckoTextAccessible->CharacterCount() : 0; |
|
380 |
|
381 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); |
|
382 } |
|
383 |
|
384 - (long)selectedTextLength |
|
385 { |
|
386 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; |
|
387 |
|
388 if (mGeckoTextAccessible) { |
|
389 int32_t start = 0, end = 0; |
|
390 mGeckoTextAccessible->SelectionBoundsAt(0, &start, &end); |
|
391 return (end - start); |
|
392 } |
|
393 return 0; |
|
394 |
|
395 NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(0); |
|
396 } |
|
397 |
|
398 - (NSString*)selectedText |
|
399 { |
|
400 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; |
|
401 |
|
402 if (mGeckoTextAccessible) { |
|
403 int32_t start = 0, end = 0; |
|
404 mGeckoTextAccessible->SelectionBoundsAt(0, &start, &end); |
|
405 if (start != end) { |
|
406 nsAutoString selText; |
|
407 mGeckoTextAccessible->TextSubstring(start, end, selText); |
|
408 return nsCocoaUtils::ToNSString(selText); |
|
409 } |
|
410 } |
|
411 return nil; |
|
412 |
|
413 NS_OBJC_END_TRY_ABORT_BLOCK_NIL; |
|
414 } |
|
415 |
|
416 - (NSValue*)selectedTextRange |
|
417 { |
|
418 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; |
|
419 |
|
420 if (mGeckoTextAccessible) { |
|
421 int32_t start = 0; |
|
422 int32_t end = 0; |
|
423 int32_t count = mGeckoTextAccessible->SelectionCount(); |
|
424 |
|
425 if (count) { |
|
426 mGeckoTextAccessible->SelectionBoundsAt(0, &start, &end); |
|
427 return [NSValue valueWithRange:NSMakeRange(start, end - start)]; |
|
428 } |
|
429 |
|
430 start = mGeckoTextAccessible->CaretOffset(); |
|
431 return [NSValue valueWithRange:NSMakeRange(start != -1 ? start : 0, 0)]; |
|
432 } |
|
433 return [NSValue valueWithRange:NSMakeRange(0, 0)]; |
|
434 |
|
435 NS_OBJC_END_TRY_ABORT_BLOCK_NIL; |
|
436 } |
|
437 |
|
438 - (NSValue*)visibleCharacterRange |
|
439 { |
|
440 // XXX this won't work with Textarea and such as we actually don't give |
|
441 // the visible character range. |
|
442 return [NSValue valueWithRange: |
|
443 NSMakeRange(0, mGeckoTextAccessible ? |
|
444 mGeckoTextAccessible->CharacterCount() : 0)]; |
|
445 } |
|
446 |
|
447 - (void)valueDidChange |
|
448 { |
|
449 NS_OBJC_BEGIN_TRY_ABORT_BLOCK; |
|
450 |
|
451 NSAccessibilityPostNotification(GetObjectOrRepresentedView(self), |
|
452 NSAccessibilityValueChangedNotification); |
|
453 |
|
454 NS_OBJC_END_TRY_ABORT_BLOCK; |
|
455 } |
|
456 |
|
457 - (void)selectedTextDidChange |
|
458 { |
|
459 NSAccessibilityPostNotification(GetObjectOrRepresentedView(self), |
|
460 NSAccessibilitySelectedTextChangedNotification); |
|
461 } |
|
462 |
|
463 - (NSString*)stringFromRange:(NSRange*)range |
|
464 { |
|
465 NS_PRECONDITION(mGeckoTextAccessible && range, "no Gecko text accessible or range"); |
|
466 |
|
467 nsAutoString text; |
|
468 mGeckoTextAccessible->TextSubstring(range->location, |
|
469 range->location + range->length, text); |
|
470 return nsCocoaUtils::ToNSString(text); |
|
471 } |
|
472 |
|
473 @end |
|
474 |
|
475 @implementation mozTextLeafAccessible |
|
476 |
|
477 - (NSArray*)accessibilityAttributeNames |
|
478 { |
|
479 static NSMutableArray* supportedAttributes = nil; |
|
480 if (!supportedAttributes) { |
|
481 supportedAttributes = [[super accessibilityAttributeNames] mutableCopy]; |
|
482 [supportedAttributes removeObject:NSAccessibilityChildrenAttribute]; |
|
483 } |
|
484 |
|
485 return supportedAttributes; |
|
486 } |
|
487 |
|
488 - (id)accessibilityAttributeValue:(NSString*)attribute |
|
489 { |
|
490 if ([attribute isEqualToString:NSAccessibilityTitleAttribute]) |
|
491 return @""; |
|
492 |
|
493 if ([attribute isEqualToString:NSAccessibilityValueAttribute]) |
|
494 return [self text]; |
|
495 |
|
496 return [super accessibilityAttributeValue:attribute]; |
|
497 } |
|
498 |
|
499 - (NSString*)text |
|
500 { |
|
501 if (!mGeckoAccessible) |
|
502 return nil; |
|
503 |
|
504 return nsCocoaUtils::ToNSString(mGeckoAccessible->AsTextLeaf()->Text()); |
|
505 } |
|
506 |
|
507 - (long)textLength |
|
508 { |
|
509 if (!mGeckoAccessible) |
|
510 return 0; |
|
511 |
|
512 return mGeckoAccessible->AsTextLeaf()->Text().Length(); |
|
513 } |
|
514 |
|
515 @end |