vcl/inc/osx/a11yfactory.h | 6 vcl/inc/osx/a11ywrapper.h | 16 + vcl/inc/osx/salframeview.h | 44 ++++ vcl/inc/quartz/salgdi.h | 2 vcl/osx/a11yactionwrapper.h | 1 vcl/osx/a11yactionwrapper.mm | 19 + vcl/osx/a11yfactory.mm | 57 ----- vcl/osx/a11ywrapper.mm | 435 +++++++++++++++++++++++++++++++++++++++++-- vcl/osx/salframe.cxx | 7 vcl/osx/salframeview.mm | 374 ++++++++++++++++++++++++++++++++++++ vcl/osx/salgdiutils.cxx | 36 +++ 11 files changed, 918 insertions(+), 79 deletions(-)
New commits: commit e103f775501dc0a00fca8724b7e4d2117c80a87c Author: Patrick Luby <plub...@neooffice.org> AuthorDate: Wed Jun 7 13:53:39 2023 -0400 Commit: Patrick Luby <plub...@neooffice.org> CommitDate: Mon Jun 12 22:09:19 2023 +0200 Partial fix tdf#155376 use NSAccessibilityElement instead of NSView On macOS, accessibility is implemented by creating a native NSView for each C++ accessible element. The problem with using an NSView for each element is that NSViews are very slow to add to or remove from an NSWindow once the number of NSViews is more than a thousand or so. Fortunately, Apple added a protocol that allows adding a native accessible element using the lightweight NSAccessiblityElement class. The topmost NSAccessiblityElement elements are connected to the single SalFrameView in each NSWindow but since NSAccessiblityElements are not NSViews, they are not connected to any of NSWindow's event or draw dispatching. This makes NSAccessiblityElements significantly faster to add to or remove from an NSWindow with no apparent loss in functionality. Note: this change is a subset of the LibreOffice 4.4 code changes that I wrote for NeoOffice. Change-Id: I408108d57217db407512dfa3457fe26d2ab455de Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152717 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.gran...@collabora.co.uk> Reviewed-by: Patrick Luby <plub...@neooffice.org> (cherry picked from commit 75dc3a54fca8b2dc775ba007190d8c2e188f1c0a) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152661 diff --git a/vcl/inc/osx/a11yfactory.h b/vcl/inc/osx/a11yfactory.h index cec49bf93b6d..9ef837dfaa9d 100644 --- a/vcl/inc/osx/a11yfactory.h +++ b/vcl/inc/osx/a11yfactory.h @@ -26,14 +26,14 @@ @interface AquaA11yFactory : NSObject { } -+(void)insertIntoWrapperRepository: (NSView *) viewElement forAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext; ++(void)insertIntoWrapperRepository: (AquaA11yWrapper *) element forAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext; +(AquaA11yWrapper *)wrapperForAccessible: (css::uno::Reference < css::accessibility::XAccessible >) rxAccessible; +(AquaA11yWrapper *)wrapperForAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext; +(AquaA11yWrapper *)wrapperForAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate; +(AquaA11yWrapper *)wrapperForAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate asRadioGroup:(BOOL) asRadioGroup; +(void)removeFromWrapperRepositoryFor: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext; -+(void)registerView: (NSView *) theView; -+(void)revokeView: (NSView *) theViewt; ++(void)registerWrapper: (AquaA11yWrapper *) theWrapper; ++(void)revokeWrapper: (AquaA11yWrapper *) theWrapper; @end diff --git a/vcl/inc/osx/a11ywrapper.h b/vcl/inc/osx/a11ywrapper.h index 469e50820e19..1eb4039c57e9 100644 --- a/vcl/inc/osx/a11ywrapper.h +++ b/vcl/inc/osx/a11ywrapper.h @@ -50,7 +50,19 @@ struct ReferenceWrapper css::uno::Reference < css::accessibility::XAccessibleTextMarkup > rAccessibleTextMarkup; }; -@interface AquaA11yWrapper : NSView +@interface AquaA11yWrapper : NSAccessibilityElement + <NSAccessibilityElement, + NSAccessibilityGroup, + NSAccessibilityButton, + NSAccessibilitySwitch, + NSAccessibilityRadioButton, + NSAccessibilityCheckBox, + NSAccessibilityStaticText, + NSAccessibilityNavigableStaticText, + NSAccessibilityProgressIndicator, + NSAccessibilityStepper, + NSAccessibilitySlider, + NSAccessibilityImage> { ReferenceWrapper maReferenceWrapper; BOOL mActsAsRadioGroup; @@ -68,6 +80,7 @@ struct ReferenceWrapper -(id)accessibilityFocusedUIElement; -(NSString *)accessibilityActionDescription:(NSString *)action; -(void)accessibilityPerformAction:(NSString *)action; +-(BOOL)performAction:(NSString *)action; -(NSArray *)accessibilityActionNames; -(id)accessibilityHitTest:(NSPoint)point; // Attribute values @@ -86,6 +99,7 @@ struct ReferenceWrapper -(void)setActsAsRadioGroup:(BOOL)actsAsRadioGroup; -(BOOL)actsAsRadioGroup; -(NSWindow*)windowForParent; +-(id)init; -(id)initWithAccessibleContext: (css::uno::Reference < css::accessibility::XAccessibleContext >) anAccessibleContext; -(void) setDefaults: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext; +(void)setPopupMenuOpen:(BOOL)popupMenuOpen; diff --git a/vcl/inc/osx/salframeview.h b/vcl/inc/osx/salframeview.h index 1282eb10cbee..f9eca27e305c 100644 --- a/vcl/inc/osx/salframeview.h +++ b/vcl/inc/osx/salframeview.h @@ -70,11 +70,22 @@ enum class SalEvent; -(void)endExtTextInput:(EndExtTextInputFlags)nFlags; -(void)windowDidResizeWithTimer:(NSTimer *)pTimer; + +-(BOOL)isIgnoredWindow; + +// NSAccessibilityElement overrides +-(id)accessibilityApplicationFocusedUIElement; +-(id)accessibilityFocusedUIElement; +-(BOOL)accessibilityIsIgnored; +-(BOOL)isAccessibilityElement; + @end -@interface SalFrameView : AquaA11yWrapper <NSTextInputClient> +@interface SalFrameView : NSView <NSTextInputClient> { AquaSalFrame* mpFrame; + AquaA11yWrapper* mpChildWrapper; + BOOL mbNeedChildWrapper; // for NSTextInput/NSTextInputClient NSEvent* mpLastEvent; @@ -228,6 +239,37 @@ enum class SalEvent; -(void)endExtTextInput:(EndExtTextInputFlags)nFlags; -(void)deleteTextInputWantsNonRepeatKeyDown; +-(void)insertRegisteredWrapperIntoWrapperRepository; +-(void)registerWrapper; +-(void)revokeWrapper; + +// NSAccessibilityElement overrides +-(id)accessibilityAttributeValue:(NSString *)pAttribute; +-(BOOL)accessibilityIsIgnored; +-(NSArray *)accessibilityAttributeNames; +-(BOOL)accessibilityIsAttributeSettable:(NSString *)pAttribute; +-(NSArray *)accessibilityParameterizedAttributeNames; +-(BOOL)accessibilitySetOverrideValue:(id)pValue forAttribute:(NSString *)pAttribute; +-(void)accessibilitySetValue:(id)pValue forAttribute:(NSString *)pAttribute; +-(id)accessibilityAttributeValue:(NSString *)pAttribute forParameter:(id)pParameter; +-(id)accessibilityFocusedUIElement; +-(NSString *)accessibilityActionDescription:(NSString *)pAction; +-(void)accessibilityPerformAction:(NSString *)pAction; +-(NSArray *)accessibilityActionNames; +-(id)accessibilityHitTest:(NSPoint)aPoint; +-(NSArray *)accessibilityVisibleChildren; +-(NSArray *)accessibilitySelectedChildren; +-(NSArray *)accessibilityChildren; +-(NSArray <id<NSAccessibilityElement>> *)accessibilityChildrenInNavigationOrder; + +@end + +@interface SalFrameViewA11yWrapper : AquaA11yWrapper +{ + SalFrameView* mpParentView; +} +-(id)initWithParent:(SalFrameView*)pParentView accessibleContext:(::com::sun::star::uno::Reference<::com::sun::star::accessibility::XAccessibleContext>&)rxAccessibleContext; +-(void)dealloc; @end #endif // INCLUDED_VCL_INC_OSX_SALFRAMEVIEW_H diff --git a/vcl/inc/quartz/salgdi.h b/vcl/inc/quartz/salgdi.h index be5075e18a55..df9eabad936f 100644 --- a/vcl/inc/quartz/salgdi.h +++ b/vcl/inc/quartz/salgdi.h @@ -133,6 +133,8 @@ private: namespace sal::aqua { +NSRect getTotalScreenBounds(); +void resetTotalScreenBounds(); float getWindowScaling(); void resetWindowScaling(); } diff --git a/vcl/osx/a11yactionwrapper.h b/vcl/osx/a11yactionwrapper.h index 51da8a2caf38..9afc08b6b970 100644 --- a/vcl/osx/a11yactionwrapper.h +++ b/vcl/osx/a11yactionwrapper.h @@ -28,6 +28,7 @@ } + (NSArray*)actionNamesForElement:(AquaA11yWrapper*)wrapper; + (void)doAction:(NSString*)action ofElement:(AquaA11yWrapper*)wrapper; ++ (NSAccessibilityActionName)actionNameForSelector:(SEL)aSelector; @end #endif // INCLUDED_VCL_OSX_A11YACTIONWRAPPER_H diff --git a/vcl/osx/a11yactionwrapper.mm b/vcl/osx/a11yactionwrapper.mm index 8af087edf75e..9bea25c11934 100644 --- a/vcl/osx/a11yactionwrapper.mm +++ b/vcl/osx/a11yactionwrapper.mm @@ -72,6 +72,25 @@ } } ++(NSAccessibilityActionName)actionNameForSelector:(SEL)aSelector +{ + NSAccessibilityActionName pRet = nil; + + if ( aSelector == @selector(accessibilityPerformDecrement) ) { + pRet = NSAccessibilityDecrementAction; + } else if ( aSelector == @selector(accessibilityPerformIncrement) ) { + pRet = NSAccessibilityIncrementAction; + } else if ( aSelector == @selector(accessibilityPerformPick) ) { + pRet = NSAccessibilityPickAction; + } else if ( aSelector == @selector(accessibilityPerformPress) ) { + pRet = NSAccessibilityPressAction; + } else if ( aSelector == @selector(accessibilityPerformShowMenu) ) { + pRet = NSAccessibilityShowMenuAction; + } + + return pRet; +} + @end /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/a11yfactory.mm b/vcl/osx/a11yfactory.mm index 56ffa9f7e008..0783252c7e1f 100644 --- a/vcl/osx/a11yfactory.mm +++ b/vcl/osx/a11yfactory.mm @@ -155,75 +155,36 @@ static bool enabled = false; #endif { [ dAllWrapper setObject: aWrapper forKey: nKey ]; - /* fdo#67410: Accessibility notifications are not delivered on NSView subclasses that do not - "reasonably" participate in NSView hierarchy (perhaps the only important point is - that the view is a transitive subview of the NSWindow's content view, but I - did not try to verify that). - - So let the superview-subviews relationship mirror the AXParent-AXChildren relationship. - */ - id parent = [aWrapper accessibilityAttributeValue:NSAccessibilityParentAttribute]; - if (parent) { - if ([parent isKindOfClass:[NSView class]]) { - NSView *parentView = static_cast<NSView *>(parent); - - // tdf#146765 Fix infinite recursion in -[NSView visibleRect] - // HACK: Adding a subview to an NSView that is not attached - // to an NSWindow leads to infinite recursion in the native - // NSViewGetVisibleRect() function. This seems to be a new - // behavior starting with macOS 12.6.2. - // In the case of tdf#146765, we end up here because - // -[AquaA11yWrapper childrenAttribute] is called by a - // wrapper that is already attached to an NSWindow. That is - // normal. What isn't normal is that the child wrapper's - // unignored accessible parent is a different wrapper than - // the caller and that different wrapper is not yet - // attached to an NSWindow. - // TODO: switch the AquaA11yWrapper class to inherit the - // lightweight NSAccessibilityElement class instead of the - // NSView class to possibly avoid the need for this hack. - NSWindow *window = [parentView window]; - SAL_WARN_IF(!window, "vcl.a11y","Can't add subview. Parent view's window is nil!"); - if (window) - [parentView addSubview:aWrapper positioned:NSWindowBelow relativeTo:nil]; - } else if ([parent isKindOfClass:NSClassFromString(@"SalFrameWindow")]) { - NSWindow *window = static_cast<NSWindow *>(parent); - NSView *salView = [window contentView]; - [salView addSubview:aWrapper positioned:NSWindowBelow relativeTo:nil]; - } - } } } return aWrapper; } -+(void)insertIntoWrapperRepository: (NSView *) viewElement forAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext { ++(void)insertIntoWrapperRepository: (AquaA11yWrapper *) element forAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext { NSMutableDictionary * dAllWrapper = [ AquaA11yFactory allWrapper ]; - [ dAllWrapper setObject: viewElement forKey: [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ] ]; + [ dAllWrapper setObject: element forKey: [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ] ]; } +(void)removeFromWrapperRepositoryFor: (css::uno::Reference < css::accessibility::XAccessibleContext >) rxAccessibleContext { // TODO: when RADIO_BUTTON search for associated RadioGroup-wrapper and delete that as well AquaA11yWrapper * theWrapper = [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: NO ]; if ( theWrapper != nil ) { - if (![theWrapper isKindOfClass:NSClassFromString(@"SalFrameView")]) { - [theWrapper removeFromSuperview]; - } + NSAccessibilityPostNotification( theWrapper, NSAccessibilityUIElementDestroyedNotification ); [ [ AquaA11yFactory allWrapper ] removeObjectForKey: [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ] ]; [ theWrapper release ]; } } -+(void)registerView: (NSView *) theView { - if ( enabled && [ theView isKindOfClass: [ AquaA11yWrapper class ] ] ) { ++(void)registerWrapper: (AquaA11yWrapper *) theWrapper { + if ( enabled && theWrapper ) { // insertIntoWrapperRepository gets called from SalFrameView itself to bootstrap the bridge initially - [ static_cast<AquaA11yWrapper *>(theView) accessibleContext ]; + [ theWrapper accessibleContext ]; } } -+(void)revokeView: (NSView *) theView { - if ( enabled && [ theView isKindOfClass: [ AquaA11yWrapper class ] ] ) { - [ AquaA11yFactory removeFromWrapperRepositoryFor: [ static_cast<AquaA11yWrapper *>(theView) accessibleContext ] ]; ++(void)revokeWrapper: (AquaA11yWrapper *) theWrapper { + if ( enabled && theWrapper ) { + [ AquaA11yFactory removeFromWrapperRepositoryFor: [ theWrapper accessibleContext ] ]; } } diff --git a/vcl/osx/a11ywrapper.mm b/vcl/osx/a11ywrapper.mm index 75763a65a131..06967df5aa3e 100644 --- a/vcl/osx/a11ywrapper.mm +++ b/vcl/osx/a11ywrapper.mm @@ -26,6 +26,7 @@ #include <osx/a11yfactory.h> #include <osx/a11yfocustracker.hxx> +#include <quartz/salgdi.h> #include <quartz/utils.h> #include "a11yfocuslistener.hxx" @@ -65,11 +66,15 @@ static std::ostream &operator<<(std::ostream &s, NSObject *obj) { return s << [[obj description] UTF8String]; } -@implementation AquaA11yWrapper : NSView +@implementation AquaA11yWrapper #pragma mark - #pragma mark Init and dealloc +-(id)init { + return [ super init ]; +} + -(id)initWithAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext { self = [ super init ]; if ( self ) { @@ -716,20 +721,27 @@ static std::ostream &operator<<(std::ostream &s, NSObject *obj) { return NO; } bool ignored = false; - sal_Int16 nRole = [ self accessibleContext ] -> getAccessibleRole(); - switch ( nRole ) { - //case AccessibleRole::PANEL: - case AccessibleRole::FRAME: - case AccessibleRole::ROOT_PANE: - case AccessibleRole::SEPARATOR: - case AccessibleRole::FILLER: - case AccessibleRole::DIALOG: - ignored = true; - break; - default: - ignored = ! ( [ self accessibleContext ] -> getAccessibleStateSet() & AccessibleStateType::VISIBLE ); - break; + try { + sal_Int16 nRole = [ self accessibleContext ] -> getAccessibleRole(); + switch ( nRole ) { + //case AccessibleRole::PANEL: + case AccessibleRole::FRAME: + case AccessibleRole::ROOT_PANE: + case AccessibleRole::SEPARATOR: + case AccessibleRole::FILLER: + case AccessibleRole::DIALOG: + ignored = true; + break; + default: + ignored = ! ( [ self accessibleContext ] -> getAccessibleStateSet() & AccessibleStateType::VISIBLE ); + break; + } + } catch ( DisposedException& ) { + ignored = true; + } catch ( RuntimeException& ) { + ignored = true; } + return ignored; // TODO: to be completed } @@ -965,6 +977,10 @@ static std::ostream &operator<<(std::ostream &s, NSObject *obj) { } -(void)accessibilityPerformAction:(NSString *)action { + [ self performAction: action ]; +} + +-(BOOL)performAction:(NSString *)action { // Related: tdf#148453 Acquire solar mutex during native accessibility calls SolarMutexGuard aGuard; @@ -972,7 +988,9 @@ static std::ostream &operator<<(std::ostream &s, NSObject *obj) { AquaA11yWrapper * actionResponder = [ self actionResponder ]; if ( actionResponder ) { [ AquaA11yActionWrapper doAction: action ofElement: actionResponder ]; + return YES; } + return NO; } -(NSArray *)accessibilityActionNames { @@ -1170,7 +1188,7 @@ static Reference < XAccessibleContext > hitTestRunner ( css::awt::Point point, } -(NSWindow*)windowForParent { - return [self window]; + return nil; } -(void)setActsAsRadioGroup:(BOOL)actsAsRadioGroup { @@ -1185,6 +1203,393 @@ static Reference < XAccessibleContext > hitTestRunner ( css::awt::Point point, isPopupMenuOpen = popupMenuOpen; } +// NSAccessibility selectors + +- (BOOL)isAccessibilityElement +{ + return ! [ self accessibilityIsIgnored ]; +} + +- (BOOL)isAccessibilityFocused +{ + NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityFocusedAttribute ]; + if ( pNumber ) + return [ pNumber boolValue ]; + else + return NO; +} + +- (id)accessibilityTopLevelUIElement +{ + return [ self accessibilityAttributeValue: NSAccessibilityTopLevelUIElementAttribute ]; +} + +- (id)accessibilityValue +{ + return [ self accessibilityAttributeValue: NSAccessibilityValueAttribute ]; +} + +- (NSArray *)accessibilityVisibleChildren +{ + return [ self accessibilityChildren ]; +} + +- (NSAccessibilitySubrole)accessibilitySubrole +{ + return [ self accessibilityAttributeValue: NSAccessibilitySubroleAttribute ]; +} + +- (NSString *)accessibilityTitle +{ + return [ self accessibilityAttributeValue: NSAccessibilityTitleAttribute ]; +} + +- (id)accessibilityTitleUIElement +{ + return [ self accessibilityAttributeValue: NSAccessibilityTitleUIElementAttribute ]; +} + +- (NSAccessibilityOrientation)accessibilityOrientation +{ + NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityOrientationAttribute ]; + if ( pNumber ) + return (NSAccessibilityOrientation)[ pNumber integerValue ]; + else + return NSAccessibilityOrientationUnknown; +} + +- (id)accessibilityParent +{ + return [ self accessibilityAttributeValue: NSAccessibilityParentAttribute ]; +} + +- (NSAccessibilityRole)accessibilityRole +{ + return [ self accessibilityAttributeValue: NSAccessibilityRoleAttribute ]; +} + +- (NSString *)accessibilityRoleDescription +{ + return [ self accessibilityAttributeValue: NSAccessibilityRoleDescriptionAttribute ]; +} + +- (BOOL)isAccessibilitySelected +{ + NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilitySelectedAttribute ]; + if ( pNumber ) + return [ pNumber boolValue ]; + else + return NO; +} + +- (NSArray *)accessibilitySelectedChildren +{ + return [ self accessibilityAttributeValue: NSAccessibilitySelectedChildrenAttribute ]; +} + +- (NSArray *)accessibilityServesAsTitleForUIElements +{ + return [ self accessibilityAttributeValue: NSAccessibilityServesAsTitleForUIElementsAttribute ]; +} + +- (id)accessibilityMinValue +{ + return [ self accessibilityAttributeValue: NSAccessibilityMinValueAttribute ]; +} + +- (id)accessibilityMaxValue +{ + return [ self accessibilityAttributeValue: NSAccessibilityMaxValueAttribute ]; +} + +- (id)accessibilityWindow +{ + return [ self accessibilityAttributeValue: NSAccessibilityWindowAttribute ]; +} + +- (NSString *)accessibilityHelp +{ + return [ self accessibilityAttributeValue: NSAccessibilityHelpAttribute ]; +} + +- (BOOL)isAccessibilityExpanded +{ + NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityExpandedAttribute ]; + if ( pNumber ) + return [ pNumber boolValue ]; + else + return NO; +} + +- (BOOL)isAccessibilityEnabled +{ + NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityEnabledAttribute ]; + if ( pNumber ) + return [ pNumber boolValue ]; + else + return NO; +} + +- (NSArray *)accessibilityChildren +{ + return [ self accessibilityAttributeValue: NSAccessibilityChildrenAttribute ]; +} + +- (NSArray <id<NSAccessibilityElement>> *)accessibilityChildrenInNavigationOrder +{ + return [ self accessibilityChildren ]; +} + +- (NSArray *)accessibilityContents +{ + return [ self accessibilityAttributeValue: NSAccessibilityContentsAttribute ]; +} + +- (NSString *)accessibilityLabel +{ + return [ self accessibilityAttributeValue: NSAccessibilityDescriptionAttribute ]; +} + +- (id)accessibilityApplicationFocusedUIElement +{ + return [ self accessibilityFocusedUIElement ]; +} + +- (BOOL)isAccessibilityDisclosed +{ + NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityDisclosingAttribute ]; + if ( pNumber ) + return [ pNumber boolValue ]; + else + return NO; +} + +- (id)accessibilityHorizontalScrollBar +{ + return [ self accessibilityAttributeValue: NSAccessibilityHorizontalScrollBarAttribute ]; +} + +- (id)accessibilityVerticalScrollBar +{ + return [ self accessibilityAttributeValue: NSAccessibilityVerticalScrollBarAttribute ]; +} + +- (NSArray *)accessibilityTabs +{ + return [ self accessibilityAttributeValue: NSAccessibilityTabsAttribute ]; +} + +- (NSArray *)accessibilityColumns +{ + return [ self accessibilityAttributeValue: NSAccessibilityColumnsAttribute ]; +} + +- (NSArray *)accessibilityRows +{ + return [ self accessibilityAttributeValue: NSAccessibilityRowsAttribute ]; +} + +- (NSRange)accessibilitySharedCharacterRange +{ + NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilitySharedCharacterRangeAttribute ]; + if ( pValue ) + return [ pValue rangeValue ]; + else + return NSMakeRange( NSNotFound, 0 ); +} + +- (NSArray *)accessibilitySharedTextUIElements +{ + return [ self accessibilityAttributeValue: NSAccessibilitySharedTextUIElementsAttribute ]; +} + +- (NSRange)accessibilityVisibleCharacterRange +{ + NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityVisibleCharacterRangeAttribute ]; + if ( pValue ) + return [ pValue rangeValue ]; + else + return NSMakeRange( NSNotFound, 0 ); +} + +- (NSInteger)accessibilityNumberOfCharacters +{ + NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityNumberOfCharactersAttribute ]; + if ( pNumber ) + return [ pNumber integerValue ]; + else + return 0; +} + +- (NSString *)accessibilitySelectedText +{ + return [ self accessibilityAttributeValue: NSAccessibilitySelectedTextAttribute ]; +} + +- (NSRange)accessibilitySelectedTextRange +{ + NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilitySelectedTextRangeAttribute ]; + if ( pValue ) + return [ pValue rangeValue ]; + else + return NSMakeRange( NSNotFound, 0 ); +} + +- (NSAttributedString *)accessibilityAttributedStringForRange:(NSRange)aRange +{ + return [ self accessibilityAttributeValue: NSAccessibilityAttributedStringForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ]; +} + +- (NSRange)accessibilityRangeForLine:(NSInteger)nLine +{ + NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityRangeForLineParameterizedAttribute forParameter: [NSNumber numberWithInteger: nLine ] ]; + if ( pValue ) + return [ pValue rangeValue ]; + else + return NSMakeRange( NSNotFound, 0 ); +} + +- (NSString *)accessibilityStringForRange:(NSRange)aRange +{ + return [ self accessibilityAttributeValue: NSAccessibilityStringForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ]; +} + +- (NSRange)accessibilityRangeForPosition:(NSPoint)aPoint +{ + NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityRangeForPositionParameterizedAttribute forParameter: [ NSValue valueWithPoint: aPoint ] ]; + if ( pValue ) + return [ pValue rangeValue ]; + else + return NSMakeRange( NSNotFound, 0 ); +} + +- (NSRange)accessibilityRangeForIndex:(NSInteger)nIndex +{ + NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityRangeForIndexParameterizedAttribute forParameter: [ NSNumber numberWithInteger: nIndex ] ]; + if ( pValue ) + return [ pValue rangeValue ]; + else + return NSMakeRange( NSNotFound, 0 ); +} + +- (NSRect)accessibilityFrameForRange:(NSRange)aRange +{ + NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityBoundsForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ]; + if ( pValue ) + return [ pValue rectValue ]; + else + return NSZeroRect; +} + +- (NSData *)accessibilityRTFForRange:(NSRange)aRange +{ + return [ self accessibilityAttributeValue: NSAccessibilityRTFForRangeParameterizedAttribute forParameter: [ NSValue valueWithRange: aRange ] ]; +} + +- (NSRange)accessibilityStyleRangeForIndex:(NSInteger)nIndex +{ + NSValue *pValue = [ self accessibilityAttributeValue: NSAccessibilityStyleRangeForIndexParameterizedAttribute forParameter: [ NSNumber numberWithInteger: nIndex ] ]; + if ( pValue ) + return [ pValue rangeValue ]; + else + return NSMakeRange( NSNotFound, 0 ); +} + +- (NSInteger)accessibilityLineForIndex:(NSInteger)nIndex +{ + NSNumber *pNumber = [ self accessibilityAttributeValue: NSAccessibilityLineForIndexParameterizedAttribute forParameter: [ NSNumber numberWithInteger: nIndex ] ]; + if ( pNumber ) + return [ pNumber integerValue ]; + else + return 0; +} + +- (BOOL)accessibilityPerformDecrement +{ + return [ self performAction: NSAccessibilityDecrementAction ]; +} + +- (BOOL)accessibilityPerformPick +{ + return [ self performAction: NSAccessibilityPickAction ]; +} + +- (BOOL)accessibilityPerformShowMenu +{ + return [ self performAction: NSAccessibilityShowMenuAction ]; +} + +- (BOOL)accessibilityPerformPress +{ + return [ self performAction: NSAccessibilityPressAction ]; +} + +- (BOOL)accessibilityPerformIncrement +{ + return [ self performAction: NSAccessibilityIncrementAction ]; +} + +// NSAccessibilityElement selectors + +- (NSRect)accessibilityFrame +{ + // Related: tdf#148453 Acquire solar mutex during native accessibility calls + SolarMutexGuard aGuard; + + try { + XAccessibleComponent *pAccessibleComponent = [ self accessibleComponent ]; + if ( pAccessibleComponent ) { + com::sun::star::awt::Point location = pAccessibleComponent->getLocationOnScreen(); + com::sun::star::awt::Size size = pAccessibleComponent->getSize(); + NSRect screenRect = sal::aqua::getTotalScreenBounds(); + NSRect frame = NSMakeRect( (float)location.X, (float)( screenRect.size.height - size.Height - location.Y ), (float)size.Width, (float)size.Height ); + return frame; + } + } catch ( DisposedException& ) { + } catch ( RuntimeException& ) { + } + + return NSZeroRect; +} + +- (BOOL)accessibilityNotifiesWhenDestroyed +{ + return YES; +} + +- (BOOL)isAccessibilitySelectorAllowed:(SEL)aSelector +{ + if ( ! aSelector ) + return NO; + + if ( [ self respondsToSelector: aSelector ] ) { + // Ignore actions if action is not supported + NSAccessibilityActionName pActionName = [ AquaA11yActionWrapper actionNameForSelector: aSelector ]; + if ( pActionName ) { + NSArray *pActionNames = [ self accessibilityActionNames ]; + if ( ! pActionNames || ! [ pActionNames containsObject: pActionName ] ) + return NO; + } else { + // Ignore "setAccessibility" selectors if attribute is not settable + static NSString *pSetPrefix = @"setAccessibility"; + NSString *pSelName = NSStringFromSelector( aSelector ); + if ( pSelName && [ pSelName hasPrefix: pSetPrefix ] && [ pSelName hasSuffix: @":" ] ) { + NSAccessibilityAttributeName pAttrName = [ pSelName substringToIndex: [ pSelName length ] - 1 ]; + if ( pAttrName && [ pAttrName length ] > [ pSetPrefix length ] ) { + pAttrName = [ pAttrName substringFromIndex: [ pSetPrefix length ] ]; + if ( pAttrName && [ pAttrName length ] ) { + pAttrName = [ @"AX" stringByAppendingString: pAttrName ]; + if ( pAttrName && [ pAttrName length ] && ! [ self accessibilityIsAttributeSettable: pAttrName ] ) + return NO; + } + } + } + } + } + + return [ super isAccessibilitySelectorAllowed: aSelector ]; +} + @end /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/salframe.cxx b/vcl/osx/salframe.cxx index b4a2d07463b8..18ab569ad7a6 100644 --- a/vcl/osx/salframe.cxx +++ b/vcl/osx/salframe.cxx @@ -156,7 +156,8 @@ AquaSalFrame::~AquaSalFrame() } } if ( mpNSView ) { - [AquaA11yFactory revokeView: mpNSView]; + if ([mpNSView isKindOfClass:[SalFrameView class]]) + [static_cast<SalFrameView*>(mpNSView) revokeWrapper]; [mpNSView release]; } if ( mpNSWindow ) @@ -287,6 +288,7 @@ void AquaSalFrame::screenParametersChanged() { OSX_SALDATA_RUNINMAIN( screenParametersChanged() ) + sal::aqua::resetTotalScreenBounds(); sal::aqua::resetWindowScaling(); UpdateFrameGeometry(); @@ -429,7 +431,8 @@ void AquaSalFrame::initShow() } // make sure the view is present in the wrapper list before any children receive focus - [AquaA11yFactory registerView: mpNSView]; + if (mpNSView && [mpNSView isKindOfClass:[SalFrameView class]]) + [static_cast<SalFrameView*>(mpNSView) registerWrapper]; } void AquaSalFrame::SendPaintEvent( const tools::Rectangle* pRect ) diff --git a/vcl/osx/salframeview.mm b/vcl/osx/salframeview.mm index ad4dd40cfebb..0ccdb048d190 100644 --- a/vcl/osx/salframeview.mm +++ b/vcl/osx/salframeview.mm @@ -165,6 +165,39 @@ static AquaSalFrame* getMouseContainerFrame() return pDispatchFrame; } +static NSArray *getMergedAccessibilityChildren(NSArray *pDefaultChildren, NSArray *pUnignoredChildrenToAdd) +{ + NSArray *pRet = pDefaultChildren; + + if (pUnignoredChildrenToAdd && [pUnignoredChildrenToAdd count]) + { + NSMutableArray *pNewChildren = [NSMutableArray arrayWithCapacity:(pRet ? [pRet count] : 0) + 1]; + if (pNewChildren) + { + if (pRet) + [pNewChildren addObjectsFromArray:pRet]; + + for (AquaA11yWrapper *pWrapper : pUnignoredChildrenToAdd) + { + if (pWrapper && ![pNewChildren containsObject:pWrapper]) + [pNewChildren addObject:pWrapper]; + } + + pRet = pNewChildren; + } + else + { + pRet = pUnignoredChildrenToAdd; + } + } + + return pRet; +} + +@interface NSResponder (SalFrameWindow) +-(BOOL)accessibilityIsIgnored; +@end + @implementation SalFrameWindow -(id)initWithSalFrame: (AquaSalFrame*)pFrame { @@ -541,6 +574,44 @@ static AquaSalFrame* getMouseContainerFrame() return mpFrame -> GetWindow() -> GetAccessible() -> getAccessibleContext(); } +-(BOOL)isIgnoredWindow +{ + SolarMutexGuard aGuard; + + // Treat tooltip windows as ignored + if( mpFrame && AquaSalFrame::isAlive( mpFrame ) ) + return (mpFrame->mnStyle & SalFrameStyleFlags::TOOLTIP) != SalFrameStyleFlags::NONE; + return YES; +} + +-(id)accessibilityApplicationFocusedUIElement +{ + return [self accessibilityFocusedUIElement]; +} + +-(id)accessibilityFocusedUIElement +{ + // Treat tooltip windows as ignored + if ([self isIgnoredWindow]) + return nil; + + return [super accessibilityFocusedUIElement]; +} + +-(BOOL)accessibilityIsIgnored +{ + // Treat tooltip windows as ignored + if ([self isIgnoredWindow]) + return YES; + + return [super accessibilityIsIgnored]; +} + +-(BOOL)isAccessibilityElement +{ + return ![self accessibilityIsIgnored]; +} + -(NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender { return [mDraggingDestinationHandler draggingEntered: sender]; @@ -615,6 +686,8 @@ static AquaSalFrame* getMouseContainerFrame() { mDraggingDestinationHandler = nil; mpFrame = pFrame; + mpChildWrapper = nil; + mbNeedChildWrapper = NO; mpLastEvent = nil; mMarkedRange = NSMakeRange(NSNotFound, 0); mSelectedRange = NSMakeRange(NSNotFound, 0); @@ -635,6 +708,7 @@ static AquaSalFrame* getMouseContainerFrame() { [self clearLastEvent]; [self clearLastMarkedText]; + [self revokeWrapper]; [super dealloc]; } @@ -2053,16 +2127,13 @@ static AquaSalFrame* getMouseContainerFrame() -(css::accessibility::XAccessibleContext *)accessibleContext { - if ( !maReferenceWrapper.rAccessibleContext ) { - // some frames never become visible .. - vcl::Window *pWindow = mpFrame -> GetWindow(); - if ( ! pWindow ) - return nil; + SolarMutexGuard aGuard; - maReferenceWrapper.rAccessibleContext = pWindow -> /*GetAccessibleChildWindow( 0 ) ->*/ GetAccessible() -> getAccessibleContext(); - [ AquaA11yFactory insertIntoWrapperRepository: self forAccessibleContext: maReferenceWrapper.rAccessibleContext ]; - } - return [ super accessibleContext ]; + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibleContext]; + + return nil; } -(NSWindow*)windowForParent @@ -2213,6 +2284,291 @@ static AquaSalFrame* getMouseContainerFrame() } } +-(void)insertRegisteredWrapperIntoWrapperRepository +{ + SolarMutexGuard aGuard; + + if (!mbNeedChildWrapper) + return; + + vcl::Window *pWindow = mpFrame->GetWindow(); + if (!pWindow) + return; + + mbNeedChildWrapper = NO; + + ::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext > xAccessibleContext( pWindow->GetAccessible()->getAccessibleContext() ); + assert(!mpChildWrapper); + mpChildWrapper = [[SalFrameViewA11yWrapper alloc] initWithParent:self accessibleContext:xAccessibleContext]; + [AquaA11yFactory insertIntoWrapperRepository:mpChildWrapper forAccessibleContext:xAccessibleContext]; +} + +-(void)registerWrapper +{ + [self revokeWrapper]; + + mbNeedChildWrapper = YES; +} + +-(void)revokeWrapper +{ + mbNeedChildWrapper = NO; + + if (mpChildWrapper) + { + [AquaA11yFactory revokeWrapper:mpChildWrapper]; + [mpChildWrapper setAccessibilityParent:nil]; + [mpChildWrapper release]; + mpChildWrapper = nil; + } +} + +-(id)accessibilityAttributeValue:(NSString *)pAttribute +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibilityAttributeValue:pAttribute]; + else + return nil; +} + +-(BOOL)accessibilityIsIgnored +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibilityIsIgnored]; + else + return YES; +} + +-(NSArray *)accessibilityAttributeNames +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibilityAttributeNames]; + else + return [NSArray array]; +} + +-(BOOL)accessibilityIsAttributeSettable:(NSString *)pAttribute +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibilityIsAttributeSettable:pAttribute]; + else + return NO; +} + +-(NSArray *)accessibilityParameterizedAttributeNames +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibilityParameterizedAttributeNames]; + else + return [NSArray array]; +} + +-(BOOL)accessibilitySetOverrideValue:(id)pValue forAttribute:(NSString *)pAttribute +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibilitySetOverrideValue:pValue forAttribute:pAttribute]; + else + return NO; +} + +-(void)accessibilitySetValue:(id)pValue forAttribute:(NSString *)pAttribute +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + [mpChildWrapper accessibilitySetValue:pValue forAttribute:pAttribute]; +} + +-(id)accessibilityAttributeValue:(NSString *)pAttribute forParameter:(id)pParameter +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibilityAttributeValue:pAttribute forParameter:pParameter]; + else + return nil; +} + +-(id)accessibilityFocusedUIElement +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibilityFocusedUIElement]; + else + return nil; +} + +-(NSString *)accessibilityActionDescription:(NSString *)pAction +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibilityActionDescription:pAction]; + else + return nil; +} + +-(void)accessibilityPerformAction:(NSString *)pAction +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + [mpChildWrapper accessibilityPerformAction:pAction]; +} + +-(NSArray *)accessibilityActionNames +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibilityActionNames]; + else + return [NSArray array]; +} + +-(id)accessibilityHitTest:(NSPoint)aPoint +{ + SolarMutexGuard aGuard; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + return [mpChildWrapper accessibilityHitTest:aPoint]; + else + return nil; +} + +-(id)accessibilityParent +{ + return [self window]; +} + +-(NSArray *)accessibilityVisibleChildren +{ + return [self accessibilityChildren]; +} + +-(NSArray *)accessibilitySelectedChildren +{ + SolarMutexGuard aGuard; + + NSArray *pRet = [super accessibilityChildren]; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + pRet = getMergedAccessibilityChildren(pRet, [mpChildWrapper accessibilitySelectedChildren]); + + return pRet; +} + +-(NSArray *)accessibilityChildren +{ + SolarMutexGuard aGuard; + + NSArray *pRet = [super accessibilityChildren]; + + [self insertRegisteredWrapperIntoWrapperRepository]; + if (mpChildWrapper) + pRet = getMergedAccessibilityChildren(pRet, [mpChildWrapper accessibilityChildren]); + + return pRet; +} + +-(NSArray <id<NSAccessibilityElement>> *)accessibilityChildrenInNavigationOrder +{ + return [self accessibilityChildren]; +} + +@end + +@implementation SalFrameViewA11yWrapper + +-(id)initWithParent:(SalFrameView *)pParentView accessibleContext:(::com::sun::star::uno::Reference< ::com::sun::star::accessibility::XAccessibleContext >&)rxAccessibleContext +{ + [super init]; + + maReferenceWrapper.rAccessibleContext = rxAccessibleContext; + + mpParentView = pParentView; + if (mpParentView) + { + [mpParentView retain]; + [self setAccessibilityParent:mpParentView]; + } + + return self; +} + +-(void)dealloc +{ + if (mpParentView) + [mpParentView release]; + + [super dealloc]; +} + +-(id)parentAttribute +{ + if (mpParentView) + return NSAccessibilityUnignoredAncestor(mpParentView); + else + return nil; +} + +-(void)setAccessibilityParent:(id)pObject +{ + if (mpParentView) + { + [mpParentView release]; + mpParentView = nil; + } + + if (pObject && [pObject isKindOfClass:[SalFrameView class]]) + { + mpParentView = (SalFrameView *)pObject; + [mpParentView retain]; + } + + [super setAccessibilityParent:mpParentView]; +} + +-(id)windowAttribute +{ + if (mpParentView) + return [mpParentView window]; + else + return nil; +} + +-(NSWindow *)windowForParent +{ + return [self windowAttribute]; +} + @end /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/osx/salgdiutils.cxx b/vcl/osx/salgdiutils.cxx index d65897e25a83..7e4bd23baac9 100644 --- a/vcl/osx/salgdiutils.cxx +++ b/vcl/osx/salgdiutils.cxx @@ -40,6 +40,9 @@ #include <vcl/skia/SkiaHelper.hxx> #endif +static bool bTotalScreenBounds = false; +static NSRect aTotalScreenBounds = NSZeroRect; + // TODO: Scale will be set to 2.0f as default after implementation of full scaled display support . This will allow moving of // windows between non retina and retina displays without blurry text and graphics. Static variables have to be removed thereafter. @@ -53,6 +56,39 @@ static float fWindowScale = 1.0f; namespace sal::aqua { +NSRect getTotalScreenBounds() +{ + if (!bTotalScreenBounds) + { + aTotalScreenBounds = NSZeroRect; + + NSArray *aScreens = [NSScreen screens]; + if (aScreens != nullptr) + { + for (NSScreen *aScreen : aScreens) + { + // Calculate total screen bounds + NSRect aScreenFrame = [aScreen frame]; + if (!NSIsEmptyRect(aScreenFrame)) + { + if (NSIsEmptyRect(aTotalScreenBounds)) + aTotalScreenBounds = aScreenFrame; + else + aTotalScreenBounds = NSUnionRect( aScreenFrame, aTotalScreenBounds ); + } + } + bTotalScreenBounds = true; + } + } + return aTotalScreenBounds; +} + +void resetTotalScreenBounds() +{ + bTotalScreenBounds = false; + getTotalScreenBounds(); +} + float getWindowScaling() { // Related: tdf#147342 Any changes to this function must be copied to the