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);
|
NSArray *unignoredChildren(QAccessibleInterface *interface);
|
||||||
NSString *getTranslatedAction(const QString &qtAction);
|
NSString *getTranslatedAction(const QString &qtAction);
|
||||||
NSMutableArray *createTranslatedActionsList(const QStringList &qtActions);
|
NSMutableArray *createTranslatedActionsList(const QStringList &qtActions);
|
||||||
QString translateAction(NSString *nsAction);
|
QString translateAction(NSString *nsAction, QAccessibleInterface *interface);
|
||||||
bool hasValueAttribute(QAccessibleInterface *interface);
|
bool hasValueAttribute(QAccessibleInterface *interface);
|
||||||
id getValueAttribute(QAccessibleInterface *interface);
|
id getValueAttribute(QAccessibleInterface *interface);
|
||||||
|
|
||||||
|
@ -284,6 +284,8 @@ NSString *getTranslatedAction(const QString &qtAction)
|
|||||||
return NSAccessibilityShowMenuAction;
|
return NSAccessibilityShowMenuAction;
|
||||||
else if (qtAction == QAccessibleActionInterface::setFocusAction()) // Not 100% sure on this one
|
else if (qtAction == QAccessibleActionInterface::setFocusAction()) // Not 100% sure on this one
|
||||||
return NSAccessibilityRaiseAction;
|
return NSAccessibilityRaiseAction;
|
||||||
|
else if (qtAction == QAccessibleActionInterface::toggleAction())
|
||||||
|
return NSAccessibilityPressAction;
|
||||||
|
|
||||||
// Not translated:
|
// Not translated:
|
||||||
//
|
//
|
||||||
@ -305,11 +307,13 @@ NSString *getTranslatedAction(const QString &qtAction)
|
|||||||
Translates between a Mac action constant and a QAccessibleActionInterface action
|
Translates between a Mac action constant and a QAccessibleActionInterface action
|
||||||
Returns an empty QString if there is no Qt predefined equivalent.
|
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();
|
return QAccessibleActionInterface::pressAction();
|
||||||
else if ([nsAction compare: NSAccessibilityIncrementAction] == NSOrderedSame)
|
} else if ([nsAction compare: NSAccessibilityIncrementAction] == NSOrderedSame)
|
||||||
return QAccessibleActionInterface::increaseAction();
|
return QAccessibleActionInterface::increaseAction();
|
||||||
else if ([nsAction compare: NSAccessibilityDecrementAction] == NSOrderedSame)
|
else if ([nsAction compare: NSAccessibilityDecrementAction] == NSOrderedSame)
|
||||||
return QAccessibleActionInterface::decreaseAction();
|
return QAccessibleActionInterface::decreaseAction();
|
||||||
|
@ -501,7 +501,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int &line, int &of
|
|||||||
QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
|
QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
|
||||||
if (!iface)
|
if (!iface)
|
||||||
return nil; // FIXME is that the right return type??
|
return nil; // FIXME is that the right return type??
|
||||||
QString qtAction = QCocoaAccessible::translateAction(action);
|
QString qtAction = QCocoaAccessible::translateAction(action, iface);
|
||||||
QString description;
|
QString description;
|
||||||
// Return a description from the action interface if this action is not known to the OS.
|
// Return a description from the action interface if this action is not known to the OS.
|
||||||
if (qtAction.isEmpty()) {
|
if (qtAction.isEmpty()) {
|
||||||
@ -518,7 +518,7 @@ static void convertLineOffset(QAccessibleTextInterface *text, int &line, int &of
|
|||||||
- (void)accessibilityPerformAction:(NSString *)action {
|
- (void)accessibilityPerformAction:(NSString *)action {
|
||||||
QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
|
QAccessibleInterface *iface = QAccessible::accessibleInterface(axid);
|
||||||
if (iface) {
|
if (iface) {
|
||||||
const QString qtAction = QCocoaAccessible::translateAction(action);
|
const QString qtAction = QCocoaAccessible::translateAction(action, iface);
|
||||||
QAccessibleBridgeUtils::performEffectiveAction(iface, qtAction);
|
QAccessibleBridgeUtils::performEffectiveAction(iface, qtAction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,6 +75,7 @@ private slots:
|
|||||||
void lineEditTest();
|
void lineEditTest();
|
||||||
void hierarchyTest();
|
void hierarchyTest();
|
||||||
void notificationsTest();
|
void notificationsTest();
|
||||||
|
void checkBoxTest();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AccessibleTestWindow *m_window;
|
AccessibleTestWindow *m_window;
|
||||||
@ -151,5 +152,19 @@ void tst_QAccessibilityMac::notificationsTest()
|
|||||||
QVERIFY(notifications(m_window));
|
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)
|
QTEST_MAIN(tst_QAccessibilityMac)
|
||||||
#include "tst_qaccessibilitymac.moc"
|
#include "tst_qaccessibilitymac.moc"
|
||||||
|
@ -44,3 +44,4 @@ bool testLineEdit();
|
|||||||
bool testHierarchy(QWidget *w);
|
bool testHierarchy(QWidget *w);
|
||||||
bool singleWidget();
|
bool singleWidget();
|
||||||
bool notifications(QWidget *w);
|
bool notifications(QWidget *w);
|
||||||
|
bool testCheckBox();
|
||||||
|
@ -114,6 +114,7 @@ QDebug operator<<(QDebug dbg, AXErrorTag err)
|
|||||||
@property (readonly) NSString *description;
|
@property (readonly) NSString *description;
|
||||||
@property (readonly) NSString *value;
|
@property (readonly) NSString *value;
|
||||||
@property (readonly) CGRect rect;
|
@property (readonly) CGRect rect;
|
||||||
|
@property (readonly) NSArray *actions;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation TestAXObject
|
@implementation TestAXObject
|
||||||
@ -329,10 +330,34 @@ QDebug operator<<(QDebug dbg, AXErrorTag err)
|
|||||||
return value;
|
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*) role { return [self _stringAttributeValue:kAXRoleAttribute]; }
|
||||||
- (NSString*) title { return [self _stringAttributeValue:kAXTitleAttribute]; }
|
- (NSString*) title { return [self _stringAttributeValue:kAXTitleAttribute]; }
|
||||||
- (NSString*) description { return [self _stringAttributeValue:kAXDescriptionAttribute]; }
|
- (NSString*) description { return [self _stringAttributeValue:kAXDescriptionAttribute]; }
|
||||||
- (NSString*) value { return [self _stringAttributeValue:kAXValueAttribute]; }
|
- (NSString*) value { return [self _stringAttributeValue:kAXValueAttribute]; }
|
||||||
|
- (NSInteger) valueNumber { return [self _numberAttributeValue:kAXValueAttribute]; }
|
||||||
- (NSRect) rect
|
- (NSRect) rect
|
||||||
{
|
{
|
||||||
NSRect rect;
|
NSRect rect;
|
||||||
@ -563,3 +588,37 @@ bool notifications(QWidget *w)
|
|||||||
|
|
||||||
return true;
|
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