Accessibility Mac: Fix handling of top level widget
This simplifies how we handle QNSView for accessibility purposes. Instead of trying to half-merge the top level widget (window->accessibleRoot) into the view, just have the view always return it as child. This makes the accessibility implementation for QNSView simpler and makes applications that show a top level widget such as a button possible. (We would return accessibility ignored for the button before). As a side effect finding the active focus and hit-testing should be more reliable as well. Task-number: QTBUG-37794 Change-Id: Ib52037f88da8887a0bdc77204b0f3daddfe7709d Reviewed-by: Morten Johan Sørvig <morten.sorvig@digia.com> Reviewed-by: Jan Arve Sæther <jan-arve.saether@digia.com>
This commit is contained in:
parent
7aec099ca3
commit
f16d690a2f
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -48,4 +48,5 @@
|
||||
bool macNativeAccessibilityEnabled();
|
||||
bool trusted();
|
||||
bool testLineEdit();
|
||||
bool testHierarchy();
|
||||
bool testHierarchy(QWidget *w);
|
||||
bool singleWidget();
|
||||
|
@ -43,8 +43,10 @@
|
||||
#define QT_NO_KEYWORDS
|
||||
|
||||
#include "tst_qaccessibilitymac_helpers.h"
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QtWidgets/qapplication.h>
|
||||
#include <QtWidgets/qlineedit.h>
|
||||
#include <QtWidgets/qpushbutton.h>
|
||||
#include <QtTest>
|
||||
#include <unistd.h>
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
@ -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<QPushButton*>(w->children().at(1));
|
||||
EXPECT(button1);
|
||||
QPushButton *button2 = qobject_cast<QPushButton*>(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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user