| @@ -117,23 +117,27 @@ private: | |||||
| addMethod (@selector (accessibilityLineForIndex:), getAccessibilityLineForIndex, "i@:i"); | addMethod (@selector (accessibilityLineForIndex:), getAccessibilityLineForIndex, "i@:i"); | ||||
| addMethod (@selector (setAccessibilitySelectedTextRange:), setAccessibilitySelectedTextRange, "v@:", @encode (NSRange)); | addMethod (@selector (setAccessibilitySelectedTextRange:), setAccessibilitySelectedTextRange, "v@:", @encode (NSRange)); | ||||
| addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount, "i@:"); | |||||
| addMethod (@selector (accessibilityRows), getAccessibilityRows, "@@:"); | |||||
| addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount, "i@:"); | |||||
| addMethod (@selector (accessibilityColumns), getAccessibilityColumns, "@@:"); | |||||
| addMethod (@selector (accessibilityRowCount), getAccessibilityRowCount, "i@:"); | |||||
| addMethod (@selector (accessibilityRows), getAccessibilityRows, "@@:"); | |||||
| addMethod (@selector (accessibilitySelectedRows), getAccessibilitySelectedRows, "@@:"); | |||||
| addMethod (@selector (setAccessibilitySelectedRows:), setAccessibilitySelectedRows, "v@:@"); | |||||
| addMethod (@selector (accessibilityColumnCount), getAccessibilityColumnCount, "i@:"); | |||||
| addMethod (@selector (accessibilityColumns), getAccessibilityColumns, "@@:"); | |||||
| addMethod (@selector (accessibilitySelectedColumns), getAccessibilitySelectedColumns, "@@:"); | |||||
| addMethod (@selector (setAccessibilitySelectedColumns:), setAccessibilitySelectedColumns, "v@:@"); | |||||
| addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange, @encode (NSRange), "@:"); | addMethod (@selector (accessibilityRowIndexRange), getAccessibilityRowIndexRange, @encode (NSRange), "@:"); | ||||
| addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange, @encode (NSRange), "@:"); | addMethod (@selector (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange, @encode (NSRange), "@:"); | ||||
| addMethod (@selector (accessibilityIndex), getAccessibilityIndex, "i@:"); | addMethod (@selector (accessibilityIndex), getAccessibilityIndex, "i@:"); | ||||
| addMethod (@selector (accessibilityDisclosureLevel), getAccessibilityDisclosureLevel, "i@:"); | addMethod (@selector (accessibilityDisclosureLevel), getAccessibilityDisclosureLevel, "i@:"); | ||||
| addMethod (@selector (isAccessibilityExpanded), getIsAccessibilityExpanded, "c@:"); | |||||
| addMethod (@selector (accessibilityPerformIncrement), accessibilityPerformIncrement, "c@:"); | addMethod (@selector (accessibilityPerformIncrement), accessibilityPerformIncrement, "c@:"); | ||||
| addMethod (@selector (accessibilityPerformDecrement), accessibilityPerformDecrement, "c@:"); | addMethod (@selector (accessibilityPerformDecrement), accessibilityPerformDecrement, "c@:"); | ||||
| addMethod (@selector (accessibilityPerformDelete), accessibilityPerformDelete, "c@:"); | addMethod (@selector (accessibilityPerformDelete), accessibilityPerformDelete, "c@:"); | ||||
| addMethod (@selector (accessibilityPerformPress), accessibilityPerformPress, "c@:"); | addMethod (@selector (accessibilityPerformPress), accessibilityPerformPress, "c@:"); | ||||
| addMethod (@selector (accessibilityPerformRaise), accessibilityPerformRaise, "c@:"); | |||||
| addMethod (@selector (accessibilityPerformShowMenu), accessibilityPerformShowMenu, "c@:"); | addMethod (@selector (accessibilityPerformShowMenu), accessibilityPerformShowMenu, "c@:"); | ||||
| addMethod (@selector (accessibilityPerformPick), accessibilityPerformPick, "c@:"); | |||||
| addMethod (@selector (accessibilityPerformRaise), accessibilityPerformRaise, "c@:"); | |||||
| addMethod (@selector (isAccessibilitySelectorAllowed:), getIsAccessibilitySelectorAllowed, "c@:@"); | addMethod (@selector (isAccessibilitySelectorAllowed:), getIsAccessibilitySelectorAllowed, "c@:@"); | ||||
| @@ -172,18 +176,49 @@ private: | |||||
| return role == AccessibilityRole::staticText; | return role == AccessibilityRole::staticText; | ||||
| } | } | ||||
| static bool hasSelection (AccessibilityRole role) noexcept | |||||
| static bool isSelectable (AccessibleState state) noexcept | |||||
| { | |||||
| return state.isSelectable() || state.isMultiSelectable(); | |||||
| } | |||||
| static NSArray* getSelectedChildren (NSArray* children) | |||||
| { | { | ||||
| return role == AccessibilityRole::list | |||||
| || role == AccessibilityRole::popupMenu | |||||
| || role == AccessibilityRole::tree; | |||||
| NSMutableArray* selected = [[NSMutableArray new] autorelease]; | |||||
| for (id child in children) | |||||
| { | |||||
| if (auto* handler = getHandler (child)) | |||||
| { | |||||
| const auto currentState = handler->getCurrentState(); | |||||
| if (isSelectable (currentState) && currentState.isSelected()) | |||||
| [selected addObject: child]; | |||||
| } | |||||
| } | |||||
| return selected; | |||||
| } | } | ||||
| static bool isSelectable (AccessibilityRole role) noexcept | |||||
| static void setSelectedChildren (NSArray* children, NSArray* selected) | |||||
| { | { | ||||
| return role == AccessibilityRole::listItem | |||||
| || role == AccessibilityRole::menuItem | |||||
| || role == AccessibilityRole::treeItem; | |||||
| for (id child in children) | |||||
| { | |||||
| if (auto* handler = getHandler (child)) | |||||
| { | |||||
| const auto currentState = handler->getCurrentState(); | |||||
| const BOOL isSelected = [selected containsObject: child]; | |||||
| if (isSelectable (currentState)) | |||||
| { | |||||
| if (currentState.isSelected() != isSelected) | |||||
| handler->getActions().invoke (AccessibilityActionType::toggle); | |||||
| } | |||||
| else if (currentState.isFocusable()) | |||||
| { | |||||
| [child setAccessibilityFocused: isSelected]; | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } | ||||
| static BOOL performActionIfSupported (id self, AccessibilityActionType actionType) | static BOOL performActionIfSupported (id self, AccessibilityActionType actionType) | ||||
| @@ -293,40 +328,12 @@ private: | |||||
| static NSArray* getAccessibilitySelectedChildren (id self, SEL) | static NSArray* getAccessibilitySelectedChildren (id self, SEL) | ||||
| { | { | ||||
| if (auto* handler = getHandler (self)) | |||||
| { | |||||
| NSMutableArray* selected = [[NSMutableArray new] autorelease]; | |||||
| for (auto* child : handler->getChildren()) | |||||
| if (isSelectable (child->getRole()) && child->getCurrentState().isSelected()) | |||||
| [selected addObject: (id) child->getNativeImplementation()]; | |||||
| return selected; | |||||
| } | |||||
| return nil; | |||||
| return getSelectedChildren ([self accessibilityChildren]); | |||||
| } | } | ||||
| static void setAccessibilitySelectedChildren (id self, SEL, NSArray* selected) | static void setAccessibilitySelectedChildren (id self, SEL, NSArray* selected) | ||||
| { | { | ||||
| if (auto* handler = getHandler (self)) | |||||
| { | |||||
| for (auto* childHandler : handler->getChildren()) | |||||
| { | |||||
| id nativeHandler = (id) childHandler->getNativeImplementation(); | |||||
| const auto isSelected = [selected containsObject: nativeHandler]; | |||||
| if (isSelectable (childHandler->getRole())) | |||||
| { | |||||
| if (childHandler->getCurrentState().isSelected() != isSelected) | |||||
| childHandler->getActions().invoke (AccessibilityActionType::toggle); | |||||
| } | |||||
| else if (childHandler->getCurrentState().isFocusable()) | |||||
| { | |||||
| [nativeHandler setAccessibilityFocused: isSelected]; | |||||
| } | |||||
| } | |||||
| } | |||||
| setSelectedChildren ([self accessibilityChildren], selected); | |||||
| } | } | ||||
| static NSAccessibilityOrientation getAccessibilityOrientation (id self, SEL) | static NSAccessibilityOrientation getAccessibilityOrientation (id self, SEL) | ||||
| @@ -349,9 +356,18 @@ private: | |||||
| if (auto* handler = getHandler (self)) | if (auto* handler = getHandler (self)) | ||||
| { | { | ||||
| if (focused) | if (focused) | ||||
| handler->grabFocus(); | |||||
| { | |||||
| const WeakReference<Component> safeComponent (&handler->getComponent()); | |||||
| performActionIfSupported (self, AccessibilityActionType::focus); | |||||
| if (safeComponent != nullptr) | |||||
| handler->grabFocus(); | |||||
| } | |||||
| else | else | ||||
| { | |||||
| handler->giveAwayFocus(); | handler->giveAwayFocus(); | ||||
| } | |||||
| } | } | ||||
| } | } | ||||
| @@ -387,16 +403,16 @@ private: | |||||
| case AccessibilityRole::editableText: return NSAccessibilityTextAreaRole; | case AccessibilityRole::editableText: return NSAccessibilityTextAreaRole; | ||||
| case AccessibilityRole::menuItem: return NSAccessibilityMenuItemRole; | case AccessibilityRole::menuItem: return NSAccessibilityMenuItemRole; | ||||
| case AccessibilityRole::menuBar: return NSAccessibilityMenuRole; | case AccessibilityRole::menuBar: return NSAccessibilityMenuRole; | ||||
| case AccessibilityRole::popupMenu: return NSAccessibilityMenuRole; | |||||
| case AccessibilityRole::table: return NSAccessibilityTableRole; | |||||
| case AccessibilityRole::popupMenu: return NSAccessibilityWindowRole; | |||||
| case AccessibilityRole::table: return NSAccessibilityListRole; | |||||
| case AccessibilityRole::tableHeader: return NSAccessibilityGroupRole; | case AccessibilityRole::tableHeader: return NSAccessibilityGroupRole; | ||||
| case AccessibilityRole::column: return NSAccessibilityColumnRole; | case AccessibilityRole::column: return NSAccessibilityColumnRole; | ||||
| case AccessibilityRole::row: return NSAccessibilityRowRole; | case AccessibilityRole::row: return NSAccessibilityRowRole; | ||||
| case AccessibilityRole::cell: return NSAccessibilityCellRole; | case AccessibilityRole::cell: return NSAccessibilityCellRole; | ||||
| case AccessibilityRole::hyperlink: return NSAccessibilityLinkRole; | case AccessibilityRole::hyperlink: return NSAccessibilityLinkRole; | ||||
| case AccessibilityRole::list: return NSAccessibilityListRole; | |||||
| case AccessibilityRole::list: return NSAccessibilityOutlineRole; | |||||
| case AccessibilityRole::listItem: return NSAccessibilityRowRole; | case AccessibilityRole::listItem: return NSAccessibilityRowRole; | ||||
| case AccessibilityRole::tree: return NSAccessibilityListRole; | |||||
| case AccessibilityRole::tree: return NSAccessibilityOutlineRole; | |||||
| case AccessibilityRole::treeItem: return NSAccessibilityRowRole; | case AccessibilityRole::treeItem: return NSAccessibilityRowRole; | ||||
| case AccessibilityRole::progressBar: return NSAccessibilityProgressIndicatorRole; | case AccessibilityRole::progressBar: return NSAccessibilityProgressIndicatorRole; | ||||
| case AccessibilityRole::group: return NSAccessibilityGroupRole; | case AccessibilityRole::group: return NSAccessibilityGroupRole; | ||||
| @@ -431,7 +447,8 @@ private: | |||||
| if (role == AccessibilityRole::tooltip | if (role == AccessibilityRole::tooltip | ||||
| || role == AccessibilityRole::splashScreen) return NSAccessibilityFloatingWindowSubrole; | || role == AccessibilityRole::splashScreen) return NSAccessibilityFloatingWindowSubrole; | ||||
| if (role == AccessibilityRole::toggleButton) return NSAccessibilityToggleSubrole; | if (role == AccessibilityRole::toggleButton) return NSAccessibilityToggleSubrole; | ||||
| if (role == AccessibilityRole::treeItem) return NSAccessibilityOutlineRowSubrole; | |||||
| if (role == AccessibilityRole::treeItem | |||||
| || role == AccessibilityRole::listItem) return NSAccessibilityOutlineRowSubrole; | |||||
| if (role == AccessibilityRole::row && getCellInterface (self) != nullptr) return NSAccessibilityTableRowSubrole; | if (role == AccessibilityRole::row && getCellInterface (self) != nullptr) return NSAccessibilityTableRowSubrole; | ||||
| const auto& component = handler->getComponent(); | const auto& component = handler->getComponent(); | ||||
| @@ -701,13 +718,36 @@ private: | |||||
| NSMutableArray* rows = [[NSMutableArray new] autorelease]; | NSMutableArray* rows = [[NSMutableArray new] autorelease]; | ||||
| if (auto* tableInterface = getTableInterface (self)) | if (auto* tableInterface = getTableInterface (self)) | ||||
| { | |||||
| for (int row = 0; row < tableInterface->getNumRows(); ++row) | for (int row = 0; row < tableInterface->getNumRows(); ++row) | ||||
| { | |||||
| if (auto* handler = tableInterface->getCellHandler (row, 0)) | if (auto* handler = tableInterface->getCellHandler (row, 0)) | ||||
| { | |||||
| [rows addObject: (id) handler->getNativeImplementation()]; | [rows addObject: (id) handler->getNativeImplementation()]; | ||||
| } | |||||
| else | |||||
| { | |||||
| [rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole | |||||
| frame: NSZeroRect | |||||
| label: @"Offscreen Row" | |||||
| parent: self]]; | |||||
| } | |||||
| } | |||||
| } | |||||
| return rows; | return rows; | ||||
| } | } | ||||
| static NSArray* getAccessibilitySelectedRows (id self, SEL) | |||||
| { | |||||
| return getSelectedChildren ([self accessibilityRows]); | |||||
| } | |||||
| static void setAccessibilitySelectedRows (id self, SEL, NSArray* selected) | |||||
| { | |||||
| setSelectedChildren ([self accessibilityRows], selected); | |||||
| } | |||||
| static NSInteger getAccessibilityColumnCount (id self, SEL) | static NSInteger getAccessibilityColumnCount (id self, SEL) | ||||
| { | { | ||||
| if (auto* tableInterface = getTableInterface (self)) | if (auto* tableInterface = getTableInterface (self)) | ||||
| @@ -721,13 +761,36 @@ private: | |||||
| NSMutableArray* columns = [[NSMutableArray new] autorelease]; | NSMutableArray* columns = [[NSMutableArray new] autorelease]; | ||||
| if (auto* tableInterface = getTableInterface (self)) | if (auto* tableInterface = getTableInterface (self)) | ||||
| { | |||||
| for (int column = 0; column < tableInterface->getNumColumns(); ++column) | for (int column = 0; column < tableInterface->getNumColumns(); ++column) | ||||
| { | |||||
| if (auto* handler = tableInterface->getCellHandler (0, column)) | if (auto* handler = tableInterface->getCellHandler (0, column)) | ||||
| { | |||||
| [columns addObject: (id) handler->getNativeImplementation()]; | [columns addObject: (id) handler->getNativeImplementation()]; | ||||
| } | |||||
| else | |||||
| { | |||||
| [columns addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityColumnRole | |||||
| frame: NSZeroRect | |||||
| label: @"Offscreen Column" | |||||
| parent: self]]; | |||||
| } | |||||
| } | |||||
| } | |||||
| return columns; | return columns; | ||||
| } | } | ||||
| static NSArray* getAccessibilitySelectedColumns (id self, SEL) | |||||
| { | |||||
| return getSelectedChildren ([self accessibilityColumns]); | |||||
| } | |||||
| static void setAccessibilitySelectedColumns (id self, SEL, NSArray* selected) | |||||
| { | |||||
| setSelectedChildren ([self accessibilityColumns], selected); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| static NSRange getAccessibilityRowIndexRange (id self, SEL) | static NSRange getAccessibilityRowIndexRange (id self, SEL) | ||||
| { | { | ||||
| @@ -775,25 +838,20 @@ private: | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| //============================================================================== | |||||
| static BOOL accessibilityPerformPress (id self, SEL) { return performActionIfSupported (self, AccessibilityActionType::press); } | |||||
| static BOOL accessibilityPerformShowMenu (id self, SEL) { return performActionIfSupported (self, AccessibilityActionType::showMenu); } | |||||
| static BOOL accessibilityPerformPick (id self, SEL) { return [self accessibilityPerformPress]; } | |||||
| static BOOL accessibilityPerformRaise (id self, SEL) | |||||
| static BOOL getIsAccessibilityExpanded (id self, SEL) | |||||
| { | { | ||||
| if (auto* handler = getHandler (self)) | if (auto* handler = getHandler (self)) | ||||
| { | |||||
| if (handler->getCurrentState().isFocusable()) | |||||
| { | |||||
| handler->grabFocus(); | |||||
| return YES; | |||||
| } | |||||
| } | |||||
| return handler->getCurrentState().isExpanded(); | |||||
| return NO; | return NO; | ||||
| } | } | ||||
| //============================================================================== | |||||
| static BOOL accessibilityPerformPress (id self, SEL) { return performActionIfSupported (self, AccessibilityActionType::press); } | |||||
| static BOOL accessibilityPerformShowMenu (id self, SEL) { return performActionIfSupported (self, AccessibilityActionType::showMenu); } | |||||
| static BOOL accessibilityPerformRaise (id self, SEL) { [self setAccessibilityFocused: YES]; return YES; } | |||||
| static BOOL accessibilityPerformIncrement (id self, SEL) | static BOOL accessibilityPerformIncrement (id self, SEL) | ||||
| { | { | ||||
| if (auto* valueInterface = getValueInterface (self)) | if (auto* valueInterface = getValueInterface (self)) | ||||
| @@ -864,6 +922,9 @@ private: | |||||
| { | { | ||||
| if (auto* handler = getHandler (self)) | if (auto* handler = getHandler (self)) | ||||
| { | { | ||||
| const auto role = handler->getRole(); | |||||
| const auto currentState = handler->getCurrentState(); | |||||
| for (auto textSelector : { @selector (accessibilityInsertionPointLineNumber), | for (auto textSelector : { @selector (accessibilityInsertionPointLineNumber), | ||||
| @selector (accessibilitySharedCharacterRange), | @selector (accessibilitySharedCharacterRange), | ||||
| @selector (accessibilitySharedTextUIElements), | @selector (accessibilitySharedTextUIElements), | ||||
| @@ -889,8 +950,10 @@ private: | |||||
| for (auto tableSelector : { @selector (accessibilityRowCount), | for (auto tableSelector : { @selector (accessibilityRowCount), | ||||
| @selector (accessibilityRows), | @selector (accessibilityRows), | ||||
| @selector (accessibilitySelectedRows), | |||||
| @selector (accessibilityColumnCount), | @selector (accessibilityColumnCount), | ||||
| @selector (accessibilityColumns) }) | |||||
| @selector (accessibilityColumns), | |||||
| @selector (accessibilitySelectedColumns) }) | |||||
| { | { | ||||
| if (selector == tableSelector) | if (selector == tableSelector) | ||||
| return handler->getTableInterface() != nullptr; | return handler->getTableInterface() != nullptr; | ||||
| @@ -919,8 +982,8 @@ private: | |||||
| if (selector == @selector (accessibilityValue)) | if (selector == @selector (accessibilityValue)) | ||||
| return valueInterface != nullptr | return valueInterface != nullptr | ||||
| || hasEditableText (*handler) | || hasEditableText (*handler) | ||||
| || nameIsAccessibilityValue (handler->getRole()) | |||||
| || handler->getCurrentState().isCheckable(); | |||||
| || nameIsAccessibilityValue (role) | |||||
| || currentState.isCheckable(); | |||||
| auto hasEditableValue = [valueInterface] { return valueInterface != nullptr && ! valueInterface->isReadOnly(); }; | auto hasEditableValue = [valueInterface] { return valueInterface != nullptr && ! valueInterface->isReadOnly(); }; | ||||
| @@ -937,32 +1000,35 @@ private: | |||||
| return NO; | return NO; | ||||
| } | } | ||||
| for (auto actionSelector : { @selector (accessibilityPerformPick), | |||||
| @selector (accessibilityPerformPress), | |||||
| @selector (accessibilityPerformRaise), | |||||
| for (auto actionSelector : { @selector (accessibilityPerformPress), | |||||
| @selector (accessibilityPerformShowMenu), | @selector (accessibilityPerformShowMenu), | ||||
| @selector (accessibilityPerformRaise), | |||||
| @selector (setAccessibilityFocused:) }) | @selector (setAccessibilityFocused:) }) | ||||
| { | { | ||||
| if (selector != actionSelector) | if (selector != actionSelector) | ||||
| continue; | continue; | ||||
| if (selector == @selector (accessibilityPerformPick) | |||||
| || selector == @selector (accessibilityPerformPress)) | |||||
| if (selector == @selector (accessibilityPerformPress)) | |||||
| return handler->getActions().contains (AccessibilityActionType::press); | return handler->getActions().contains (AccessibilityActionType::press); | ||||
| if (selector == @selector (accessibilityPerformRaise) | |||||
| || selector == @selector (setAccessibilityFocused:)) | |||||
| return handler->getCurrentState().isFocusable(); | |||||
| if (selector == @selector (accessibilityPerformShowMenu)) | if (selector == @selector (accessibilityPerformShowMenu)) | ||||
| return handler->getActions().contains (AccessibilityActionType::showMenu); | return handler->getActions().contains (AccessibilityActionType::showMenu); | ||||
| if (selector == @selector (accessibilityPerformRaise)) | |||||
| return [[self accessibilityRole] isEqual: NSAccessibilityWindowRole]; | |||||
| if (selector == @selector (setAccessibilityFocused:)) | |||||
| return currentState.isFocusable(); | |||||
| } | } | ||||
| if (selector == @selector (accessibilitySelectedChildren)) | if (selector == @selector (accessibilitySelectedChildren)) | ||||
| return hasSelection (handler->getRole()); | |||||
| return role == AccessibilityRole::popupMenu; | |||||
| if (selector == @selector (accessibilityOrientation)) | if (selector == @selector (accessibilityOrientation)) | ||||
| return handler->getRole() == AccessibilityRole::scrollBar; | |||||
| return role == AccessibilityRole::scrollBar; | |||||
| if (selector == @selector (isAccessibilityExpanded)) | |||||
| return currentState.isExpandable(); | |||||
| return sendSuperclassMessage<BOOL> (self, @selector (isAccessibilitySelectorAllowed:), selector); | return sendSuperclassMessage<BOOL> (self, @selector (isAccessibilitySelectorAllowed:), selector); | ||||
| } | } | ||||