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:
parent
c37f2e8e45
commit
76c94be4e7
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -44,3 +44,4 @@ bool testLineEdit();
|
||||
bool testHierarchy(QWidget *w);
|
||||
bool singleWidget();
|
||||
bool notifications(QWidget *w);
|
||||
bool testCheckBox();
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user