Browse Source

Accessibility: Improved macOS support for offscreen rows/cells

v6.1.6
ed 4 years ago
parent
commit
c359f99fb5
1 changed files with 143 additions and 77 deletions
  1. +143
    -77
      modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm

+ 143
- 77
modules/juce_gui_basics/native/accessibility/juce_mac_Accessibility.mm View File

@@ -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);
} }


Loading…
Cancel
Save