| @@ -117,23 +117,27 @@ private: | |||
| addMethod (@selector (accessibilityLineForIndex:), getAccessibilityLineForIndex, "i@:i"); | |||
| 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 (accessibilityColumnIndexRange), getAccessibilityColumnIndexRange, @encode (NSRange), "@:"); | |||
| addMethod (@selector (accessibilityIndex), getAccessibilityIndex, "i@:"); | |||
| addMethod (@selector (accessibilityDisclosureLevel), getAccessibilityDisclosureLevel, "i@:"); | |||
| addMethod (@selector (isAccessibilityExpanded), getIsAccessibilityExpanded, "c@:"); | |||
| addMethod (@selector (accessibilityPerformIncrement), accessibilityPerformIncrement, "c@:"); | |||
| addMethod (@selector (accessibilityPerformDecrement), accessibilityPerformDecrement, "c@:"); | |||
| addMethod (@selector (accessibilityPerformDelete), accessibilityPerformDelete, "c@:"); | |||
| addMethod (@selector (accessibilityPerformPress), accessibilityPerformPress, "c@:"); | |||
| addMethod (@selector (accessibilityPerformRaise), accessibilityPerformRaise, "c@:"); | |||
| addMethod (@selector (accessibilityPerformShowMenu), accessibilityPerformShowMenu, "c@:"); | |||
| addMethod (@selector (accessibilityPerformPick), accessibilityPerformPick, "c@:"); | |||
| addMethod (@selector (accessibilityPerformRaise), accessibilityPerformRaise, "c@:"); | |||
| addMethod (@selector (isAccessibilitySelectorAllowed:), getIsAccessibilitySelectorAllowed, "c@:@"); | |||
| @@ -172,18 +176,49 @@ private: | |||
| 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) | |||
| @@ -293,40 +328,12 @@ private: | |||
| 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) | |||
| { | |||
| 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) | |||
| @@ -349,9 +356,18 @@ private: | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| if (focused) | |||
| handler->grabFocus(); | |||
| { | |||
| const WeakReference<Component> safeComponent (&handler->getComponent()); | |||
| performActionIfSupported (self, AccessibilityActionType::focus); | |||
| if (safeComponent != nullptr) | |||
| handler->grabFocus(); | |||
| } | |||
| else | |||
| { | |||
| handler->giveAwayFocus(); | |||
| } | |||
| } | |||
| } | |||
| @@ -387,16 +403,16 @@ private: | |||
| case AccessibilityRole::editableText: return NSAccessibilityTextAreaRole; | |||
| case AccessibilityRole::menuItem: return NSAccessibilityMenuItemRole; | |||
| 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::column: return NSAccessibilityColumnRole; | |||
| case AccessibilityRole::row: return NSAccessibilityRowRole; | |||
| case AccessibilityRole::cell: return NSAccessibilityCellRole; | |||
| case AccessibilityRole::hyperlink: return NSAccessibilityLinkRole; | |||
| case AccessibilityRole::list: return NSAccessibilityListRole; | |||
| case AccessibilityRole::list: return NSAccessibilityOutlineRole; | |||
| case AccessibilityRole::listItem: return NSAccessibilityRowRole; | |||
| case AccessibilityRole::tree: return NSAccessibilityListRole; | |||
| case AccessibilityRole::tree: return NSAccessibilityOutlineRole; | |||
| case AccessibilityRole::treeItem: return NSAccessibilityRowRole; | |||
| case AccessibilityRole::progressBar: return NSAccessibilityProgressIndicatorRole; | |||
| case AccessibilityRole::group: return NSAccessibilityGroupRole; | |||
| @@ -431,7 +447,8 @@ private: | |||
| if (role == AccessibilityRole::tooltip | |||
| || role == AccessibilityRole::splashScreen) return NSAccessibilityFloatingWindowSubrole; | |||
| 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; | |||
| const auto& component = handler->getComponent(); | |||
| @@ -701,13 +718,36 @@ private: | |||
| NSMutableArray* rows = [[NSMutableArray new] autorelease]; | |||
| if (auto* tableInterface = getTableInterface (self)) | |||
| { | |||
| for (int row = 0; row < tableInterface->getNumRows(); ++row) | |||
| { | |||
| if (auto* handler = tableInterface->getCellHandler (row, 0)) | |||
| { | |||
| [rows addObject: (id) handler->getNativeImplementation()]; | |||
| } | |||
| else | |||
| { | |||
| [rows addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityRowRole | |||
| frame: NSZeroRect | |||
| label: @"Offscreen Row" | |||
| parent: self]]; | |||
| } | |||
| } | |||
| } | |||
| 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) | |||
| { | |||
| if (auto* tableInterface = getTableInterface (self)) | |||
| @@ -721,13 +761,36 @@ private: | |||
| NSMutableArray* columns = [[NSMutableArray new] autorelease]; | |||
| if (auto* tableInterface = getTableInterface (self)) | |||
| { | |||
| for (int column = 0; column < tableInterface->getNumColumns(); ++column) | |||
| { | |||
| if (auto* handler = tableInterface->getCellHandler (0, column)) | |||
| { | |||
| [columns addObject: (id) handler->getNativeImplementation()]; | |||
| } | |||
| else | |||
| { | |||
| [columns addObject: [NSAccessibilityElement accessibilityElementWithRole: NSAccessibilityColumnRole | |||
| frame: NSZeroRect | |||
| label: @"Offscreen Column" | |||
| parent: self]]; | |||
| } | |||
| } | |||
| } | |||
| 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) | |||
| { | |||
| @@ -775,25 +838,20 @@ private: | |||
| 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 (handler->getCurrentState().isFocusable()) | |||
| { | |||
| handler->grabFocus(); | |||
| return YES; | |||
| } | |||
| } | |||
| return handler->getCurrentState().isExpanded(); | |||
| 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) | |||
| { | |||
| if (auto* valueInterface = getValueInterface (self)) | |||
| @@ -864,6 +922,9 @@ private: | |||
| { | |||
| if (auto* handler = getHandler (self)) | |||
| { | |||
| const auto role = handler->getRole(); | |||
| const auto currentState = handler->getCurrentState(); | |||
| for (auto textSelector : { @selector (accessibilityInsertionPointLineNumber), | |||
| @selector (accessibilitySharedCharacterRange), | |||
| @selector (accessibilitySharedTextUIElements), | |||
| @@ -889,8 +950,10 @@ private: | |||
| for (auto tableSelector : { @selector (accessibilityRowCount), | |||
| @selector (accessibilityRows), | |||
| @selector (accessibilitySelectedRows), | |||
| @selector (accessibilityColumnCount), | |||
| @selector (accessibilityColumns) }) | |||
| @selector (accessibilityColumns), | |||
| @selector (accessibilitySelectedColumns) }) | |||
| { | |||
| if (selector == tableSelector) | |||
| return handler->getTableInterface() != nullptr; | |||
| @@ -919,8 +982,8 @@ private: | |||
| if (selector == @selector (accessibilityValue)) | |||
| return valueInterface != nullptr | |||
| || hasEditableText (*handler) | |||
| || nameIsAccessibilityValue (handler->getRole()) | |||
| || handler->getCurrentState().isCheckable(); | |||
| || nameIsAccessibilityValue (role) | |||
| || currentState.isCheckable(); | |||
| auto hasEditableValue = [valueInterface] { return valueInterface != nullptr && ! valueInterface->isReadOnly(); }; | |||
| @@ -937,32 +1000,35 @@ private: | |||
| return NO; | |||
| } | |||
| for (auto actionSelector : { @selector (accessibilityPerformPick), | |||
| @selector (accessibilityPerformPress), | |||
| @selector (accessibilityPerformRaise), | |||
| for (auto actionSelector : { @selector (accessibilityPerformPress), | |||
| @selector (accessibilityPerformShowMenu), | |||
| @selector (accessibilityPerformRaise), | |||
| @selector (setAccessibilityFocused:) }) | |||
| { | |||
| if (selector != actionSelector) | |||
| continue; | |||
| if (selector == @selector (accessibilityPerformPick) | |||
| || selector == @selector (accessibilityPerformPress)) | |||
| if (selector == @selector (accessibilityPerformPress)) | |||
| return handler->getActions().contains (AccessibilityActionType::press); | |||
| if (selector == @selector (accessibilityPerformRaise) | |||
| || selector == @selector (setAccessibilityFocused:)) | |||
| return handler->getCurrentState().isFocusable(); | |||
| if (selector == @selector (accessibilityPerformShowMenu)) | |||
| 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)) | |||
| return hasSelection (handler->getRole()); | |||
| return role == AccessibilityRole::popupMenu; | |||
| 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); | |||
| } | |||