diff options
author | Boris Dušek <me@dusek.me> | 2015-03-17 23:10:07 +0100 |
---|---|---|
committer | Boris Dušek <me@dusek.me> | 2015-03-25 09:48:43 +0000 |
commit | 76c94be4e77bfef6ee3642cb175ff34ba6c694db (patch) | |
tree | db08527e89cdb89a64505d2772160347942c8b4e | |
parent | c37f2e8e45a48ed3912ecaf3d69ccd71691fd2b0 (diff) |
OS X Accessibility: Make checkboxes etc. checkable with VoiceOver
NSAccessibility has no explicit analog for
QAccessibleActionInterface::toggleAction(), checking checkboxes/radio
buttons is handled by NSAccessibilityPressAction. So ensure exposing
the action properly on OS X so that VoiceOver users can check/uncheck
checkboxes, select radio buttons etc.
Change-Id: Idc8b048de2313a3e875a929516baf3dded9c68cc
Task-number: QTBUG-44852
Reviewed-by: Jan Arve Sæther <jan-arve.saether@theqtcompany.com>
6 files changed, 85 insertions, 6 deletions
diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.h b/src/plugins/platforms/cocoa/qcocoaaccessibility.h index d232298396..061dfac156 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibility.h +++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.h @@ -77,7 +77,7 @@ bool shouldBeIgnored(QAccessibleInterface *interface); NSArray *unignoredChildren(QAccessibleInterface *interface); NSString *getTranslatedAction(const QString &qtAction); NSMutableArray *createTranslatedActionsList(const QStringList &qtActions); -QString translateAction(NSString *nsAction); +QString translateAction(NSString *nsAction, QAccessibleInterface *interface); bool hasValueAttribute(QAccessibleInterface *interface); id getValueAttribute(QAccessibleInterface *interface); diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm index 0107d7a673..03f585d19d 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibility.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibility.mm @@ -284,6 +284,8 @@ NSString *getTranslatedAction(const QString &qtAction) return NSAccessibilityShowMenuAction; else if (qtAction == QAccessibleActionInterface::setFocusAction()) // Not 100% sure on this one return NSAccessibilityRaiseAction; + else if (qtAction == QAccessibleActionInterface::toggleAction()) + return NSAccessibilityPressAction; // Not translated: // @@ -305,11 +307,13 @@ NSString *getTranslatedAction(const QString &qtAction) Translates between a Mac action constant and a QAccessibleActionInterface action Returns an empty QString if there is no Qt predefined equivalent. */ -QString translateAction(NSString *nsAction) +QString translateAction(NSString *nsAction, QAccessibleInterface *interface) { - if ([nsAction compare: NSAccessibilityPressAction] == NSOrderedSame) + if ([nsAction compare: NSAccessibilityPressAction] == NSOrderedSame) { + if (interface->role() == QAccessible::CheckBox || interface->role() == QAccessible::RadioButton) + return QAccessibleActionInterface::toggleAction(); return QAccessibleActionInterface::pressAction(); - else if ([nsAction compare: NSAccessibilityIncrementAction] == NSOrderedSame) + } else if ([nsAction compare: NSAccessibilityIncrementAction] == NSOrderedSame) return QAccessibleActionInterface::increaseAction(); else if ([nsAction compare: NSAccessibilityDecrementAction] == NSOrderedSame) return QAccessibleActionInterface::decreaseAction(); diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm index cf82c9cb37..84d60df3ef 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm @@ -501,7 +501,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int &line, int &of QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); if (!iface) return nil; // FIXME is that the right return type?? - QString qtAction = QCocoaAccessible::translateAction(action); + QString qtAction = QCocoaAccessible::translateAction(action, iface); QString description; // Return a description from the action interface if this action is not known to the OS. if (qtAction.isEmpty()) { @@ -518,7 +518,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int &line, int &of - (void)accessibilityPerformAction:(NSString *)action { QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); if (iface) { - const QString qtAction = QCocoaAccessible::translateAction(action); + const QString qtAction = QCocoaAccessible::translateAction(action, iface); QAccessibleBridgeUtils::performEffectiveAction(iface, qtAction); } } diff --git a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac.cpp b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac.cpp index 8e439a61cf..92af5a0757 100644 --- a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac.cpp +++ b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac.cpp @@ -75,6 +75,7 @@ private slots: void lineEditTest(); void hierarchyTest(); void notificationsTest(); + void checkBoxTest(); private: AccessibleTestWindow *m_window; @@ -151,5 +152,19 @@ void tst_QAccessibilityMac::notificationsTest() QVERIFY(notifications(m_window)); } +void tst_QAccessibilityMac::checkBoxTest() +{ + if (!macNativeAccessibilityEnabled()) + return; + + QCheckBox *cb = new QCheckBox(m_window); + cb->setText("Great option"); + m_window->addWidget(cb); + QVERIFY(QTest::qWaitForWindowExposed(m_window)); + QCoreApplication::processEvents(); + + QVERIFY(testCheckBox()); +} + QTEST_MAIN(tst_QAccessibilityMac) #include "tst_qaccessibilitymac.moc" diff --git a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.h b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.h index 78615fa9cd..cdf12489a6 100644 --- a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.h +++ b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.h @@ -44,3 +44,4 @@ bool testLineEdit(); bool testHierarchy(QWidget *w); bool singleWidget(); bool notifications(QWidget *w); +bool testCheckBox(); diff --git a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.mm b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.mm index a182fc27d5..d0ff6af640 100644 --- a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.mm +++ b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.mm @@ -114,6 +114,7 @@ QDebug operator<<(QDebug dbg, AXErrorTag err) @property (readonly) NSString *description; @property (readonly) NSString *value; @property (readonly) CGRect rect; + @property (readonly) NSArray *actions; @end @implementation TestAXObject @@ -329,10 +330,34 @@ QDebug operator<<(QDebug dbg, AXErrorTag err) return value; } +- (NSArray*)actions +{ + AXError err; + CFArrayRef actions; + + if (kAXErrorSuccess != (err = AXUIElementCopyActionNames(reference, &actions))) + { + qDebug() << "AXUIElementCopyActionNames(...) returned error = " << AXErrorTag(err); + } + + return (NSArray*)actions; +} + +- (void)performAction:(CFStringRef)action +{ + AXError err; + + if (kAXErrorSuccess != (err = AXUIElementPerformAction(reference, action))) + { + qDebug() << "AXUIElementPerformAction(" << QString::fromCFString(action) << ") returned error = " << AXErrorTag(err); + } +} + - (NSString*) role { return [self _stringAttributeValue:kAXRoleAttribute]; } - (NSString*) title { return [self _stringAttributeValue:kAXTitleAttribute]; } - (NSString*) description { return [self _stringAttributeValue:kAXDescriptionAttribute]; } - (NSString*) value { return [self _stringAttributeValue:kAXValueAttribute]; } +- (NSInteger) valueNumber { return [self _numberAttributeValue:kAXValueAttribute]; } - (NSRect) rect { NSRect rect; @@ -563,3 +588,37 @@ bool notifications(QWidget *w) return true; } + +bool testCheckBox() +{ + TestAXObject *appObject = [TestAXObject getApplicationAXObject]; + EXPECT(appObject); + + NSArray *windowList = [appObject windowList]; + // one window + EXPECT([windowList count] == 1); + AXUIElementRef windowRef = (AXUIElementRef) [windowList objectAtIndex: 0]; + EXPECT(windowRef != nil); + TestAXObject *window = [[TestAXObject alloc] initWithAXUIElementRef: windowRef]; + + // children of window: + AXUIElementRef checkBox = [window findDirectChildByRole: kAXCheckBoxRole]; + EXPECT(checkBox != nil); + + TestAXObject *cb = [[TestAXObject alloc] initWithAXUIElementRef: checkBox]; + + // here start actual checkbox tests + EXPECT([cb valueNumber] == 0); + EXPECT([cb.title isEqualToString:@"Great option"]); + // EXPECT(cb.description == nil); // currently returns "" instead of nil + + EXPECT([cb.actions containsObject:(NSString*)kAXPressAction]); + + [cb performAction:kAXPressAction]; + EXPECT([cb valueNumber] == 1); + + [cb performAction:kAXPressAction]; + EXPECT([cb valueNumber] == 0); + + return true; +} |