diff --git a/modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm b/modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm index 408e531452..9bd38e7891 100644 --- a/modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm +++ b/modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm @@ -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 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 (self, @selector (isAccessibilitySelectorAllowed:), selector); }