mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2024-12-02 16:43:00 +08:00
809 lines
29 KiB
XML
809 lines
29 KiB
XML
// Copyright (C) 2016 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
|
|
|
#include <QApplication>
|
|
#include <QtWidgets>
|
|
#include <QTest>
|
|
#include <QtCore/qcoreapplication.h>
|
|
|
|
// some versions of CALayer.h use 'slots' as an identifier
|
|
#define QT_NO_KEYWORDS
|
|
|
|
#include <QtWidgets/qapplication.h>
|
|
#include <QtWidgets/qlineedit.h>
|
|
#include <QtWidgets/qpushbutton.h>
|
|
#include <QtWidgets>
|
|
#include <QTest>
|
|
#include <unistd.h>
|
|
|
|
#import <AppKit/AppKit.h>
|
|
#import <ApplicationServices/ApplicationServices.h>
|
|
|
|
QT_USE_NAMESPACE
|
|
|
|
struct AXErrorTag {
|
|
AXError err;
|
|
explicit AXErrorTag(AXError theErr) : err(theErr) {}
|
|
};
|
|
|
|
QDebug operator<<(QDebug dbg, AXErrorTag err)
|
|
{
|
|
QDebugStateSaver saver(dbg);
|
|
|
|
const char *errDesc = 0;
|
|
const char *errName = 0;
|
|
switch (err.err) {
|
|
#define HANDLE_ERR(error, desc) case kAXError##error: errName = "kAXError" #error; errDesc = desc; break
|
|
HANDLE_ERR(Success, "Success");
|
|
HANDLE_ERR(Failure, "A system error occurred, such as the failure to allocate an object.");
|
|
HANDLE_ERR(IllegalArgument, "An illegal argument was passed to the function.");
|
|
HANDLE_ERR(InvalidUIElement, "The AXUIElementRef passed to the function is invalid.");
|
|
HANDLE_ERR(InvalidUIElementObserver, "The AXObserverRef passed to the function is not a valid observer.");
|
|
HANDLE_ERR(CannotComplete, "The function cannot complete because messaging failed in some way or because the application with which the function is communicating is busy or unresponsive.");
|
|
HANDLE_ERR(AttributeUnsupported, "The attribute is not supported by the AXUIElementRef.");
|
|
HANDLE_ERR(ActionUnsupported, "The action is not supported by the AXUIElementRef.");
|
|
HANDLE_ERR(NotificationUnsupported, "The notification is not supported by the AXUIElementRef.");
|
|
HANDLE_ERR(NotImplemented, "Indicates that the function or method is not implemented (this can be returned if a process does not support the accessibility API).");
|
|
HANDLE_ERR(NotificationAlreadyRegistered, "This notification has already been registered for.");
|
|
HANDLE_ERR(NotificationNotRegistered, "Indicates that a notification is not registered yet.");
|
|
HANDLE_ERR(APIDisabled, "The accessibility API is disabled (as when, for example, the user deselects \"Enable access for assistive devices\" in Universal Access Preferences).");
|
|
HANDLE_ERR(NoValue, "The requested value or AXUIElementRef does not exist.");
|
|
HANDLE_ERR(ParameterizedAttributeUnsupported, "The parameterized attribute is not supported by the AXUIElementRef.");
|
|
HANDLE_ERR(NotEnoughPrecision, "Not enough precision.");
|
|
default: errName = "<unknown error>"; errDesc = "UNKNOWN ERROR"; break;
|
|
}
|
|
#undef HANDLE_ERR
|
|
|
|
dbg.nospace() << "AXError(value=" << err.err << ", name=" << errName << ", description=\"" << errDesc << "\")";
|
|
|
|
return dbg;
|
|
}
|
|
|
|
@interface TestAXObject : NSObject
|
|
{
|
|
AXUIElementRef reference;
|
|
bool axError;
|
|
}
|
|
@property (readonly) NSString *role;
|
|
@property (readonly) NSString *title;
|
|
@property (readonly) NSString *description;
|
|
@property (readonly) NSString *value;
|
|
@property (readonly) CGRect rect;
|
|
@property (readonly) NSArray *actions;
|
|
@end
|
|
|
|
@implementation TestAXObject
|
|
|
|
- (instancetype)initWithAXUIElementRef:(AXUIElementRef)ref {
|
|
|
|
if ((self = [super init])) {
|
|
reference = ref;
|
|
axError = false;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (AXUIElementRef) ref { return reference; }
|
|
- (bool)errorOccurred { return axError; }
|
|
- (void) print {
|
|
NSLog(@"Accessible Object role: '%@', title: '%@', description: '%@', value: '%@', rect: '%@'", self.role, self.title, self.description, self.value, NSStringFromRect(NSRectFromCGRect(self.rect)));
|
|
NSLog(@" Children: %ld", [[self childList] count]);
|
|
}
|
|
|
|
- (NSArray*) windowList
|
|
{
|
|
NSArray *list;
|
|
AXUIElementCopyAttributeValues(
|
|
reference,
|
|
kAXWindowsAttribute,
|
|
0, 100, /*min, max*/
|
|
(CFArrayRef *) &list);
|
|
return list;
|
|
}
|
|
|
|
- (NSArray*) childList
|
|
{
|
|
NSArray *list;
|
|
AXUIElementCopyAttributeValues(
|
|
reference,
|
|
kAXChildrenAttribute,
|
|
0, 100, /*min, max*/
|
|
(CFArrayRef *) &list);
|
|
return list;
|
|
}
|
|
|
|
- (NSArray *)tableRows
|
|
{
|
|
NSArray *arr;
|
|
AXUIElementCopyAttributeValues(
|
|
reference,
|
|
kAXRowsAttribute,
|
|
0, 100, /*min, max*/
|
|
(CFArrayRef *) &arr);
|
|
return arr;
|
|
}
|
|
|
|
- (NSArray *)tableColumns
|
|
{
|
|
NSArray *arr;
|
|
AXUIElementCopyAttributeValues(
|
|
reference,
|
|
kAXColumnsAttribute,
|
|
0, 100, /*min, max*/
|
|
(CFArrayRef *) &arr);
|
|
return arr;
|
|
}
|
|
|
|
- (AXUIElementRef) findDirectChildByRole: (CFStringRef) role
|
|
{
|
|
TestAXObject *result = nil;
|
|
NSArray *childList = [self childList];
|
|
for (id child in childList) {
|
|
TestAXObject *childObject = [[TestAXObject alloc] initWithAXUIElementRef:(AXUIElementRef)child];
|
|
if ([childObject.role isEqualToString:(NSString*)role]) {
|
|
result = childObject;
|
|
break;
|
|
}
|
|
}
|
|
AXUIElementRef ret = [result ref];
|
|
[result release];
|
|
return ret;
|
|
}
|
|
|
|
+ (TestAXObject *) getApplicationAXObject
|
|
{
|
|
pid_t pid = getpid();
|
|
AXUIElementRef appRef = AXUIElementCreateApplication(pid);
|
|
TestAXObject *appObject = [[TestAXObject alloc] initWithAXUIElementRef: appRef];
|
|
return appObject;
|
|
}
|
|
|
|
+ (NSInteger)_numberFromValue:(CFTypeRef)value
|
|
{
|
|
NSInteger number = -1;
|
|
if (!CFNumberGetValue((CFNumberRef)value, kCFNumberNSIntegerType, &number))
|
|
{
|
|
qDebug() << "Could not get NSInteger value out of CFNumberRef";
|
|
}
|
|
return number;
|
|
}
|
|
|
|
+ (BOOL)_boolFromValue:(CFTypeRef)value
|
|
{
|
|
return CFBooleanGetValue((CFBooleanRef)value);
|
|
}
|
|
|
|
+ (NSRange)_rangeFromValue:(CFTypeRef)value
|
|
{
|
|
CFRange cfRange;
|
|
NSRange range = NSMakeRange(0, 0);
|
|
|
|
if (!AXValueGetValue(AXValueRef(value), AXValueType(kAXValueCFRangeType), &cfRange))
|
|
qDebug() << "Could not get CFRange value out of AXValueRef";
|
|
else if (cfRange.location < 0 || cfRange.length < 0)
|
|
qDebug() << "Cannot convert CFRange with negative location or length to NSRange";
|
|
else if (static_cast<uintmax_t>(cfRange.location) > NSUIntegerMax || static_cast<uintmax_t>(cfRange.length) > NSUIntegerMax)
|
|
qDebug() << "Cannot convert CFRange with location or length out of bounds for NSUInteger";
|
|
else
|
|
{
|
|
range.length = static_cast<NSUInteger>(cfRange.length);
|
|
range.location = static_cast<NSUInteger>(cfRange.location);
|
|
}
|
|
|
|
return range;
|
|
}
|
|
|
|
+ (NSRect)_rectFromValue:(CFTypeRef)value
|
|
{
|
|
NSRect rect = NSMakeRect(0, 0, 0, 0);
|
|
if (!AXValueGetValue(AXValueRef(value), AXValueType(kAXValueCGRectType), reinterpret_cast<CGRect*>(&rect)))
|
|
{
|
|
qDebug() << "Could not get CGRect value out of AXValueRef";
|
|
}
|
|
return rect;
|
|
}
|
|
|
|
+ (NSPoint)_pointFromValue:(CFTypeRef)value
|
|
{
|
|
NSPoint point = NSMakePoint(0, 0);
|
|
if (!AXValueGetValue(AXValueRef(value), AXValueType(kAXValueCGPointType), reinterpret_cast<CGPoint*>(&point)))
|
|
{
|
|
qDebug() << "Could not get CGPoint value out of AXValueRef";
|
|
}
|
|
return point;
|
|
}
|
|
|
|
+ (NSSize)_sizeFromValue:(CFTypeRef)value
|
|
{
|
|
NSSize size = NSMakeSize(0, 0);
|
|
if (!AXValueGetValue(AXValueRef(value), AXValueType(kAXValueCGSizeType), reinterpret_cast<CGSize*>(&size)))
|
|
{
|
|
qDebug() << "Could not get CGSize value out of AXValueRef";
|
|
}
|
|
return size;
|
|
}
|
|
|
|
- (CFTypeRef)_attributeValue:(CFStringRef)attribute
|
|
{
|
|
CFTypeRef value = NULL;
|
|
AXError err;
|
|
|
|
if (kAXErrorSuccess != (err = AXUIElementCopyAttributeValue(reference, attribute, &value))) {
|
|
axError = true;
|
|
qDebug() << "AXUIElementCopyAttributeValue(" << QString::fromCFString(attribute) << ") returned error = " << AXErrorTag(err);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
- (NSString*)_stringAttributeValue:(CFStringRef)attribute
|
|
{
|
|
return (NSString*)[self _attributeValue:attribute];
|
|
}
|
|
|
|
- (NSInteger)_numberAttributeValue:(CFStringRef)attribute
|
|
{
|
|
return [[self class] _numberFromValue:[self _attributeValue:attribute]];
|
|
}
|
|
|
|
- (BOOL)_boolAttributeValue:(CFStringRef)attribute
|
|
{
|
|
return [[self class] _boolFromValue:[self _attributeValue:attribute]];
|
|
}
|
|
|
|
- (NSRange)_rangeAttributeValue:(CFStringRef)attribute
|
|
{
|
|
return [[self class] _rangeFromValue:[self _attributeValue:attribute]];
|
|
}
|
|
|
|
- (NSRect)_rectAttributeValue:(CFStringRef)attribute
|
|
{
|
|
return [[self class] _rectFromValue:[self _attributeValue:attribute]];
|
|
}
|
|
|
|
- (NSPoint)_pointAttributeValue:(CFStringRef)attribute
|
|
{
|
|
return [[self class] _pointFromValue:[self _attributeValue:attribute]];
|
|
}
|
|
|
|
- (NSSize)_sizeAttributeValue:(CFStringRef)attribute
|
|
{
|
|
return [[self class] _sizeFromValue:[self _attributeValue:attribute]];
|
|
}
|
|
|
|
- (CFTypeRef)_attributeValue:(CFStringRef)attribute forParameter:(CFTypeRef)parameter
|
|
{
|
|
CFTypeRef value = NULL;
|
|
AXError err;
|
|
|
|
if (kAXErrorSuccess != (err = AXUIElementCopyParameterizedAttributeValue(reference, attribute, parameter, &value))) {
|
|
axError = true;
|
|
CFStringRef description = CFCopyDescription(parameter);
|
|
qDebug() << "AXUIElementCopyParameterizedAttributeValue(" << QString::fromCFString(attribute) << ", parameter=" << QString::fromCFString(description) << ") returned error = " << AXErrorTag(err);
|
|
CFRelease(description);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
- (CFTypeRef)_attributeValue:(CFStringRef)attribute forRange:(NSRange)aRange
|
|
{
|
|
CFRange cfRange = CFRangeMake(aRange.location, aRange.length);
|
|
AXValueRef range = AXValueCreate(AXValueType(kAXValueCFRangeType), &cfRange);
|
|
CFTypeRef value = [self _attributeValue:attribute forParameter:range];
|
|
CFRelease(range);
|
|
return value;
|
|
}
|
|
|
|
- (CFTypeRef)_attributeValue:(CFStringRef)attribute forNumber:(NSInteger)aNumber
|
|
{
|
|
CFNumberRef number = CFNumberCreate(NULL, kCFNumberNSIntegerType, &aNumber);
|
|
CFTypeRef value = [self _attributeValue:attribute forParameter:number];
|
|
CFRelease(number);
|
|
return value;
|
|
}
|
|
|
|
- (CFTypeRef)_attributeValue:(CFStringRef)attribute forPoint:(CGPoint)aPoint
|
|
{
|
|
AXValueRef point = AXValueCreate(AXValueType(kAXValueCGPointType), &aPoint);
|
|
CFTypeRef value = [self _attributeValue:attribute forParameter:point];
|
|
CFRelease(point);
|
|
return value;
|
|
}
|
|
|
|
- (NSArray*)actions
|
|
{
|
|
AXError err;
|
|
CFArrayRef actions;
|
|
|
|
if (kAXErrorSuccess != (err = AXUIElementCopyActionNames(reference, &actions))) {
|
|
axError = true;
|
|
qDebug() << "AXUIElementCopyActionNames(...) returned error = " << AXErrorTag(err);
|
|
}
|
|
|
|
return (NSArray*)actions;
|
|
}
|
|
|
|
- (void)performAction:(CFStringRef)action
|
|
{
|
|
AXError err;
|
|
|
|
if (kAXErrorSuccess != (err = AXUIElementPerformAction(reference, action))) {
|
|
axError = true;
|
|
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;
|
|
rect.origin = [self _pointAttributeValue:kAXPositionAttribute];
|
|
rect.size = [self _sizeAttributeValue:kAXSizeAttribute];
|
|
return rect;
|
|
}
|
|
- (AXUIElementRef) parent { return (AXUIElementRef)[self _attributeValue:kAXParentAttribute]; }
|
|
- (BOOL) focused { return [self _boolAttributeValue:kAXFocusedAttribute]; }
|
|
- (NSInteger) numberOfCharacters { return [self _numberAttributeValue:kAXNumberOfCharactersAttribute]; }
|
|
- (NSString*) selectedText { return [self _stringAttributeValue:kAXSelectedTextAttribute]; }
|
|
- (NSRange) selectedTextRange { return [self _rangeAttributeValue:kAXSelectedTextRangeAttribute]; }
|
|
- (NSRange) visibleCharacterRange { return [self _rangeAttributeValue:kAXVisibleCharacterRangeAttribute]; }
|
|
- (NSString*) help { return [self _stringAttributeValue:kAXHelpAttribute]; }
|
|
- (NSInteger) insertionPointLineNumber { return [self _numberAttributeValue:kAXInsertionPointLineNumberAttribute]; }
|
|
|
|
- (NSInteger) lineForIndex:(NSInteger)index { return [[self class] _numberFromValue:[self _attributeValue:kAXLineForIndexParameterizedAttribute forNumber:index]]; }
|
|
- (NSRange) rangeForLine:(NSInteger)line { return [[self class] _rangeFromValue:[self _attributeValue:kAXRangeForLineParameterizedAttribute forNumber:line]]; }
|
|
- (NSString*) stringForRange:(NSRange)range { return (NSString*)[self _attributeValue:kAXStringForRangeParameterizedAttribute forRange:range]; }
|
|
- (NSAttributedString*) attributedStringForRange:(NSRange)range { return (NSAttributedString*)[self _attributeValue:kAXAttributedStringForRangeParameterizedAttribute forRange:range]; }
|
|
- (NSRect) boundsForRange:(NSRange)range { return [[self class] _rectFromValue:[self _attributeValue:kAXBoundsForRangeParameterizedAttribute forRange:range]]; }
|
|
- (NSRange) styleRangeForIndex:(NSInteger)index { return [[self class] _rangeFromValue:[self _attributeValue:kAXStyleRangeForIndexParameterizedAttribute forNumber:index]]; }
|
|
|
|
@end
|
|
|
|
QVector<int> notificationList;
|
|
|
|
void observerCallback(AXObserverRef /*observer*/, AXUIElementRef /*element*/, CFStringRef notification, void *)
|
|
{
|
|
if ([(NSString*)notification isEqualToString: NSAccessibilityFocusedUIElementChangedNotification])
|
|
notificationList.append(QAccessible::Focus);
|
|
else if ([(NSString*)notification isEqualToString: NSAccessibilityValueChangedNotification])
|
|
notificationList.append(QAccessible::ValueChanged);
|
|
else
|
|
notificationList.append(-1);
|
|
}
|
|
|
|
class AccessibleTestWindow : public QWidget
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
AccessibleTestWindow()
|
|
{
|
|
new QHBoxLayout(this);
|
|
}
|
|
|
|
void addWidget(QWidget* widget)
|
|
{
|
|
layout()->addWidget(widget);
|
|
widget->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(widget));
|
|
}
|
|
|
|
void clearChildren()
|
|
{
|
|
qDeleteAll(children());
|
|
new QHBoxLayout(this);
|
|
}
|
|
};
|
|
|
|
class tst_QAccessibilityMac : public QObject
|
|
{
|
|
Q_OBJECT
|
|
private Q_SLOTS:
|
|
void init();
|
|
void cleanup();
|
|
|
|
void singleWidgetTest();
|
|
void lineEditTest();
|
|
void hierarchyTest();
|
|
void notificationsTest();
|
|
void checkBoxTest();
|
|
void tableViewTest();
|
|
void treeViewTest();
|
|
|
|
private:
|
|
AccessibleTestWindow *m_window;
|
|
};
|
|
|
|
|
|
void tst_QAccessibilityMac::init()
|
|
{
|
|
m_window = new AccessibleTestWindow();
|
|
m_window->setWindowTitle(QString("Test window - %1").arg(QTest::currentTestFunction()));
|
|
m_window->show();
|
|
m_window->resize(400, 400);
|
|
|
|
QVERIFY(QTest::qWaitForWindowExposed(m_window));
|
|
}
|
|
|
|
void tst_QAccessibilityMac::cleanup()
|
|
{
|
|
delete m_window;
|
|
}
|
|
|
|
void tst_QAccessibilityMac::singleWidgetTest()
|
|
{
|
|
delete m_window;
|
|
m_window = 0;
|
|
|
|
QLineEdit *le = new QLineEdit();
|
|
le->setText("button");
|
|
le->show();
|
|
QVERIFY(QTest::qWaitForWindowExposed(le));
|
|
QCoreApplication::processEvents();
|
|
|
|
TestAXObject *appObject = [TestAXObject getApplicationAXObject];
|
|
QVERIFY(appObject);
|
|
|
|
QTRY_VERIFY(appObject.windowList.count == 1);
|
|
|
|
AXUIElementRef windowRef = (AXUIElementRef) [appObject.windowList objectAtIndex: 0];
|
|
QVERIFY(windowRef != nil);
|
|
TestAXObject *window = [[TestAXObject alloc] initWithAXUIElementRef: windowRef];
|
|
|
|
AXUIElementRef lineEditRef = [window findDirectChildByRole: kAXTextFieldRole];
|
|
QVERIFY(lineEditRef != nil);
|
|
TestAXObject *lineEdit = [[TestAXObject alloc] initWithAXUIElementRef: lineEditRef];
|
|
QVERIFY([[lineEdit value] isEqualToString:@"button"]);
|
|
|
|
// Access invalid reference, should return empty value
|
|
delete le;
|
|
QCoreApplication::processEvents();
|
|
TestAXObject *lineEditInvalid = [[TestAXObject alloc] initWithAXUIElementRef: lineEditRef];
|
|
QVERIFY([[lineEditInvalid value] length] == 0);
|
|
}
|
|
|
|
void tst_QAccessibilityMac::lineEditTest()
|
|
{
|
|
QLineEdit *lineEdit = new QLineEdit(m_window);
|
|
lineEdit->setText("a11y test QLineEdit");
|
|
m_window->addWidget(lineEdit);
|
|
QVERIFY(QTest::qWaitForWindowExposed(m_window));
|
|
QCoreApplication::processEvents();
|
|
|
|
TestAXObject *appObject = [TestAXObject getApplicationAXObject];
|
|
QVERIFY(appObject);
|
|
|
|
// one window
|
|
QTRY_VERIFY(appObject.windowList.count == 1);
|
|
AXUIElementRef windowRef = (AXUIElementRef) [appObject.windowList objectAtIndex: 0];
|
|
QVERIFY(windowRef != nil);
|
|
TestAXObject *window = [[TestAXObject alloc] initWithAXUIElementRef: windowRef];
|
|
|
|
QVERIFY([window rect].size.width == 400);
|
|
// height of window includes title bar
|
|
QVERIFY([window rect].size.height >= 400);
|
|
|
|
QVERIFY([window.title isEqualToString:@"Test window - lineEditTest"]);
|
|
|
|
// children of window:
|
|
AXUIElementRef lineEditElement = [window findDirectChildByRole: kAXTextFieldRole];
|
|
QVERIFY(lineEditElement != nil);
|
|
|
|
TestAXObject *le = [[TestAXObject alloc] initWithAXUIElementRef: lineEditElement];
|
|
NSString *value = @"a11y test QLineEdit";
|
|
QVERIFY([le.value isEqualToString:value]);
|
|
QVERIFY(value.length <= NSIntegerMax);
|
|
QVERIFY(le.numberOfCharacters == static_cast<NSInteger>(value.length));
|
|
const NSRange ranges[] = {
|
|
{ 0, 0},
|
|
{ 0, 1},
|
|
{ 0, 5},
|
|
{ 5, 0},
|
|
{ 5, 1},
|
|
{ 0, value.length},
|
|
{ value.length, 0},
|
|
};
|
|
for (size_t i = 0; i < sizeof(ranges)/sizeof(ranges[0]); ++i) {
|
|
NSRange range = ranges[i];
|
|
NSString *expectedSubstring = [value substringWithRange:range];
|
|
NSString *actualSubstring = [le stringForRange:range];
|
|
NSString *actualAttributedSubstring = [le attributedStringForRange:range].string;
|
|
QVERIFY([actualSubstring isEqualTo:expectedSubstring]);
|
|
QVERIFY([actualAttributedSubstring isEqualTo:expectedSubstring]);
|
|
}
|
|
}
|
|
|
|
void tst_QAccessibilityMac::hierarchyTest()
|
|
{
|
|
QWidget *w = new QWidget(m_window);
|
|
m_window->addWidget(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();
|
|
|
|
TestAXObject *appObject = [TestAXObject getApplicationAXObject];
|
|
QVERIFY(appObject);
|
|
|
|
// one window
|
|
QTRY_VERIFY(appObject.windowList.count == 1);
|
|
AXUIElementRef windowRef = (AXUIElementRef) [appObject.windowList objectAtIndex: 0];
|
|
QVERIFY(windowRef != nil);
|
|
TestAXObject *window = [[TestAXObject alloc] initWithAXUIElementRef: windowRef];
|
|
|
|
// Because the plain widget is filtered out of the hierarchy, we expect the button
|
|
// to be a direct child of the window
|
|
AXUIElementRef buttonRef = [window findDirectChildByRole: kAXButtonRole];
|
|
QVERIFY(buttonRef != nil);
|
|
|
|
TestAXObject *buttonObject = [[TestAXObject alloc] initWithAXUIElementRef: buttonRef];
|
|
TestAXObject *parentObject = [[TestAXObject alloc] initWithAXUIElementRef: [buttonObject parent]];
|
|
|
|
// check that the parent is a window
|
|
QVERIFY([[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));
|
|
QVERIFY(button1);
|
|
QPushButton *button2 = qobject_cast<QPushButton*>(w->children().at(2));
|
|
QVERIFY(button2);
|
|
button2->setFocus();
|
|
|
|
AXUIElementRef systemWideElement = AXUIElementCreateSystemWide();
|
|
AXUIElementRef focussedElement = NULL;
|
|
AXError error = AXUIElementCopyAttributeValue(systemWideElement,
|
|
(CFStringRef)NSAccessibilityFocusedUIElementAttribute, (CFTypeRef*)&focussedElement);
|
|
QVERIFY(!error);
|
|
QVERIFY(focussedElement);
|
|
TestAXObject *focusButton2 = [[TestAXObject alloc] initWithAXUIElementRef: focussedElement];
|
|
|
|
QVERIFY([[focusButton2 role] isEqualToString: NSAccessibilityButtonRole]);
|
|
QVERIFY([[focusButton2 title] isEqualToString: @"Button 2"]);
|
|
|
|
|
|
button1->setFocus();
|
|
error = AXUIElementCopyAttributeValue(systemWideElement,
|
|
(CFStringRef)NSAccessibilityFocusedUIElementAttribute, (CFTypeRef*)&focussedElement);
|
|
QVERIFY(!error);
|
|
QVERIFY(focussedElement);
|
|
TestAXObject *focusButton1 = [[TestAXObject alloc] initWithAXUIElementRef: focussedElement];
|
|
QVERIFY([[focusButton1 role] isEqualToString: NSAccessibilityButtonRole]);
|
|
QVERIFY([[focusButton1 title] isEqualToString: @"I am a button"]);
|
|
}
|
|
|
|
void tst_QAccessibilityMac::notificationsTest()
|
|
{
|
|
auto *w = m_window;
|
|
QLineEdit *le1 = new QLineEdit(w);
|
|
QLineEdit *le2 = new QLineEdit(w);
|
|
w->layout()->addWidget(le1);
|
|
w->layout()->addWidget(le2);
|
|
|
|
QCoreApplication::processEvents();
|
|
QTest::qWait(100);
|
|
|
|
TestAXObject *appObject = [TestAXObject getApplicationAXObject];
|
|
QVERIFY(appObject);
|
|
|
|
// one window
|
|
QTRY_VERIFY(appObject.windowList.count == 1);
|
|
AXUIElementRef windowRef = (AXUIElementRef) [appObject.windowList objectAtIndex: 0];
|
|
QVERIFY(windowRef != nil);
|
|
TestAXObject *window = [[TestAXObject alloc] initWithAXUIElementRef: windowRef];
|
|
|
|
AXUIElementRef lineEdit1 = [window findDirectChildByRole: kAXTextFieldRole];
|
|
QVERIFY(lineEdit1 != nil);
|
|
|
|
AXObserverRef observer = 0;
|
|
AXError err = AXObserverCreate(getpid(), observerCallback, &observer);
|
|
QVERIFY(!err);
|
|
AXObserverAddNotification(observer, appObject.ref, kAXFocusedUIElementChangedNotification, 0);
|
|
AXObserverAddNotification(observer, lineEdit1, kAXValueChangedNotification, 0);
|
|
|
|
CFRunLoopAddSource( [[NSRunLoop currentRunLoop] getCFRunLoop], AXObserverGetRunLoopSource(observer), kCFRunLoopDefaultMode);
|
|
|
|
QVERIFY(notificationList.length() == 0);
|
|
le2->setFocus();
|
|
QTRY_VERIFY(notificationList.length() == 1);
|
|
QTRY_VERIFY(notificationList.at(0) == QAccessible::Focus);
|
|
le1->setFocus();
|
|
QTRY_VERIFY(notificationList.length() == 2);
|
|
QTRY_VERIFY(notificationList.at(1) == QAccessible::Focus);
|
|
le1->setText("hello");
|
|
QTRY_VERIFY(notificationList.length() == 3);
|
|
QTRY_VERIFY(notificationList.at(2) == QAccessible::ValueChanged);
|
|
le1->setText("foo");
|
|
QTRY_VERIFY(notificationList.length() == 4);
|
|
QTRY_VERIFY(notificationList.at(3) == QAccessible::ValueChanged);
|
|
}
|
|
|
|
void tst_QAccessibilityMac::checkBoxTest()
|
|
{
|
|
QCheckBox *ckBox = new QCheckBox(m_window);
|
|
ckBox->setText("Great option");
|
|
m_window->addWidget(ckBox);
|
|
QVERIFY(QTest::qWaitForWindowExposed(m_window));
|
|
QCoreApplication::processEvents();
|
|
|
|
TestAXObject *appObject = [TestAXObject getApplicationAXObject];
|
|
QVERIFY(appObject);
|
|
|
|
// one window
|
|
QTRY_VERIFY(appObject.windowList.count == 1);
|
|
AXUIElementRef windowRef = (AXUIElementRef) [appObject.windowList objectAtIndex: 0];
|
|
QVERIFY(windowRef != nil);
|
|
TestAXObject *window = [[TestAXObject alloc] initWithAXUIElementRef: windowRef];
|
|
|
|
// children of window:
|
|
AXUIElementRef checkBox = [window findDirectChildByRole: kAXCheckBoxRole];
|
|
QVERIFY(checkBox != nil);
|
|
|
|
TestAXObject *cb = [[TestAXObject alloc] initWithAXUIElementRef: checkBox];
|
|
|
|
// here start actual checkbox tests
|
|
QVERIFY([cb valueNumber] == 0);
|
|
QVERIFY([cb.title isEqualToString:@"Great option"]);
|
|
// EXPECT(cb.description == nil); // currently returns "" instead of nil
|
|
|
|
QVERIFY([cb.actions containsObject:(NSString*)kAXPressAction]);
|
|
|
|
[cb performAction:kAXPressAction];
|
|
QVERIFY([cb valueNumber] == 1);
|
|
|
|
[cb performAction:kAXPressAction];
|
|
QVERIFY([cb valueNumber] == 0);
|
|
|
|
ckBox->setCheckState(Qt::PartiallyChecked);
|
|
QVERIFY([cb valueNumber] == 2);
|
|
}
|
|
|
|
void tst_QAccessibilityMac::tableViewTest()
|
|
{
|
|
QTableWidget *tw = new QTableWidget(3, 2, m_window);
|
|
struct Person
|
|
{
|
|
const char *name;
|
|
const char *address;
|
|
};
|
|
const Person contents[] = { { "Socrates", "Greece" },
|
|
{ "Confucius", "China" },
|
|
{ "Kant", "Preussia" }
|
|
};
|
|
for (int i = 0; i < int(sizeof(contents) / sizeof(Person)); ++i) {
|
|
Person p = contents[i];
|
|
QTableWidgetItem *name = new QTableWidgetItem(QString::fromLatin1(p.name));
|
|
tw->setItem(i, 0, name);
|
|
QTableWidgetItem *address = new QTableWidgetItem(QString::fromLatin1(p.address));
|
|
tw->setItem(i, 1, address);
|
|
}
|
|
m_window->addWidget(tw);
|
|
QVERIFY(QTest::qWaitForWindowExposed(m_window));
|
|
QCoreApplication::processEvents();
|
|
|
|
TestAXObject *appObject = [TestAXObject getApplicationAXObject];
|
|
QVERIFY(appObject);
|
|
|
|
NSArray *windowList = [appObject windowList];
|
|
// one window
|
|
QVERIFY([windowList count] == 1);
|
|
AXUIElementRef windowRef = (AXUIElementRef)[windowList objectAtIndex:0];
|
|
QVERIFY(windowRef != nil);
|
|
TestAXObject *window = [[TestAXObject alloc] initWithAXUIElementRef:windowRef];
|
|
|
|
// children of window:
|
|
AXUIElementRef tableView = [window findDirectChildByRole:kAXTableRole];
|
|
QVERIFY(tableView != nil);
|
|
|
|
TestAXObject *tv = [[TestAXObject alloc] initWithAXUIElementRef:tableView];
|
|
|
|
// here start actual tableview tests
|
|
// Should have 2 columns
|
|
const unsigned int columnCount = 2;
|
|
NSArray *columnArray = [tv tableColumns];
|
|
QCOMPARE([columnArray count], columnCount);
|
|
|
|
// should have 3 rows
|
|
const unsigned int rowCount = 3;
|
|
NSArray *rowArray = [tv tableRows];
|
|
QCOMPARE([rowArray count], rowCount);
|
|
|
|
// The individual cells are children of the rows
|
|
TestAXObject *row = [[TestAXObject alloc] initWithAXUIElementRef:(AXUIElementRef)rowArray[0]];
|
|
TestAXObject *cell = [[TestAXObject alloc] initWithAXUIElementRef:(AXUIElementRef)[row childList][0]];
|
|
QVERIFY([cell.title isEqualToString:@"Socrates"]);
|
|
row = [[TestAXObject alloc] initWithAXUIElementRef:(AXUIElementRef)rowArray[2]];
|
|
cell = [[TestAXObject alloc] initWithAXUIElementRef:(AXUIElementRef)[row childList][1]];
|
|
QVERIFY([cell.title isEqualToString:@"Preussia"]);
|
|
|
|
// both rows and columns are direct children of the table
|
|
NSArray *childList = [tv childList];
|
|
QCOMPARE([childList count], columnCount + rowCount);
|
|
for (id child in childList) {
|
|
TestAXObject *childObject = [[TestAXObject alloc] initWithAXUIElementRef:(AXUIElementRef)child];
|
|
QVERIFY([childObject.role isEqualToString:NSAccessibilityRowRole] ||
|
|
[childObject.role isEqualToString:NSAccessibilityColumnRole]);
|
|
}
|
|
}
|
|
|
|
void tst_QAccessibilityMac::treeViewTest()
|
|
{
|
|
QTreeWidget *tw = new QTreeWidget;
|
|
tw->setColumnCount(2);
|
|
QTreeWidgetItem *root = new QTreeWidgetItem(tw, {"/", "0"});
|
|
root->setExpanded(false);
|
|
QTreeWidgetItem *users = new QTreeWidgetItem(root,{ "Users", "1"});
|
|
(void)new QTreeWidgetItem(root, {"Applications", "2"});
|
|
QTreeWidgetItem *lastChild = new QTreeWidgetItem(root, {"Libraries", "3"});
|
|
|
|
m_window->addWidget(tw);
|
|
QVERIFY(QTest::qWaitForWindowExposed(m_window));
|
|
QCoreApplication::processEvents();
|
|
|
|
TestAXObject *appObject = [TestAXObject getApplicationAXObject];
|
|
QVERIFY(appObject);
|
|
|
|
NSArray *windowList = [appObject windowList];
|
|
// one window
|
|
QVERIFY([windowList count] == 1);
|
|
AXUIElementRef windowRef = (AXUIElementRef)[windowList objectAtIndex:0];
|
|
QVERIFY(windowRef != nil);
|
|
TestAXObject *window = [[TestAXObject alloc] initWithAXUIElementRef:windowRef];
|
|
|
|
// children of window
|
|
AXUIElementRef treeView = [window findDirectChildByRole:kAXOutlineRole];
|
|
QVERIFY(treeView != nil);
|
|
|
|
TestAXObject *tv = [[TestAXObject alloc] initWithAXUIElementRef:treeView];
|
|
|
|
// here start actual treeview tests. NSAccessibilityOutline is a specialization
|
|
// of NSAccessibilityTable, and we represent trees as tables.
|
|
// Should have 2 columns
|
|
const unsigned int columnCount = 2;
|
|
NSArray *columnArray = [tv tableColumns];
|
|
QCOMPARE([columnArray count], columnCount);
|
|
|
|
// should have 1 row for now - as long as the root item is not expanded
|
|
NSArray *rowArray = [tv tableRows];
|
|
QCOMPARE(int([rowArray count]), 1);
|
|
|
|
root->setExpanded(true);
|
|
rowArray = [tv tableRows];
|
|
QCOMPARE(int([rowArray count]), root->childCount() + 1);
|
|
|
|
// this should not trigger any assert
|
|
tw->setCurrentItem(lastChild);
|
|
|
|
bool errorOccurred = false;
|
|
|
|
const auto cellText = [rowArray, &errorOccurred](int rowIndex, int columnIndex) -> QString {
|
|
TestAXObject *row = [[TestAXObject alloc] initWithAXUIElementRef:(AXUIElementRef)rowArray[rowIndex]];
|
|
Q_ASSERT(row);
|
|
TestAXObject *cell = [[TestAXObject alloc] initWithAXUIElementRef:(AXUIElementRef)[row childList][columnIndex]];
|
|
Q_ASSERT(cell);
|
|
const QString result = QString::fromNSString(cell.title);
|
|
errorOccurred = cell.errorOccurred;
|
|
return result;
|
|
};
|
|
|
|
QString text = cellText(0, 0);
|
|
if (errorOccurred)
|
|
QSKIP("Cocoa Accessibility API error, aborting");
|
|
QCOMPARE(text, root->text(0));
|
|
QCOMPARE(cellText(1, 0), users->text(0));
|
|
QCOMPARE(cellText(1, 1), users->text(1));
|
|
}
|
|
|
|
QTEST_MAIN(tst_QAccessibilityMac)
|
|
#include "tst_qaccessibilitymac.moc"
|