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>
This commit is contained in:
Boris Dušek 2015-03-17 23:10:07 +01:00
parent c37f2e8e45
commit 76c94be4e7
6 changed files with 85 additions and 6 deletions

View File

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

View File

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

View File

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

View File

@ -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"

View File

@ -44,3 +44,4 @@ bool testLineEdit();
bool testHierarchy(QWidget *w);
bool singleWidget();
bool notifications(QWidget *w);
bool testCheckBox();

View File

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