diff options
Diffstat (limited to 'vcl/osx/a11yfactory.mm')
-rw-r--r-- | vcl/osx/a11yfactory.mm | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/vcl/osx/a11yfactory.mm b/vcl/osx/a11yfactory.mm new file mode 100644 index 000000000..19a8d4c8d --- /dev/null +++ b/vcl/osx/a11yfactory.mm @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <osx/salinst.h> +#include <osx/a11yfactory.h> +#include <osx/a11yfocustracker.hxx> + +#include "a11yfocuslistener.hxx" +#include "a11yrolehelper.h" +#include "a11ywrapperbutton.h" +#include "a11ywrapperstatictext.h" +#include "a11ywrappertextarea.h" +#include "a11ywrappercheckbox.h" +#include "a11ywrappercombobox.h" +#include "a11ywrappergroup.h" +#include "a11ywrapperlist.h" +#include "a11ywrapperradiobutton.h" +#include "a11ywrapperradiogroup.h" +#include "a11ywrapperrow.h" +#include "a11ywrapperscrollarea.h" +#include "a11ywrapperscrollbar.h" +#include "a11ywrappersplitter.h" +#include "a11ywrappertabgroup.h" +#include "a11ywrappertoolbar.h" +#include "a11ytablewrapper.h" + +#include <com/sun/star/accessibility/AccessibleStateType.hpp> + +using namespace ::com::sun::star::accessibility; +using namespace ::com::sun::star::uno; + +static bool enabled = false; + +@implementation AquaA11yFactory : NSObject + +#pragma mark - +#pragma mark Wrapper Repository + ++(NSMutableDictionary *)allWrapper { + static NSMutableDictionary * mdAllWrapper = nil; + if ( mdAllWrapper == nil ) { + mdAllWrapper = [ [ [ NSMutableDictionary alloc ] init ] retain ]; + // initialize keyboard focus tracker + rtl::Reference< AquaA11yFocusListener > listener( AquaA11yFocusListener::get() ); + TheAquaA11yFocusTracker().setFocusListener(listener); + enabled = true; + } + return mdAllWrapper; +} + ++(NSValue *)keyForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext { + return [ NSValue valueWithPointer: rxAccessibleContext.get() ]; +} + ++(NSValue *)keyForAccessibleContextAsRadioGroup: (Reference < XAccessibleContext >) rxAccessibleContext { + return [ NSValue valueWithPointer: ( rxAccessibleContext.get() + 2 ) ]; +} + ++(AquaA11yWrapper *)wrapperForAccessible: (Reference < XAccessible >) rxAccessible { + if ( rxAccessible.is() ) { + Reference< XAccessibleContext > xAccessibleContext = rxAccessible->getAccessibleContext(); + if( xAccessibleContext.is() ) { + return [ AquaA11yFactory wrapperForAccessibleContext: xAccessibleContext ]; + } + } + return nil; +} + ++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext { + return [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: YES asRadioGroup: NO ]; +} + ++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate { + return [ AquaA11yFactory wrapperForAccessibleContext: rxAccessibleContext createIfNotExists: bCreate asRadioGroup: NO ]; +} + ++(AquaA11yWrapper *)wrapperForAccessibleContext: (Reference < XAccessibleContext >) rxAccessibleContext createIfNotExists:(BOOL) bCreate asRadioGroup:(BOOL) asRadioGroup{ + NSMutableDictionary * dAllWrapper = [ AquaA11yFactory allWrapper ]; + NSValue * nKey = nil; + if ( asRadioGroup ) { + nKey = [ AquaA11yFactory keyForAccessibleContextAsRadioGroup: rxAccessibleContext ]; + } else { + nKey = [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ]; + } + AquaA11yWrapper * aWrapper = static_cast<AquaA11yWrapper *>([ dAllWrapper objectForKey: nKey ]); + if ( aWrapper != nil ) { + [ aWrapper retain ]; + } else if ( bCreate ) { + NSString * nativeRole = [ AquaA11yRoleHelper getNativeRoleFrom: rxAccessibleContext.get() ]; + // TODO: reflection + if ( [ nativeRole isEqualToString: NSAccessibilityButtonRole ] ) { + aWrapper = [ [ AquaA11yWrapperButton alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityTextAreaRole ] ) { + aWrapper = [ [ AquaA11yWrapperTextArea alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityStaticTextRole ] ) { + aWrapper = [ [ AquaA11yWrapperStaticText alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityComboBoxRole ] ) { + aWrapper = [ [ AquaA11yWrapperComboBox alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityGroupRole ] ) { + aWrapper = [ [ AquaA11yWrapperGroup alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityToolbarRole ] ) { + aWrapper = [ [ AquaA11yWrapperToolbar alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityScrollAreaRole ] ) { + aWrapper = [ [ AquaA11yWrapperScrollArea alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityTabGroupRole ] ) { + aWrapper = [ [ AquaA11yWrapperTabGroup alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityScrollBarRole ] ) { + aWrapper = [ [ AquaA11yWrapperScrollBar alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityCheckBoxRole ] ) { + aWrapper = [ [ AquaA11yWrapperCheckBox alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityRadioGroupRole ] ) { + aWrapper = [ [ AquaA11yWrapperRadioGroup alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityRadioButtonRole ] ) { + aWrapper = [ [ AquaA11yWrapperRadioButton alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityRowRole ] ) { + aWrapper = [ [ AquaA11yWrapperRow alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityListRole ] ) { + aWrapper = [ [ AquaA11yWrapperList alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilitySplitterRole ] ) { + aWrapper = [ [ AquaA11yWrapperSplitter alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else if ( [ nativeRole isEqualToString: NSAccessibilityTableRole ] ) { + aWrapper = [ [ AquaA11yTableWrapper alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } else { + aWrapper = [ [ AquaA11yWrapper alloc ] initWithAccessibleContext: rxAccessibleContext ]; + } + [ nativeRole release ]; + [ aWrapper setActsAsRadioGroup: asRadioGroup ]; + #if 0 + /* #i102033# NSAccessibility does not seemt to know an equivalent for transient children. + That means we need to cache this, else e.g. tree list boxes are not accessible (moreover + it crashes by notifying dead objects - which would seemt o be another bug) + + FIXME: + Unfortunately this can increase memory consumption drastically until the non transient parent + is destroyed and finally all the transients are released. + */ + if ( ! rxAccessibleContext -> getAccessibleStateSet() -> contains ( AccessibleStateType::TRANSIENT ) ) + #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 differnt 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 { + NSMutableDictionary * dAllWrapper = [ AquaA11yFactory allWrapper ]; + [ dAllWrapper setObject: viewElement 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]; + } + [ [ AquaA11yFactory allWrapper ] removeObjectForKey: [ AquaA11yFactory keyForAccessibleContext: rxAccessibleContext ] ]; + [ theWrapper release ]; + } +} + ++(void)registerView: (NSView *) theView { + if ( enabled && [ theView isKindOfClass: [ AquaA11yWrapper class ] ] ) { + // insertIntoWrapperRepository gets called from SalFrameView itself to bootstrap the bridge initially + [ static_cast<AquaA11yWrapper *>(theView) accessibleContext ]; + } +} + ++(void)revokeView: (NSView *) theView { + if ( enabled && [ theView isKindOfClass: [ AquaA11yWrapper class ] ] ) { + [ AquaA11yFactory removeFromWrapperRepositoryFor: [ static_cast<AquaA11yWrapper *>(theView) accessibleContext ] ]; + } +} + +@end + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |