Improve macos tabs accessbility support
Map PageTabList controls (i.e. QTabBar) to the TabGroup role, and report the list of PageTabs as the tabs. Add a predicate-argument to the unignoredChildren() function so that we can re-use that logic to return the list of tabs, while also ignoring otherwise accessible children, such as the scroll buttons. Change-Id: I57472e9213dd178e018449e542de276051097073 Fixes: QTBUG-134807 Pick-to: 6.9 6.8 Reviewed-by: Michael Weghorn <m.weghorn@posteo.de> Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
parent
420db269fe
commit
0475bc57d6
@ -12,6 +12,8 @@
|
||||
|
||||
#include "qcocoaaccessibilityelement.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
class QCocoaAccessibility : public QPlatformAccessibility
|
||||
@ -49,7 +51,9 @@ namespace QCocoaAccessible {
|
||||
NSString *macRole(QAccessibleInterface *interface);
|
||||
NSString *macSubrole(QAccessibleInterface *interface);
|
||||
bool shouldBeIgnored(QAccessibleInterface *interface);
|
||||
NSArray<QMacAccessibilityElement *> *unignoredChildren(QAccessibleInterface *interface);
|
||||
bool defaultUnignored(QAccessibleInterface *interface);
|
||||
NSArray<QMacAccessibilityElement *> *unignoredChildren(QAccessibleInterface *interface,
|
||||
const std::function<bool(QAccessibleInterface *)> &p = defaultUnignored);
|
||||
NSString *getTranslatedAction(const QString &qtAction);
|
||||
QString translateAction(NSString *nsAction, QAccessibleInterface *interface);
|
||||
bool hasValueAttribute(QAccessibleInterface *interface);
|
||||
|
@ -122,6 +122,7 @@ static void populateRoleMap()
|
||||
roleMap[QAccessible::Separator] = NSAccessibilitySplitterRole;
|
||||
roleMap[QAccessible::ToolBar] = NSAccessibilityToolbarRole;
|
||||
roleMap[QAccessible::PageTab] = NSAccessibilityRadioButtonRole;
|
||||
roleMap[QAccessible::PageTabList] = NSAccessibilityTabGroupRole;
|
||||
roleMap[QAccessible::ButtonMenu] = NSAccessibilityMenuButtonRole;
|
||||
roleMap[QAccessible::ButtonDropDown] = NSAccessibilityPopUpButtonRole;
|
||||
roleMap[QAccessible::SpinBox] = NSAccessibilityIncrementorRole;
|
||||
@ -200,6 +201,8 @@ NSString *macSubrole(QAccessibleInterface *interface)
|
||||
return NSAccessibilitySearchFieldSubrole;
|
||||
if (s.passwordEdit)
|
||||
return NSAccessibilitySecureTextFieldSubrole;
|
||||
if (interface->role() == QAccessible::PageTab)
|
||||
return NSAccessibilityTabButtonSubrole;
|
||||
return nil;
|
||||
}
|
||||
|
||||
@ -249,14 +252,25 @@ bool shouldBeIgnored(QAccessibleInterface *interface)
|
||||
return false;
|
||||
}
|
||||
|
||||
NSArray<QMacAccessibilityElement *> *unignoredChildren(QAccessibleInterface *interface)
|
||||
bool defaultUnignored(QAccessibleInterface *child)
|
||||
{
|
||||
if (child && child->isValid()) {
|
||||
const auto state = child->state();
|
||||
return !state.invalid && !state.invisible;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
NSArray<QMacAccessibilityElement *> *unignoredChildren(QAccessibleInterface *interface,
|
||||
const std::function<bool(QAccessibleInterface *child)> &pred)
|
||||
{
|
||||
int numKids = interface->childCount();
|
||||
|
||||
NSMutableArray<QMacAccessibilityElement *> *kids = [NSMutableArray<QMacAccessibilityElement *> arrayWithCapacity:numKids];
|
||||
for (int i = 0; i < numKids; ++i) {
|
||||
QAccessibleInterface *child = interface->child(i);
|
||||
if (!child || !child->isValid() || child->state().invalid || child->state().invisible)
|
||||
|
||||
if (!pred(child))
|
||||
continue;
|
||||
|
||||
QAccessible::Id childId = QAccessible::uniqueId(child);
|
||||
@ -269,6 +283,7 @@ NSArray<QMacAccessibilityElement *> *unignoredChildren(QAccessibleInterface *int
|
||||
}
|
||||
return NSAccessibilityUnignoredChildren(kids);
|
||||
}
|
||||
|
||||
/*
|
||||
Translates a predefined QAccessibleActionInterface action to a Mac action constant.
|
||||
Returns 0 if the Qt Action has no mac equivalent. Ownership of the NSString is
|
||||
|
@ -1093,6 +1093,19 @@ static void convertLineOffset(QAccessibleTextInterface *text, int *line, int *of
|
||||
return nil;
|
||||
}
|
||||
|
||||
// tabs
|
||||
|
||||
- (NSArray *) accessibilityTabs {
|
||||
QAccessibleInterface *iface = self.qtInterface;
|
||||
if (iface && iface->role() == QAccessible::PageTabList) {
|
||||
return QCocoaAccessible::unignoredChildren(iface, [](QAccessibleInterface *child){
|
||||
return QCocoaAccessible::defaultUnignored(child)
|
||||
&& child->role() == QAccessible::PageTab;
|
||||
});
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#endif // QT_CONFIG(accessibility)
|
||||
|
@ -66,6 +66,7 @@ QDebug operator<<(QDebug dbg, AXErrorTag err)
|
||||
bool axError;
|
||||
}
|
||||
@property (readonly) NSString *role;
|
||||
@property (readonly) NSString *roleDescription;
|
||||
@property (readonly) NSString *title;
|
||||
@property (readonly) NSString *description;
|
||||
@property (readonly) NSString *value;
|
||||
@ -153,6 +154,21 @@ QDebug operator<<(QDebug dbg, AXErrorTag err)
|
||||
return arr;
|
||||
}
|
||||
|
||||
- (NSArray *)tabs
|
||||
{
|
||||
NSArray *arr;
|
||||
AXError err;
|
||||
|
||||
if (kAXErrorSuccess != (err = AXUIElementCopyAttributeValues(reference, kAXTabsAttribute,
|
||||
0, 100, /*min, max*/
|
||||
(CFArrayRef *) &arr))) {
|
||||
axError = true;
|
||||
qDebug() << "AXUIElementCopyAttributeValue(kAXTabsAttribute) returned error = "
|
||||
<< AXErrorTag(err) << "with reference" << reference;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
- (AXUIElementRef) findDirectChildByRole: (CFStringRef) role
|
||||
{
|
||||
TestAXObject *result = nil;
|
||||
@ -353,6 +369,7 @@ QDebug operator<<(QDebug dbg, AXErrorTag err)
|
||||
|
||||
- (bool) valid { return reference != nil; }
|
||||
- (NSString*) role { return [self _stringAttributeValue:kAXRoleAttribute]; }
|
||||
- (NSString*) roleDescription { return [self _stringAttributeValue:kAXRoleDescriptionAttribute]; }
|
||||
- (NSString*) title { return [self _stringAttributeValue:kAXTitleAttribute]; }
|
||||
- (NSString*) description { return [self _stringAttributeValue:kAXDescriptionAttribute]; }
|
||||
- (NSString*) value { return [self _stringAttributeValue:kAXValueAttribute]; }
|
||||
@ -431,6 +448,7 @@ private Q_SLOTS:
|
||||
void checkBoxTest();
|
||||
void tableViewTest();
|
||||
void treeViewTest();
|
||||
void tabBarTest();
|
||||
|
||||
private:
|
||||
AccessibleTestWindow *m_window;
|
||||
@ -863,5 +881,57 @@ void tst_QAccessibilityMac::treeViewTest()
|
||||
[window release];
|
||||
}
|
||||
|
||||
void tst_QAccessibilityMac::tabBarTest()
|
||||
{
|
||||
QTabBar *tbar = new QTabBar;
|
||||
static const unsigned int nTabs = 20;
|
||||
for (unsigned int i = 0; i < nTabs; ++i)
|
||||
tbar->addTab(QString::number(i));
|
||||
tbar->setUsesScrollButtons(true);
|
||||
|
||||
m_window->addWidget(tbar);
|
||||
QVERIFY(QTest::qWaitForWindowExposed(m_window));
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
TestAXObject *appObject = [TestAXObject getApplicationAXObject];
|
||||
QVERIFY(appObject);
|
||||
|
||||
NSArray *windowList = [appObject windowList];
|
||||
// one window
|
||||
QVERIFY([windowList count] == 1);
|
||||
AXUIElementRef windowRef = (AXUIElementRef)[windowList objectAtIndex:0];
|
||||
QVERIFY(windowRef != nil);
|
||||
TestAXObject *window = [[TestAXObject alloc] initWithAXUIElementRef:windowRef];
|
||||
QVERIFY(window.valid);
|
||||
|
||||
// children of window
|
||||
AXUIElementRef axTarBar = [window findDirectChildByRole:kAXTabGroupRole];
|
||||
QVERIFY(axTarBar != nil);
|
||||
|
||||
TestAXObject *tb = [[TestAXObject alloc] initWithAXUIElementRef:axTarBar];
|
||||
QVERIFY(tb.valid);
|
||||
|
||||
[appObject release];
|
||||
[window release];
|
||||
|
||||
NSArray *tbChildList = [tb childList];
|
||||
// +2 because of the scroll buttons
|
||||
QCOMPARE([tbChildList count], nTabs + 2);
|
||||
|
||||
NSArray *tbTabsList = [tb tabs];
|
||||
QCOMPARE([tbTabsList count], nTabs);
|
||||
|
||||
for (unsigned int i = 0; i < nTabs; ++i) {
|
||||
AXUIElementRef axTab = (AXUIElementRef)[tbTabsList objectAtIndex:i];
|
||||
QVERIFY(axTab != nil);
|
||||
|
||||
TestAXObject *tab = [[TestAXObject alloc] initWithAXUIElementRef:axTab];
|
||||
QVERIFY(tab.valid);
|
||||
QCOMPARE(QString::fromNSString(tab.role), QString::fromCFString(kAXRadioButtonRole));
|
||||
QCOMPARE(QString::fromNSString(tab.title), QString::number(i));
|
||||
QCOMPARE(QString::fromNSString(tab.roleDescription), "tab");
|
||||
}
|
||||
}
|
||||
|
||||
QTEST_MAIN(tst_QAccessibilityMac)
|
||||
#include "tst_qaccessibilitymac.moc"
|
||||
|
Loading…
x
Reference in New Issue
Block a user