1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <osx/salinst.h>
22 #include <osx/saldata.hxx>
24 #include <osx/a11ywrapper.h>
25 #include <osx/a11ylistener.hxx>
26 #include <osx/a11yfactory.h>
27 #include <osx/a11yfocustracker.hxx>
29 #include <quartz/utils.h>
31 #include "a11yfocuslistener.hxx"
32 #include "a11yactionwrapper.h"
33 #include "a11ycomponentwrapper.h"
34 #include "a11yselectionwrapper.h"
35 #include "a11ytablewrapper.h"
36 #include "a11ytextwrapper.h"
37 #include "a11yvaluewrapper.h"
38 #include "a11yrolehelper.h"
40 #include <com/sun/star/accessibility/AccessibleRole.hpp>
41 #include <com/sun/star/accessibility/AccessibleStateType.hpp>
42 #include <com/sun/star/accessibility/XAccessibleEventBroadcaster.hpp>
43 #include <com/sun/star/awt/Size.hpp>
44 #include <com/sun/star/accessibility/XAccessibleRelationSet.hpp>
45 #include <com/sun/star/accessibility/AccessibleRelationType.hpp>
46 #include <com/sun/star/lang/DisposedException.hpp>
48 #include <sal/log.hxx>
49 #include <osl/diagnose.h>
51 using namespace ::com::sun::star::accessibility;
52 using namespace ::com::sun::star::awt;
53 using namespace ::com::sun::star::lang;
54 using namespace ::com::sun::star::uno;
56 @interface SalFrameWindow : NSWindow
59 -(Reference<XAccessibleContext>)accessibleContext;
62 static bool isPopupMenuOpen = false;
64 static std::ostream &operator<<(std::ostream &s, NSObject *obj) {
65 return s << [[obj description] UTF8String];
68 @implementation AquaA11yWrapper : NSView
71 #pragma mark Init and dealloc
73 -(id)initWithAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext {
74 self = [ super init ];
76 [ self setDefaults: rxAccessibleContext ];
81 -(void) setDefaults: (Reference < XAccessibleContext >) rxAccessibleContext {
82 mpReferenceWrapper = new ReferenceWrapper;
83 mActsAsRadioGroup = NO;
84 mpReferenceWrapper -> rAccessibleContext = rxAccessibleContext;
86 // Querying all supported interfaces
88 // XAccessibleComponent
89 mpReferenceWrapper->rAccessibleComponent.set( rxAccessibleContext, UNO_QUERY );
90 // XAccessibleExtendedComponent
91 mpReferenceWrapper->rAccessibleExtendedComponent.set( rxAccessibleContext, UNO_QUERY );
92 // XAccessibleSelection
93 mpReferenceWrapper->rAccessibleSelection.set( rxAccessibleContext, UNO_QUERY );
95 mpReferenceWrapper->rAccessibleTable.set( rxAccessibleContext, UNO_QUERY );
97 mpReferenceWrapper->rAccessibleText.set( rxAccessibleContext, UNO_QUERY );
98 // XAccessibleEditableText
99 mpReferenceWrapper->rAccessibleEditableText.set( rxAccessibleContext, UNO_QUERY );
101 mpReferenceWrapper->rAccessibleValue.set( rxAccessibleContext, UNO_QUERY );
103 mpReferenceWrapper->rAccessibleAction.set( rxAccessibleContext, UNO_QUERY );
104 // XAccessibleTextAttributes
105 mpReferenceWrapper->rAccessibleTextAttributes.set( rxAccessibleContext, UNO_QUERY );
106 // XAccessibleMultiLineText
107 mpReferenceWrapper->rAccessibleMultiLineText.set( rxAccessibleContext, UNO_QUERY );
108 // XAccessibleTextMarkup
109 mpReferenceWrapper->rAccessibleTextMarkup.set( rxAccessibleContext, UNO_QUERY );
110 // XAccessibleEventBroadcaster
112 /* #i102033# NSAccessibility does not seemt to know an equivalent for transient children.
113 That means we need to cache this, else e.g. tree list boxes are not accessible (moreover
114 it crashes by notifying dead objects - which would seemt o be another bug)
117 Unfortunately this can increase memory consumption drastically until the non transient parent
118 is destroyed and finally all the transients are released.
120 if ( ! rxAccessibleContext -> getAccessibleStateSet() -> contains ( AccessibleStateType::TRANSIENT ) )
123 Reference< XAccessibleEventBroadcaster > xBroadcaster(rxAccessibleContext, UNO_QUERY);
124 if( xBroadcaster.is() ) {
126 * We intentionally do not hold a reference to the event listener in the wrapper object,
127 * but let the listener control the life cycle of the wrapper instead ..
129 xBroadcaster->addAccessibleEventListener( new AquaA11yEventListener( self, rxAccessibleContext -> getAccessibleRole() ) );
133 if ( rxAccessibleContext -> getAccessibleRole() == AccessibleRole::TABLE_CELL ) {
136 } catch ( const Exception ) {
141 if ( mpReferenceWrapper ) {
142 delete mpReferenceWrapper;
148 #pragma mark Utility Section
150 // generates selectors for attribute name AXAttributeNameHere
151 // (getter without parameter) attributeNameHereAttribute
152 // (getter with parameter) attributeNameHereAttributeForParameter:
153 // (setter) setAttributeNameHereAttributeForElement:to:
154 -(SEL)selectorForAttribute:(NSString *)attribute asGetter:(BOOL)asGetter withGetterParameter:(BOOL)withGetterParameter {
155 SEL selector = static_cast<SEL>(nil);
156 NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
158 // step 1: create method name from attribute name
159 NSMutableString * methodName = [ NSMutableString string ];
161 [ methodName appendString: @"set" ];
163 NSRange const aRange = { 2, 1 };
164 NSString * firstChar = [ attribute substringWithRange: aRange ]; // drop leading "AX" and get first char
166 [ methodName appendString: [ firstChar lowercaseString ] ];
168 [ methodName appendString: firstChar ];
170 [ methodName appendString: [ attribute substringFromIndex: 3 ] ]; // append rest of attribute name
171 // append rest of method name
172 [ methodName appendString: @"Attribute" ];
174 [ methodName appendString: @"ForElement:to:" ];
175 } else if ( asGetter && withGetterParameter ) {
176 [ methodName appendString: @"ForParameter:" ];
178 // step 2: create selector
179 selector = NSSelectorFromString ( methodName );
181 selector = static_cast<SEL>(nil);
187 -(Reference < XAccessible >)getFirstRadioButtonInGroup {
188 Reference < XAccessibleRelationSet > rxAccessibleRelationSet = [ self accessibleContext ] -> getAccessibleRelationSet();
189 if( rxAccessibleRelationSet.is() )
191 AccessibleRelation relationMemberOf = rxAccessibleRelationSet -> getRelationByType ( AccessibleRelationType::MEMBER_OF );
192 if ( relationMemberOf.RelationType == AccessibleRelationType::MEMBER_OF && relationMemberOf.TargetSet.hasElements() )
193 return Reference < XAccessible > ( relationMemberOf.TargetSet[0], UNO_QUERY );
195 return Reference < XAccessible > ();
198 -(BOOL)isFirstRadioButtonInGroup {
199 Reference < XAccessible > rFirstMateAccessible = [ self getFirstRadioButtonInGroup ];
200 if ( rFirstMateAccessible.is() && rFirstMateAccessible -> getAccessibleContext().get() == [ self accessibleContext ] ) {
207 #pragma mark Attribute Value Getters
208 // ( called via Reflection by accessibilityAttributeValue )
211 Radiobutton grouping is done differently in NSAccessibility and the UNO-API. In UNO related radio buttons share an entry in their
212 RelationSet. In NSAccessibility the relationship is expressed through the hierarchy. An AXRadioGroup contains two or more AXRadioButton
213 objects. Since this group is not available in the UNO hierarchy, an extra wrapper is used for it. This wrapper shares almost all
214 attributes with the first radio button of the group, except for the role, subrole, role description, parent and children attributes.
215 So in this five methods there is a special treatment for radio buttons and groups.
219 if ( mActsAsRadioGroup ) {
220 return NSAccessibilityRadioGroupRole;
223 return [ AquaA11yRoleHelper getNativeRoleFrom: [ self accessibleContext ] ];
227 -(id)subroleAttribute {
228 if ( mActsAsRadioGroup ) {
231 NSString * subRole = [ AquaA11yRoleHelper getNativeSubroleFrom: [ self accessibleContext ] -> getAccessibleRole() ];
232 if ( ! [ subRole isEqualToString: @"" ] ) {
236 SAL_WNODEPRECATED_DECLARATIONS_PUSH
237 //TODO: 10.10 accessibilityAttributeValue:
238 return [ super accessibilityAttributeValue: NSAccessibilitySubroleAttribute ];
239 SAL_WNODEPRECATED_DECLARATIONS_POP
244 -(id)titleAttribute {
245 return CreateNSString ( [ self accessibleContext ] -> getAccessibleName() );
248 -(id)descriptionAttribute {
249 if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) {
250 return [ self titleAttribute ];
251 } else if ( [ self accessibleExtendedComponent ] ) {
252 return [ AquaA11yComponentWrapper descriptionAttributeForElement: self ];
254 return CreateNSString ( [ self accessibleContext ] -> getAccessibleDescription() );
258 -(id)enabledAttribute {
259 if ( [ self accessibleContext ] -> getAccessibleStateSet().is() ) {
260 return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::ENABLED ) ];
266 -(id)focusedAttribute {
267 if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::COMBO_BOX ) {
269 Reference < XAccessible > rxParent = [ self accessibleContext ] -> getAccessibleParent();
270 if ( rxParent.is() ) {
271 Reference < XAccessibleContext > rxContext = rxParent -> getAccessibleContext();
272 if ( rxContext.is() && rxContext -> getAccessibleStateSet().is() ) {
273 isFocused = [ NSNumber numberWithBool: rxContext -> getAccessibleStateSet() -> contains ( AccessibleStateType::FOCUSED ) ];
277 } else if ( [ self accessibleContext ] -> getAccessibleStateSet().is() ) {
278 return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::FOCUSED ) ];
284 -(id)parentAttribute {
285 if ( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::RADIO_BUTTON && ! mActsAsRadioGroup ) {
286 Reference < XAccessible > rxAccessible = [ self getFirstRadioButtonInGroup ];
287 if ( rxAccessible.is() && rxAccessible -> getAccessibleContext().is() ) {
288 Reference < XAccessibleContext > rxAccessibleContext = rxAccessible -> getAccessibleContext();
289 id parent_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: YES asRadioGroup: YES ];
290 [ parent_wrapper autorelease ];
291 return NSAccessibilityUnignoredAncestor( parent_wrapper );
296 Reference< XAccessible > xParent( [ self accessibleContext ] -> getAccessibleParent() );
297 if ( xParent.is() ) {
298 Reference< XAccessibleContext > xContext( xParent -> getAccessibleContext() );
299 if ( xContext.is() ) {
300 id parent_wrapper = [ AquaA11yFactory wrapperForAccessibleContext: xContext ];
301 [ parent_wrapper autorelease ];
302 return NSAccessibilityUnignoredAncestor( parent_wrapper );
305 } catch (const Exception&) {
312 -(id)childrenAttribute {
313 if ( mActsAsRadioGroup ) {
314 NSMutableArray * children = [ [ NSMutableArray alloc ] init ];
315 Reference < XAccessibleRelationSet > rxAccessibleRelationSet = [ self accessibleContext ] -> getAccessibleRelationSet();
316 AccessibleRelation const relationMemberOf = rxAccessibleRelationSet -> getRelationByType ( AccessibleRelationType::MEMBER_OF );
317 if ( relationMemberOf.RelationType == AccessibleRelationType::MEMBER_OF && relationMemberOf.TargetSet.hasElements() ) {
318 for ( const auto& i : relationMemberOf.TargetSet ) {
319 Reference < XAccessible > rMateAccessible( i, UNO_QUERY );
320 if ( rMateAccessible.is() ) {
321 Reference< XAccessibleContext > rMateAccessibleContext( rMateAccessible -> getAccessibleContext() );
322 if ( rMateAccessibleContext.is() ) {
323 id wrapper = [ AquaA11yFactory wrapperForAccessibleContext: rMateAccessibleContext ];
324 [ children addObject: wrapper ];
331 } else if ( [ self accessibleTable ] )
333 AquaA11yTableWrapper* pTable = [self isKindOfClass: [AquaA11yTableWrapper class]] ? static_cast<AquaA11yTableWrapper*>(self) : nil;
334 return [ AquaA11yTableWrapper childrenAttributeForElement: pTable ];
337 NSMutableArray * children = [ [ NSMutableArray alloc ] init ];
338 Reference< XAccessibleContext > xContext( [ self accessibleContext ] );
340 sal_Int32 cnt = xContext -> getAccessibleChildCount();
341 for ( sal_Int32 i = 0; i < cnt; i++ ) {
342 Reference< XAccessible > xChild( xContext -> getAccessibleChild( i ) );
344 Reference< XAccessibleContext > xChildContext( xChild -> getAccessibleContext() );
345 // the menubar is already accessible (including Apple- and Application-Menu) through NSApplication => omit it here
346 if ( xChildContext.is() && AccessibleRole::MENU_BAR != xChildContext -> getAccessibleRole() ) {
347 id wrapper = [ AquaA11yFactory wrapperForAccessibleContext: xChildContext ];
348 [ children addObject: wrapper ];
354 // if not already acting as RadioGroup now is the time to replace RadioButtons with RadioGroups and remove RadioButtons
355 if ( ! mActsAsRadioGroup ) {
356 NSEnumerator * enumerator = [ children objectEnumerator ];
357 AquaA11yWrapper * element;
358 while ( ( element = static_cast<AquaA11yWrapper *>([ enumerator nextObject ]) ) ) {
359 if ( [ element accessibleContext ] -> getAccessibleRole() == AccessibleRole::RADIO_BUTTON ) {
360 if ( [ element isFirstRadioButtonInGroup ] ) {
361 id wrapper = [ AquaA11yFactory wrapperForAccessibleContext: [ element accessibleContext ] createIfNotExists: YES asRadioGroup: YES ];
362 [ children replaceObjectAtIndex: [ children indexOfObjectIdenticalTo: element ] withObject: wrapper ];
364 [ children removeObject: element ];
369 [ children autorelease ];
370 return NSAccessibilityUnignoredChildren( children );
371 } catch (const Exception &) {
378 -(id)windowAttribute {
379 // go upstairs until reaching the broken connection
380 AquaA11yWrapper * aWrapper = self;
382 while ( [ aWrapper accessibleContext ] -> getAccessibleParent().is() ) {
383 AquaA11yWrapper *aTentativeParentWrapper = [ AquaA11yFactory wrapperForAccessibleContext: [ aWrapper accessibleContext ] -> getAccessibleParent() -> getAccessibleContext() ];
384 // Quick-and-dirty fix for infinite loop after fixing crash in
386 if ( aTentativeParentWrapper == aWrapper )
388 // Even dirtier fix for infinite loop in fdo#55156
389 if ( loops++ == 100 )
391 aWrapper = aTentativeParentWrapper;
392 [ aWrapper autorelease ];
394 // get associated NSWindow
395 NSWindow* theWindow = [ aWrapper windowForParent ];
399 -(id)topLevelUIElementAttribute {
400 return [ self windowAttribute ];
404 if ( [ self accessibleComponent ] ) {
405 return [ AquaA11yComponentWrapper sizeAttributeForElement: self ];
411 -(id)positionAttribute {
412 if ( [ self accessibleComponent ] ) {
413 return [ AquaA11yComponentWrapper positionAttributeForElement: self ];
420 return CreateNSString ( [ self accessibleContext ] -> getAccessibleDescription() );
423 -(id)roleDescriptionAttribute {
424 if ( mActsAsRadioGroup ) {
425 return [ AquaA11yRoleHelper getRoleDescriptionFrom: NSAccessibilityRadioGroupRole with: @"" ];
426 } else if( [ self accessibleContext ] -> getAccessibleRole() == AccessibleRole::RADIO_BUTTON ) {
427 // FIXME: VO should read this because of hierarchy, this is just a workaround
428 // get parent and its children
429 AquaA11yWrapper * parent = [ self parentAttribute ];
430 NSArray * children = [ parent childrenAttribute ];
431 // find index of self
433 NSEnumerator * enumerator = [ children objectEnumerator ];
434 AquaA11yWrapper * child = nil;
435 while ( ( child = [ enumerator nextObject ] ) ) {
436 if ( self == child ) {
442 NSNumber * nIndex = [ NSNumber numberWithInt: index ];
443 NSNumber * nGroupsize = [ NSNumber numberWithInt: [ children count ] ];
444 NSMutableString * value = [ [ NSMutableString alloc ] init ];
445 [ value appendString: @"radio button " ];
446 [ value appendString: [ nIndex stringValue ] ];
447 [ value appendString: @" of " ];
448 [ value appendString: [ nGroupsize stringValue ] ];
449 // clean up and return string
451 [ nGroupsize release ];
452 [ children release ];
455 return [ AquaA11yRoleHelper getRoleDescriptionFrom:
456 [ AquaA11yRoleHelper getNativeRoleFrom: [ self accessibleContext ] ]
457 with: [ AquaA11yRoleHelper getNativeSubroleFrom: [ self accessibleContext ] -> getAccessibleRole() ] ];
461 -(id)valueAttribute {
462 if ( [ [ self roleAttribute ] isEqualToString: NSAccessibilityMenuItemRole ] ) {
464 } else if ( [ self accessibleText ] ) {
465 return [ AquaA11yTextWrapper valueAttributeForElement: self ];
466 } else if ( [ self accessibleValue ] ) {
467 return [ AquaA11yValueWrapper valueAttributeForElement: self ];
473 -(id)minValueAttribute {
474 if ( [ self accessibleValue ] ) {
475 return [ AquaA11yValueWrapper minValueAttributeForElement: self ];
481 -(id)maxValueAttribute {
482 if ( [ self accessibleValue ] ) {
483 return [ AquaA11yValueWrapper maxValueAttributeForElement: self ];
489 -(id)contentsAttribute {
490 return [ self childrenAttribute ];
493 -(id)selectedChildrenAttribute {
494 return [ AquaA11ySelectionWrapper selectedChildrenAttributeForElement: self ];
497 -(id)numberOfCharactersAttribute {
498 if ( [ self accessibleText ] ) {
499 return [ AquaA11yTextWrapper numberOfCharactersAttributeForElement: self ];
505 -(id)selectedTextAttribute {
506 if ( [ self accessibleText ] ) {
507 return [ AquaA11yTextWrapper selectedTextAttributeForElement: self ];
513 -(id)selectedTextRangeAttribute {
514 if ( [ self accessibleText ] ) {
515 return [ AquaA11yTextWrapper selectedTextRangeAttributeForElement: self ];
521 -(id)visibleCharacterRangeAttribute {
522 if ( [ self accessibleText ] ) {
523 return [ AquaA11yTextWrapper visibleCharacterRangeAttributeForElement: self ];
530 return self; // TODO ???
533 -(id)sharedTextUIElementsAttribute {
534 if ( [ self accessibleText ] ) {
535 return [ AquaA11yTextWrapper sharedTextUIElementsAttributeForElement: self ];
541 -(id)sharedCharacterRangeAttribute {
542 if ( [ self accessibleText ] ) {
543 return [ AquaA11yTextWrapper sharedCharacterRangeAttributeForElement: self ];
549 -(id)expandedAttribute {
550 return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::EXPANDED ) ];
553 -(id)selectedAttribute {
554 return [ NSNumber numberWithBool: [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::SELECTED ) ];
557 -(id)stringForRangeAttributeForParameter:(id)range {
558 if ( [ self accessibleText ] ) {
559 return [ AquaA11yTextWrapper stringForRangeAttributeForElement: self forParameter: range ];
565 -(id)attributedStringForRangeAttributeForParameter:(id)range {
566 if ( [ self accessibleText ] ) {
567 return [ AquaA11yTextWrapper attributedStringForRangeAttributeForElement: self forParameter: range ];
573 -(id)rangeForIndexAttributeForParameter:(id)index {
574 if ( [ self accessibleText ] ) {
575 return [ AquaA11yTextWrapper rangeForIndexAttributeForElement: self forParameter: index ];
581 -(id)rangeForPositionAttributeForParameter:(id)point {
582 if ( [ self accessibleText ] ) {
583 return [ AquaA11yTextWrapper rangeForPositionAttributeForElement: self forParameter: point ];
589 -(id)boundsForRangeAttributeForParameter:(id)range {
590 if ( [ self accessibleText ] ) {
591 return [ AquaA11yTextWrapper boundsForRangeAttributeForElement: self forParameter: range ];
597 -(id)styleRangeForIndexAttributeForParameter:(id)index {
598 if ( [ self accessibleText ] ) {
599 return [ AquaA11yTextWrapper styleRangeForIndexAttributeForElement: self forParameter: index ];
605 -(id)rTFForRangeAttributeForParameter:(id)range {
606 if ( [ self accessibleText ] ) {
607 return [ AquaA11yTextWrapper rTFForRangeAttributeForElement: self forParameter: range ];
613 -(id)orientationAttribute {
614 NSString * orientation = nil;
615 Reference < XAccessibleStateSet > stateSet = [ self accessibleContext ] -> getAccessibleStateSet();
616 if ( stateSet -> contains ( AccessibleStateType::HORIZONTAL ) ) {
617 orientation = NSAccessibilityHorizontalOrientationValue;
618 } else if ( stateSet -> contains ( AccessibleStateType::VERTICAL ) ) {
619 orientation = NSAccessibilityVerticalOrientationValue;
624 -(id)titleUIElementAttribute {
625 if ( [ self accessibleContext ] -> getAccessibleRelationSet().is() ) {
626 NSString * title = [ self titleAttribute ];
627 id titleElement = nil;
628 if ( [ title length ] == 0 ) {
629 AccessibleRelation relationLabeledBy = [ self accessibleContext ] -> getAccessibleRelationSet() -> getRelationByType ( AccessibleRelationType::LABELED_BY );
630 if ( relationLabeledBy.RelationType == AccessibleRelationType::LABELED_BY && relationLabeledBy.TargetSet.hasElements() ) {
631 Reference < XAccessible > rxAccessible ( relationLabeledBy.TargetSet[0], UNO_QUERY );
632 titleElement = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessible -> getAccessibleContext() ];
644 -(id)servesAsTitleForUIElementsAttribute {
645 if ( [ self accessibleContext ] -> getAccessibleRelationSet().is() ) {
646 id titleForElement = nil;
647 AccessibleRelation relationLabelFor = [ self accessibleContext ] -> getAccessibleRelationSet() -> getRelationByType ( AccessibleRelationType::LABEL_FOR );
648 if ( relationLabelFor.RelationType == AccessibleRelationType::LABEL_FOR && relationLabelFor.TargetSet.hasElements() ) {
649 Reference < XAccessible > rxAccessible ( relationLabelFor.TargetSet[0], UNO_QUERY );
650 titleForElement = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessible -> getAccessibleContext() ];
652 return titleForElement;
658 -(id)lineForIndexAttributeForParameter:(id)index {
659 if ( [ self accessibleMultiLineText ] ) {
660 return [ AquaA11yTextWrapper lineForIndexAttributeForElement: self forParameter: index ];
666 -(id)rangeForLineAttributeForParameter:(id)line {
667 if ( [ self accessibleMultiLineText ] ) {
668 return [ AquaA11yTextWrapper rangeForLineAttributeForElement: self forParameter: line ];
675 #pragma mark Accessibility Protocol
677 -(id)accessibilityAttributeValue:(NSString *)attribute {
678 SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeValue:" << attribute << "]");
679 // #i90575# guard NSAccessibility protocol against unwanted access
680 if ( isPopupMenuOpen ) {
685 // if we are no longer in the wrapper repository, we have been disposed
686 AquaA11yWrapper * theWrapper = [ AquaA11yFactory wrapperForAccessibleContext: [ self accessibleContext ] createIfNotExists: NO ];
687 if ( theWrapper || mIsTableCell ) {
689 SEL methodSelector = [ self selectorForAttribute: attribute asGetter: YES withGetterParameter: NO ];
690 if ( [ self respondsToSelector: methodSelector ] ) {
691 value = [ self performSelector: methodSelector ];
693 } catch ( const DisposedException & ) {
694 mIsTableCell = NO; // just to be sure
695 [ AquaA11yFactory removeFromWrapperRepositoryFor: [ self accessibleContext ] ];
697 } catch ( const Exception & ) {
702 [ theWrapper release ]; // the above called method calls retain on the returned Wrapper
707 -(BOOL)accessibilityIsIgnored {
708 SAL_INFO("vcl.a11y", "[" << self << " accessibilityIsIgnored]");
709 // #i90575# guard NSAccessibility protocol against unwanted access
710 if ( isPopupMenuOpen ) {
713 bool ignored = false;
714 sal_Int16 nRole = [ self accessibleContext ] -> getAccessibleRole();
716 //case AccessibleRole::PANEL:
717 case AccessibleRole::FRAME:
718 case AccessibleRole::ROOT_PANE:
719 case AccessibleRole::SEPARATOR:
720 case AccessibleRole::FILLER:
721 case AccessibleRole::DIALOG:
725 ignored = ! ( [ self accessibleContext ] -> getAccessibleStateSet() -> contains ( AccessibleStateType::VISIBLE ) );
728 return ignored; // TODO: to be completed
731 -(NSArray *)accessibilityAttributeNames {
732 SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeNames]");
733 // #i90575# guard NSAccessibility protocol against unwanted access
734 if ( isPopupMenuOpen ) {
737 NSString * nativeSubrole = nil;
738 NSString * title = nil;
739 NSMutableArray * attributeNames = nil;
740 sal_Int32 nAccessibleChildren = 0;
742 // Default Attributes
743 attributeNames = [ NSMutableArray arrayWithObjects:
744 NSAccessibilityRoleAttribute,
745 NSAccessibilityDescriptionAttribute,
746 NSAccessibilityParentAttribute,
747 NSAccessibilityWindowAttribute,
748 NSAccessibilityHelpAttribute,
749 NSAccessibilityTopLevelUIElementAttribute,
750 NSAccessibilityRoleDescriptionAttribute,
752 nativeSubrole = static_cast<NSString *>([ AquaA11yRoleHelper getNativeSubroleFrom: [ self accessibleContext ] -> getAccessibleRole() ]);
753 title = static_cast<NSString *>([ self titleAttribute ]);
754 Reference < XAccessibleRelationSet > rxRelationSet = [ self accessibleContext ] -> getAccessibleRelationSet();
755 // Special Attributes depending on attribute values
756 if ( nativeSubrole && ! [ nativeSubrole isEqualToString: @"" ] ) {
757 [ attributeNames addObject: NSAccessibilitySubroleAttribute ];
761 nAccessibleChildren = [ self accessibleContext ] -> getAccessibleChildCount();
762 if ( nAccessibleChildren > 0 ) {
763 [ attributeNames addObject: NSAccessibilityChildrenAttribute ];
766 catch( DisposedException& ) {}
767 catch( RuntimeException& ) {}
769 if ( title && ! [ title isEqualToString: @"" ] ) {
770 [ attributeNames addObject: NSAccessibilityTitleAttribute ];
772 if ( [ title length ] == 0 && rxRelationSet.is() && rxRelationSet -> containsRelation ( AccessibleRelationType::LABELED_BY ) ) {
773 [ attributeNames addObject: NSAccessibilityTitleUIElementAttribute ];
775 if ( rxRelationSet.is() && rxRelationSet -> containsRelation ( AccessibleRelationType::LABEL_FOR ) ) {
776 [ attributeNames addObject: NSAccessibilityServesAsTitleForUIElementsAttribute ];
778 // Special Attributes depending on interface
779 if( [self accessibleContext ] -> getAccessibleRole() == AccessibleRole::TABLE )
780 [AquaA11yTableWrapper addAttributeNamesTo: attributeNames object: self];
782 if ( [ self accessibleText ] ) {
783 [ AquaA11yTextWrapper addAttributeNamesTo: attributeNames ];
785 if ( [ self accessibleComponent ] ) {
786 [ AquaA11yComponentWrapper addAttributeNamesTo: attributeNames ];
788 if ( [ self accessibleSelection ] ) {
789 [ AquaA11ySelectionWrapper addAttributeNamesTo: attributeNames ];
791 if ( [ self accessibleValue ] ) {
792 [ AquaA11yValueWrapper addAttributeNamesTo: attributeNames ];
794 [ nativeSubrole release ];
796 return attributeNames;
797 } catch ( DisposedException & ) { // Object is no longer available
798 if ( nativeSubrole ) {
799 [ nativeSubrole release ];
804 if ( attributeNames ) {
805 [ attributeNames release ];
807 [ AquaA11yFactory removeFromWrapperRepositoryFor: [ self accessibleContext ] ];
808 return [ [ NSArray alloc ] init ];
812 -(BOOL)accessibilityIsAttributeSettable:(NSString *)attribute {
813 SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeIsSettable:" << attribute << "]");
814 bool isSettable = false;
815 if ( [ self accessibleText ] ) {
816 isSettable = [ AquaA11yTextWrapper isAttributeSettable: attribute forElement: self ];
818 if ( ! isSettable && [ self accessibleComponent ] ) {
819 isSettable = [ AquaA11yComponentWrapper isAttributeSettable: attribute forElement: self ];
821 if ( ! isSettable && [ self accessibleSelection ] ) {
822 isSettable = [ AquaA11ySelectionWrapper isAttributeSettable: attribute forElement: self ];
824 if ( ! isSettable && [ self accessibleValue ] ) {
825 isSettable = [ AquaA11yValueWrapper isAttributeSettable: attribute forElement: self ];
827 return isSettable; // TODO: to be completed
830 -(NSArray *)accessibilityParameterizedAttributeNames {
831 SAL_INFO("vcl.a11y", "[" << self << " accessibilityParameterizedAttributeNames]");
832 NSMutableArray * attributeNames = [ [ NSMutableArray alloc ] init ];
833 // Special Attributes depending on interface
834 if ( [ self accessibleText ] ) {
835 [ AquaA11yTextWrapper addParameterizedAttributeNamesTo: attributeNames ];
837 return attributeNames; // TODO: to be completed
840 -(id)accessibilityAttributeValue:(NSString *)attribute forParameter:(id)parameter {
841 SAL_INFO("vcl.a11y", "[" << self << " accessibilityAttributeValue:" << attribute << " forParameter:" << (static_cast<NSObject*>(parameter)) << "]");
842 SEL methodSelector = [ self selectorForAttribute: attribute asGetter: YES withGetterParameter: YES ];
843 if ( [ self respondsToSelector: methodSelector ] ) {
844 return [ self performSelector: methodSelector withObject: parameter ];
846 return nil; // TODO: to be completed
849 -(BOOL)accessibilitySetOverrideValue:(id)value forAttribute:(NSString *)attribute
851 SAL_INFO("vcl.a11y", "[" << self << " accessibilitySetOverrideValue:" << (static_cast<NSObject*>(value)) << " forAttribute:" << attribute << "]");
855 -(void)accessibilitySetValue:(id)value forAttribute:(NSString *)attribute {
856 SAL_INFO("vcl.a11y", "[" << self << " accessibilitySetValue:" << (static_cast<NSObject*>(value)) << " forAttribute:" << attribute << "]");
857 SEL methodSelector = [ self selectorForAttribute: attribute asGetter: NO withGetterParameter: NO ];
858 if ( [ AquaA11yComponentWrapper respondsToSelector: methodSelector ] ) {
859 [ AquaA11yComponentWrapper performSelector: methodSelector withObject: self withObject: value ];
861 if ( [ AquaA11yTextWrapper respondsToSelector: methodSelector ] ) {
862 [ AquaA11yTextWrapper performSelector: methodSelector withObject: self withObject: value ];
864 if ( [ AquaA11ySelectionWrapper respondsToSelector: methodSelector ] ) {
865 [ AquaA11ySelectionWrapper performSelector: methodSelector withObject: self withObject: value ];
867 if ( [ AquaA11yValueWrapper respondsToSelector: methodSelector ] ) {
868 [ AquaA11yValueWrapper performSelector: methodSelector withObject: self withObject: value ];
872 -(id)accessibilityFocusedUIElement {
873 SAL_INFO("vcl.a11y", "[" << self << " accessibilityFocusedUIElement]");
874 // #i90575# guard NSAccessibility protocol against unwanted access
875 if ( isPopupMenuOpen ) {
879 // as this seems to be the first API call on a newly created SalFrameView object,
880 // make sure self gets registered in the repository ..
881 [ self accessibleContext ];
883 AquaA11yWrapper * focusedUIElement = AquaA11yFocusListener::get()->getFocusedUIElement();
884 // AquaA11yWrapper * ancestor = focusedUIElement;
886 // Make sure the focused object is a descendant of self
888 // if( self == ancestor )
889 return focusedUIElement;
891 // ancestor = [ ancestor accessibilityAttributeValue: NSAccessibilityParentAttribute ];
892 // } while( nil != ancestor );
897 -(NSString *)accessibilityActionDescription:(NSString *)action {
898 SAL_INFO("vcl.a11y", "[" << self << " accessibilityActionDescription:" << action << "]");
899 return NSAccessibilityActionDescription(action);
902 -(AquaA11yWrapper *)actionResponder {
903 AquaA11yWrapper * wrapper = nil;
904 // get some information
905 NSString * role = static_cast<NSString *>([ self accessibilityAttributeValue: NSAccessibilityRoleAttribute ]);
906 id enabledAttr = [ self enabledAttribute ];
907 bool enabled = [ enabledAttr boolValue ];
908 NSView * parent = static_cast<NSView *>([ self accessibilityAttributeValue: NSAccessibilityParentAttribute ]);
909 AquaA11yWrapper * parentAsWrapper = nil;
910 if ( [ parent isKindOfClass: [ AquaA11yWrapper class ] ] ) {
911 parentAsWrapper = static_cast<AquaA11yWrapper *>(parent);
913 SAL_WNODEPRECATED_DECLARATIONS_PUSH
914 //TODO: 10.10 accessibilityAttributeValue:
915 NSString * parentRole = static_cast<NSString *>([ parent accessibilityAttributeValue: NSAccessibilityRoleAttribute ]);
916 SAL_WNODEPRECATED_DECLARATIONS_POP
917 // if we are a textarea inside a combobox, then the combobox is the action responder
919 && [ role isEqualToString: NSAccessibilityTextAreaRole ]
920 && [ parentRole isEqualToString: NSAccessibilityComboBoxRole ]
921 && parentAsWrapper ) {
922 wrapper = parentAsWrapper;
923 } else if ( enabled && [ self accessibleAction ] ) {
926 [ parentRole release ];
927 [ enabledAttr release ];
932 -(void)accessibilityPerformAction:(NSString *)action {
933 SAL_INFO("vcl.a11y", "[" << self << " accessibilityPerformAction:" << action << "]");
934 AquaA11yWrapper * actionResponder = [ self actionResponder ];
935 if ( actionResponder ) {
936 [ AquaA11yActionWrapper doAction: action ofElement: actionResponder ];
940 -(NSArray *)accessibilityActionNames {
941 SAL_INFO("vcl.a11y", "[" << self << " accessibilityActionNames]");
942 NSArray * actionNames = nil;
943 AquaA11yWrapper * actionResponder = [ self actionResponder ];
944 if ( actionResponder ) {
945 actionNames = [ AquaA11yActionWrapper actionNamesForElement: actionResponder ];
947 actionNames = [ [ NSArray alloc ] init ];
953 #pragma mark Hit Test
955 -(BOOL)isViewElement:(NSObject *)viewElement hitByPoint:(NSPoint)point {
957 NSAutoreleasePool * pool = [ [ NSAutoreleasePool alloc ] init ];
958 SAL_WNODEPRECATED_DECLARATIONS_PUSH
959 //TODO: 10.10 accessibilityAttributeValue:
960 NSValue * position = [ viewElement accessibilityAttributeValue: NSAccessibilityPositionAttribute ];
961 NSValue * size = [ viewElement accessibilityAttributeValue: NSAccessibilitySizeAttribute ];
962 SAL_WNODEPRECATED_DECLARATIONS_POP
963 if ( position && size ) {
964 float minX = [ position pointValue ].x;
965 float minY = [ position pointValue ].y;
966 float maxX = minX + [ size sizeValue ].width;
967 float maxY = minY + [ size sizeValue ].height;
968 if ( minX < point.x && maxX > point.x && minY < point.y && maxY > point.y ) {
976 static Reference < XAccessibleContext > hitTestRunner ( css::awt::Point point,
977 Reference < XAccessibleContext > const & rxAccessibleContext ) {
978 Reference < XAccessibleContext > hitChild;
979 Reference < XAccessibleContext > emptyReference;
981 Reference < XAccessibleComponent > rxAccessibleComponent ( rxAccessibleContext, UNO_QUERY );
982 if ( rxAccessibleComponent.is() ) {
983 css::awt::Point location = rxAccessibleComponent -> getLocationOnScreen();
984 css::awt::Point hitPoint ( point.X - location.X , point.Y - location.Y);
985 Reference < XAccessible > rxAccessible = rxAccessibleComponent -> getAccessibleAtPoint ( hitPoint );
986 if ( rxAccessible.is() && rxAccessible -> getAccessibleContext().is() &&
987 rxAccessible -> getAccessibleContext() -> getAccessibleChildCount() == 0 ) {
988 hitChild = rxAccessible -> getAccessibleContext();
992 // iterate the hierarchy looking doing recursive hit testing.
993 // apparently necessary as a special treatment for e.g. comboboxes
994 if ( !hitChild.is() ) {
995 bool bSafeToIterate = true;
996 sal_Int32 nCount = rxAccessibleContext -> getAccessibleChildCount();
998 if (nCount < 0 || nCount > SAL_MAX_UINT16 /* slow enough for anyone */)
999 bSafeToIterate = false;
1000 else { // manages descendants is an horror from the a11y standards guys.
1001 Reference< XAccessibleStateSet > xStateSet;
1002 xStateSet = rxAccessibleContext -> getAccessibleStateSet();
1003 if (xStateSet.is() && xStateSet -> contains(AccessibleStateType::MANAGES_DESCENDANTS ) )
1004 bSafeToIterate = false;
1007 if( bSafeToIterate ) {
1008 for ( int i = 0; i < rxAccessibleContext -> getAccessibleChildCount(); i++ ) {
1009 Reference < XAccessible > rxAccessibleChild = rxAccessibleContext -> getAccessibleChild ( i );
1010 if ( rxAccessibleChild.is() && rxAccessibleChild -> getAccessibleContext().is() && rxAccessibleChild -> getAccessibleContext() -> getAccessibleRole() != AccessibleRole::LIST ) {
1011 Reference < XAccessibleContext > myHitChild = hitTestRunner ( point, rxAccessibleChild -> getAccessibleContext() );
1012 if ( myHitChild.is() ) {
1013 hitChild = myHitChild;
1020 } catch ( RuntimeException ) {
1021 return emptyReference;
1026 -(id)accessibilityHitTest:(NSPoint)point {
1027 SAL_INFO("vcl.a11y", "[" << self << " accessibilityHitTest:" << point << "]");
1028 static id wrapper = nil;
1029 if ( nil != wrapper ) {
1030 [ wrapper release ];
1033 Reference < XAccessibleContext > hitChild;
1034 NSRect screenRect = [ [ NSScreen mainScreen ] frame ];
1035 css::awt::Point hitPoint ( static_cast<sal_Int32>(point.x) , static_cast<sal_Int32>(screenRect.size.height - point.y) );
1036 // check child windows first
1037 NSWindow * window = static_cast<NSWindow *>([ self accessibilityAttributeValue: NSAccessibilityWindowAttribute ]);
1038 NSArray * childWindows = [ window childWindows ];
1039 if ( [ childWindows count ] > 0 ) {
1040 NSWindow * element = nil;
1041 NSEnumerator * enumerator = [ childWindows objectEnumerator ];
1042 while ( ( element = [ enumerator nextObject ] ) && !hitChild.is() ) {
1043 if ( [ element isKindOfClass: [ SalFrameWindow class ] ] && [ self isViewElement: element hitByPoint: point ] ) {
1044 // we have a child window that is hit
1045 Reference < XAccessibleRelationSet > relationSet = [ static_cast<SalFrameWindow *>(element) accessibleContext ] -> getAccessibleRelationSet();
1046 if ( relationSet.is() && relationSet -> containsRelation ( AccessibleRelationType::SUB_WINDOW_OF )) {
1047 // we have a valid relation to the parent element
1048 AccessibleRelation const relation = relationSet -> getRelationByType ( AccessibleRelationType::SUB_WINDOW_OF );
1049 for ( const auto & i : relation.TargetSet ) {
1050 Reference < XAccessible > rxAccessible ( i, UNO_QUERY );
1051 if ( rxAccessible.is() && rxAccessible -> getAccessibleContext().is() ) {
1052 // hit test for children of parent
1053 hitChild = hitTestRunner ( hitPoint, rxAccessible -> getAccessibleContext() );
1062 // nothing hit yet, so check ourself
1063 if ( ! hitChild.is() ) {
1064 if ( !mpReferenceWrapper ) {
1065 [ self setDefaults: [ self accessibleContext ] ];
1067 hitChild = hitTestRunner ( hitPoint, mpReferenceWrapper -> rAccessibleContext );
1069 if ( hitChild.is() ) {
1070 wrapper = [ AquaA11yFactory wrapperForAccessibleContext: hitChild ];
1073 [ wrapper retain ]; // TODO: retain only when transient ?
1079 #pragma mark Access Methods
1081 -(XAccessibleAction *)accessibleAction {
1082 return mpReferenceWrapper -> rAccessibleAction.get();
1085 -(XAccessibleContext *)accessibleContext {
1086 return mpReferenceWrapper -> rAccessibleContext.get();
1089 -(XAccessibleComponent *)accessibleComponent {
1090 return mpReferenceWrapper -> rAccessibleComponent.get();
1093 -(XAccessibleExtendedComponent *)accessibleExtendedComponent {
1094 return mpReferenceWrapper -> rAccessibleExtendedComponent.get();
1097 -(XAccessibleSelection *)accessibleSelection {
1098 return mpReferenceWrapper -> rAccessibleSelection.get();
1101 -(XAccessibleTable *)accessibleTable {
1102 return mpReferenceWrapper -> rAccessibleTable.get();
1105 -(XAccessibleText *)accessibleText {
1106 return mpReferenceWrapper -> rAccessibleText.get();
1109 -(XAccessibleEditableText *)accessibleEditableText {
1110 return mpReferenceWrapper -> rAccessibleEditableText.get();
1113 -(XAccessibleValue *)accessibleValue {
1114 return mpReferenceWrapper -> rAccessibleValue.get();
1117 -(XAccessibleTextAttributes *)accessibleTextAttributes {
1118 return mpReferenceWrapper -> rAccessibleTextAttributes.get();
1121 -(XAccessibleMultiLineText *)accessibleMultiLineText {
1122 return mpReferenceWrapper -> rAccessibleMultiLineText.get();
1125 -(XAccessibleTextMarkup *)accessibleTextMarkup {
1126 return mpReferenceWrapper -> rAccessibleTextMarkup.get();
1129 -(NSWindow*)windowForParent {
1130 return [self window];
1133 -(void)setActsAsRadioGroup:(BOOL)actsAsRadioGroup {
1134 mActsAsRadioGroup = actsAsRadioGroup;
1137 -(BOOL)actsAsRadioGroup {
1138 return mActsAsRadioGroup;
1141 +(void)setPopupMenuOpen:(BOOL)popupMenuOpen {
1142 isPopupMenuOpen = popupMenuOpen;
1147 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */