/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtTest/QtTest>

#if defined(Q_WS_MAC) && !defined (QT_MAC_USE_COCOA)

#include <private/qt_mac_p.h>
#undef verify // yes, lets reserve the word "verify"

#include <QApplication>
#include <QDebug>
#include <QTimer>
#include <QString>
#include <QFile>
#include <QVariant>
#include <QPushButton>
#include <QToolBar>
#include <QSlider>
#include <QListWidget>
#include <QTableWidget>
#include <QScrollArea>
#include <QLabel>
#include <QScrollBar>
#include <QTextEdit>
#include <QAccessibleInterface>
#include <QAccessible>
#include <QPluginLoader>
#include <private/qaccessible_mac_p.h>
#include <quiloader.h>

#include <sys/types.h> // for getpid()
#include <unistd.h>

Q_DECLARE_METATYPE(AXUIElementRef);

typedef QCFType<CFArrayRef> QCFArrayRef;

class tst_qaccessibility_mac : public QObject
{
Q_OBJECT
public slots:
    void printInfo();
    void testForm();
    void testButtons();
    void testLineEdit();
    void testLabel();
    void testGroups();
    void testTabWidget();
    void testTabBar();
    void testComboBox();
    void testDeleteWidget();
    void testDeleteWidgets();
    void testMultipleWindows();
    void testHiddenWidgets();
    void testActions();
    void testChangeState();
    void testSlider();
    void testScrollArea();
    void testListView();
    void testTableView();
    void testScrollBar();
    void testSplitter();
    void testTextEdit();
    void testItemViewsWithoutModel();
private slots:
    void testQAElement();
    void testQAInterface();

    // ui tests load an .ui file.
    void uitests_data();
    void uitests();
    
    void tests_data();
    void tests();
private:
    void runTest(const QString &testSlot);
};

/*
    VERIFYs that there is no error and prints an error message if there is.
*/
void testError(AXError error, const QString &text)
{
    if (error)
        qDebug() << "Error" << error << text;
    QVERIFY(error == 0);
}

/*
    Prints an CFArray holding CFStrings.
*/
void printCFStringArray(CFArrayRef array, const QString &title)
{
    const int numElements = CFArrayGetCount(array);
    qDebug() << "num" << title << " " <<  numElements;

    for (int i = 0; i < numElements; ++i) {
       CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(array, i);
       qDebug() << QCFString::toQString(str);
    }
}

QStringList toQStringList(const CFArrayRef array)
{
    const int numElements = CFArrayGetCount(array);
    QStringList qtStrings;
    
    for (int i = 0; i < numElements; ++i) {
        CFStringRef str = (CFStringRef)CFArrayGetValueAtIndex(array, i);
        qtStrings.append(QCFString::toQString(str));
    }
    
    return qtStrings;
}

QVariant AXValueToQVariant(AXValueRef value)
{
    QVariant var;
    const AXValueType type = AXValueGetType(value);
    switch (type) {
        case kAXValueCGPointType : {
            CGPoint point;
            if (AXValueGetValue(value, type, &point))
                var = QPointF(point.x, point.y);
        } break;
        case kAXValueCGSizeType : {
            CGSize size;
            if (AXValueGetValue(value, type, &size))
                var = QSizeF(size.width, size.height);
        } break;
        case kAXValueCGRectType :  {
            CGRect rect;
            if (AXValueGetValue(value, type, &rect))
                var = QRectF(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
        } break;
        case kAXValueCFRangeType :
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
        case kAXValueAXErrorType :
#endif
        case kAXValueIllegalType :
        default:
        qDebug() << "Illegal/Unsuported AXValue:" << type;
        break;
    };
    return var;
}

/*
    Converts a CFTypeRef to a QVariant, for certain selected types. Prints
    an error message and returns QVariant() if the type is not supported.
*/
QVariant CFTypeToQVariant(CFTypeRef value)
{
    QVariant var;
    if (value == 0)
        return var;
    const uint typeID = CFGetTypeID(value);
    if (typeID == CFStringGetTypeID()) {
        var.setValue(QCFString::toQString((CFStringRef)value));
    } else if (typeID == CFBooleanGetTypeID()) {
        var.setValue((bool)CFBooleanGetValue((CFBooleanRef)value));
    } else if (typeID == AXUIElementGetTypeID()) {
        var.setValue((AXUIElementRef)value);
    } else if (typeID == AXValueGetTypeID()) {
        var = AXValueToQVariant((AXValueRef)value);
    } else if (typeID == CFNumberGetTypeID()) {
        CFNumberRef number = (CFNumberRef)value;
        if (CFNumberGetType(number) != kCFNumberSInt32Type)
            qDebug() << "unsupported number type" << CFNumberGetType(number);
        int theNumber;
        CFNumberGetValue(number, kCFNumberSInt32Type, &theNumber);
        var.setValue(theNumber);
    } else if (typeID == CFArrayGetTypeID()) {
        CFArrayRef cfarray = static_cast<CFArrayRef>(value);
        QVariantList list;
        CFIndex size = CFArrayGetCount(cfarray);
        for (CFIndex i = 0; i < size; ++i)
            list << CFTypeToQVariant(CFArrayGetValueAtIndex(cfarray, i));
        var.setValue(list);
    } else {
        QCFString str = CFCopyTypeIDDescription(typeID);
        qDebug() << "Unknown CFType: " << typeID << (QString)str;
    }
    return var;
}

/*
    Tests if a given attribute is supported by an element. Expects either
    no error or error -25205 (Not supported). Causes a test failure
    on other error values.
*/
bool supportsAttribute(AXUIElementRef element, CFStringRef attribute)
{
    CFArrayRef array;
    AXError err = AXUIElementCopyAttributeNames(element, &array);
    if (err) {
        testError(err, QLatin1String("unexpected error when testing for supported attribute") + QCFString::toQString(attribute));
        return false;
    }
    CFRange range;
    range.location = 0;
    range.length = CFArrayGetCount(array);
    return CFArrayContainsValue(array, range, attribute);
}

/*
    Returns the accessibility attribute specified with attribute in a QVariant
*/
QVariant attribute(AXUIElementRef element, CFStringRef attribute)
{
    CFTypeRef value = 0;
    AXError err = AXUIElementCopyAttributeValue(element, attribute, &value);

    testError(err, QString("Error getting element attribute ") + QCFString::toQString(attribute));

    if (err)
        return QVariant();

    return CFTypeToQVariant(value);
}

/*
    Returns the title for an element.
*/
QString title(AXUIElementRef element)
{
    return attribute(element, kAXTitleAttribute).toString();
}

/*
    Returns the role for an element.
*/
QString role(AXUIElementRef element)
{
    return attribute(element, kAXRoleAttribute).toString();
}

/*
    Returns the subrole for an element.
*/
QString subrole(AXUIElementRef element)
{
    return attribute(element, kAXSubroleAttribute).toString();
}

/*
    Returns the role description for an element.
*/
QString roleDescription(AXUIElementRef element)
{
    return attribute(element, kAXRoleDescriptionAttribute).toString();
}

/*
    Returns the enabled attribute for an element.
*/
bool enabled(AXUIElementRef element)
{
    return attribute(element, kAXEnabledAttribute).toBool();
}

/*
    Returns the value attribute for an element as an QVariant.
*/
QVariant value(AXUIElementRef element)
{
    return attribute(element, kAXValueAttribute);
}

QVariant value(QAElement element)
{
    return value(element.element());
}

/*
    Returns the description attribute for an element as an QVariant.
*/
QVariant description(AXUIElementRef element)
{
    return attribute(element, kAXDescriptionAttribute);
}

/*
    Returns the value attribute for an element as an bool.
*/
bool boolValue(AXUIElementRef element)
{
    return attribute(element, kAXValueAttribute).toBool();
}

/*
    Returns the parent for an element
*/
AXUIElementRef parent(AXUIElementRef element)
{
    return attribute(element, kAXParentAttribute).value<AXUIElementRef>();
}

/*
    Returns the (top-level) window(not a sheet or a drawer) for an element
*/
AXUIElementRef window(AXUIElementRef element)
{
    return attribute(element, kAXWindowAttribute).value<AXUIElementRef>();
}

/*
    Returns the (top-level) UI element(can also be a sheet or drawer) for an element
*/
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
AXUIElementRef topLevelUIElement(AXUIElementRef element)
{
    return attribute(element, kAXTopLevelUIElementAttribute).value<AXUIElementRef>();
}
#endif

/*
    Returns thie size of the element.
*/
QSizeF size(AXUIElementRef element)
{
    return attribute(element, kAXSizeAttribute).value<QSizeF>();
}

/*
    Returns the position of the element.
*/
QPointF position(AXUIElementRef element)
{
    return attribute(element, kAXPositionAttribute).value<QPointF>();
}

/*
    Returns the rect of the element.
*/
QRectF rect(AXUIElementRef element)
{
    return QRectF(position(element), size(element));
}

bool above(AXUIElementRef a, AXUIElementRef b)
{
    return (position(a).y() + size(a).height() <= position(b).y());
}

bool contains(AXUIElementRef a, AXUIElementRef b)
{
    return rect(a).contains(rect(b));
}

QList<AXUIElementRef> tabs(AXUIElementRef element)
{
    CFTypeRef value;
    AXError err = AXUIElementCopyAttributeValue(element, kAXTabsAttribute, &value);
    if (err)
        return QList<AXUIElementRef>();
        
    CFArrayRef array = (CFArrayRef)value;
    QList<AXUIElementRef> elements;
    const int count = CFArrayGetCount(array);
    for (int i = 0; i < count; ++i)
        elements.append((AXUIElementRef)CFArrayGetValueAtIndex(array, i));
   
    return elements;
}

QList<AXUIElementRef> elementListAttribute(AXUIElementRef element, CFStringRef attributeName)
{
    QList<AXUIElementRef> elementList;
    QVariantList variants = attribute(element, attributeName).value<QVariantList>();
    foreach(QVariant variant, variants)
        elementList.append(variant.value<AXUIElementRef>());
    return elementList;
}

AXUIElementRef elementAttribute(AXUIElementRef element, CFStringRef attributeName)
{
    return attribute(element, attributeName).value<AXUIElementRef>();
}

QString stringAttribute(AXUIElementRef element, CFStringRef attributeName)
{
    return attribute(element, attributeName).value<QString>();
}


/*
    Returns the UIElement at the given position.
*/
AXUIElementRef childAtPoint(QPointF position)
{
    AXUIElementRef element = 0;
    const AXError err = AXUIElementCopyElementAtPosition(AXUIElementCreateApplication(getpid()), position.x(), position.y(), &element);
    if (err) {
        qDebug() << "Error getting element at " << position;
        return 0;
    }

    return element;
}

/*
    Returns a QStringList containing the names of the actions the ui element supports
*/
QStringList actionNames(AXUIElementRef element)
{
    CFArrayRef cfStrings;
    const AXError err = AXUIElementCopyActionNames(element, &cfStrings);
    testError(err, "Unable to get action names");
    return toQStringList(cfStrings);
}

bool supportsAction(const AXUIElementRef element, const QString &actionName)
{
    const QStringList actions = actionNames(element);
    return actions.contains(actionName);
}

bool performAction(const AXUIElementRef element, const QString &actionName)
{
    const AXError err = AXUIElementPerformAction(element, QCFString(actionName));
    return (err == 0);
}

/*
    Om 10.4 and up, verifyes the AXRoleDescription attribute for an element,
    on 10.3 and below this test always passes.

    The reason for this is that the HICopyAccessibilityRoleDescription call
    used to implement this functionality was introduced in 10.4.
*/
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4)
    #define VERIFY_ROLE_DESCRIPTION(ELEMENT,  TEXT) \
            QCOMPARE(roleDescription(ELEMENT), QString(TEXT))
#else
    #define VERIFY_ROLE_DESCRIPTION(ELEMENT,  TEXT) QVERIFY(true)
#endif


CFArrayRef childrenArray(AXUIElementRef element)
{
    CFTypeRef value = 0;
    AXError err = AXUIElementCopyAttributeValue(element, kAXChildrenAttribute, &value);
    if (!err && CFGetTypeID(value) == CFArrayGetTypeID()) {
        return (CFArrayRef)value;
    }
   
    return CFArrayCreate(0,0,0,0);
}

/*
    Gest the child count from an element.
*/
int numChildren(AXUIElementRef element)
{
    return CFArrayGetCount(childrenArray(element));
}

/*
    Gets the child with index childIndex from element. Returns 0 if not found.
*/
AXUIElementRef child(AXUIElementRef element, int childIndex)
{
    CFArrayRef children = childrenArray(element);
    if (childIndex >= CFArrayGetCount(children))
        return 0;

    const void *data  = CFArrayGetValueAtIndex(children, childIndex);
    return (AXUIElementRef)data;
}

/*
    Gets the child titled childTitle from element. Returns 0 if not found.
*/
AXUIElementRef childByTitle(AXUIElementRef element, const QString &childTitle)
{
    CFArrayRef children  = childrenArray(element);
    const int numChildren = CFArrayGetCount(children);
    for (int i = 0; i < numChildren; ++i) {
        const AXUIElementRef childElement = (AXUIElementRef)CFArrayGetValueAtIndex(children, i);
        // Test for support for title attribute before getting it to avoid test fail.
        if (supportsAttribute(childElement, kAXTitleAttribute) && title(childElement) == childTitle)
            return childElement;
    }
    return 0;
}

/*
    Gets the child with the given value from element. Returns 0 if not found.
*/
AXUIElementRef childByValue(AXUIElementRef element, const QVariant &testValue)
{
    CFArrayRef children  = childrenArray(element);
    const int numChildren = CFArrayGetCount(children);
    for (int i = 0; i < numChildren; ++i) {
        const AXUIElementRef childElement = (AXUIElementRef)CFArrayGetValueAtIndex(children, i);
        // Test for support for value attribute before getting it to avoid test fail.
        if (supportsAttribute(childElement, kAXValueAttribute) && value(childElement) == testValue)
            return childElement;
    }
    return 0;
}

/*
    Gets the child by role from element. Returns 0 if not found.
*/
AXUIElementRef childByRole(AXUIElementRef element, const QString &macRole)
{
    CFArrayRef children  = childrenArray(element);
    const int numChildren = CFArrayGetCount(children);
    for (int i = 0; i < numChildren; ++i) {
        const AXUIElementRef childElement = (AXUIElementRef)CFArrayGetValueAtIndex(children, i);
        if (role(childElement) == macRole)
            return childElement;
    }
    return 0;
}

void printTypeForAttribute(AXUIElementRef element, CFStringRef attribute)
{
    CFTypeRef value = 0;
    AXError err = AXUIElementCopyAttributeValue(element, attribute, &value);
    if (!err) {
        qDebug() << "type id" << CFGetTypeID(value);
        QCFString str = CFCopyTypeIDDescription(CFGetTypeID(value));
        qDebug() << (QString)str;
    } else {
        qDebug() << "Attribute Get error" << endl;
    }
}

int indent = 0;
QString space()
{
    QString space;
    for (int i = 0; i < indent; ++i) {
        space += " ";
    }
    return space;
}


/*
    Recursively prints acccesibility info for currentElement and all its children.
*/
void printElementInfo(AXUIElementRef currentElement)
{
    if (HIObjectIsAccessibilityIgnored(AXUIElementGetHIObject(currentElement))) {
        qDebug() << space() << "Ignoring element with role" << role(currentElement);
        return;
    }
    
    qDebug() << space() <<"Role" << role(currentElement);
    if (supportsAttribute(currentElement, kAXTitleAttribute))
        qDebug() << space() << "Title" << title(currentElement);
    else
        qDebug() << space() << "Title not supported";
    
    if (supportsAttribute(currentElement, kAXValueAttribute))
        qDebug() << space() << "Value" << attribute(currentElement, kAXValueAttribute);
    else
        qDebug() << space() << "Value not supported";
    
    qDebug() << space() << "Number of children" << numChildren(currentElement);
    for (int i = 0; i < numChildren(currentElement); ++i) {
        AXUIElementRef childElement = child(currentElement, i);
        // Skip the menu bar.
        if (role(childElement) != "AXMenuBar") {
            indent+= 4;
            printElementInfo(childElement);
            indent-= 4;
        }
    }
    qDebug() << " ";
}

/*
    Recursively prints the child interfaces belonging to interface.
*/

void printChildren(const QAInterface &interface)
{
    if (interface.isValid() == false)
        return;
        
    QList<QAInterface> children = interface.children();
    if (children.isEmpty())
        return;

    qDebug() << "## Children for" << interface;
    foreach (const QAInterface &child, children) {
        qDebug() << child << "index in parent" << interface.indexOfChild(child);
    }
    foreach (const QAInterface &child, children) {
        printChildren(child);
    }
}

bool isIgnored(AXUIElementRef currentElement)
{
    return HIObjectIsAccessibilityIgnored(AXUIElementGetHIObject(currentElement));
}

bool equal(CFTypeRef o1, CFTypeRef o2)
{
    if (o1 == 0 || o2 == 0)
        return false;
    return CFEqual(o1, o2);
}

/*
    Verifies basic element info.
*/
#define VERIFY_ELEMENT(element, _parent, _role) \
    QVERIFY(element != 0); \
    QVERIFY(role(element) == _role); \
    QVERIFY(equal(::parent(element), _parent));
/*
    Verifies that the application and the main form is there has the right info.
*/
void testAppAndForm(AXUIElementRef application)
{
    QVERIFY(title(application) == "tst_qaccessibility_mac");
    QVERIFY(role(application) == "AXApplication");

    AXUIElementRef form = childByTitle(application, "Form");
    VERIFY_ELEMENT(form, application, "AXWindow");
}

void tst_qaccessibility_mac::printInfo()
{
    AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
    printElementInfo(currentApplication);
}

/*
    Tests for form.ui
*/
void tst_qaccessibility_mac::testForm()
{
    // Get reference to the current application.
    AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
    testAppAndForm(currentApplication);
    childByTitle(currentApplication, "Form");
}

/*
    Tests for buttons.ui
*/
void tst_qaccessibility_mac::testButtons()
{
    // Get reference to the current application.
    AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
    testAppAndForm(currentApplication);
    AXUIElementRef form = childByTitle(currentApplication, "Form");

    AXUIElementRef ren = childByTitle(form, "Ren");
    VERIFY_ELEMENT(ren, form, "AXButton");
    QVERIFY(enabled(ren) == true);
    VERIFY_ROLE_DESCRIPTION(ren, "button");

    AXUIElementRef stimpy = childByTitle(form, "Stimpy");
    VERIFY_ELEMENT(stimpy, form, "AXRadioButton");
    QVERIFY(enabled(stimpy) == true);
    QVERIFY(value(stimpy).toInt() == 1); // checked;
    VERIFY_ROLE_DESCRIPTION(stimpy, "radio button");

    AXUIElementRef pinky = childByTitle(form, "Pinky");
    VERIFY_ELEMENT(pinky, form, "AXCheckBox");
    QVERIFY(enabled(pinky) == false);
    QVERIFY(value(pinky).toInt() == 0); // unchecked;
    VERIFY_ROLE_DESCRIPTION(pinky, "check box");

    AXUIElementRef brain = childByTitle(form, "Brain");
    VERIFY_ELEMENT(brain, form, "AXButton");
    VERIFY_ROLE_DESCRIPTION(brain, "button");
}

void tst_qaccessibility_mac::testLabel()
{
    // Get reference to the current application.
    AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());

    testAppAndForm(currentApplication);
    AXUIElementRef form = childByTitle(currentApplication, "Form");
    AXUIElementRef label = childByValue(form, "This is a Text Label");
    QVERIFY(label);
    VERIFY_ELEMENT(label, form, "AXStaticText");
    VERIFY_ROLE_DESCRIPTION(label, "text");
    QCOMPARE(supportsAttribute(label, kAXDescriptionAttribute), false);
}

/*
    Tests for lineedit.ui
*/
void tst_qaccessibility_mac::testLineEdit()
{
    // Get reference to the current application.
    AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());

    testAppAndForm(currentApplication);
    AXUIElementRef form = childByTitle(currentApplication, "Form");
    AXUIElementRef lineEdit = childByValue(form, "Line edit");
    VERIFY_ELEMENT(lineEdit, form, "AXTextField");
    VERIFY_ROLE_DESCRIPTION(lineEdit, "text field");
}

/*
    Tests for groups.ui
*/
void tst_qaccessibility_mac::testGroups()
{
    // Get reference to the current application.
    AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());

    testAppAndForm(currentApplication);
    AXUIElementRef form = childByTitle(currentApplication, "Form");

    AXUIElementRef groupA = childByTitle(form, "Group A");
    VERIFY_ELEMENT(groupA, form, "AXGroup");
    AXUIElementRef button1 = childByTitle(groupA, "PushButton 1");
    VERIFY_ELEMENT(button1, groupA, "AXButton");
    VERIFY_ROLE_DESCRIPTION(groupA, "group");

    AXUIElementRef groupB = childByTitle(form, "Group B");
    VERIFY_ELEMENT(groupB, form, "AXGroup");
    AXUIElementRef button3 = childByTitle(groupB, "PushButton 3");
    VERIFY_ELEMENT(button3, groupB, "AXButton");
}

/*
    Tests for tabs.ui
*/
void tst_qaccessibility_mac::testTabWidget()
{
    {   // Test that the QTabWidget hierarchy is what we expect it to be.
        QTabWidget tabWidget;
        tabWidget.show();
        QAInterface interface = QAccessible::queryAccessibleInterface(&tabWidget);
        tabWidget.addTab(new QPushButton("Foo"), "FooTab");
        tabWidget.addTab(new QPushButton("Bar"), "BarTab");
        QCOMPARE(interface.childCount(), 2);
        const QList<QAInterface> children = interface.children();
        QVERIFY(children.at(0).object()->inherits("QStackedWidget"));
        QVERIFY(children.at(1).object()->inherits("QTabBar"));
        
        const QList<QAInterface> tabBarChildren = children.at(1).children();
        QCOMPARE(tabBarChildren.count(), 4);
        QCOMPARE(tabBarChildren.at(0).text(QAccessible::Name), QLatin1String("FooTab"));
        QCOMPARE(tabBarChildren.at(1).text(QAccessible::Name), QLatin1String("BarTab"));
        QCOMPARE(tabBarChildren.at(0).role(), QAccessible::PageTab);
        QCOMPARE(tabBarChildren.at(1).role(), QAccessible::PageTab);

        // Check that the hierarchy manager is able to register the tab bar children.
        QAccessibleHierarchyManager *manager = QAccessibleHierarchyManager::instance();
        QAInterface tabBarInterface = children.at(1);
        QAElement tabBarElement = manager->registerInterface(tabBarInterface);
        QCOMPARE(manager->lookup(tabBarElement).childCount(), 4);
        manager->registerChildren(tabBarInterface);
        QAElement tabButtonElement = manager->lookup(tabBarChildren.at(1));
        QAInterface tabButtonInterface = manager->lookup(tabButtonElement);
        QCOMPARE(tabButtonInterface.text(QAccessible::Name), QLatin1String("BarTab"));
        QVERIFY(isItInteresting(tabButtonInterface) == true);
    }

    // Get reference to the current application.
    AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());

    testAppAndForm(currentApplication);
    const QString formTitle = "Form";
    AXUIElementRef form = childByTitle(currentApplication, formTitle);
    QVERIFY(form);

    const QString tabRole = "AXTabGroup";
    AXUIElementRef tabGroup = childByRole(form, tabRole);
    QVERIFY(tabGroup);

    // Test that we have three child buttons (the tab buttons + plus the contents of the first tab)
    const int numChildren = ::numChildren(tabGroup);
    QCOMPARE(numChildren, 3);
    
    const QString tab1Title = "Tab 1";
    AXUIElementRef tabButton1 = childByTitle(tabGroup, tab1Title);
    QVERIFY (tabButton1);
    VERIFY_ELEMENT(tabButton1, tabGroup, "AXRadioButton");
    QCOMPARE(title(tabButton1), tab1Title);

    const QString tab2Title = "Tab 2";
    const AXUIElementRef tabButton2 = childByTitle(tabGroup, tab2Title);
    QVERIFY(tabButton2);
    VERIFY_ELEMENT(tabButton2, tabGroup, "AXRadioButton");
    QCOMPARE(title(tabButton2), tab2Title);
    
    // Test that the window and top-level-ui-elment is the form.
    // Window is not reported properly on 10.5
    if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_5) {
        QVERIFY(equal(window(tabGroup), form));

    //   ### hangs on 10.4
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
    QVERIFY(equal(window(tabButton1), form));
#endif
    }
//   ### hangs on 10.4
#if (MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5)
    QVERIFY(equal(topLevelUIElement(tabGroup), form));
    QVERIFY(equal(topLevelUIElement(tabButton1), form));    
#endif
    // Test the bounding rectangles for the tab group and buttons.
    const QRectF groupRect(position(tabGroup), size(tabGroup));
    const QRectF tabButton1Rect(position(tabButton1), size(tabButton1));
    const QRectF tabButton2Rect(position(tabButton2), size(tabButton2));
    
    QVERIFY(groupRect.isNull() == false);
    QVERIFY(tabButton1Rect.isNull() == false);
    QVERIFY(tabButton1Rect.isNull() == false);
        
    QVERIFY(groupRect.contains(tabButton1Rect));
    QVERIFY(groupRect.contains(tabButton2Rect));
    QVERIFY(tabButton2Rect.contains(tabButton1Rect) == false);
    
    // Test the childAtPoint event.
    const AXUIElementRef childAtTab1Position = childAtPoint(position(tabButton1) + QPointF(5,5));
    QVERIFY(equal(childAtTab1Position, tabButton1));
    const AXUIElementRef childAtOtherPosition = childAtPoint(position(tabButton1) - QPointF(5,5));
    QVERIFY(equal(childAtOtherPosition, tabButton1) == false);

    // Test AXTabs attribute
    QVERIFY(supportsAttribute(tabGroup, kAXTabsAttribute));
    QList<AXUIElementRef> tabElements = tabs(tabGroup);
    QCOMPARE(tabElements.count(), 2);
    QVERIFY(equal(tabElements.at(0), tabButton1));
    QVERIFY(equal(tabElements.at(1), tabButton2));
    
    // Perform the press action on each child.
    for (int i = 0; i < numChildren; ++i) {
        const AXUIElementRef child = ::child(tabGroup, i);
        QVERIFY(supportsAction(child, "AXPress"));
        QVERIFY(performAction(child, "AXPress"));
    }
}

void tst_qaccessibility_mac::testTabBar()
{
    QTabBar tabBar;
    tabBar.addTab("Tab A");
    tabBar.addTab("Tab B");
    tabBar.show();

    AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
    AXUIElementRef window = childByRole(currentApplication, "AXWindow");
    QVERIFY(window);

    const QString tabRole = "AXTabGroup";
    AXUIElementRef tabGroup = childByRole(window, tabRole);
    QVERIFY(tabGroup);

    const int numChildren = ::numChildren(tabGroup);
    QCOMPARE(numChildren, 2);
    
    const QString tab1Title = "Tab A";
    AXUIElementRef tabButton1 = childByTitle(tabGroup, tab1Title);
    QVERIFY (tabButton1);
    VERIFY_ELEMENT(tabButton1, tabGroup, "AXRadioButton");
    QCOMPARE(title(tabButton1), tab1Title);

    const QString tab2Title = "Tab B";
    const AXUIElementRef tabButton2 = childByTitle(tabGroup, tab2Title);
    QVERIFY(tabButton2);
    VERIFY_ELEMENT(tabButton2, tabGroup, "AXRadioButton");
    QCOMPARE(title(tabButton2), tab2Title);

    // Test the childAtPoint event.
    const AXUIElementRef childAtTab1Position = childAtPoint(position(tabButton1) + QPointF(5,5));
    QVERIFY(equal(childAtTab1Position, tabButton1));
    const AXUIElementRef childAtOtherPosition = childAtPoint(position(tabButton1) - QPointF(5,5));
    QVERIFY(equal(childAtOtherPosition, tabButton1) == false);

    // Test AXTabs attribute
    QVERIFY(supportsAttribute(tabGroup, kAXTabsAttribute));
    QList<AXUIElementRef> tabElements = tabs(tabGroup);
    QCOMPARE(tabElements.count(), 2);
    QVERIFY(equal(tabElements.at(0), tabButton1));
    QVERIFY(equal(tabElements.at(1), tabButton2));
    
    // Perform the press action on each child.
    for (int i = 0; i < numChildren; ++i) {
        const AXUIElementRef child = ::child(tabGroup, i);
        QVERIFY(supportsAction(child, "AXPress"));
        QVERIFY(performAction(child, "AXPress"));
    }
}

void tst_qaccessibility_mac::testComboBox()
{
    // Get reference to the current application.
    AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());

    testAppAndForm(currentApplication);
    const QString formTitle = "Form";
    AXUIElementRef form = childByTitle(currentApplication, formTitle);
    
    const QString comboBoxRole = "AXPopUpButton";
    AXUIElementRef comboBox = childByRole(form, comboBoxRole);
    QVERIFY(comboBox != 0);
    QVERIFY(supportsAction(comboBox, "AXPress"));
    QVERIFY(performAction(comboBox, "AXPress"));
}

void tst_qaccessibility_mac::testDeleteWidget()
{
    const QString buttonTitle = "Hi there";
    QWidget *form = new QWidget(0, Qt::Window);
    form->setWindowTitle("Form");
    form->show();
    QPushButton *button = new QPushButton(buttonTitle, form);
    button->show();

    AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
    testAppAndForm(currentApplication);
    AXUIElementRef formElement = childByTitle(currentApplication, "Form");

    AXUIElementRef buttonElement = childByTitle(formElement, buttonTitle);
    QVERIFY(buttonElement);

    button->hide();
    delete button;

    buttonElement = childByTitle(formElement, buttonTitle);
    QVERIFY(!buttonElement);
    delete form;
}

void tst_qaccessibility_mac::testDeleteWidgets()
{
    const QString buttonTitle = "Hi there";
    const int repeats = 10;

    for (int i = 0; i < repeats; ++i) {

        QWidget *form = new QWidget(0, Qt::Window);
        form->setWindowTitle("Form");
        form->show();
    
        QPushButton *button = new QPushButton(buttonTitle, form);
        button->show();
    
        AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
        testAppAndForm(currentApplication);
        AXUIElementRef formElement = childByTitle(currentApplication, "Form");
        AXUIElementRef buttonElement = childByTitle(formElement, buttonTitle);
        QVERIFY(buttonElement);
        delete form;

        {
            AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
            QVERIFY(currentApplication);
            AXUIElementRef formElement = childByTitle(currentApplication, "Form");
            QVERIFY(!formElement);
        }
    }

    for (int i = 0; i < repeats; ++i) {
        QWidget *form = new QWidget(0, Qt::Window);
        form->setWindowTitle("Form");

    
        new QScrollBar(form);
        form->show();
    
        AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
        testAppAndForm(currentApplication);
        AXUIElementRef formElement = childByTitle(currentApplication, "Form");
        
        const AXUIElementRef scrollBarElement = childByRole(formElement, "AXScrollBar");
        QVERIFY(scrollBarElement);
        delete form;

        {
            AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
            QVERIFY(currentApplication);
            AXUIElementRef formElement = childByTitle(currentApplication, "Form");
            QVERIFY(!formElement);
        }
    }

    for (int i = 0; i < repeats; ++i) {
        QWidget *form = new QWidget(0, Qt::Window);
        form->setWindowTitle("Form");
    
        QListWidget *listWidget = new QListWidget(form);
        listWidget->addItem("Foo");
        listWidget->addItem("Bar");
        form->show();
    
        AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
        testAppAndForm(currentApplication);
        AXUIElementRef formElement = childByTitle(currentApplication, "Form");

        const AXUIElementRef scrollAreaElement = childByRole(formElement, "AXScrollArea");
        QVERIFY(scrollAreaElement);

        const AXUIElementRef listElement = childByRole(scrollAreaElement, "AXList");
        QVERIFY(listElement);
        delete form;

        {
            AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
            QVERIFY(currentApplication);
            AXUIElementRef formElement = childByTitle(currentApplication, "Form");
            QVERIFY(!formElement);
        }
    }

}

void tst_qaccessibility_mac::testMultipleWindows()
{
    const QString formATitle("FormA");
    const QString formBTitle("FormB");

    // Create a window
    QWidget *formA = new QWidget(0, Qt::Window);
    formA->setWindowTitle(formATitle);
    formA->show();

    // Test if we can access the window
    AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
    AXUIElementRef formAElement = childByTitle(currentApplication, formATitle);
    QVERIFY(formAElement);

    // Create another window
    QWidget *formB = new QWidget(0, Qt::Window);
    formB->setWindowTitle(formBTitle);
    formB->show();

    // Test if we can access both windows
    formAElement = childByTitle(currentApplication, formATitle);
    QVERIFY(formAElement);

    AXUIElementRef formBElement = childByTitle(currentApplication, formBTitle);
    QVERIFY(formBElement);
    
    delete formA;
}

void tst_qaccessibility_mac::testHiddenWidgets()
{
    const QString windowTitle ="a widget";
    QWidget * const window = new QWidget(0);
    window->setWindowTitle(windowTitle);
    window->show();

    const AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
    const AXUIElementRef windowElement = childByTitle(currentApplication, windowTitle);
    QVERIFY(windowElement);
    QCOMPARE(isIgnored(windowElement), false);
    
    const QString buttonTitle = "a button";
    QPushButton * const button = new QPushButton(window);    
    button->setText(buttonTitle);
    button->show();
    
    const AXUIElementRef buttonElement = childByTitle(windowElement, buttonTitle);
    QVERIFY(buttonElement);
    QCOMPARE(isIgnored(buttonElement), false);

    const QString toolbarTitle = "a toolbar";
    QToolBar * const toolbar = new QToolBar(toolbarTitle, window);
    toolbar->show();
    
    const AXUIElementRef toolBarElement = childByTitle(windowElement, toolbarTitle);
    QVERIFY(toolBarElement == 0);

    delete window;
};

void tst_qaccessibility_mac::testActions()
{
    // create a window with a push button
    const QString windowTitle ="a widget";
    QWidget * const window = new QWidget();
    window->setWindowTitle(windowTitle);
    window->show();

    const AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
    const AXUIElementRef windowElement = childByTitle(currentApplication, windowTitle);
    QVERIFY(windowElement);

    const QString buttonTitle = "a button";
    QPushButton * const button = new QPushButton(window);    
    button->setText(buttonTitle);
    button->show();
    
    const AXUIElementRef buttonElement = childByTitle(windowElement, buttonTitle);
    QVERIFY(buttonElement);

    // Verify that the button has the Press action.
    const QStringList actions = actionNames(buttonElement);
    const QString pressActionName("AXPress");
    QVERIFY(actions.contains(pressActionName));
    
    // Press button and check the pressed signal
    QSignalSpy pressed(button, SIGNAL(pressed()));
    QVERIFY(performAction(buttonElement, pressActionName));
    QCOMPARE(pressed.count(), 1);
    
    pressed.clear();
    QVERIFY(performAction(buttonElement, QString("does not exist")));
    QCOMPARE(pressed.count(), 0);

    delete window;
};

void tst_qaccessibility_mac::testChangeState()
{
    const QString windowTitle ="a widget";
    QWidget * const window = new QWidget();
    window->setWindowTitle(windowTitle);
    window->show();
 
    const AXUIElementRef applicationElement = AXUIElementCreateApplication(getpid());
    const AXUIElementRef windowElement = childByTitle(applicationElement, windowTitle);
    QVERIFY(windowElement);
    const int otherChildren = numChildren(windowElement);
    
    const QString buttonTitle = "Button";
    QPushButton * const button = new QPushButton(buttonTitle, window);
    button->setText(buttonTitle);  

    // Test that show/hide adds/removes the button from the hierachy.
    QVERIFY(childByTitle(windowElement, buttonTitle) == 0);
    QCOMPARE(numChildren(windowElement), otherChildren);
    button->show();
    QVERIFY(childByTitle(windowElement, buttonTitle) != 0);
    QCOMPARE(numChildren(windowElement), otherChildren + 1);
    button->hide();
    QVERIFY(childByTitle(windowElement, buttonTitle) == 0);
    QCOMPARE(numChildren(windowElement), otherChildren);
    button->show();
    QVERIFY(childByTitle(windowElement, buttonTitle) != 0);
    QCOMPARE(numChildren(windowElement), otherChildren + 1);

    // Test that hiding and showing a widget also removes and adds all its children.
    {
        QWidget * const parent = new QWidget(window);
        const int otherChildren = numChildren(windowElement);
        
        QPushButton * const child = new QPushButton(parent);
        const QString childButtonTitle = "child button";
        child->setText(childButtonTitle);
        
        parent->show();
        QVERIFY(childByTitle(windowElement, childButtonTitle) != 0);
        QCOMPARE(numChildren(windowElement), otherChildren + 1);

        parent->hide();
        QVERIFY(childByTitle(windowElement, childButtonTitle) == 0);
        QCOMPARE(numChildren(windowElement), otherChildren );

        parent->show();
        QVERIFY(childByTitle(windowElement, childButtonTitle) != 0);
        QCOMPARE(numChildren(windowElement), otherChildren + 1);
        
        delete parent;
    }

    // Test that the enabled attribute is updated after a call to setEnabled.
    const AXUIElementRef buttonElement = childByTitle(windowElement, buttonTitle);
    QVERIFY(enabled(buttonElement));
    button->setEnabled(false);
    QVERIFY(enabled(buttonElement) == false);
    button->setEnabled(true);
    QVERIFY(enabled(buttonElement));

    // Test that changing the title updates the accessibility information.
    const QString buttonTitle2 = "Button 2";    
    button->setText(buttonTitle2);
    QVERIFY(childByTitle(windowElement, buttonTitle2) != 0);
    QVERIFY(childByTitle(windowElement, buttonTitle) == 0);

    delete window;
}

void tst_qaccessibility_mac::testSlider()
{
    const QString windowTitle = "a widget";
    QWidget * const window = new QWidget();
    window->setWindowTitle(windowTitle);
    window->show();
 
    const AXUIElementRef applicationElement = AXUIElementCreateApplication(getpid());
    QVERIFY(applicationElement);
    const AXUIElementRef windowElement = childByTitle(applicationElement, windowTitle);
    QVERIFY(windowElement);
    const int windowChildren = numChildren(windowElement);
    
    QSlider * const slider = new QSlider(window);
    slider->show();
    const AXUIElementRef sliderElement = childByRole(windowElement, "AXSlider");
    QVERIFY(sliderElement);

    // Test that the slider and its children are removed from the hierachy when we call hide().
    QCOMPARE(numChildren(windowElement), windowChildren + 1);
    slider->hide();
    QCOMPARE(numChildren(windowElement), windowChildren);
    
    delete slider;
}

void tst_qaccessibility_mac::testScrollArea()
{
    QWidget window;
    const QString windowTitle = "window";
    window.setWindowTitle(windowTitle);
    window.resize(300, 300);

    QScrollArea scrollArea(&window);
    scrollArea.resize(300, 300);
    scrollArea.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);

    QLabel label;
    label.setText("Foo"); 
    scrollArea.setWidget(&label);

    window.show();

    // Verify that the QAinterface returns the correct children
    QAInterface interface = QAccessible::queryAccessibleInterface(&scrollArea);
    QCOMPARE(interface.childCount(), 3);
    
    QAInterface viewport = interface.navigate(QAccessible::Child, 1);
    QVERIFY(viewport.isValid());

    QAInterface scrollBarContainer1 = interface.navigate(QAccessible::Child, 2);
    QVERIFY(scrollBarContainer1.isValid());

    QAInterface scrollBar1 = scrollBarContainer1.navigate(QAccessible::Child, 1);

    QVERIFY(scrollBar1.isValid());
    QVERIFY(scrollBar1.role() == QAccessible::ScrollBar);

    QAInterface scrollBarContainer2 = interface.navigate(QAccessible::Child, 3);
    QVERIFY(scrollBarContainer1.isValid());

    QAInterface scrollBar2 = scrollBarContainer2.navigate(QAccessible::Child, 1);
    QVERIFY(scrollBar2.isValid());
    QVERIFY(scrollBar2.role() == QAccessible::ScrollBar);    

    // Navigate to the scroll area from the application
    const AXUIElementRef applicationElement = AXUIElementCreateApplication(getpid());
    QVERIFY(applicationElement);
    const AXUIElementRef windowElement = childByTitle(applicationElement, windowTitle);
    QVERIFY(windowElement);
    const AXUIElementRef scrollAreaElement = childByRole(windowElement, "AXScrollArea");
    QVERIFY(scrollAreaElement);

    // Get the scroll bars
    QVERIFY(supportsAttribute(scrollAreaElement, kAXHorizontalScrollBarAttribute));
    const AXUIElementRef horizontalScrollBar = elementAttribute(scrollAreaElement, kAXHorizontalScrollBarAttribute);
    QVERIFY(horizontalScrollBar);
    QVERIFY(role(horizontalScrollBar) == "AXScrollBar");
    QVERIFY(stringAttribute(horizontalScrollBar, kAXOrientationAttribute) == "AXHorizontalOrientation");

    QVERIFY(supportsAttribute(scrollAreaElement, kAXVerticalScrollBarAttribute));
    const AXUIElementRef verticalScrollBar = elementAttribute(scrollAreaElement, kAXVerticalScrollBarAttribute);
    QVERIFY(verticalScrollBar);
    QVERIFY(role(verticalScrollBar) == "AXScrollBar");
    QVERIFY(stringAttribute(verticalScrollBar, kAXOrientationAttribute) == "AXVerticalOrientation");

    // Get the contents and verify that we get the label.
    QVERIFY(supportsAttribute(scrollAreaElement, kAXContentsAttribute));
    const QList<AXUIElementRef> contents = elementListAttribute(scrollAreaElement, kAXContentsAttribute);
    QCOMPARE(contents.count(), 1);
    AXUIElementRef content = contents.at(0);
    QVERIFY(role(content) == "AXStaticText");
    QVERIFY(title(content) == "Foo");

    // Turn scroll bars off
    {
    scrollArea.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    QVERIFY(supportsAttribute(scrollAreaElement, kAXHorizontalScrollBarAttribute) == false);
    QVERIFY(supportsAttribute(scrollAreaElement, kAXVerticalScrollBarAttribute) == false);

    QVERIFY(supportsAttribute(scrollAreaElement, kAXContentsAttribute));
    const QList<AXUIElementRef> contents = elementListAttribute(scrollAreaElement, kAXContentsAttribute);
    QCOMPARE(contents.count(), 1);
    AXUIElementRef content = contents.at(0);
    
    QVERIFY(role(content) == "AXStaticText");
    QVERIFY(title(content) == "Foo");
    }

    // Turn the horizontal scrollbar on.
    {
    scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    scrollArea.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    QVERIFY(supportsAttribute(scrollAreaElement, kAXHorizontalScrollBarAttribute) == true);
    QVERIFY(supportsAttribute(scrollAreaElement, kAXVerticalScrollBarAttribute) == false);

    const AXUIElementRef horizontalScrollBar = elementAttribute(scrollAreaElement, kAXHorizontalScrollBarAttribute);
    QVERIFY(horizontalScrollBar);
    QVERIFY(role(horizontalScrollBar) == "AXScrollBar");
    QVERIFY(stringAttribute(horizontalScrollBar, kAXOrientationAttribute) == "AXHorizontalOrientation");

    QVERIFY(supportsAttribute(scrollAreaElement, kAXContentsAttribute));
    const QList<AXUIElementRef> contents = elementListAttribute(scrollAreaElement, kAXContentsAttribute);
    QCOMPARE(contents.count(), 1);
    AXUIElementRef content = contents.at(0);
    QVERIFY(role(content) == "AXStaticText");
    QVERIFY(title(content) == "Foo");
    }

    // Turn the vertical scrollbar on.
    {
    scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    scrollArea.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    QVERIFY(supportsAttribute(scrollAreaElement, kAXHorizontalScrollBarAttribute) == false);
    QVERIFY(supportsAttribute(scrollAreaElement, kAXVerticalScrollBarAttribute) == true);

    QVERIFY(supportsAttribute(scrollAreaElement, kAXVerticalScrollBarAttribute));
    const AXUIElementRef verticalScrollBar = elementAttribute(scrollAreaElement, kAXVerticalScrollBarAttribute);
    QVERIFY(verticalScrollBar);
    QVERIFY(role(verticalScrollBar) == "AXScrollBar");
    QVERIFY(stringAttribute(verticalScrollBar, kAXOrientationAttribute) == "AXVerticalOrientation");

    QVERIFY(supportsAttribute(scrollAreaElement, kAXContentsAttribute));
    const QList<AXUIElementRef> contents = elementListAttribute(scrollAreaElement, kAXContentsAttribute);
    QCOMPARE(contents.count(), 1);
    AXUIElementRef content = contents.at(0);
    QVERIFY(role(content) == "AXStaticText");
    QVERIFY(title(content) == "Foo");
    }
}

void tst_qaccessibility_mac::testListView()
{
    QWidget window;
    const QString windowTitle("window");
    window.setWindowTitle(windowTitle);
    window.resize(300, 300);

    QListWidget *listWidget = new QListWidget(&window);
    listWidget->setObjectName("listwidget");
    listWidget->addItem("A");
    listWidget->addItem("B");
    listWidget->addItem("C");
    
    window.show();
    QTest::qWait(1);
        
    {
        // Verify that QAInterface works as expected for list views
        QAInterface listWidgetInterface = QAccessible::queryAccessibleInterface(listWidget);
        QCOMPARE(listWidgetInterface.role(), QAccessible::Client);
        QCOMPARE(listWidgetInterface.childCount(), 1);
        QAInterface viewPort = listWidgetInterface.childAt(1);
        QCOMPARE(viewPort.role(), QAccessible::List);
        QVERIFY(viewPort.object() != 0);
        QCOMPARE(viewPort.childCount(), 3);
        const QList<QAInterface> rows = viewPort.children();
        QCOMPARE(rows.count(), 3);
        QVERIFY(rows.at(0).object() == 0);
        QCOMPARE(rows.at(0).parent().indexOfChild(rows.at(0)), 1);
        QCOMPARE(rows.at(1).parent().indexOfChild(rows.at(1)), 2);
        QCOMPARE(rows.at(2).parent().indexOfChild(rows.at(2)), 3);         
        
        // test the QAInterface comparison operator
        QVERIFY(rows.at(0) == rows.at(0));
        QVERIFY(rows.at(0) != rows.at(1));
        QVERIFY(rows.at(0) != viewPort);
        QVERIFY(viewPort == viewPort);
        QVERIFY(listWidgetInterface != viewPort);
        QVERIFY(listWidgetInterface == listWidgetInterface);
        
        // test QAInterface::isHIView()
        QVERIFY(viewPort.isHIView());
        QVERIFY(listWidgetInterface.isHIView());
        QVERIFY(rows.at(0).isHIView() == false);
    }

    const AXUIElementRef applicationElement = AXUIElementCreateApplication(getpid());
    QVERIFY(applicationElement);
    const AXUIElementRef windowElement = childByTitle(applicationElement, windowTitle);
    QVERIFY(windowElement);
    const AXUIElementRef scrollAreaElement = childByRole(windowElement, "AXScrollArea");
    QVERIFY(scrollAreaElement);
    const AXUIElementRef listElement = childByRole(scrollAreaElement, "AXList");
    QVERIFY(listElement);
    QVERIFY(equal(::parent(listElement), scrollAreaElement));
    // Window is not reported properly on 10.5
    if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_5) 
        QVERIFY(equal(::window(listElement), windowElement));
    
    const AXUIElementRef A = childByTitle(listElement, "A");
    QVERIFY(A);
    const AXUIElementRef B = childByTitle(listElement, "B");
    QVERIFY(B);
    const AXUIElementRef C = childByTitle(listElement, "C");
    QVERIFY(C);

    QVERIFY(value(A) == "A");
    QVERIFY(equal(::parent(A), listElement));
    QVERIFY(enabled(A));

    // Window is not reported properly on 10.5, this test
    // hangs on 10.4. Disable it for now.
    //    if (QSysInfo::MacintoshVersion < QSysInfo::MV_10_5) 
    //        QVERIFY(equal(::window(A), windowElement));
    
    QVERIFY(above(A, B));
    QVERIFY(!above(B, A));
    QVERIFY(above(B, C));
    QVERIFY(contains(listElement, A));
    QVERIFY(!contains(A, listElement));
    QVERIFY(contains(listElement, B));
    QVERIFY(contains(listElement, C));
}

void tst_qaccessibility_mac::testTableView()
{
    QWidget window;
    const QString windowTitle("window");
    window.setWindowTitle(windowTitle);
    window.resize(300, 300);

    QTableWidget *tableWidget = new QTableWidget(&window);
    tableWidget->setObjectName("tablewidget");
    tableWidget->setRowCount(3);
    tableWidget->setColumnCount(2);

    tableWidget->setItem(0, 0, new QTableWidgetItem("A1"));
    tableWidget->setItem(0, 1, new QTableWidgetItem("A2"));

    tableWidget->setItem(1, 0, new QTableWidgetItem("B1"));
    tableWidget->setItem(1, 1, new QTableWidgetItem("B2"));

    tableWidget->setItem(2, 0, new QTableWidgetItem("C1"));
    tableWidget->setItem(2, 1, new QTableWidgetItem("C2"));

    window.show();
    
    {
        // Verify that QAInterface works as expected for table view children.
        QAInterface tableWidgetInterface = QAccessible::queryAccessibleInterface(tableWidget);
        QCOMPARE(tableWidgetInterface.role(), QAccessible::Client);
        QCOMPARE(tableWidgetInterface.childCount(), 1);
        QAInterface viewPort = tableWidgetInterface.childAt(1);
        QCOMPARE(viewPort.childCount(), 4);
        QCOMPARE(viewPort.role(), QAccessible::Table);
        QVERIFY(viewPort.object() != 0);
        const QList<QAInterface> rows = viewPort.children();
        QCOMPARE(rows.count(), 4);
        QVERIFY(rows.at(0).object() == 0);
        QCOMPARE(rows.at(0).parent().indexOfChild(rows.at(0)), 1);
        QCOMPARE(rows.at(1).parent().indexOfChild(rows.at(1)), 2);
        QCOMPARE(rows.at(2).parent().indexOfChild(rows.at(2)), 3);         

        QAInterface Arow = rows.at(1);
        QCOMPARE(Arow.role(), QAccessible::Row);
        QAInterface Brow = rows.at(2);

        QVERIFY(Arow.name() == "1");
        QVERIFY(Brow.name() == "2");

        QVERIFY(Arow == Arow);
        QVERIFY(Brow != Arow);
        QVERIFY(Arow.isHIView() == false);
        QCOMPARE(Arow.childCount(), 3);
        QList<QAInterface> Achildren = Arow.children();
        QCOMPARE(Achildren.count(), 3);
        QAInterface A1 = Achildren.at(1);
        QAInterface A2 = Achildren.at(2);
        QCOMPARE(Arow.indexOfChild(A1), 2);
        QCOMPARE(Arow.indexOfChild(A2), 3);
        QCOMPARE(A1.role(), QAccessible::Cell);

        QList<QAInterface> Bchildren = Brow.children();
        QCOMPARE(Bchildren.count(), 3);
        QAInterface B1 = Bchildren.at(1);
        QAInterface B2 = Bchildren.at(2);
        QVERIFY(B1.parent() == Brow);
        QVERIFY(B1.parent() != Arow);
        QCOMPARE(Arow.indexOfChild(B1), -1);

        QVERIFY(A1 == A1);
        QVERIFY(A1 != A2);
        QVERIFY(B1 != A1);
        QVERIFY(B1 != A2);
        QVERIFY(A1 != Arow);
        QVERIFY(A1 != Brow);
        QVERIFY(A1 != viewPort);
        QVERIFY(A1.isHIView() == false);

        QVERIFY(B1.parent() == Brow);
        QVERIFY(A1.parent() == Arow);
        B1 = A1;
        QVERIFY(B1.parent() == Arow);
    }
    
    const AXUIElementRef applicationElement = AXUIElementCreateApplication(getpid());
    QVERIFY(applicationElement);
    const AXUIElementRef windowElement = childByTitle(applicationElement, windowTitle);
    QVERIFY(windowElement);
    const AXUIElementRef scrollAreaElement = childByRole(windowElement, "AXScrollArea");
    QVERIFY(scrollAreaElement);
    const AXUIElementRef tableElement = childByRole(scrollAreaElement, "AXTable");
    QVERIFY(tableElement);
   
    {
        // Verify that QAccessibleHierarchyManager can look up table view children correctly
        QAccessibleHierarchyManager *manager = QAccessibleHierarchyManager::instance();
        QAInterface tableInterface = manager->lookup(tableElement);
        QVERIFY(tableInterface.isValid());
        QVERIFY(tableInterface.role() == QAccessible::Table);
   
        QAInterface ArowInterface = tableInterface.childAt(2);

        QVERIFY(ArowInterface.name() == "1");
        QVERIFY(manager->lookup(manager->lookup(ArowInterface)).name() == "1");

        QCOMPARE(ArowInterface.childCount(), 3);
        QAInterface A1Interface = ArowInterface.childAt(2);

        QCOMPARE(A1Interface.value(), QString("A1"));
        QVERIFY(value(manager->lookup(A1Interface)).toString() == "A1");

        QAInterface A2Interface = ArowInterface.childAt(3);
        QAElement A2Element = manager->lookup(A2Interface);
        QVERIFY(manager->lookup(A2Element).value() == "A2");
        QVERIFY(value(A2Element).toString() == "A2");

        QAInterface BrowInterface = tableInterface.childAt(3);
        QVERIFY(BrowInterface.value() == "2");
        QVERIFY(manager->lookup(manager->lookup(BrowInterface)).value() == "2");

        QCOMPARE(BrowInterface.childCount(), 3);
        QAInterface B1Interface = BrowInterface.childAt(2);
        QVERIFY(value(manager->lookup(B1Interface)).toString() == "B1");

        QAInterface B2Interface = BrowInterface.childAt(3);
        QAElement B2Element = manager->lookup(B2Interface);
        QVERIFY(manager->lookup(B2Element).value() == "B2");
        QVERIFY(value(B2Element).toString() == "B2");
    }



    const AXUIElementRef Arow = childByTitle(tableElement, "1");
    QVERIFY(Arow);
    const AXUIElementRef Brow = childByTitle(tableElement, "2");
    QVERIFY(Brow);
    const AXUIElementRef Crow = childByTitle(tableElement, "3");
    QVERIFY(Crow);

    QCOMPARE(numChildren(Arow), 3);
    const AXUIElementRef A1cell = childByTitle(Arow, "A1");
    QVERIFY(A1cell);
    QVERIFY(role(A1cell) == "AXTextField");
    const AXUIElementRef A2cell = childByTitle(Arow, "A2");
    QVERIFY(A2cell);
    QVERIFY(equal(::parent(A2cell), Arow));

    const AXUIElementRef B2cell = childByTitle(Brow, "B2");
    QVERIFY(B2cell);
    QVERIFY(equal(::parent(B2cell), Brow));

    {
        QVERIFY(supportsAttribute(tableElement, kAXRowsAttribute));
        const QList<AXUIElementRef> rows = elementListAttribute(tableElement, kAXRowsAttribute);
        QCOMPARE(rows.count(), 3); // the header is not a row
        QVERIFY(value(rows.at(1)) == "2");
        QVERIFY(value(rows.at(2)) == "3");
    }

    {
        QVERIFY(supportsAttribute(tableElement, kAXVisibleRowsAttribute));
        const QList<AXUIElementRef> rows = elementListAttribute(tableElement, kAXVisibleRowsAttribute);
        QCOMPARE(rows.count(), 3);
        QVERIFY(value(rows.at(1)) == "2");
    }
    {
        QVERIFY(supportsAttribute(tableElement, kAXSelectedRowsAttribute));
        const QList<AXUIElementRef> rows = elementListAttribute(tableElement, kAXSelectedRowsAttribute);
        QCOMPARE(rows.count(), 0);
    }

    // test row visibility
    {
        QTableWidget tableWidget;
        tableWidget.setObjectName("tablewidget");
        tableWidget.setRowCount(1000);
        tableWidget.setColumnCount(1);

        for (int i =0; i < 1000; ++i) {
            tableWidget.setItem(i, 0, new QTableWidgetItem("item"));
        }
        tableWidget.show();
        
        QAInterface tableWidgetInterface = QAccessible::queryAccessibleInterface(&tableWidget);
        QAInterface viewPortInterface = tableWidgetInterface.childAt(1);
        QCOMPARE(viewPortInterface.childCount(), 1001);

        QVERIFY((viewPortInterface.childAt(2).state() & QAccessible::Invisible) == false);
        QVERIFY((viewPortInterface.childAt(2).state() & QAccessible::Offscreen) == false);
        
        QVERIFY(viewPortInterface.childAt(500).state() & QAccessible::Invisible);
//        QVERIFY(viewPortInterface.childAt(500).state() & QAccessible::Offscreen);
        tableWidget.hide();
    }

//    printElementInfo(tableElement);
//    QTest::qWait(1000000);
}

void tst_qaccessibility_mac::testScrollBar()
{
    {
        QScrollBar scrollBar;
        scrollBar.show();

        QAInterface scrollBarInterface = QAccessible::queryAccessibleInterface(&scrollBar);
        QVERIFY(scrollBarInterface.isValid());
        QCOMPARE(scrollBarInterface.childCount(), 5);
        QCOMPARE(scrollBarInterface.indexOfChild(scrollBarInterface.childAt(1)), 1);
        QCOMPARE(scrollBarInterface.indexOfChild(scrollBarInterface.childAt(2)), 2);
        QCOMPARE(scrollBarInterface.indexOfChild(scrollBarInterface.childAt(5)), 5);
        QCOMPARE(scrollBarInterface.indexOfChild(scrollBarInterface), -1);
    }

    const AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
    testAppAndForm(currentApplication);
    const AXUIElementRef form = childByTitle(currentApplication, "Form");
    QVERIFY(form);
    const AXUIElementRef scrollBarElement = childByRole(form, "AXScrollBar");
    QVERIFY(scrollBarElement);
    QCOMPARE(attribute(scrollBarElement, kAXOrientationAttribute).toString(), QLatin1String("AXVerticalOrientation"));

    {
        const AXUIElementRef lineUpElement = childByTitle(scrollBarElement, "Line up");
        QVERIFY(lineUpElement);
        QCOMPARE (subrole(lineUpElement), QLatin1String("AXDecrementArrow"));
    }{
        const AXUIElementRef lineDownElement = childByTitle(scrollBarElement, "Line down");
        QVERIFY(lineDownElement);
        QCOMPARE (subrole(lineDownElement), QLatin1String("AXIncrementArrow"));
    }{
        const AXUIElementRef pageUpElement = childByTitle(scrollBarElement, "Page up");
        QVERIFY(pageUpElement);
        QCOMPARE (subrole(pageUpElement), QLatin1String("AXDecrementPage"));
    }{
        const AXUIElementRef pageDownElement = childByTitle(scrollBarElement, "Page down");
        QVERIFY(pageDownElement);
        QCOMPARE (subrole(pageDownElement), QLatin1String("AXIncrementPage"));
    }{
        const AXUIElementRef valueIndicatorElement = childByTitle(scrollBarElement, "Position");
        QVERIFY(valueIndicatorElement);
        QCOMPARE(value(valueIndicatorElement).toInt(), 50);
    }
}

void tst_qaccessibility_mac::testSplitter()
{
    const AXUIElementRef currentApplication = AXUIElementCreateApplication(getpid());
    testAppAndForm(currentApplication);
    const AXUIElementRef form = childByTitle(currentApplication, "Form");
    QVERIFY(form);
    
    const AXUIElementRef splitGroupElement = childByRole(form, "AXSplitGroup");
    QVERIFY(splitGroupElement);

    for (int i = 0; i < numChildren(splitGroupElement); ++i)
        QVERIFY(child(splitGroupElement, 3));

    // Visual Order: Foo splitter Bar splitter Baz
    QList<AXUIElementRef> splitterList = elementListAttribute(splitGroupElement, kAXSplittersAttribute);
    QCOMPARE(splitterList.count(), 2); 
    foreach (AXUIElementRef splitter, splitterList) {
        QCOMPARE(role(splitter), QLatin1String("AXSplitter"));
        QVERIFY(supportsAttribute(splitter, kAXPreviousContentsAttribute));
        QVERIFY(supportsAttribute(splitter, kAXNextContentsAttribute));
        QCOMPARE(attribute(splitter, kAXOrientationAttribute).toString(), QLatin1String("AXVerticalOrientation"));
        QList<AXUIElementRef> prevList = elementListAttribute(splitter, kAXPreviousContentsAttribute);  
        QCOMPARE(prevList.count(), 1); 
        QList<AXUIElementRef> nextList = elementListAttribute(splitter, kAXNextContentsAttribute);  
        QCOMPARE(nextList.count(), 1);
        
        // verify order
        if (title(prevList.at(0)) == QLatin1String("Foo"))
            QCOMPARE(title(nextList.at(0)), QLatin1String("Bar"));
        else if (title(prevList.at(0)) == QLatin1String("Bar"))
            QCOMPARE(title(nextList.at(0)), QLatin1String("Baz"));
        else {
            QFAIL("Splitter contents and handles are out of order"); 
        }
    }
}

void tst_qaccessibility_mac::testTextEdit()
{
    QWidget window;
    const QString windowTitle("window");
    window.setWindowTitle(windowTitle);
    window.resize(300, 300);

    QTextEdit *textEdit = new QTextEdit(&window);
    textEdit->resize(300, 300);
    const QString textLine("this is a line");
    textEdit->setText(textLine);
    
    window.show();

    const AXUIElementRef applicationElement = AXUIElementCreateApplication(getpid());
    QVERIFY(applicationElement);
    const AXUIElementRef windowElement = childByTitle(applicationElement, windowTitle);
    QVERIFY(windowElement);
    const AXUIElementRef scrollAreaElement = childByRole(windowElement, "AXScrollArea");
    QVERIFY(scrollAreaElement);
    const AXUIElementRef textElement = childByRole(scrollAreaElement, "AXTextField");
    QVERIFY(textElement);
    QVERIFY(value(textElement) == textLine);
}

void testModelLessItemView(QAbstractItemView *itemView, const QByteArray &role)
{
    const QString windowTitle("window");
    itemView->setWindowTitle(windowTitle);
    itemView->show();

    QTest::qWait(100);
#if defined(Q_WS_X11)
    qt_x11_wait_for_window_manager(w);
#endif

    QAccessibleInterface *acc = QAccessible::queryAccessibleInterface(itemView);
    QVERIFY(acc->isValid());
    QCOMPARE(acc->childCount(), 1);
    acc->role(0);
    acc->rect(0);

    QAccessibleInterface *accViewport = 0;
    int entry = acc->navigate(QAccessible::Child, 1, &accViewport);
    QVERIFY(accViewport);
    QCOMPARE(entry, 0);
    QVERIFY(accViewport->isValid());
    QCOMPARE(accViewport->childCount(), 0);
    accViewport->role(0);
    accViewport->rect(0);

    delete acc;
    delete accViewport;
        
    const AXUIElementRef applicationElement = AXUIElementCreateApplication(getpid());
    QVERIFY(applicationElement);
    const AXUIElementRef windowElement = childByTitle(applicationElement, windowTitle);
    QVERIFY(windowElement);
    const AXUIElementRef scrollAreaElement = childByRole(windowElement, "AXScrollArea");
    QVERIFY(scrollAreaElement);
    const AXUIElementRef tableElement = childByRole(scrollAreaElement, role);
    QVERIFY(tableElement);
    
    delete itemView;
}

void tst_qaccessibility_mac::testItemViewsWithoutModel()
{
    testModelLessItemView(new QListView(), "AXList");
    testModelLessItemView(new QTableView(), "AXTable");
}

void tst_qaccessibility_mac::testQAElement()
{
    {
        QAElement element;
        QVERIFY(element.isValid() == false);
    }
    
    {
        QAElement element(0, 0);
        QVERIFY(element.isValid() == false);
    }

    {
        int argc = 0;
        char **argv = 0;
        QApplication app(argc, argv);
        QWidget w;
        QAElement element(reinterpret_cast<HIObjectRef>(w.winId()), 0);
        QVERIFY(element.isValid() == true);
    }

}

void tst_qaccessibility_mac::testQAInterface()
{
    {
        QAInterface interface;
        QVERIFY(interface.isValid() == false);
    }
    
    {
        QAInterface interface(0, 0);
        QVERIFY(interface.isValid() == false);
    }

    {
        int argc = 0;
        char **argv = 0;
        QApplication app(argc, argv);

        {
            QWidget w;
            QAInterface element(QAccessible::queryAccessibleInterface(&w), 0);
            QVERIFY(element.isValid() == true);
        }
        {
            QWidget w;
            QAInterface element(QAccessible::queryAccessibleInterface(&w), 100);
            QVERIFY(element.isValid() == false);
        }
    }
}

void tst_qaccessibility_mac::uitests_data()
{
    QTest::addColumn<QString>("uiFilename");
    QTest::addColumn<QString>("testSlot");

    QTest::newRow("form") << "form.ui" << SLOT(testForm());
    QTest::newRow("buttons") << "buttons.ui" << SLOT(testButtons());
    QTest::newRow("label") << "label.ui" << SLOT(testLabel());
    QTest::newRow("line edit") << "lineedit.ui" << SLOT(testLineEdit());
    QTest::newRow("groups") << "groups.ui" << SLOT(testGroups());
    QTest::newRow("tabs") << "tabs.ui" << SLOT(testTabWidget());
    QTest::newRow("combobox") << "combobox.ui" << SLOT(testComboBox());
    QTest::newRow("scrollbar") << "scrollbar.ui" << SLOT(testScrollBar());
    QTest::newRow("splitters") << "splitters.ui" << SLOT(testSplitter());
}

void tst_qaccessibility_mac::uitests()
{
    QFETCH(QString, uiFilename);
    QFETCH(QString, testSlot);

    // The Accessibility interface must be enabled to run this test.
    if (!AXAPIEnabled())
        QSKIP("Accessibility not enabled. Check \"Enable access for assistive devices\" in the system preferences -> universal access to run this test.", SkipAll);

    int argc = 0;
    char **argv = 0;
    QApplication app(argc, argv);

    // Create and display form.
    QUiLoader loader;
    QFile file(":" + uiFilename);
    QVERIFY(file.exists());
    file.open(QFile::ReadOnly);
    QWidget *window = loader.load(&file, 0);
    QVERIFY(window);
    file.close();
    window->show();

    QTimer::singleShot(50, this, qPrintable(testSlot));
    // Quit when returning to the main event loop after running tests.
    QTimer::singleShot(200, &app, SLOT(quit()));
    app.exec();
    delete window;
}

void tst_qaccessibility_mac::tests_data()
{
    QTest::addColumn<QString>("testSlot");
    QTest::newRow("deleteWidget") << SLOT(testDeleteWidget());
    QTest::newRow("deleteWidgets") << SLOT(testDeleteWidgets());
    QTest::newRow("multipleWindows") << SLOT(testMultipleWindows());
    QTest::newRow("hiddenWidgets") << SLOT(testHiddenWidgets());
    QTest::newRow("actions") << SLOT(testActions());
    QTest::newRow("changeState") << SLOT(testChangeState());
    QTest::newRow("slider") << SLOT(testSlider());
    QTest::newRow("scrollArea") << SLOT(testScrollArea());
    QTest::newRow("listView") << SLOT(testListView());
    QTest::newRow("tableView") << SLOT(testTableView());
    QTest::newRow("textEdit") << SLOT(testTextEdit());
    QTest::newRow("ItemViews without model") << SLOT(testItemViewsWithoutModel());
    QTest::newRow("tabbar") << SLOT(testTabBar());
}

void tst_qaccessibility_mac::tests()
{
    QFETCH(QString, testSlot);
    runTest(testSlot);
}

/*
    Tests show that querying the accessibility interface directly does not work. (I get a
    kAXErrorAPIDisabled error, indicating that the accessible API is disabled, which it isn't.)
    To work around this, we run the tests in a callback slot called from the main event loop.
*/
void tst_qaccessibility_mac::runTest(const QString &testSlot)
{
    // The Accessibility interface must be enabled to run this test.
    if (!AXAPIEnabled())
        QSKIP("Accessibility not enabled. Check \"Enable access for assistive devices\" in the system preferences -> universal access to run this test.", SkipAll);

    int argc = 0;
    char **argv = 0;
    QApplication app(argc, argv);

    QTimer::singleShot(50, this, qPrintable(testSlot));
    // Quit when returning to the main event loop after running tests.
    QTimer::singleShot(200, &app, SLOT(quit()));
    app.exec();

}

QTEST_APPLESS_MAIN(tst_qaccessibility_mac)

#else // defined(Q_WS_MAC) && !defined (QT_MAC_USE_COCOA)

QTEST_NOOP_MAIN

#endif

#include "tst_qaccessibility_mac.moc"


