diff --git a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm index bc98d002f0..0b674b8d2f 100644 --- a/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm +++ b/src/plugins/platforms/cocoa/qcocoaaccessibilityelement.mm @@ -417,7 +417,23 @@ } - (id)accessibilityFocusedUIElement { - return NSAccessibilityUnignoredAncestor(self); + QAccessibleInterface *iface = QAccessible::accessibleInterface(axid); + + if (!iface || !iface->isValid()) { + qWarning() << "FocusedUIElement for INVALID"; + return nil; + } + QAccessibleInterface *childInterface = iface->focusChild(); + if (childInterface) { + QAccessible::Id childAxid = QAccessible::uniqueId(childInterface); + // FIXME: parent could be wrong + QCocoaAccessibleElement *accessibleElement = [QCocoaAccessibleElement createElementWithId:childAxid parent:self]; + [accessibleElement autorelease]; + return accessibleElement; + } + + // no focus found + return nil; } @end diff --git a/src/plugins/platforms/cocoa/qnsviewaccessibility.mm b/src/plugins/platforms/cocoa/qnsviewaccessibility.mm index a438950a55..31e3e343b9 100644 --- a/src/plugins/platforms/cocoa/qnsviewaccessibility.mm +++ b/src/plugins/platforms/cocoa/qnsviewaccessibility.mm @@ -54,6 +54,15 @@ @implementation QNSView (QNSViewAccessibility) +- (id)childAccessibleElement { + if (!m_window->accessibleRoot()) + return nil; + + QAccessible::Id childId = QAccessible::uniqueId(m_window->accessibleRoot()); + QCocoaAccessibleElement *child = [QCocoaAccessibleElement createElementWithId: childId parent: self]; + return [child autorelease]; +} + // The QNSView is a container that the user does not interact directly with: // Remove it from the user-visible accessibility tree. - (BOOL)accessibilityIsIgnored { @@ -61,58 +70,22 @@ } - (id)accessibilityAttributeValue:(NSString *)attribute { - // activate accessibility updates QCocoaIntegration::instance()->accessibility()->setActive(true); - if ([attribute isEqualToString:NSAccessibilityRoleAttribute]) { - if (m_window->accessibleRoot()) - return QCocoaAccessible::macRole(m_window->accessibleRoot()); - return NSAccessibilityUnknownRole; - } else if ([attribute isEqualToString:NSAccessibilityRoleDescriptionAttribute]) { - return NSAccessibilityRoleDescriptionForUIElement(self); - } else if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { - if (!m_window->accessibleRoot()) - return [super accessibilityAttributeValue:attribute]; - return QCocoaAccessible::unignoredChildren(self, m_window->accessibleRoot()); + if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { + return NSAccessibilityUnignoredChildrenForOnlyChild([self childAccessibleElement]); } else { return [super accessibilityAttributeValue:attribute]; } } - (id)accessibilityHitTest:(NSPoint)point { - if (!m_window->accessibleRoot()) - return [super accessibilityHitTest:point]; - - QAccessibleInterface *childInterface = m_window->accessibleRoot()->childAt(point.x, qt_mac_flipYCoordinate(point.y)); - // No child found, meaning we hit the NSView - if (!childInterface) { - return [super accessibilityHitTest:point]; - } - - // Hit a child, forward to child accessible interface. - QAccessible::Id childAxid = QAccessible::uniqueId(childInterface); - // FIXME: parent could be wrong - QCocoaAccessibleElement *accessibleElement = [QCocoaAccessibleElement createElementWithId:childAxid parent:self ]; - [accessibleElement autorelease]; - return [accessibleElement accessibilityHitTest:point]; + return [[self childAccessibleElement] accessibilityHitTest: point]; } - (id)accessibilityFocusedUIElement { - if (!m_window->accessibleRoot()) - return [super accessibilityFocusedUIElement]; - - QAccessibleInterface *childInterface = m_window->accessibleRoot()->focusChild(); - if (childInterface) { - QAccessible::Id childAxid = QAccessible::uniqueId(childInterface); - // FIXME: parent could be wrong - QCocoaAccessibleElement *accessibleElement = [QCocoaAccessibleElement createElementWithId:childAxid parent:self]; - [accessibleElement autorelease]; - return accessibleElement; - } - - // should not happen - return nil; + return [[self childAccessibleElement] accessibilityFocusedUIElement]; } @end diff --git a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac.cpp b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac.cpp index 25dd0d39dd..25b47ee836 100644 --- a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac.cpp +++ b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac.cpp @@ -79,6 +79,7 @@ private slots: void init(); void cleanup(); + void singleWidgetTest(); void lineEditTest(); void hierarchyTest(); private: @@ -101,6 +102,16 @@ void tst_QAccessibilityMac::cleanup() delete m_window; } +void tst_QAccessibilityMac::singleWidgetTest() +{ + if (!macNativeAccessibilityEnabled()) + return; + + delete m_window; + m_window = 0; + + QVERIFY(singleWidget()); +} void tst_QAccessibilityMac::lineEditTest() { @@ -122,14 +133,19 @@ void tst_QAccessibilityMac::hierarchyTest() QWidget *w = new QWidget(m_window); m_window->addWidget(w); - QPushButton *b = new QPushButton(w); + w->setLayout(new QVBoxLayout()); + QPushButton *b = new QPushButton(w); w->layout()->addWidget(b); b->setText("I am a button"); + QPushButton *b2 = new QPushButton(w); + w->layout()->addWidget(b2); + b2->setText("Button 2"); + QVERIFY(QTest::qWaitForWindowExposed(m_window)); QCoreApplication::processEvents(); - QVERIFY(testHierarchy()); + QVERIFY(testHierarchy(w)); } QTEST_MAIN(tst_QAccessibilityMac) diff --git a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.h b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.h index ec5beab125..635b6383ab 100644 --- a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.h +++ b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.h @@ -48,4 +48,5 @@ bool macNativeAccessibilityEnabled(); bool trusted(); bool testLineEdit(); -bool testHierarchy(); +bool testHierarchy(QWidget *w); +bool singleWidget(); diff --git a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.mm b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.mm index 8620b7dd2f..bc89ac858b 100644 --- a/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.mm +++ b/tests/auto/other/qaccessibilitymac/tst_qaccessibilitymac_helpers.mm @@ -43,8 +43,10 @@ #define QT_NO_KEYWORDS #include "tst_qaccessibilitymac_helpers.h" -#include -#include +#include +#include +#include +#include #include #import @@ -148,9 +150,41 @@ bool trusted() return p; } ++ (TestAXObject *) getApplicationAXObject +{ + pid_t pid = getpid(); + AXUIElementRef appRef = AXUIElementCreateApplication(pid); + TestAXObject *appObject = [[TestAXObject alloc] initWithAXUIElementRef: appRef]; + return appObject; +} + @end +bool singleWidget() +{ + QLineEdit le; + le.setText("button"); + le.show(); + EXPECT(QTest::qWaitForWindowExposed(&le)); + QCoreApplication::processEvents(); + + TestAXObject *appObject = [TestAXObject getApplicationAXObject]; + EXPECT(appObject); + + NSArray *windows = [appObject windowList]; + EXPECT([windows count] == 1); + + AXUIElementRef windowRef = (AXUIElementRef) [windows objectAtIndex: 0]; + EXPECT(windowRef != nil); + TestAXObject *window = [[TestAXObject alloc] initWithAXUIElementRef: windowRef]; + + AXUIElementRef lineEdit = [window findDirectChildByRole: kAXTextFieldRole]; + EXPECT(lineEdit != nil); + + return true; +} + bool testLineEdit() { // not sure if this is needed. on my machine the calls succeed. @@ -159,10 +193,8 @@ bool testLineEdit() // AXError e = AXMakeProcessTrusted((CFStringRef) path); // NSLog(@"error: %i", e); - pid_t pid = getpid(); - AXUIElementRef app = AXUIElementCreateApplication(pid); - EXPECT(app != nil); - TestAXObject *appObject = [[TestAXObject alloc] initWithAXUIElementRef: app]; + TestAXObject *appObject = [TestAXObject getApplicationAXObject]; + EXPECT(appObject); NSArray *windowList = [appObject windowList]; // one window @@ -184,12 +216,10 @@ bool testLineEdit() return true; } -bool testHierarchy() +bool testHierarchy(QWidget *w) { - pid_t pid = getpid(); - AXUIElementRef app = AXUIElementCreateApplication(pid); - EXPECT(app != nil); - TestAXObject *appObject = [[TestAXObject alloc] initWithAXUIElementRef: app]; + TestAXObject *appObject = [TestAXObject getApplicationAXObject]; + EXPECT(appObject); NSArray *windowList = [appObject windowList]; // one window @@ -207,7 +237,36 @@ bool testHierarchy() TestAXObject *parentObject = [[TestAXObject alloc] initWithAXUIElementRef: [buttonObject parent]]; // check that the parent is a window - EXPECT([[parentObject role] isEqualToString: (NSString *)kAXWindowRole]); + EXPECT([[parentObject role] isEqualToString: NSAccessibilityWindowRole]); + + // test the focus + // child 0 is the layout, then button1 and 2 + QPushButton *button1 = qobject_cast(w->children().at(1)); + EXPECT(button1); + QPushButton *button2 = qobject_cast(w->children().at(2)); + EXPECT(button2); + button2->setFocus(); + + AXUIElementRef systemWideElement = AXUIElementCreateSystemWide(); + AXUIElementRef focussedElement = NULL; + AXError error = AXUIElementCopyAttributeValue(systemWideElement, + (CFStringRef)NSAccessibilityFocusedUIElementAttribute, (CFTypeRef*)&focussedElement); + EXPECT(!error); + EXPECT(focussedElement); + TestAXObject *focusButton2 = [[TestAXObject alloc] initWithAXUIElementRef: focussedElement]; + + EXPECT([[focusButton2 role] isEqualToString: NSAccessibilityButtonRole]); + EXPECT([[focusButton2 description] isEqualToString: @"Button 2"]); + + + button1->setFocus(); + error = AXUIElementCopyAttributeValue(systemWideElement, + (CFStringRef)NSAccessibilityFocusedUIElementAttribute, (CFTypeRef*)&focussedElement); + EXPECT(!error); + EXPECT(focussedElement); + TestAXObject *focusButton1 = [[TestAXObject alloc] initWithAXUIElementRef: focussedElement]; + EXPECT([[focusButton1 role] isEqualToString: NSAccessibilityButtonRole]); + EXPECT([[focusButton1 description] isEqualToString: @"I am a button"]); return true; }