/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qtest.h>
#include <QtTest/QSignalSpy>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <QtQml/qqmlcontext.h>
#include <QtQuick/qquickitemgrabresult.h>
#include <QtQuick/qquickview.h>
#include <QtGui/private/qinputmethod_p.h>
#include <QtQuick/private/qquickrectangle_p.h>
#include <QtQuick/private/qquicktextinput_p.h>
#include <QtQuick/private/qquickitemchangelistener_p.h>
#include <QtGui/qstylehints.h>
#include <private/qquickitem_p.h>
#include "../../shared/util.h"
#include "../shared/visualtestutil.h"
#include "../../shared/platforminputcontext.h"

using namespace QQuickVisualTestUtil;

class tst_QQuickItem : public QQmlDataTest
{
    Q_OBJECT
public:
    tst_QQuickItem();

private slots:
    void initTestCase();
    void cleanup();

    void activeFocusOnTab();
    void activeFocusOnTab2();
    void activeFocusOnTab3();
    void activeFocusOnTab4();
    void activeFocusOnTab5();
    void activeFocusOnTab6();
    void activeFocusOnTab7();
    void activeFocusOnTab8();
    void activeFocusOnTab9();
    void activeFocusOnTab10();
    void activeFocusOnTab_infiniteLoop();

    void nextItemInFocusChain();
    void nextItemInFocusChain2();
    void nextItemInFocusChain3();

    void tabFence();
    void qtbug_50516();
    void qtbug_50516_2_data();
    void qtbug_50516_2();

    void keys();
    void standardKeys_data();
    void standardKeys();
    void keysProcessingOrder();
    void keysim();
    void keysForward();
    void keyNavigation_data();
    void keyNavigation();
    void keyNavigation_RightToLeft();
    void keyNavigation_skipNotVisible();
    void keyNavigation_implicitSetting();
    void keyNavigation_implicitDestroy();
    void keyNavigation_focusReason();
    void keyNavigation_loop();
    void layoutMirroring();
    void layoutMirroringWindow();
    void layoutMirroringIllegalParent();
    void smooth();
    void antialiasing();
    void clip();
    void mapCoordinates();
    void mapCoordinates_data();
    void mapCoordinatesRect();
    void mapCoordinatesRect_data();
    void propertyChanges();
    void nonexistentPropertyConnection();
    void transforms();
    void transforms_data();
    void childrenRect();
    void childrenRectBug();
    void childrenRectBug2();
    void childrenRectBug3();
    void childrenRectBottomRightCorner();

    void childrenProperty();
    void resourcesProperty();

    void changeListener();
    void transformCrash();
    void implicitSize();
    void qtbug_16871();
    void visibleChildren();
    void parentLoop();
    void contains_data();
    void contains();
    void childAt();
    void isAncestorOf();

    void grab();

private:
    QQmlEngine engine;
    bool qt_tab_all_widgets() {
        return QGuiApplication::styleHints()->tabFocusBehavior() == Qt::TabFocusAllControls;
    }
};

class KeysTestObject : public QObject
{
    Q_OBJECT

    Q_PROPERTY(bool processLast READ processLast NOTIFY processLastChanged)

public:
    KeysTestObject() : mKey(0), mModifiers(0), mForwardedKey(0), mLast(false), mNativeScanCode(0) {}

    void reset() {
        mKey = 0;
        mText = QString();
        mModifiers = 0;
        mForwardedKey = 0;
        mNativeScanCode = 0;
    }

    bool processLast() const { return mLast; }
    void setProcessLast(bool b) {
        if (b != mLast) {
            mLast = b;
            emit processLastChanged();
        }
    }

public slots:
    void keyPress(int key, QString text, int modifiers) {
        mKey = key;
        mText = text;
        mModifiers = modifiers;
    }
    void keyRelease(int key, QString text, int modifiers) {
        mKey = key;
        mText = text;
        mModifiers = modifiers;
    }
    void forwardedKey(int key) {
        mForwardedKey = key;
    }
    void specialKey(int key, QString text, quint32 nativeScanCode) {
        mKey = key;
        mText = text;
        mNativeScanCode = nativeScanCode;
    }

signals:
    void processLastChanged();

public:
    int mKey;
    QString mText;
    int mModifiers;
    int mForwardedKey;
    bool mLast;
    quint32 mNativeScanCode;

private:
};

class KeyTestItem : public QQuickItem
{
    Q_OBJECT
public:
    KeyTestItem(QQuickItem *parent=nullptr) : QQuickItem(parent), mKey(0) {}

protected:
    void keyPressEvent(QKeyEvent *e) {
        mKey = e->key();

        if (e->key() == Qt::Key_A)
            e->accept();
        else
            e->ignore();
    }

    void keyReleaseEvent(QKeyEvent *e) {
        if (e->key() == Qt::Key_B)
            e->accept();
        else
            e->ignore();
    }

public:
    int mKey;
};

class FocusEventFilter : public QObject
{
protected:
    bool eventFilter(QObject *watched, QEvent *event) {
        if ((event->type() == QEvent::FocusIn) ||  (event->type() == QEvent::FocusOut)) {
            QFocusEvent *focusEvent = static_cast<QFocusEvent *>(event);
            lastFocusReason = focusEvent->reason();
            return false;
        } else
            return QObject::eventFilter(watched, event);
    }
public:
    Qt::FocusReason lastFocusReason;
};

QML_DECLARE_TYPE(KeyTestItem);

class HollowTestItem : public QQuickItem
{
    Q_OBJECT
    Q_PROPERTY(bool circle READ isCircle WRITE setCircle)
    Q_PROPERTY(qreal holeRadius READ holeRadius WRITE setHoleRadius)

public:
    HollowTestItem(QQuickItem *parent = nullptr)
        : QQuickItem(parent),
          m_isPressed(false),
          m_isHovered(false),
          m_isCircle(false),
          m_holeRadius(50)
    {
        setAcceptHoverEvents(true);
        setAcceptedMouseButtons(Qt::LeftButton);
    }

    bool isPressed() const { return m_isPressed; }
    bool isHovered() const { return m_isHovered; }

    bool isCircle() const { return m_isCircle; }
    void setCircle(bool circle) { m_isCircle = circle; }

    qreal holeRadius() const { return m_holeRadius; }
    void setHoleRadius(qreal radius) { m_holeRadius = radius; }

    bool contains(const QPointF &point) const {
        const qreal w = width();
        const qreal h = height();
        const qreal r = m_holeRadius;

        // check boundaries
        if (!QRectF(0, 0, w, h).contains(point))
            return false;

        // square shape
        if (!m_isCircle)
            return !QRectF(w / 2 - r, h / 2 - r, r * 2, r * 2).contains(point);

        // circle shape
        const qreal dx = point.x() - (w / 2);
        const qreal dy = point.y() - (h / 2);
        const qreal dd = (dx * dx) + (dy * dy);
        const qreal outerRadius = qMin<qreal>(w / 2, h / 2);
        return dd > (r * r) && dd <= outerRadius * outerRadius;
    }

protected:
    void hoverEnterEvent(QHoverEvent *) { m_isHovered = true; }
    void hoverLeaveEvent(QHoverEvent *) { m_isHovered = false; }
    void mousePressEvent(QMouseEvent *) { m_isPressed = true; }
    void mouseReleaseEvent(QMouseEvent *) { m_isPressed = false; }

private:
    bool m_isPressed;
    bool m_isHovered;
    bool m_isCircle;
    qreal m_holeRadius;
};

QML_DECLARE_TYPE(HollowTestItem);

class TabFenceItem : public QQuickItem
{
    Q_OBJECT

public:
    TabFenceItem(QQuickItem *parent = nullptr)
        : QQuickItem(parent)
    {
        QQuickItemPrivate *d = QQuickItemPrivate::get(this);
        d->isTabFence = true;
    }
};

QML_DECLARE_TYPE(TabFenceItem);

class TabFenceItem2 : public QQuickItem
{
    Q_OBJECT

public:
    TabFenceItem2(QQuickItem *parent = nullptr)
        : QQuickItem(parent)
    {
        QQuickItemPrivate *d = QQuickItemPrivate::get(this);
        d->isTabFence = true;
        setFlag(ItemIsFocusScope);
    }
};

QML_DECLARE_TYPE(TabFenceItem2);

tst_QQuickItem::tst_QQuickItem()
{
}

void tst_QQuickItem::initTestCase()
{
    QQmlDataTest::initTestCase();
    qmlRegisterType<KeyTestItem>("Test",1,0,"KeyTestItem");
    qmlRegisterType<HollowTestItem>("Test", 1, 0, "HollowTestItem");
    qmlRegisterType<TabFenceItem>("Test", 1, 0, "TabFence");
    qmlRegisterType<TabFenceItem2>("Test", 1, 0, "TabFence2");
}

void tst_QQuickItem::cleanup()
{
    QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
    inputMethodPrivate->testContext = nullptr;
}

void tst_QQuickItem::activeFocusOnTab()
{
    if (!qt_tab_all_widgets())
        QSKIP("This function doesn't support NOT iterating all.");

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl("activeFocusOnTab.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    // original: button12
    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "button12");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // Tab: button12->sub2
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "sub2");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // Tab: sub2->button21
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "button21");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // Tab: button21->button22
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "button22");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // Tab: button22->edit
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "edit");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // BackTab: edit->button22
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "button22");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // BackTab: button22->button21
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "button21");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // BackTab: button21->sub2
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "sub2");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // BackTab: sub2->button12
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "button12");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // BackTab: button12->button11
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "button11");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // BackTab: button11->edit
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "edit");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::activeFocusOnTab2()
{
    if (!qt_tab_all_widgets())
        QSKIP("This function doesn't support NOT iterating all.");

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl("activeFocusOnTab.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    // original: button12
    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "button12");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // BackTab: button12->button11
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "button11");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // BackTab: button11->edit
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "edit");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::activeFocusOnTab3()
{
    if (!qt_tab_all_widgets())
        QSKIP("This function doesn't support NOT iterating all.");

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl("activeFocusOnTab3.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    // original: button1
    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "button1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 Tabs: button1->button2, through a repeater
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);;
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button2");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 Tabs: button2->button3, through a row
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);;
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button3");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 Tabs: button3->button4, through a flow
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);;
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button4");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 Tabs: button4->button5, through a focusscope
    // parent is activeFocusOnTab:false, one of children is focus:true
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);;
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button5");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 Tabs: button5->button6, through a focusscope
    // parent is activeFocusOnTab:true, one of children is focus:true
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);;
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button6");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 5 Tabs: button6->button7, through a focusscope
    // parent is activeFocusOnTab:true, none of children is focus:true
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);;
    for (int i = 0; i < 5; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button7");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 BackTabs: button7->button6, through a focusscope
    // parent is activeFocusOnTab:true, one of children got focus:true in previous code
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button6");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 Tabs: button6->button7, through a focusscope
    // parent is activeFocusOnTab:true, one of children got focus:true in previous code
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);;
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button7");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 BackTabs: button7->button6, through a focusscope
    // parent is activeFocusOnTab:true, one of children got focus:true in previous code
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button6");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 BackTabs: button6->button5, through a focusscope(parent is activeFocusOnTab: false)
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button5");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 BackTabs: button5->button4, through a focusscope(parent is activeFocusOnTab: false)
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button4");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 BackTabs: button4->button3, through a flow
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button3");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 BackTabs: button3->button2, through a row
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button2");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // 4 BackTabs: button2->button1, through a repeater
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    for (int i = 0; i < 4; ++i) {
        QGuiApplication::sendEvent(window, &key);
        QVERIFY(key.isAccepted());
    }

    item = findItem<QQuickItem>(window->rootObject(), "button1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::activeFocusOnTab4()
{
    if (!qt_tab_all_widgets())
        QSKIP("This function doesn't support NOT iterating all.");

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl("activeFocusOnTab4.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    // original: button11
    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "button11");
    item->setActiveFocusOnTab(true);
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // Tab: button11->button21
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "button21");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::activeFocusOnTab5()
{
    if (!qt_tab_all_widgets())
        QSKIP("This function doesn't support NOT iterating all.");

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl("activeFocusOnTab4.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    // original: button11 in sub1
    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "button11");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    QQuickItem *item2 = findItem<QQuickItem>(window->rootObject(), "sub1");
    item2->setActiveFocusOnTab(true);

    // Tab: button11->button21
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "button21");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::activeFocusOnTab6()
{
    if (qt_tab_all_widgets())
        QSKIP("This function doesn't support iterating all.");

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl("activeFocusOnTab6.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    // original: button12
    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "button12");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // Tab: button12->edit
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "edit");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // BackTab: edit->button12
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "button12");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // BackTab: button12->button11
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "button11");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // BackTab: button11->edit
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "edit");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::activeFocusOnTab7()
{
    if (qt_tab_all_widgets())
        QSKIP("This function doesn't support iterating all.");

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(300,300));

    window->setSource(testFileUrl("activeFocusOnTab7.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "button1");
    QVERIFY(item);
    item->forceActiveFocus();
    QVERIFY(item->hasActiveFocus());

    // Tab: button1->button1
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(!key.isAccepted());

    QVERIFY(item->hasActiveFocus());

    // BackTab: button1->button1
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(!key.isAccepted());

    QVERIFY(item->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::activeFocusOnTab8()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(300,300));

    window->setSource(testFileUrl("activeFocusOnTab8.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *content = window->contentItem();
    QVERIFY(content);
    QVERIFY(content->hasActiveFocus());

    QQuickItem *button1 = findItem<QQuickItem>(window->rootObject(), "button1");
    QVERIFY(button1);
    QVERIFY(!button1->hasActiveFocus());

    QQuickItem *button2 = findItem<QQuickItem>(window->rootObject(), "button2");
    QVERIFY(button2);
    QVERIFY(!button2->hasActiveFocus());

    // Tab: contentItem->button1
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(button1->hasActiveFocus());

    // Tab: button1->button2
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(button2->hasActiveFocus());
    QVERIFY(!button1->hasActiveFocus());

    // BackTab: button2->button1
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(button1->hasActiveFocus());
    QVERIFY(!button2->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::activeFocusOnTab9()
{
    if (qt_tab_all_widgets())
        QSKIP("This function doesn't support iterating all.");

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(300,300));

    window->setSource(testFileUrl("activeFocusOnTab9.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *content = window->contentItem();
    QVERIFY(content);
    QVERIFY(content->hasActiveFocus());

    QQuickItem *textinput1 = findItem<QQuickItem>(window->rootObject(), "textinput1");
    QVERIFY(textinput1);
    QQuickItem *textedit1 = findItem<QQuickItem>(window->rootObject(), "textedit1");
    QVERIFY(textedit1);

    QVERIFY(!textinput1->hasActiveFocus());
    textinput1->forceActiveFocus();
    QVERIFY(textinput1->hasActiveFocus());

    // Tab: textinput1->textedit1
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(textedit1->hasActiveFocus());

    // BackTab: textedit1->textinput1
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(textinput1->hasActiveFocus());

    // BackTab: textinput1->textedit1
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(textedit1->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::activeFocusOnTab10()
{
    if (!qt_tab_all_widgets())
        QSKIP("This function doesn't support NOT iterating all.");

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(300,300));

    window->setSource(testFileUrl("activeFocusOnTab9.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *content = window->contentItem();
    QVERIFY(content);
    QVERIFY(content->hasActiveFocus());

    QQuickItem *textinput1 = findItem<QQuickItem>(window->rootObject(), "textinput1");
    QVERIFY(textinput1);
    QQuickItem *textedit1 = findItem<QQuickItem>(window->rootObject(), "textedit1");
    QVERIFY(textedit1);
    QQuickItem *textinput2 = findItem<QQuickItem>(window->rootObject(), "textinput2");
    QVERIFY(textinput2);
    QQuickItem *textedit2 = findItem<QQuickItem>(window->rootObject(), "textedit2");
    QVERIFY(textedit2);

    QVERIFY(!textinput1->hasActiveFocus());
    textinput1->forceActiveFocus();
    QVERIFY(textinput1->hasActiveFocus());

    // Tab: textinput1->textinput2
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(textinput2->hasActiveFocus());

    // Tab: textinput2->textedit1
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(textedit1->hasActiveFocus());

    // BackTab: textedit1->textinput2
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(textinput2->hasActiveFocus());

    // BackTab: textinput2->textinput1
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(textinput1->hasActiveFocus());

    // BackTab: textinput1->textedit2
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(textedit2->hasActiveFocus());

    // BackTab: textedit2->textedit1
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(textedit1->hasActiveFocus());

    // BackTab: textedit1->textinput2
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::ShiftModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    QVERIFY(textinput2->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::activeFocusOnTab_infiniteLoop()
{
    // see QTBUG-68271
    // create a window where the currently focused item is not visible
    QScopedPointer<QQuickView>window(new QQuickView());
    window->setSource(testFileUrl("activeFocusOnTab_infiniteLoop.qml"));
    window->show();
    auto *hiddenChild = findItem<QQuickItem>(window->rootObject(), "hiddenChild");
    QVERIFY(hiddenChild);

    // move the focus - this used to result in an infinite loop
    auto *item = hiddenChild->nextItemInFocusChain();
    // focus is moved to the root object since there is no other candidate
    QCOMPARE(item, window->rootObject());
}

void tst_QQuickItem::nextItemInFocusChain()
{
    if (!qt_tab_all_widgets())
        QSKIP("This function doesn't support NOT iterating all.");

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl("activeFocusOnTab.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *button11 = findItem<QQuickItem>(window->rootObject(), "button11");
    QVERIFY(button11);
    QQuickItem *button12 = findItem<QQuickItem>(window->rootObject(), "button12");
    QVERIFY(button12);

    QQuickItem *sub2 = findItem<QQuickItem>(window->rootObject(), "sub2");
    QVERIFY(sub2);
    QQuickItem *button21 = findItem<QQuickItem>(window->rootObject(), "button21");
    QVERIFY(button21);
    QQuickItem *button22 = findItem<QQuickItem>(window->rootObject(), "button22");
    QVERIFY(button22);

    QQuickItem *edit = findItem<QQuickItem>(window->rootObject(), "edit");
    QVERIFY(edit);

    QQuickItem *next, *prev;

    next = button11->nextItemInFocusChain(true);
    QVERIFY(next);
    QCOMPARE(next, button12);
    prev = button11->nextItemInFocusChain(false);
    QVERIFY(prev);
    QCOMPARE(prev, edit);

    next = button12->nextItemInFocusChain();
    QVERIFY(next);
    QCOMPARE(next, sub2);
    prev = button12->nextItemInFocusChain(false);
    QVERIFY(prev);
    QCOMPARE(prev, button11);

    next = sub2->nextItemInFocusChain(true);
    QVERIFY(next);
    QCOMPARE(next, button21);
    prev = sub2->nextItemInFocusChain(false);
    QVERIFY(prev);
    QCOMPARE(prev, button12);

    next = button21->nextItemInFocusChain();
    QVERIFY(next);
    QCOMPARE(next, button22);
    prev = button21->nextItemInFocusChain(false);
    QVERIFY(prev);
    QCOMPARE(prev, sub2);

    next = button22->nextItemInFocusChain(true);
    QVERIFY(next);
    QCOMPARE(next, edit);
    prev = button22->nextItemInFocusChain(false);
    QVERIFY(prev);
    QCOMPARE(prev, button21);

    next = edit->nextItemInFocusChain();
    QVERIFY(next);
    QCOMPARE(next, button11);
    prev = edit->nextItemInFocusChain(false);
    QVERIFY(prev);
    QCOMPARE(prev, button22);

    delete window;
}

void tst_QQuickItem::nextItemInFocusChain2()
{
    if (qt_tab_all_widgets())
        QSKIP("This function doesn't support iterating all.");

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl("activeFocusOnTab6.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *button11 = findItem<QQuickItem>(window->rootObject(), "button11");
    QVERIFY(button11);
    QQuickItem *button12 = findItem<QQuickItem>(window->rootObject(), "button12");
    QVERIFY(button12);

    QQuickItem *edit = findItem<QQuickItem>(window->rootObject(), "edit");
    QVERIFY(edit);

    QQuickItem *next, *prev;

    next = button11->nextItemInFocusChain(true);
    QVERIFY(next);
    QCOMPARE(next, button12);
    prev = button11->nextItemInFocusChain(false);
    QVERIFY(prev);
    QCOMPARE(prev, edit);

    next = button12->nextItemInFocusChain();
    QVERIFY(next);
    QCOMPARE(next, edit);
    prev = button12->nextItemInFocusChain(false);
    QVERIFY(prev);
    QCOMPARE(prev, button11);

    next = edit->nextItemInFocusChain();
    QVERIFY(next);
    QCOMPARE(next, button11);
    prev = edit->nextItemInFocusChain(false);
    QVERIFY(prev);
    QCOMPARE(prev, button12);

    delete window;
}

void tst_QQuickItem::nextItemInFocusChain3()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl("nextItemInFocusChain3.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);
}

void verifyTabFocusChain(QQuickView *window, const char **focusChain, bool forward)
{
    int idx = 0;
    for (const char **objectName = focusChain; *objectName; ++objectName, ++idx) {
        const QString &descrStr = QString("idx=%1 objectName=\"%2\"").arg(idx).arg(*objectName);
        const char *descr = descrStr.toLocal8Bit().data();
        QKeyEvent key(QEvent::KeyPress, Qt::Key_Tab, forward ? Qt::NoModifier : Qt::ShiftModifier);
        QGuiApplication::sendEvent(window, &key);
        QVERIFY2(key.isAccepted(), descr);

        QQuickItem *item = findItem<QQuickItem>(window->rootObject(), *objectName);
        QVERIFY2(item, descr);
        QVERIFY2(item->hasActiveFocus(), descr);
    }
}

void tst_QQuickItem::tabFence()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl("tabFence.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QVERIFY(QGuiApplication::focusWindow() == window);
    QVERIFY(window->rootObject()->hasActiveFocus());

    const char *rootTabFocusChain[] = {
          "input1", "input2", "input3", "input1", nullptr
    };
    verifyTabFocusChain(window, rootTabFocusChain, true /* forward */);

    const char *rootBacktabFocusChain[] = {
          "input3", "input2", "input1", "input3", nullptr
    };
    verifyTabFocusChain(window, rootBacktabFocusChain, false /* forward */);

    // Give focus to input11 in fence1
    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "input11");
    item->setFocus(true);
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    const char *fence1TabFocusChain[] = {
          "input12", "input13", "input11", "input12", nullptr
    };
    verifyTabFocusChain(window, fence1TabFocusChain, true /* forward */);

    const char *fence1BacktabFocusChain[] = {
          "input11", "input13", "input12", "input11", nullptr
    };
    verifyTabFocusChain(window, fence1BacktabFocusChain, false /* forward */);
}

void tst_QQuickItem::qtbug_50516()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl("qtbug_50516.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QVERIFY(QGuiApplication::focusWindow() == window);
    QVERIFY(window->rootObject()->hasActiveFocus());

    QQuickItem *contentItem = window->rootObject();
    QQuickItem *next = contentItem->nextItemInFocusChain(true);
    QCOMPARE(next, contentItem);
    next = contentItem->nextItemInFocusChain(false);
    QCOMPARE(next, contentItem);

    delete window;
}

void tst_QQuickItem::qtbug_50516_2_data()
{
    QTest::addColumn<QString>("filename");
    QTest::addColumn<QString>("item1");
    QTest::addColumn<QString>("item2");

    QTest::newRow("FocusScope TabFence with one Item(focused)")
            << QStringLiteral("qtbug_50516_2_1.qml") << QStringLiteral("root") << QStringLiteral("root");
    QTest::newRow("FocusScope TabFence with one Item(unfocused)")
            << QStringLiteral("qtbug_50516_2_2.qml") << QStringLiteral("root") << QStringLiteral("root");
    QTest::newRow("FocusScope TabFence with two Items(focused)")
            << QStringLiteral("qtbug_50516_2_3.qml") << QStringLiteral("root") << QStringLiteral("root");
    QTest::newRow("FocusScope TabFence with two Items(unfocused)")
            << QStringLiteral("qtbug_50516_2_4.qml") << QStringLiteral("root") << QStringLiteral("root");
    QTest::newRow("FocusScope TabFence with one Item and one TextInput(unfocused)")
            << QStringLiteral("qtbug_50516_2_5.qml") << QStringLiteral("item1") << QStringLiteral("item1");
    QTest::newRow("FocusScope TabFence with two TextInputs(unfocused)")
            << QStringLiteral("qtbug_50516_2_6.qml") << QStringLiteral("item1") << QStringLiteral("item2");
}

void tst_QQuickItem::qtbug_50516_2()
{
    QFETCH(QString, filename);
    QFETCH(QString, item1);
    QFETCH(QString, item2);

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(800,600));

    window->setSource(testFileUrl(filename));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QVERIFY(QGuiApplication::focusWindow() == window);
    QVERIFY(window->rootObject()->hasActiveFocus());

    QQuickItem *contentItem = window->rootObject();
    QQuickItem *next = contentItem->nextItemInFocusChain(true);
    QCOMPARE(next->objectName(), item1);
    next = contentItem->nextItemInFocusChain(false);
    QCOMPARE(next->objectName(), item2);

    delete window;
}

void tst_QQuickItem::keys()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(240,320));

    KeysTestObject *testObject = new KeysTestObject;
    window->rootContext()->setContextProperty("keysTestObject", testObject);

    window->rootContext()->setContextProperty("enableKeyHanding", QVariant(true));
    window->rootContext()->setContextProperty("forwardeeVisible", QVariant(true));

    window->setSource(testFileUrl("keystest.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QVERIFY(window->rootObject());
    QCOMPARE(window->rootObject()->property("isEnabled").toBool(), true);

    QKeyEvent key(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier, "A", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_A));
    QCOMPARE(testObject->mForwardedKey, int(Qt::Key_A));
    QCOMPARE(testObject->mText, QLatin1String("A"));
    QCOMPARE(testObject->mModifiers, int(Qt::NoModifier));
    QVERIFY(!key.isAccepted());

    testObject->reset();

    key = QKeyEvent(QEvent::KeyRelease, Qt::Key_A, Qt::ShiftModifier, "A", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_A));
    QCOMPARE(testObject->mForwardedKey, int(Qt::Key_A));
    QCOMPARE(testObject->mText, QLatin1String("A"));
    QCOMPARE(testObject->mModifiers, int(Qt::ShiftModifier));
    QVERIFY(key.isAccepted());

    testObject->reset();

    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_Return));
    QCOMPARE(testObject->mForwardedKey, int(Qt::Key_Return));
    QCOMPARE(testObject->mText, QLatin1String("Return"));
    QCOMPARE(testObject->mModifiers, int(Qt::NoModifier));
    QVERIFY(key.isAccepted());

    testObject->reset();

    key = QKeyEvent(QEvent::KeyPress, Qt::Key_0, Qt::NoModifier, "0", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_0));
    QCOMPARE(testObject->mForwardedKey, int(Qt::Key_0));
    QCOMPARE(testObject->mText, QLatin1String("0"));
    QCOMPARE(testObject->mModifiers, int(Qt::NoModifier));
    QVERIFY(key.isAccepted());

    testObject->reset();

    key = QKeyEvent(QEvent::KeyPress, Qt::Key_9, Qt::NoModifier, "9", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_9));
    QCOMPARE(testObject->mForwardedKey, int(Qt::Key_9));
    QCOMPARE(testObject->mText, QLatin1String("9"));
    QCOMPARE(testObject->mModifiers, int(Qt::NoModifier));
    QVERIFY(!key.isAccepted());

    testObject->reset();

    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_Tab));
    QCOMPARE(testObject->mForwardedKey, int(Qt::Key_Tab));
    QCOMPARE(testObject->mText, QLatin1String("Tab"));
    QCOMPARE(testObject->mModifiers, int(Qt::NoModifier));
    QVERIFY(key.isAccepted());

    testObject->reset();

    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_Backtab));
    QCOMPARE(testObject->mForwardedKey, int(Qt::Key_Backtab));
    QCOMPARE(testObject->mText, QLatin1String("Backtab"));
    QCOMPARE(testObject->mModifiers, int(Qt::NoModifier));
    QVERIFY(key.isAccepted());

    testObject->reset();

    key = QKeyEvent(QEvent::KeyPress, Qt::Key_VolumeUp, Qt::NoModifier, 1234, 0, 0);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_VolumeUp));
    QCOMPARE(testObject->mForwardedKey, int(Qt::Key_VolumeUp));
    QCOMPARE(testObject->mModifiers, int(Qt::NoModifier));
    QCOMPARE(testObject->mNativeScanCode, quint32(1234));
    QVERIFY(key.isAccepted());

    testObject->reset();

    window->rootContext()->setContextProperty("forwardeeVisible", QVariant(false));
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier, "A", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_A));
    QCOMPARE(testObject->mForwardedKey, 0);
    QCOMPARE(testObject->mText, QLatin1String("A"));
    QCOMPARE(testObject->mModifiers, int(Qt::NoModifier));
    QVERIFY(!key.isAccepted());

    testObject->reset();

    window->rootContext()->setContextProperty("enableKeyHanding", QVariant(false));
    QCOMPARE(window->rootObject()->property("isEnabled").toBool(), false);

    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, 0);
    QVERIFY(!key.isAccepted());

    window->rootContext()->setContextProperty("enableKeyHanding", QVariant(true));
    QCOMPARE(window->rootObject()->property("isEnabled").toBool(), true);

    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Return, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_Return));
    QVERIFY(key.isAccepted());

    delete window;
    delete testObject;
}

Q_DECLARE_METATYPE(QEvent::Type);
Q_DECLARE_METATYPE(QKeySequence::StandardKey);

void tst_QQuickItem::standardKeys_data()
{
    QTest::addColumn<QKeySequence::StandardKey>("standardKey");
    QTest::addColumn<QKeySequence::StandardKey>("contextProperty");
    QTest::addColumn<QEvent::Type>("eventType");
    QTest::addColumn<bool>("pressed");
    QTest::addColumn<bool>("released");

    QTest::newRow("Press: Open") << QKeySequence::Open << QKeySequence::Open << QEvent::KeyPress << true << false;
    QTest::newRow("Press: Close") << QKeySequence::Close << QKeySequence::Close << QEvent::KeyPress << true << false;
    QTest::newRow("Press: Save") << QKeySequence::Save << QKeySequence::Save << QEvent::KeyPress << true << false;
    QTest::newRow("Press: Quit") << QKeySequence::Quit << QKeySequence::Quit << QEvent::KeyPress << true << false;

    QTest::newRow("Release: New") << QKeySequence::New << QKeySequence::New << QEvent::KeyRelease << false << true;
    QTest::newRow("Release: Delete") << QKeySequence::Delete << QKeySequence::Delete << QEvent::KeyRelease << false << true;
    QTest::newRow("Release: Undo") << QKeySequence::Undo << QKeySequence::Undo << QEvent::KeyRelease << false << true;
    QTest::newRow("Release: Redo") << QKeySequence::Redo << QKeySequence::Redo << QEvent::KeyRelease << false << true;

    QTest::newRow("Mismatch: Cut") << QKeySequence::Cut << QKeySequence::Copy << QEvent::KeyPress << false << false;
    QTest::newRow("Mismatch: Copy") << QKeySequence::Copy << QKeySequence::Paste << QEvent::KeyPress << false << false;
    QTest::newRow("Mismatch: Paste") << QKeySequence::Paste << QKeySequence::Cut << QEvent::KeyRelease << false << false;
    QTest::newRow("Mismatch: Quit") << QKeySequence::Quit << QKeySequence::New << QEvent::KeyRelease << false << false;
}

void tst_QQuickItem::standardKeys()
{
    QFETCH(QKeySequence::StandardKey, standardKey);
    QFETCH(QKeySequence::StandardKey, contextProperty);
    QFETCH(QEvent::Type, eventType);
    QFETCH(bool, pressed);
    QFETCH(bool, released);

    QKeySequence keySequence(standardKey);
    if (keySequence.isEmpty())
        QSKIP("Undefined key sequence.");

    QQuickView view;
    view.rootContext()->setContextProperty("standardKey", contextProperty);
    view.setSource(testFileUrl("standardkeys.qml"));
    view.show();
    view.requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(&view));

    QQuickItem *item = qobject_cast<QQuickItem*>(view.rootObject());
    QVERIFY(item);

    const int key = keySequence[0] & Qt::Key_unknown;
    const int modifiers = keySequence[0] & Qt::KeyboardModifierMask;
    QKeyEvent keyEvent(eventType, key, static_cast<Qt::KeyboardModifiers>(modifiers));
    QGuiApplication::sendEvent(&view, &keyEvent);

    QCOMPARE(item->property("pressed").toBool(), pressed);
    QCOMPARE(item->property("released").toBool(), released);
}

void tst_QQuickItem::keysProcessingOrder()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(240,320));

    KeysTestObject *testObject = new KeysTestObject;
    window->rootContext()->setContextProperty("keysTestObject", testObject);

    window->setSource(testFileUrl("keyspriority.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    KeyTestItem *testItem = qobject_cast<KeyTestItem*>(window->rootObject());
    QVERIFY(testItem);

    QCOMPARE(testItem->property("priorityTest").toInt(), 0);

    QKeyEvent key(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier, "A", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_A));
    QCOMPARE(testObject->mText, QLatin1String("A"));
    QCOMPARE(testObject->mModifiers, int(Qt::NoModifier));
    QVERIFY(key.isAccepted());

    testObject->reset();

    testObject->setProcessLast(true);

    QCOMPARE(testItem->property("priorityTest").toInt(), 1);

    key = QKeyEvent(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier, "A", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, 0);
    QVERIFY(key.isAccepted());

    testObject->reset();

    key = QKeyEvent(QEvent::KeyPress, Qt::Key_B, Qt::NoModifier, "B", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, int(Qt::Key_B));
    QCOMPARE(testObject->mText, QLatin1String("B"));
    QCOMPARE(testObject->mModifiers, int(Qt::NoModifier));
    QVERIFY(!key.isAccepted());

    testObject->reset();

    key = QKeyEvent(QEvent::KeyRelease, Qt::Key_B, Qt::NoModifier, "B", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QCOMPARE(testObject->mKey, 0);
    QVERIFY(key.isAccepted());

    delete window;
    delete testObject;
}

void tst_QQuickItem::keysim()
{
    PlatformInputContext platformInputContext;
    QInputMethodPrivate *inputMethodPrivate = QInputMethodPrivate::get(qApp->inputMethod());
    inputMethodPrivate->testContext = &platformInputContext;

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(240,320));

    window->setSource(testFileUrl("keysim.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QVERIFY(window->rootObject());
    QVERIFY(window->rootObject()->hasFocus() && window->rootObject()->hasActiveFocus());

    QQuickTextInput *input = window->rootObject()->findChild<QQuickTextInput*>();
    QVERIFY(input);

    QInputMethodEvent ev("Hello world!", QList<QInputMethodEvent::Attribute>());
    QGuiApplication::sendEvent(qGuiApp->focusObject(), &ev);

    QEXPECT_FAIL("", "QTBUG-24280", Continue);
    QCOMPARE(input->text(), QLatin1String("Hello world!"));

    delete window;
}

void tst_QQuickItem::keysForward()
{
    QQuickView window;
    window.setBaseSize(QSize(240,320));

    window.setSource(testFileUrl("keysforward.qml"));
    window.show();
    window.requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(&window));
    QCOMPARE(QGuiApplication::focusWindow(), &window);

    QQuickItem *rootItem = qobject_cast<QQuickItem *>(window.rootObject());
    QVERIFY(rootItem);
    QQuickItem *sourceItem = rootItem->property("source").value<QQuickItem *>();
    QVERIFY(sourceItem);
    QQuickItem *primaryTarget = rootItem->property("primaryTarget").value<QQuickItem *>();
    QVERIFY(primaryTarget);
    QQuickItem *secondaryTarget = rootItem->property("secondaryTarget").value<QQuickItem *>();
    QVERIFY(secondaryTarget);

    // primary target accepts/consumes Key_P
    QKeyEvent pressKeyP(QEvent::KeyPress, Qt::Key_P, Qt::NoModifier, "P");
    QCoreApplication::sendEvent(sourceItem, &pressKeyP);
    QCOMPARE(rootItem->property("pressedKeys").toList(), QVariantList());
    QCOMPARE(sourceItem->property("pressedKeys").toList(), QVariantList());
    QCOMPARE(primaryTarget->property("pressedKeys").toList(), QVariantList() << Qt::Key_P);
    QCOMPARE(secondaryTarget->property("pressedKeys").toList(), QVariantList() << Qt::Key_P);
    QVERIFY(pressKeyP.isAccepted());

    QKeyEvent releaseKeyP(QEvent::KeyRelease, Qt::Key_P, Qt::NoModifier, "P");
    QCoreApplication::sendEvent(sourceItem, &releaseKeyP);
    QCOMPARE(rootItem->property("releasedKeys").toList(), QVariantList());
    QCOMPARE(sourceItem->property("releasedKeys").toList(), QVariantList());
    QCOMPARE(primaryTarget->property("releasedKeys").toList(), QVariantList() << Qt::Key_P);
    QCOMPARE(secondaryTarget->property("releasedKeys").toList(), QVariantList() << Qt::Key_P);
    QVERIFY(releaseKeyP.isAccepted());

    // secondary target accepts/consumes Key_S
    QKeyEvent pressKeyS(QEvent::KeyPress, Qt::Key_S, Qt::NoModifier, "S");
    QCoreApplication::sendEvent(sourceItem, &pressKeyS);
    QCOMPARE(rootItem->property("pressedKeys").toList(), QVariantList());
    QCOMPARE(sourceItem->property("pressedKeys").toList(), QVariantList());
    QCOMPARE(primaryTarget->property("pressedKeys").toList(), QVariantList() << Qt::Key_P);
    QCOMPARE(secondaryTarget->property("pressedKeys").toList(), QVariantList() << Qt::Key_P << Qt::Key_S);
    QVERIFY(pressKeyS.isAccepted());

    QKeyEvent releaseKeyS(QEvent::KeyRelease, Qt::Key_S, Qt::NoModifier, "S");
    QCoreApplication::sendEvent(sourceItem, &releaseKeyS);
    QCOMPARE(rootItem->property("releasedKeys").toList(), QVariantList());
    QCOMPARE(sourceItem->property("releasedKeys").toList(), QVariantList());
    QCOMPARE(primaryTarget->property("releasedKeys").toList(), QVariantList() << Qt::Key_P);
    QCOMPARE(secondaryTarget->property("releasedKeys").toList(), QVariantList() << Qt::Key_P << Qt::Key_S);
    QVERIFY(releaseKeyS.isAccepted());

    // neither target accepts/consumes Key_Q
    QKeyEvent pressKeyQ(QEvent::KeyPress, Qt::Key_Q, Qt::NoModifier, "Q");
    QCoreApplication::sendEvent(sourceItem, &pressKeyQ);
    QCOMPARE(rootItem->property("pressedKeys").toList(), QVariantList());
    QCOMPARE(sourceItem->property("pressedKeys").toList(), QVariantList() << Qt::Key_Q);
    QCOMPARE(primaryTarget->property("pressedKeys").toList(), QVariantList() << Qt::Key_P << Qt::Key_Q);
    QCOMPARE(secondaryTarget->property("pressedKeys").toList(), QVariantList() << Qt::Key_P << Qt::Key_S << Qt::Key_Q);
    QVERIFY(!pressKeyQ.isAccepted());

    QKeyEvent releaseKeyQ(QEvent::KeyRelease, Qt::Key_Q, Qt::NoModifier, "Q");
    QCoreApplication::sendEvent(sourceItem, &releaseKeyQ);
    QCOMPARE(rootItem->property("releasedKeys").toList(), QVariantList());
    QCOMPARE(sourceItem->property("releasedKeys").toList(), QVariantList() << Qt::Key_Q);
    QCOMPARE(primaryTarget->property("releasedKeys").toList(), QVariantList() << Qt::Key_P << Qt::Key_Q);
    QCOMPARE(secondaryTarget->property("releasedKeys").toList(), QVariantList() << Qt::Key_P << Qt::Key_S << Qt::Key_Q);
    QVERIFY(!releaseKeyQ.isAccepted());
}

QQuickItemPrivate *childPrivate(QQuickItem *rootItem, const char * itemString)
{
    QQuickItem *item = findItem<QQuickItem>(rootItem, QString(QLatin1String(itemString)));
    QQuickItemPrivate* itemPrivate = QQuickItemPrivate::get(item);
    return itemPrivate;
}

QVariant childProperty(QQuickItem *rootItem, const char * itemString, const char * property)
{
    QQuickItem *item = findItem<QQuickItem>(rootItem, QString(QLatin1String(itemString)));
    return item->property(property);
}

bool anchorsMirrored(QQuickItem *rootItem, const char * itemString)
{
    QQuickItem *item = findItem<QQuickItem>(rootItem, QString(QLatin1String(itemString)));
    QQuickItemPrivate* itemPrivate = QQuickItemPrivate::get(item);
    return itemPrivate->anchors()->mirrored();
}

void tst_QQuickItem::layoutMirroring()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setSource(testFileUrl("layoutmirroring.qml"));
    window->show();

    QQuickItem *rootItem = qobject_cast<QQuickItem*>(window->rootObject());
    QVERIFY(rootItem);
    QQuickItemPrivate *rootPrivate = QQuickItemPrivate::get(rootItem);
    QVERIFY(rootPrivate);

    QCOMPARE(childPrivate(rootItem, "mirrored1")->effectiveLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "mirrored2")->effectiveLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "notMirrored1")->effectiveLayoutMirror, false);
    QCOMPARE(childPrivate(rootItem, "notMirrored2")->effectiveLayoutMirror, false);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror1")->effectiveLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror2")->effectiveLayoutMirror, true);

    QCOMPARE(anchorsMirrored(rootItem, "mirrored1"), true);
    QCOMPARE(anchorsMirrored(rootItem, "mirrored2"), true);
    QCOMPARE(anchorsMirrored(rootItem, "notMirrored1"), false);
    QCOMPARE(anchorsMirrored(rootItem, "notMirrored2"), false);
    QCOMPARE(anchorsMirrored(rootItem, "inheritedMirror1"), true);
    QCOMPARE(anchorsMirrored(rootItem, "inheritedMirror2"), true);

    QCOMPARE(childPrivate(rootItem, "mirrored1")->inheritedLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "mirrored2")->inheritedLayoutMirror, false);
    QCOMPARE(childPrivate(rootItem, "notMirrored1")->inheritedLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "notMirrored2")->inheritedLayoutMirror, false);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror1")->inheritedLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror2")->inheritedLayoutMirror, true);

    QCOMPARE(childPrivate(rootItem, "mirrored1")->isMirrorImplicit, false);
    QCOMPARE(childPrivate(rootItem, "mirrored2")->isMirrorImplicit, false);
    QCOMPARE(childPrivate(rootItem, "notMirrored1")->isMirrorImplicit, false);
    QCOMPARE(childPrivate(rootItem, "notMirrored2")->isMirrorImplicit, true);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror1")->isMirrorImplicit, true);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror2")->isMirrorImplicit, true);

    QCOMPARE(childPrivate(rootItem, "mirrored1")->inheritMirrorFromParent, true);
    QCOMPARE(childPrivate(rootItem, "mirrored2")->inheritMirrorFromParent, false);
    QCOMPARE(childPrivate(rootItem, "notMirrored1")->inheritMirrorFromParent, true);
    QCOMPARE(childPrivate(rootItem, "notMirrored2")->inheritMirrorFromParent, false);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror1")->inheritMirrorFromParent, true);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror2")->inheritMirrorFromParent, true);

    QCOMPARE(childPrivate(rootItem, "mirrored1")->inheritMirrorFromItem, true);
    QCOMPARE(childPrivate(rootItem, "mirrored2")->inheritMirrorFromItem, false);
    QCOMPARE(childPrivate(rootItem, "notMirrored1")->inheritMirrorFromItem, false);
    QCOMPARE(childPrivate(rootItem, "notMirrored2")->inheritMirrorFromItem, false);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror1")->inheritMirrorFromItem, false);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror2")->inheritMirrorFromItem, false);

    // load dynamic content using Loader that needs to inherit mirroring
    rootItem->setProperty("state", "newContent");
    QCOMPARE(childPrivate(rootItem, "notMirrored3")->effectiveLayoutMirror, false);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror3")->effectiveLayoutMirror, true);

    QCOMPARE(childPrivate(rootItem, "notMirrored3")->inheritedLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror3")->inheritedLayoutMirror, true);

    QCOMPARE(childPrivate(rootItem, "notMirrored3")->isMirrorImplicit, false);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror3")->isMirrorImplicit, true);

    QCOMPARE(childPrivate(rootItem, "notMirrored3")->inheritMirrorFromParent, true);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror3")->inheritMirrorFromParent, true);

    QCOMPARE(childPrivate(rootItem, "notMirrored3")->inheritMirrorFromItem, false);
    QCOMPARE(childPrivate(rootItem, "notMirrored3")->inheritMirrorFromItem, false);

    // disable inheritance
    rootItem->setProperty("childrenInherit", false);

    QCOMPARE(childPrivate(rootItem, "inheritedMirror1")->effectiveLayoutMirror, false);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror2")->effectiveLayoutMirror, false);
    QCOMPARE(childPrivate(rootItem, "mirrored1")->effectiveLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "notMirrored1")->effectiveLayoutMirror, false);

    QCOMPARE(childPrivate(rootItem, "inheritedMirror1")->inheritedLayoutMirror, false);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror2")->inheritedLayoutMirror, false);
    QCOMPARE(childPrivate(rootItem, "mirrored1")->inheritedLayoutMirror, false);
    QCOMPARE(childPrivate(rootItem, "notMirrored1")->inheritedLayoutMirror, false);

    // re-enable inheritance
    rootItem->setProperty("childrenInherit", true);

    QCOMPARE(childPrivate(rootItem, "inheritedMirror1")->effectiveLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror2")->effectiveLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "mirrored1")->effectiveLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "notMirrored1")->effectiveLayoutMirror, false);

    QCOMPARE(childPrivate(rootItem, "inheritedMirror1")->inheritedLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "inheritedMirror2")->inheritedLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "mirrored1")->inheritedLayoutMirror, true);
    QCOMPARE(childPrivate(rootItem, "notMirrored1")->inheritedLayoutMirror, true);

    //
    // dynamic parenting
    //
    QQuickItem *parentItem1 = new QQuickItem();
    QQuickItemPrivate::get(parentItem1)->effectiveLayoutMirror = true; // LayoutMirroring.enabled: true
    QQuickItemPrivate::get(parentItem1)->isMirrorImplicit = false;
    QQuickItemPrivate::get(parentItem1)->inheritMirrorFromItem = true; // LayoutMirroring.childrenInherit: true
    QQuickItemPrivate::get(parentItem1)->resolveLayoutMirror();

    // inherit in constructor
    QQuickItem *childItem1 = new QQuickItem(parentItem1);
    QCOMPARE(QQuickItemPrivate::get(childItem1)->effectiveLayoutMirror, true);
    QCOMPARE(QQuickItemPrivate::get(childItem1)->inheritMirrorFromParent, true);

    // inherit through a parent change
    QQuickItem *childItem2 = new QQuickItem();
    QCOMPARE(QQuickItemPrivate::get(childItem2)->effectiveLayoutMirror, false);
    QCOMPARE(QQuickItemPrivate::get(childItem2)->inheritMirrorFromParent, false);
    childItem2->setParentItem(parentItem1);
    QCOMPARE(QQuickItemPrivate::get(childItem2)->effectiveLayoutMirror, true);
    QCOMPARE(QQuickItemPrivate::get(childItem2)->inheritMirrorFromParent, true);

    // stop inherting through a parent change
    QQuickItem *parentItem2 = new QQuickItem();
    QQuickItemPrivate::get(parentItem2)->effectiveLayoutMirror = true; // LayoutMirroring.enabled: true
    QQuickItemPrivate::get(parentItem2)->resolveLayoutMirror();
    childItem2->setParentItem(parentItem2);
    QCOMPARE(QQuickItemPrivate::get(childItem2)->effectiveLayoutMirror, false);
    QCOMPARE(QQuickItemPrivate::get(childItem2)->inheritMirrorFromParent, false);

    delete parentItem1;
    delete parentItem2;
}

void tst_QQuickItem::layoutMirroringWindow()
{
    QQmlComponent component(&engine);
    component.loadUrl(testFileUrl("layoutmirroring_window.qml"));
    QScopedPointer<QObject> object(component.create());
    QQuickWindow *window = qobject_cast<QQuickWindow *>(object.data());
    QVERIFY(window);
    window->show();

    QQuickItemPrivate *content = QQuickItemPrivate::get(window->contentItem());
    QCOMPARE(content->effectiveLayoutMirror, true);
    QCOMPARE(content->inheritedLayoutMirror, true);
    QCOMPARE(content->isMirrorImplicit, false);
    QCOMPARE(content->inheritMirrorFromParent, true);
    QCOMPARE(content->inheritMirrorFromItem, true);
}

void tst_QQuickItem::layoutMirroringIllegalParent()
{
    QQmlComponent component(&engine);
    component.setData("import QtQuick 2.0; QtObject { LayoutMirroring.enabled: true; LayoutMirroring.childrenInherit: true }", QUrl::fromLocalFile(""));
    QTest::ignoreMessage(QtWarningMsg, "<Unknown File>:1:21: QML QtObject: LayoutDirection attached property only works with Items and Windows");
    QObject *object = component.create();
    QVERIFY(object != nullptr);
}

void tst_QQuickItem::keyNavigation_data()
{
    QTest::addColumn<QString>("source");
    QTest::newRow("KeyNavigation") << QStringLiteral("keynavigationtest.qml");
    QTest::newRow("KeyNavigation_FocusScope") << QStringLiteral("keynavigationtest_focusscope.qml");
}

void tst_QQuickItem::keyNavigation()
{
    QFETCH(QString, source);

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(240,320));

    window->setSource(testFileUrl(source));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    QVariant result;
    QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "verify",
            Q_RETURN_ARG(QVariant, result)));
    QVERIFY(result.toBool());

    // right
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item2");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // down
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item4");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // left
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item3");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // up
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // tab
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item2");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // backtab
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::keyNavigation_RightToLeft()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(240,320));

    window->setSource(testFileUrl("keynavigationtest.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *rootItem = qobject_cast<QQuickItem*>(window->rootObject());
    QVERIFY(rootItem);
    QQuickItemPrivate* rootItemPrivate = QQuickItemPrivate::get(rootItem);

    rootItemPrivate->effectiveLayoutMirror = true; // LayoutMirroring.mirror: true
    rootItemPrivate->isMirrorImplicit = false;
    rootItemPrivate->inheritMirrorFromItem = true; // LayoutMirroring.inherit: true
    rootItemPrivate->resolveLayoutMirror();

    QEvent wa(QEvent::WindowActivate);
    QGuiApplication::sendEvent(window, &wa);
    QFocusEvent fe(QEvent::FocusIn);
    QGuiApplication::sendEvent(window, &fe);

    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    QVariant result;
    QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "verify",
            Q_RETURN_ARG(QVariant, result)));
    QVERIFY(result.toBool());

    // right
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item2");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // left
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::keyNavigation_skipNotVisible()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(240,320));

    window->setSource(testFileUrl("keynavigationtest.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // Set item 2 to not visible
    item = findItem<QQuickItem>(window->rootObject(), "item2");
    QVERIFY(item);
    item->setVisible(false);
    QVERIFY(!item->isVisible());

    // right
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // tab
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item3");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // backtab
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    //Set item 3 to not visible
    item = findItem<QQuickItem>(window->rootObject(), "item3");
    QVERIFY(item);
    item->setVisible(false);
    QVERIFY(!item->isVisible());

    // tab
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item4");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // backtab
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::keyNavigation_implicitSetting()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(240,320));

    window->setSource(testFileUrl("keynavigationtest_implicit.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QEvent wa(QEvent::WindowActivate);
    QGuiApplication::sendEvent(window, &wa);
    QFocusEvent fe(QEvent::FocusIn);
    QGuiApplication::sendEvent(window, &fe);

    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    QVariant result;
    QVERIFY(QMetaObject::invokeMethod(window->rootObject(), "verify",
            Q_RETURN_ARG(QVariant, result)));
    QVERIFY(result.toBool());

    // right
    QKeyEvent key(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item2");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // back to item1
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // down
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item3");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // move to item4
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item4");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // left
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Left, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item3");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // back to item4
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item4");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // up
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Up, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item2");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // back to item4
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item4");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // tab
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // back to item4
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item4");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    // backtab
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());

    item = findItem<QQuickItem>(window->rootObject(), "item3");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    delete window;
}

// QTBUG-75399
void tst_QQuickItem::keyNavigation_implicitDestroy()
{
    QQuickView view;
    view.setSource(testFileUrl("keynavigationtest_implicitDestroy.qml"));
    view.show();

    QVERIFY(QTest::qWaitForWindowActive(&view));

    QQuickItem *root = view.rootObject();
    QVERIFY(QMetaObject::invokeMethod(root, "createImplicitKeyNavigation"));

    // process events is necessary to trigger upcoming memory access violation
    QTest::qWait(0);

    QVERIFY(root->hasActiveFocus());

    QKeyEvent keyPress = QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(&view, &keyPress); // <-- access violation happens here
    // this should fail the test, even if the access violation does not occur
    QVERIFY(!keyPress.isAccepted());
}

void tst_QQuickItem::keyNavigation_focusReason()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(240,320));

    FocusEventFilter focusEventFilter;

    window->setSource(testFileUrl("keynavigationtest.qml"));
    window->show();
    window->requestActivate();

    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    // install event filter on first item
    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());
    item->installEventFilter(&focusEventFilter);

    //install event filter on second item
    item = findItem<QQuickItem>(window->rootObject(), "item2");
    QVERIFY(item);
    item->installEventFilter(&focusEventFilter);

    //install event filter on third item
    item = findItem<QQuickItem>(window->rootObject(), "item3");
    QVERIFY(item);
    item->installEventFilter(&focusEventFilter);

    //install event filter on last item
    item = findItem<QQuickItem>(window->rootObject(), "item4");
    QVERIFY(item);
    item->installEventFilter(&focusEventFilter);

    // tab
    QKeyEvent key = QKeyEvent(QEvent::KeyPress, Qt::Key_Tab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());
    QCOMPARE(focusEventFilter.lastFocusReason, Qt::TabFocusReason);

    // backtab
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Backtab, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());
    QCOMPARE(focusEventFilter.lastFocusReason, Qt::BacktabFocusReason);

    // right - it's also one kind of key navigation
    key = QKeyEvent(QEvent::KeyPress, Qt::Key_Right, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());
    QCOMPARE(focusEventFilter.lastFocusReason, Qt::TabFocusReason);

    item->setFocus(true, Qt::OtherFocusReason);
    QVERIFY(item->hasActiveFocus());
    QCOMPARE(focusEventFilter.lastFocusReason, Qt::OtherFocusReason);

    delete window;
}

void tst_QQuickItem::keyNavigation_loop()
{
    // QTBUG-47229
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(240,320));

    window->setSource(testFileUrl("keynavigationtest_loop.qml"));
    window->show();
    window->requestActivate();

    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "item1");
    QVERIFY(item);
    QVERIFY(item->hasActiveFocus());

    QKeyEvent key = QKeyEvent(QEvent::KeyPress, Qt::Key_Down, Qt::NoModifier, "", false, 1);
    QGuiApplication::sendEvent(window, &key);
    QVERIFY(key.isAccepted());
    QVERIFY(item->hasActiveFocus());

    delete window;
}

void tst_QQuickItem::smooth()
{
    QQmlComponent component(&engine);
    component.setData("import QtQuick 2.0; Item { smooth: false; }", QUrl::fromLocalFile(""));
    QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
    QSignalSpy spy(item, SIGNAL(smoothChanged(bool)));

    QVERIFY(item);
    QVERIFY(!item->smooth());

    item->setSmooth(true);
    QVERIFY(item->smooth());
    QCOMPARE(spy.count(),1);
    QList<QVariant> arguments = spy.first();
    QCOMPARE(arguments.count(), 1);
    QVERIFY(arguments.at(0).toBool());

    item->setSmooth(true);
    QCOMPARE(spy.count(),1);

    item->setSmooth(false);
    QVERIFY(!item->smooth());
    QCOMPARE(spy.count(),2);
    item->setSmooth(false);
    QCOMPARE(spy.count(),2);

    delete item;
}

void tst_QQuickItem::antialiasing()
{
    QQmlComponent component(&engine);
    component.setData("import QtQuick 2.0; Item { antialiasing: false; }", QUrl::fromLocalFile(""));
    QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
    QSignalSpy spy(item, SIGNAL(antialiasingChanged(bool)));

    QVERIFY(item);
    QVERIFY(!item->antialiasing());

    item->setAntialiasing(true);
    QVERIFY(item->antialiasing());
    QCOMPARE(spy.count(),1);
    QList<QVariant> arguments = spy.first();
    QCOMPARE(arguments.count(), 1);
    QVERIFY(arguments.at(0).toBool());

    item->setAntialiasing(true);
    QCOMPARE(spy.count(),1);

    item->setAntialiasing(false);
    QVERIFY(!item->antialiasing());
    QCOMPARE(spy.count(),2);
    item->setAntialiasing(false);
    QCOMPARE(spy.count(),2);

    delete item;
}

void tst_QQuickItem::clip()
{
    QQmlComponent component(&engine);
    component.setData("import QtQuick 2.0\nItem { clip: false\n }", QUrl::fromLocalFile(""));
    QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
    QSignalSpy spy(item, SIGNAL(clipChanged(bool)));

    QVERIFY(item);
    QVERIFY(!item->clip());

    item->setClip(true);
    QVERIFY(item->clip());

    QList<QVariant> arguments = spy.first();
    QCOMPARE(arguments.count(), 1);
    QVERIFY(arguments.at(0).toBool());

    QCOMPARE(spy.count(),1);
    item->setClip(true);
    QCOMPARE(spy.count(),1);

    item->setClip(false);
    QVERIFY(!item->clip());
    QCOMPARE(spy.count(),2);
    item->setClip(false);
    QCOMPARE(spy.count(),2);

    delete item;
}

void tst_QQuickItem::mapCoordinates()
{
    QFETCH(int, x);
    QFETCH(int, y);

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(300, 300));
    window->setSource(testFileUrl("mapCoordinates.qml"));
    window->show();
    qApp->processEvents();

    QQuickItem *root = qobject_cast<QQuickItem*>(window->rootObject());
    QVERIFY(root != nullptr);
    QQuickItem *a = findItem<QQuickItem>(window->rootObject(), "itemA");
    QVERIFY(a != nullptr);
    QQuickItem *b = findItem<QQuickItem>(window->rootObject(), "itemB");
    QVERIFY(b != nullptr);

    QVariant result;

    QVERIFY(QMetaObject::invokeMethod(root, "mapAToB",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y)));
    QCOMPARE(result.value<QPointF>(), qobject_cast<QQuickItem*>(a)->mapToItem(b, QPointF(x, y)));

    QVERIFY(QMetaObject::invokeMethod(root, "mapAFromB",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y)));
    QCOMPARE(result.value<QPointF>(), qobject_cast<QQuickItem*>(a)->mapFromItem(b, QPointF(x, y)));

    QVERIFY(QMetaObject::invokeMethod(root, "mapAToNull",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y)));
    QCOMPARE(result.value<QPointF>(), qobject_cast<QQuickItem*>(a)->mapToScene(QPointF(x, y)));

    QVERIFY(QMetaObject::invokeMethod(root, "mapAFromNull",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y)));
    QCOMPARE(result.value<QPointF>(), qobject_cast<QQuickItem*>(a)->mapFromScene(QPointF(x, y)));

    QVERIFY(QMetaObject::invokeMethod(root, "mapAToGlobal",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y)));
    QCOMPARE(result.value<QPointF>(), qobject_cast<QQuickItem*>(a)->mapToGlobal(QPointF(x, y)));

    QVERIFY(QMetaObject::invokeMethod(root, "mapAFromGlobal",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y)));
    QCOMPARE(result.value<QPointF>(), qobject_cast<QQuickItem*>(a)->mapFromGlobal(QPointF(x, y)));

    // for orphans we are primarily testing that we don't crash.
    // when orphaned the final position is the original position of the item translated by x,y
    QVERIFY(QMetaObject::invokeMethod(root, "mapOrphanToGlobal",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y)));
    QCOMPARE(result.value<QPointF>(), QPointF(150,150) + QPointF(x, y));

    QVERIFY(QMetaObject::invokeMethod(root, "mapOrphanFromGlobal",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y)));
    QCOMPARE(result.value<QPointF>(), -QPointF(150,150) + QPointF(x, y));

    QString warning1 = testFileUrl("mapCoordinates.qml").toString() + ":35:5: QML Item: mapToItem() given argument \"1122\" which is neither null nor an Item";
    QString warning2 = testFileUrl("mapCoordinates.qml").toString() + ":35:5: QML Item: mapFromItem() given argument \"1122\" which is neither null nor an Item";

    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning1));
    QVERIFY(QMetaObject::invokeMethod(root, "checkMapAToInvalid",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y)));
    QVERIFY(result.toBool());

    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning2));
    QVERIFY(QMetaObject::invokeMethod(root, "checkMapAFromInvalid",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y)));
    QVERIFY(result.toBool());

    delete window;
}

void tst_QQuickItem::mapCoordinates_data()
{
    QTest::addColumn<int>("x");
    QTest::addColumn<int>("y");

    for (int i=-20; i<=20; i+=10)
        QTest::newRow(QTest::toString(i)) << i << i;
}

void tst_QQuickItem::mapCoordinatesRect()
{
    QFETCH(int, x);
    QFETCH(int, y);
    QFETCH(int, width);
    QFETCH(int, height);

    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(300, 300));
    window->setSource(testFileUrl("mapCoordinatesRect.qml"));
    window->show();
    qApp->processEvents();

    QQuickItem *root = qobject_cast<QQuickItem*>(window->rootObject());
    QVERIFY(root != nullptr);
    QQuickItem *a = findItem<QQuickItem>(window->rootObject(), "itemA");
    QVERIFY(a != nullptr);
    QQuickItem *b = findItem<QQuickItem>(window->rootObject(), "itemB");
    QVERIFY(b != nullptr);

    QVariant result;

    QVERIFY(QMetaObject::invokeMethod(root, "mapAToB",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y), Q_ARG(QVariant, width), Q_ARG(QVariant, height)));
    QCOMPARE(result.value<QRectF>(), qobject_cast<QQuickItem*>(a)->mapRectToItem(b, QRectF(x, y, width, height)));

    QVERIFY(QMetaObject::invokeMethod(root, "mapAFromB",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y), Q_ARG(QVariant, width), Q_ARG(QVariant, height)));
    QCOMPARE(result.value<QRectF>(), qobject_cast<QQuickItem*>(a)->mapRectFromItem(b, QRectF(x, y, width, height)));

    QVERIFY(QMetaObject::invokeMethod(root, "mapAToNull",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y), Q_ARG(QVariant, width), Q_ARG(QVariant, height)));
    QCOMPARE(result.value<QRectF>(), qobject_cast<QQuickItem*>(a)->mapRectToScene(QRectF(x, y, width, height)));

    QVERIFY(QMetaObject::invokeMethod(root, "mapAFromNull",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y), Q_ARG(QVariant, width), Q_ARG(QVariant, height)));
    QCOMPARE(result.value<QRectF>(), qobject_cast<QQuickItem*>(a)->mapRectFromScene(QRectF(x, y, width, height)));

    QString warning1 = testFileUrl("mapCoordinatesRect.qml").toString() + ":35:5: QML Item: mapToItem() given argument \"1122\" which is neither null nor an Item";
    QString warning2 = testFileUrl("mapCoordinatesRect.qml").toString() + ":35:5: QML Item: mapFromItem() given argument \"1122\" which is neither null nor an Item";

    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning1));
    QVERIFY(QMetaObject::invokeMethod(root, "checkMapAToInvalid",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y), Q_ARG(QVariant, width), Q_ARG(QVariant, height)));
    QVERIFY(result.toBool());

    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning2));
    QVERIFY(QMetaObject::invokeMethod(root, "checkMapAFromInvalid",
            Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, x), Q_ARG(QVariant, y), Q_ARG(QVariant, width), Q_ARG(QVariant, height)));
    QVERIFY(result.toBool());

    delete window;
}

void tst_QQuickItem::mapCoordinatesRect_data()
{
    QTest::addColumn<int>("x");
    QTest::addColumn<int>("y");
    QTest::addColumn<int>("width");
    QTest::addColumn<int>("height");

    for (int i=-20; i<=20; i+=5)
        QTest::newRow(QTest::toString(i)) << i << i << i << i;
}

void tst_QQuickItem::transforms_data()
{
    QTest::addColumn<QByteArray>("qml");
    QTest::addColumn<QTransform>("transform");
    QTest::newRow("translate") << QByteArray("Translate { x: 10; y: 20 }")
        << QTransform(1,0,0,0,1,0,10,20,1);
    QTest::newRow("matrix4x4") << QByteArray("Matrix4x4 { matrix: Qt.matrix4x4(1,0,0,10, 0,1,0,15, 0,0,1,0, 0,0,0,1) }")
        << QTransform(1,0,0,0,1,0,10,15,1);
    QTest::newRow("rotation") << QByteArray("Rotation { angle: 90 }")
        << QTransform(0,1,0,-1,0,0,0,0,1);
    QTest::newRow("scale") << QByteArray("Scale { xScale: 1.5; yScale: -2  }")
        << QTransform(1.5,0,0,0,-2,0,0,0,1);
    QTest::newRow("sequence") << QByteArray("[ Translate { x: 10; y: 20 }, Scale { xScale: 1.5; yScale: -2  } ]")
        << QTransform(1,0,0,0,1,0,10,20,1) * QTransform(1.5,0,0,0,-2,0,0,0,1);
}

void tst_QQuickItem::transforms()
{
    QFETCH(QByteArray, qml);
    QFETCH(QTransform, transform);
    QQmlComponent component(&engine);
    component.setData("import QtQuick 2.3\nItem { transform: "+qml+"}", QUrl::fromLocalFile(""));
    QQuickItem *item = qobject_cast<QQuickItem*>(component.create());
    QVERIFY(item);
    QCOMPARE(item->itemTransform(nullptr,nullptr), transform);
}

void tst_QQuickItem::childrenProperty()
{
    QQmlComponent component(&engine, testFileUrl("childrenProperty.qml"));

    QObject *o = component.create();
    QVERIFY(o != nullptr);

    QCOMPARE(o->property("test1").toBool(), true);
    QCOMPARE(o->property("test2").toBool(), true);
    QCOMPARE(o->property("test3").toBool(), true);
    QCOMPARE(o->property("test4").toBool(), true);
    QCOMPARE(o->property("test5").toBool(), true);
    delete o;
}

void tst_QQuickItem::resourcesProperty()
{
    QQmlComponent component(&engine, testFileUrl("resourcesProperty.qml"));

    QObject *object = component.create();
    QVERIFY(object != nullptr);

    QQmlProperty property(object, "resources", component.creationContext());

    QVERIFY(property.isValid());
    QQmlListReference list = qvariant_cast<QQmlListReference>(property.read());
    QVERIFY(list.isValid());

    QCOMPARE(list.count(), 4);

    QCOMPARE(object->property("test1").toBool(), true);
    QCOMPARE(object->property("test2").toBool(), true);
    QCOMPARE(object->property("test3").toBool(), true);
    QCOMPARE(object->property("test4").toBool(), true);
    QCOMPARE(object->property("test5").toBool(), true);
    QCOMPARE(object->property("test6").toBool(), true);

    QObject *subObject = object->findChild<QObject *>("subObject");

    QVERIFY(subObject);

    QCOMPARE(object, subObject->parent());

    delete subObject;

    QCOMPARE(list.count(), 3);

    delete object;
}

void tst_QQuickItem::propertyChanges()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setBaseSize(QSize(300, 300));
    window->setSource(testFileUrl("propertychanges.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *item = findItem<QQuickItem>(window->rootObject(), "item");
    QQuickItem *parentItem = findItem<QQuickItem>(window->rootObject(), "parentItem");

    QVERIFY(item);
    QVERIFY(parentItem);

    QSignalSpy parentSpy(item, SIGNAL(parentChanged(QQuickItem*)));
    QSignalSpy widthSpy(item, SIGNAL(widthChanged()));
    QSignalSpy heightSpy(item, SIGNAL(heightChanged()));
    QSignalSpy baselineOffsetSpy(item, SIGNAL(baselineOffsetChanged(qreal)));
    QSignalSpy childrenRectSpy(parentItem, SIGNAL(childrenRectChanged(QRectF)));
    QSignalSpy focusSpy(item, SIGNAL(focusChanged(bool)));
    QSignalSpy wantsFocusSpy(parentItem, SIGNAL(activeFocusChanged(bool)));
    QSignalSpy childrenChangedSpy(parentItem, SIGNAL(childrenChanged()));
    QSignalSpy xSpy(item, SIGNAL(xChanged()));
    QSignalSpy ySpy(item, SIGNAL(yChanged()));

    item->setParentItem(parentItem);
    item->setWidth(100.0);
    item->setHeight(200.0);
    item->setFocus(true);
    item->setBaselineOffset(10.0);

    QCOMPARE(item->parentItem(), parentItem);
    QCOMPARE(parentSpy.count(),1);
    QList<QVariant> parentArguments = parentSpy.first();
    QCOMPARE(parentArguments.count(), 1);
    QCOMPARE(item->parentItem(), qvariant_cast<QQuickItem *>(parentArguments.at(0)));
    QCOMPARE(childrenChangedSpy.count(),1);

    item->setParentItem(parentItem);
    QCOMPARE(childrenChangedSpy.count(),1);

    QCOMPARE(item->width(), 100.0);
    QCOMPARE(widthSpy.count(),1);

    QCOMPARE(item->height(), 200.0);
    QCOMPARE(heightSpy.count(),1);

    QCOMPARE(item->baselineOffset(), 10.0);
    QCOMPARE(baselineOffsetSpy.count(),1);
    QList<QVariant> baselineOffsetArguments = baselineOffsetSpy.first();
    QCOMPARE(baselineOffsetArguments.count(), 1);
    QCOMPARE(item->baselineOffset(), baselineOffsetArguments.at(0).toReal());

    QCOMPARE(parentItem->childrenRect(), QRectF(0.0,0.0,100.0,200.0));
    QCOMPARE(childrenRectSpy.count(),1);
    QList<QVariant> childrenRectArguments = childrenRectSpy.at(0);
    QCOMPARE(childrenRectArguments.count(), 1);
    QCOMPARE(parentItem->childrenRect(), childrenRectArguments.at(0).toRectF());

    QCOMPARE(item->hasActiveFocus(), true);
    QCOMPARE(focusSpy.count(),1);
    QList<QVariant> focusArguments = focusSpy.first();
    QCOMPARE(focusArguments.count(), 1);
    QCOMPARE(focusArguments.at(0).toBool(), true);

    QCOMPARE(parentItem->hasActiveFocus(), false);
    QCOMPARE(parentItem->hasFocus(), false);
    QCOMPARE(wantsFocusSpy.count(),0);

    item->setX(10.0);
    QCOMPARE(item->x(), 10.0);
    QCOMPARE(xSpy.count(), 1);

    item->setY(10.0);
    QCOMPARE(item->y(), 10.0);
    QCOMPARE(ySpy.count(), 1);

    delete window;
}

void tst_QQuickItem::nonexistentPropertyConnection()
{
    // QTBUG-56551: don't crash
    QQmlComponent component(&engine, testFileUrl("nonexistentPropertyConnection.qml"));
    QObject *o = component.create();
    QVERIFY(o);
    delete o;
}

void tst_QQuickItem::childrenRect()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setSource(testFileUrl("childrenRect.qml"));
    window->setBaseSize(QSize(240,320));
    window->show();

    QQuickItem *o = window->rootObject();
    QQuickItem *item = o->findChild<QQuickItem*>("testItem");
    QCOMPARE(item->width(), qreal(0));
    QCOMPARE(item->height(), qreal(0));

    o->setProperty("childCount", 1);
    QCOMPARE(item->width(), qreal(10));
    QCOMPARE(item->height(), qreal(20));

    o->setProperty("childCount", 5);
    QCOMPARE(item->width(), qreal(50));
    QCOMPARE(item->height(), qreal(100));

    o->setProperty("childCount", 0);
    QCOMPARE(item->width(), qreal(0));
    QCOMPARE(item->height(), qreal(0));

    delete o;
    delete window;
}

// QTBUG-11383
void tst_QQuickItem::childrenRectBug()
{
    QQuickView *window = new QQuickView(nullptr);

    QString warning = testFileUrl("childrenRectBug.qml").toString() + ":7:5: QML Item: Binding loop detected for property \"height\"";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning));

    window->setSource(testFileUrl("childrenRectBug.qml"));
    window->show();

    QQuickItem *o = window->rootObject();
    QQuickItem *item = o->findChild<QQuickItem*>("theItem");
    QCOMPARE(item->width(), qreal(200));
    QCOMPARE(item->height(), qreal(100));
    QCOMPARE(item->x(), qreal(100));

    delete window;
}

// QTBUG-11465
void tst_QQuickItem::childrenRectBug2()
{
    QQuickView *window = new QQuickView(nullptr);

    QString warning1 = testFileUrl("childrenRectBug2.qml").toString() + ":7:5: QML Item: Binding loop detected for property \"width\"";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning1));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning1));

    QString warning2 = testFileUrl("childrenRectBug2.qml").toString() + ":7:5: QML Item: Binding loop detected for property \"height\"";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning2));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning2));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning2));
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning2));

    window->setSource(testFileUrl("childrenRectBug2.qml"));
    window->show();

    QQuickRectangle *rect = qobject_cast<QQuickRectangle*>(window->rootObject());
    QVERIFY(rect);
    QQuickItem *item = rect->findChild<QQuickItem*>("theItem");
    QCOMPARE(item->width(), qreal(100));
    QCOMPARE(item->height(), qreal(110));
    QCOMPARE(item->x(), qreal(130));

    QQuickItemPrivate *rectPrivate = QQuickItemPrivate::get(rect);
    rectPrivate->setState("row");
    QCOMPARE(item->width(), qreal(210));
    QCOMPARE(item->height(), qreal(50));
    QCOMPARE(item->x(), qreal(75));

    delete window;
}

// QTBUG-12722
void tst_QQuickItem::childrenRectBug3()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setSource(testFileUrl("childrenRectBug3.qml"));
    window->show();

    //don't crash on delete
    delete window;
}

// QTBUG-38732
void tst_QQuickItem::childrenRectBottomRightCorner()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setSource(testFileUrl("childrenRectBottomRightCorner.qml"));
    window->show();

    QQuickItem *rect = window->rootObject()->findChild<QQuickItem*>("childrenRectProxy");
    QCOMPARE(rect->x(), qreal(-100));
    QCOMPARE(rect->y(), qreal(-100));
    QCOMPARE(rect->width(), qreal(50));
    QCOMPARE(rect->height(), qreal(50));

    delete window;
}

struct TestListener : public QQuickItemChangeListener
{
    TestListener(bool remove = false) : remove(remove) { }

    void itemGeometryChanged(QQuickItem *item, QQuickGeometryChange, const QRectF &oldGeometry) override
    {
        record(item, QQuickItemPrivate::Geometry, oldGeometry);
    }
    void itemSiblingOrderChanged(QQuickItem *item) override
    {
        record(item, QQuickItemPrivate::SiblingOrder);
    }
    void itemVisibilityChanged(QQuickItem *item) override
    {
        record(item, QQuickItemPrivate::Visibility);
    }
    void itemOpacityChanged(QQuickItem *item) override
    {
        record(item, QQuickItemPrivate::Opacity);
    }
    void itemRotationChanged(QQuickItem *item) override
    {
        record(item, QQuickItemPrivate::Rotation);
    }
    void itemImplicitWidthChanged(QQuickItem *item) override
    {
        record(item, QQuickItemPrivate::ImplicitWidth);
    }
    void itemImplicitHeightChanged(QQuickItem *item) override
    {
        record(item, QQuickItemPrivate::ImplicitHeight);
    }
    void itemDestroyed(QQuickItem *item) override
    {
        record(item, QQuickItemPrivate::Destroyed);
    }
    void itemChildAdded(QQuickItem *item, QQuickItem *child) override
    {
        record(item, QQuickItemPrivate::Children, QVariant::fromValue(child));
    }
    void itemChildRemoved(QQuickItem *item, QQuickItem *child) override
    {
        record(item, QQuickItemPrivate::Children, QVariant::fromValue(child));
    }
    void itemParentChanged(QQuickItem *item, QQuickItem *parent) override
    {
        record(item, QQuickItemPrivate::Parent, QVariant::fromValue(parent));
    }

    QQuickAnchorsPrivate *anchorPrivate() override { return nullptr; }

    void record(QQuickItem *item, QQuickItemPrivate::ChangeType change, const QVariant &value = QVariant())
    {
        changes += change;
        values[change] = value;
        // QTBUG-54732
        if (remove)
            QQuickItemPrivate::get(item)->removeItemChangeListener(this, change);
    }

    int count(QQuickItemPrivate::ChangeType change) const
    {
        return changes.count(change);
    }

    QVariant value(QQuickItemPrivate::ChangeType change) const
    {
        return values.value(change);
    }

    bool remove;
    QList<QQuickItemPrivate::ChangeType> changes;
    QHash<QQuickItemPrivate::ChangeType, QVariant> values;
};

void tst_QQuickItem::changeListener()
{
    QQuickWindow window;
    window.show();
    QVERIFY(QTest::qWaitForWindowExposed(&window));

    QQuickItem *item = new QQuickItem;
    TestListener itemListener;
    QQuickItemPrivate::get(item)->addItemChangeListener(&itemListener, QQuickItemPrivate::ChangeTypes(0xffff));

    item->setImplicitWidth(10);
    QCOMPARE(itemListener.count(QQuickItemPrivate::ImplicitWidth), 1);
    QCOMPARE(itemListener.count(QQuickItemPrivate::Geometry), 1);
    QCOMPARE(itemListener.value(QQuickItemPrivate::Geometry), QVariant(QRectF(0,0,0,0)));

    item->setImplicitHeight(20);
    QCOMPARE(itemListener.count(QQuickItemPrivate::ImplicitHeight), 1);
    QCOMPARE(itemListener.count(QQuickItemPrivate::Geometry), 2);
    QCOMPARE(itemListener.value(QQuickItemPrivate::Geometry), QVariant(QRectF(0,0,10,0)));

    item->setWidth(item->width() + 30);
    QCOMPARE(itemListener.count(QQuickItemPrivate::Geometry), 3);
    QCOMPARE(itemListener.value(QQuickItemPrivate::Geometry), QVariant(QRectF(0,0,10,20)));

    item->setHeight(item->height() + 40);
    QCOMPARE(itemListener.count(QQuickItemPrivate::Geometry), 4);
    QCOMPARE(itemListener.value(QQuickItemPrivate::Geometry), QVariant(QRectF(0,0,40,20)));

    item->setOpacity(0.5);
    QCOMPARE(itemListener.count(QQuickItemPrivate::Opacity), 1);

    item->setRotation(90);
    QCOMPARE(itemListener.count(QQuickItemPrivate::Rotation), 1);

    item->setParentItem(window.contentItem());
    QCOMPARE(itemListener.count(QQuickItemPrivate::Parent), 1);

    item->setVisible(false);
    QCOMPARE(itemListener.count(QQuickItemPrivate::Visibility), 1);

    QQuickItemPrivate::get(item)->removeItemChangeListener(&itemListener, QQuickItemPrivate::ChangeTypes(0xffff));

    QQuickItem *parent = new QQuickItem(window.contentItem());
    TestListener parentListener;
    QQuickItemPrivate::get(parent)->addItemChangeListener(&parentListener, QQuickItemPrivate::Children);

    QQuickItem *child1 = new QQuickItem;
    QQuickItem *child2 = new QQuickItem;
    TestListener child1Listener;
    TestListener child2Listener;
    QQuickItemPrivate::get(child1)->addItemChangeListener(&child1Listener, QQuickItemPrivate::Parent | QQuickItemPrivate::SiblingOrder | QQuickItemPrivate::Destroyed);
    QQuickItemPrivate::get(child2)->addItemChangeListener(&child2Listener, QQuickItemPrivate::Parent | QQuickItemPrivate::SiblingOrder | QQuickItemPrivate::Destroyed);

    child1->setParentItem(parent);
    QCOMPARE(parentListener.count(QQuickItemPrivate::Children), 1);
    QCOMPARE(parentListener.value(QQuickItemPrivate::Children), QVariant::fromValue(child1));
    QCOMPARE(child1Listener.count(QQuickItemPrivate::Parent), 1);
    QCOMPARE(child1Listener.value(QQuickItemPrivate::Parent), QVariant::fromValue(parent));

    child2->setParentItem(parent);
    QCOMPARE(parentListener.count(QQuickItemPrivate::Children), 2);
    QCOMPARE(parentListener.value(QQuickItemPrivate::Children), QVariant::fromValue(child2));
    QCOMPARE(child2Listener.count(QQuickItemPrivate::Parent), 1);
    QCOMPARE(child2Listener.value(QQuickItemPrivate::Parent), QVariant::fromValue(parent));

    child2->stackBefore(child1);
    QCOMPARE(child1Listener.count(QQuickItemPrivate::SiblingOrder), 1);
    QCOMPARE(child2Listener.count(QQuickItemPrivate::SiblingOrder), 1);

    child1->setParentItem(nullptr);
    QCOMPARE(parentListener.count(QQuickItemPrivate::Children), 3);
    QCOMPARE(parentListener.value(QQuickItemPrivate::Children), QVariant::fromValue(child1));
    QCOMPARE(child1Listener.count(QQuickItemPrivate::Parent), 2);
    QCOMPARE(child1Listener.value(QQuickItemPrivate::Parent), QVariant::fromValue<QQuickItem *>(nullptr));

    delete child1;
    QCOMPARE(child1Listener.count(QQuickItemPrivate::Destroyed), 1);

    delete child2;
    QCOMPARE(parentListener.count(QQuickItemPrivate::Children), 4);
    QCOMPARE(parentListener.value(QQuickItemPrivate::Children), QVariant::fromValue(child2));
    QCOMPARE(child2Listener.count(QQuickItemPrivate::Parent), 2);
    QCOMPARE(child2Listener.value(QQuickItemPrivate::Parent), QVariant::fromValue<QQuickItem *>(nullptr));
    QCOMPARE(child2Listener.count(QQuickItemPrivate::Destroyed), 1);

    QQuickItemPrivate::get(parent)->removeItemChangeListener(&parentListener, QQuickItemPrivate::Children);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);

    // QTBUG-54732: all listeners should get invoked even if they remove themselves while iterating the listeners
    QList<TestListener *> listeners;
    for (int i = 0; i < 5; ++i)
        listeners << new TestListener(true);

    // itemVisibilityChanged x 5
    foreach (TestListener *listener, listeners)
        QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::Visibility);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
    parent->setVisible(false);
    foreach (TestListener *listener, listeners)
        QCOMPARE(listener->count(QQuickItemPrivate::Visibility), 1);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);

    // itemRotationChanged x 5
    foreach (TestListener *listener, listeners)
        QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::Rotation);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
    parent->setRotation(90);
    foreach (TestListener *listener, listeners)
        QCOMPARE(listener->count(QQuickItemPrivate::Rotation), 1);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);

    // itemOpacityChanged x 5
    foreach (TestListener *listener, listeners)
        QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::Opacity);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
    parent->setOpacity(0.5);
    foreach (TestListener *listener, listeners)
        QCOMPARE(listener->count(QQuickItemPrivate::Opacity), 1);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);

    // itemChildAdded() x 5
    foreach (TestListener *listener, listeners)
        QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::Children);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
    child1 = new QQuickItem(parent);
    foreach (TestListener *listener, listeners)
        QCOMPARE(listener->count(QQuickItemPrivate::Children), 1);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);

    // itemParentChanged() x 5
    foreach (TestListener *listener, listeners)
        QQuickItemPrivate::get(child1)->addItemChangeListener(listener, QQuickItemPrivate::Parent);
    QCOMPARE(QQuickItemPrivate::get(child1)->changeListeners.count(), listeners.count());
    child1->setParentItem(nullptr);
    foreach (TestListener *listener, listeners)
        QCOMPARE(listener->count(QQuickItemPrivate::Parent), 1);
    QCOMPARE(QQuickItemPrivate::get(child1)->changeListeners.count(), 0);

    // itemImplicitWidthChanged() x 5
    foreach (TestListener *listener, listeners)
        QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::ImplicitWidth);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
    parent->setImplicitWidth(parent->implicitWidth() + 1);
    foreach (TestListener *listener, listeners)
        QCOMPARE(listener->count(QQuickItemPrivate::ImplicitWidth), 1);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);

    // itemImplicitHeightChanged() x 5
    foreach (TestListener *listener, listeners)
        QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::ImplicitHeight);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
    parent->setImplicitHeight(parent->implicitHeight() + 1);
    foreach (TestListener *listener, listeners)
        QCOMPARE(listener->count(QQuickItemPrivate::ImplicitHeight), 1);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);

    // itemGeometryChanged() x 5
    foreach (TestListener *listener, listeners)
        QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::Geometry);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
    parent->setWidth(parent->width() + 1);
    foreach (TestListener *listener, listeners)
        QCOMPARE(listener->count(QQuickItemPrivate::Geometry), 1);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);

    // itemChildRemoved() x 5
    child1->setParentItem(parent);
    foreach (TestListener *listener, listeners)
        QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::Children);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
    delete child1;
    foreach (TestListener *listener, listeners)
        QCOMPARE(listener->count(QQuickItemPrivate::Children), 2);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), 0);

    // itemDestroyed() x 5
    foreach (TestListener *listener, listeners)
        QQuickItemPrivate::get(parent)->addItemChangeListener(listener, QQuickItemPrivate::Destroyed);
    QCOMPARE(QQuickItemPrivate::get(parent)->changeListeners.count(), listeners.count());
    delete parent;
    foreach (TestListener *listener, listeners)
        QCOMPARE(listener->count(QQuickItemPrivate::Destroyed), 1);
}

// QTBUG-13893
void tst_QQuickItem::transformCrash()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setSource(testFileUrl("transformCrash.qml"));
    window->show();

    delete window;
}

void tst_QQuickItem::implicitSize()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setSource(testFileUrl("implicitsize.qml"));
    window->show();

    QQuickItem *item = qobject_cast<QQuickItem*>(window->rootObject());
    QVERIFY(item);
    QCOMPARE(item->width(), qreal(80));
    QCOMPARE(item->height(), qreal(60));

    QCOMPARE(item->implicitWidth(), qreal(200));
    QCOMPARE(item->implicitHeight(), qreal(100));

    QMetaObject::invokeMethod(item, "resetSize");

    QCOMPARE(item->width(), qreal(200));
    QCOMPARE(item->height(), qreal(100));

    QMetaObject::invokeMethod(item, "changeImplicit");

    QCOMPARE(item->implicitWidth(), qreal(150));
    QCOMPARE(item->implicitHeight(), qreal(80));
    QCOMPARE(item->width(), qreal(150));
    QCOMPARE(item->height(), qreal(80));

    QMetaObject::invokeMethod(item, "assignImplicitBinding");

    QCOMPARE(item->implicitWidth(), qreal(150));
    QCOMPARE(item->implicitHeight(), qreal(80));
    QCOMPARE(item->width(), qreal(150));
    QCOMPARE(item->height(), qreal(80));

    QMetaObject::invokeMethod(item, "increaseImplicit");

    QCOMPARE(item->implicitWidth(), qreal(200));
    QCOMPARE(item->implicitHeight(), qreal(100));
    QCOMPARE(item->width(), qreal(175));
    QCOMPARE(item->height(), qreal(90));

    QMetaObject::invokeMethod(item, "changeImplicit");

    QCOMPARE(item->implicitWidth(), qreal(150));
    QCOMPARE(item->implicitHeight(), qreal(80));
    QCOMPARE(item->width(), qreal(150));
    QCOMPARE(item->height(), qreal(80));

    QMetaObject::invokeMethod(item, "assignUndefinedBinding");

    QCOMPARE(item->implicitWidth(), qreal(150));
    QCOMPARE(item->implicitHeight(), qreal(80));
    QCOMPARE(item->width(), qreal(150));
    QCOMPARE(item->height(), qreal(80));

    QMetaObject::invokeMethod(item, "increaseImplicit");

    QCOMPARE(item->implicitWidth(), qreal(200));
    QCOMPARE(item->implicitHeight(), qreal(100));
    QCOMPARE(item->width(), qreal(175));
    QCOMPARE(item->height(), qreal(90));

    QMetaObject::invokeMethod(item, "changeImplicit");

    QCOMPARE(item->implicitWidth(), qreal(150));
    QCOMPARE(item->implicitHeight(), qreal(80));
    QCOMPARE(item->width(), qreal(150));
    QCOMPARE(item->height(), qreal(80));

    delete window;
}

void tst_QQuickItem::qtbug_16871()
{
    QQmlComponent component(&engine, testFileUrl("qtbug_16871.qml"));
    QObject *o = component.create();
    QVERIFY(o != nullptr);
    delete o;
}


void tst_QQuickItem::visibleChildren()
{
    QQuickView *window = new QQuickView(nullptr);
    window->setSource(testFileUrl("visiblechildren.qml"));
    window->show();

    QQuickItem *root = qobject_cast<QQuickItem*>(window->rootObject());
    QVERIFY(root);

    QCOMPARE(root->property("test1_1").toBool(), true);
    QCOMPARE(root->property("test1_2").toBool(), true);
    QCOMPARE(root->property("test1_3").toBool(), true);
    QCOMPARE(root->property("test1_4").toBool(), true);

    QMetaObject::invokeMethod(root, "hideFirstAndLastRowChild");
    QCOMPARE(root->property("test2_1").toBool(), true);
    QCOMPARE(root->property("test2_2").toBool(), true);
    QCOMPARE(root->property("test2_3").toBool(), true);
    QCOMPARE(root->property("test2_4").toBool(), true);

    QMetaObject::invokeMethod(root, "showLastRowChildsLastChild");
    QCOMPARE(root->property("test3_1").toBool(), true);
    QCOMPARE(root->property("test3_2").toBool(), true);
    QCOMPARE(root->property("test3_3").toBool(), true);
    QCOMPARE(root->property("test3_4").toBool(), true);

    QMetaObject::invokeMethod(root, "showLastRowChild");
    QCOMPARE(root->property("test4_1").toBool(), true);
    QCOMPARE(root->property("test4_2").toBool(), true);
    QCOMPARE(root->property("test4_3").toBool(), true);
    QCOMPARE(root->property("test4_4").toBool(), true);

    QString warning1 = testFileUrl("visiblechildren.qml").toString() + ":87: TypeError: Cannot read property 'visibleChildren' of null";
    QTest::ignoreMessage(QtWarningMsg, qPrintable(warning1));
    QMetaObject::invokeMethod(root, "tryWriteToReadonlyVisibleChildren");

    QMetaObject::invokeMethod(root, "reparentVisibleItem3");
    QCOMPARE(root->property("test6_1").toBool(), true);
    QCOMPARE(root->property("test6_2").toBool(), true);
    QCOMPARE(root->property("test6_3").toBool(), true);
    QCOMPARE(root->property("test6_4").toBool(), true);

    QMetaObject::invokeMethod(root, "reparentImlicitlyInvisibleItem4_1");
    QCOMPARE(root->property("test7_1").toBool(), true);
    QCOMPARE(root->property("test7_2").toBool(), true);
    QCOMPARE(root->property("test7_3").toBool(), true);
    QCOMPARE(root->property("test7_4").toBool(), true);

    // FINALLY TEST THAT EVERYTHING IS AS EXPECTED
    QCOMPARE(root->property("test8_1").toBool(), true);
    QCOMPARE(root->property("test8_2").toBool(), true);
    QCOMPARE(root->property("test8_3").toBool(), true);
    QCOMPARE(root->property("test8_4").toBool(), true);
    QCOMPARE(root->property("test8_5").toBool(), true);

    delete window;
}

void tst_QQuickItem::parentLoop()
{
    QQuickView *window = new QQuickView(nullptr);

#if QT_CONFIG(regularexpression)
    QRegularExpression msgRegexp = QRegularExpression("QQuickItem::setParentItem: Parent QQuickItem\\(.*\\) is already part of the subtree of QQuickItem\\(.*\\)");
    QTest::ignoreMessage(QtWarningMsg, msgRegexp);
#endif
    window->setSource(testFileUrl("parentLoop.qml"));

    QQuickItem *root = qobject_cast<QQuickItem*>(window->rootObject());
    QVERIFY(root);

    QQuickItem *item1 = root->findChild<QQuickItem*>("item1");
    QVERIFY(item1);
    QCOMPARE(item1->parentItem(), root);

    QQuickItem *item2 = root->findChild<QQuickItem*>("item2");
    QVERIFY(item2);
    QCOMPARE(item2->parentItem(), item1);

    delete window;
}

void tst_QQuickItem::contains_data()
{
    QTest::addColumn<bool>("circleTest");
    QTest::addColumn<bool>("insideTarget");
    QTest::addColumn<QList<QPoint> >("points");

    QList<QPoint> points;

    points << QPoint(176, 176)
           << QPoint(176, 226)
           << QPoint(226, 176)
           << QPoint(226, 226)
           << QPoint(150, 200)
           << QPoint(200, 150)
           << QPoint(200, 250)
           << QPoint(250, 200);
    QTest::newRow("hollow square: testing points inside") << false << true << points;

    points.clear();
    points << QPoint(162, 162)
           << QPoint(162, 242)
           << QPoint(242, 162)
           << QPoint(242, 242)
           << QPoint(200, 200)
           << QPoint(175, 200)
           << QPoint(200, 175)
           << QPoint(200, 228)
           << QPoint(228, 200)
           << QPoint(200, 122)
           << QPoint(122, 200)
           << QPoint(200, 280)
           << QPoint(280, 200);
    QTest::newRow("hollow square: testing points outside") << false << false << points;

    points.clear();
    points << QPoint(174, 174)
           << QPoint(174, 225)
           << QPoint(225, 174)
           << QPoint(225, 225)
           << QPoint(165, 200)
           << QPoint(200, 165)
           << QPoint(200, 235)
           << QPoint(235, 200);
    QTest::newRow("hollow circle: testing points inside") << true << true << points;

    points.clear();
    points << QPoint(160, 160)
           << QPoint(160, 240)
           << QPoint(240, 160)
           << QPoint(240, 240)
           << QPoint(200, 200)
           << QPoint(185, 185)
           << QPoint(185, 216)
           << QPoint(216, 185)
           << QPoint(216, 216)
           << QPoint(145, 200)
           << QPoint(200, 145)
           << QPoint(255, 200)
           << QPoint(200, 255);
    QTest::newRow("hollow circle: testing points outside") << true << false << points;
}

void tst_QQuickItem::contains()
{
    QFETCH(bool, circleTest);
    QFETCH(bool, insideTarget);
    QFETCH(QList<QPoint>, points);

    QQuickView *window = new QQuickView(nullptr);
    window->rootContext()->setContextProperty("circleShapeTest", circleTest);
    window->setBaseSize(QSize(400, 400));
    window->setSource(testFileUrl("hollowTestItem.qml"));
    window->show();
    window->requestActivate();
    QVERIFY(QTest::qWaitForWindowActive(window));
    QCOMPARE(QGuiApplication::focusWindow(), window);

    QQuickItem *root = qobject_cast<QQuickItem *>(window->rootObject());
    QVERIFY(root);

    HollowTestItem *hollowItem = root->findChild<HollowTestItem *>("hollowItem");
    QVERIFY(hollowItem);

    foreach (const QPoint &point, points) {
        // check mouse hover
        QTest::mouseMove(window, point);
        QTest::qWait(10);
        QCOMPARE(hollowItem->isHovered(), insideTarget);

        // check mouse press
        QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, point);
        QTest::qWait(10);
        QCOMPARE(hollowItem->isPressed(), insideTarget);

        // check mouse release
        QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, point);
        QTest::qWait(10);
        QCOMPARE(hollowItem->isPressed(), false);
    }

    delete window;
}

void tst_QQuickItem::childAt()
{
    QQuickItem parent;

    QQuickItem child1;
    child1.setX(0);
    child1.setY(0);
    child1.setWidth(100);
    child1.setHeight(100);
    child1.setParentItem(&parent);

    QQuickItem child2;
    child2.setX(50);
    child2.setY(50);
    child2.setWidth(100);
    child2.setHeight(100);
    child2.setParentItem(&parent);

    QQuickItem child3;
    child3.setX(0);
    child3.setY(200);
    child3.setWidth(50);
    child3.setHeight(50);
    child3.setParentItem(&parent);

    QCOMPARE(parent.childAt(0, 0), &child1);
    QCOMPARE(parent.childAt(0, 99), &child1);
    QCOMPARE(parent.childAt(25, 25), &child1);
    QCOMPARE(parent.childAt(25, 75), &child1);
    QCOMPARE(parent.childAt(75, 25), &child1);
    QCOMPARE(parent.childAt(75, 75), &child2);
    QCOMPARE(parent.childAt(149, 149), &child2);
    QCOMPARE(parent.childAt(25, 200), &child3);
    QCOMPARE(parent.childAt(0, 150), static_cast<QQuickItem *>(nullptr));
    QCOMPARE(parent.childAt(300, 300), static_cast<QQuickItem *>(nullptr));
}

void tst_QQuickItem::grab()
{
    if ((QGuiApplication::platformName() == QLatin1String("offscreen"))
        || (QGuiApplication::platformName() == QLatin1String("minimal")))
        QSKIP("Skipping due to grabToImage not functional on offscreen/minimimal platforms");

    QQuickView view;
    view.setSource(testFileUrl("grabToImage.qml"));
    view.show();
    QVERIFY(QTest::qWaitForWindowExposed(&view));

    QQuickItem *root = qobject_cast<QQuickItem *>(view.rootObject());
    QVERIFY(root);
    QQuickItem *item = root->findChild<QQuickItem *>("myItem");
    QVERIFY(item);
#if QT_CONFIG(opengl)
    { // Default size (item is 100x100)
        QSharedPointer<QQuickItemGrabResult> result = item->grabToImage();
        QSignalSpy spy(result.data(), SIGNAL(ready()));
        QTRY_VERIFY(spy.size() > 0);
        QVERIFY(!result->url().isEmpty());
        QImage image = result->image();
        QCOMPARE(image.pixel(0, 0), qRgb(255, 0, 0));
        QCOMPARE(image.pixel(99, 99), qRgb(0, 0, 255));
    }

    { // Smaller size
        QSharedPointer<QQuickItemGrabResult> result = item->grabToImage(QSize(50, 50));
        QVERIFY(!result.isNull());
        QSignalSpy spy(result.data(), SIGNAL(ready()));
        QTRY_VERIFY(spy.size() > 0);
        QVERIFY(!result->url().isEmpty());
        QImage image = result->image();
        QCOMPARE(image.pixel(0, 0), qRgb(255, 0, 0));
        QCOMPARE(image.pixel(49, 49), qRgb(0, 0, 255));
    }
#endif
}

void tst_QQuickItem::isAncestorOf()
{
    QQuickItem parent;

    QQuickItem sub1;
    sub1.setParentItem(&parent);

    QQuickItem child1;
    child1.setParentItem(&sub1);
    QQuickItem child2;
    child2.setParentItem(&sub1);

    QQuickItem sub2;
    sub2.setParentItem(&parent);

    QQuickItem child3;
    child3.setParentItem(&sub2);
    QQuickItem child4;
    child4.setParentItem(&sub2);

    QVERIFY(parent.isAncestorOf(&sub1));
    QVERIFY(parent.isAncestorOf(&sub2));
    QVERIFY(parent.isAncestorOf(&child1));
    QVERIFY(parent.isAncestorOf(&child2));
    QVERIFY(parent.isAncestorOf(&child3));
    QVERIFY(parent.isAncestorOf(&child4));
    QVERIFY(sub1.isAncestorOf(&child1));
    QVERIFY(sub1.isAncestorOf(&child2));
    QVERIFY(!sub1.isAncestorOf(&child3));
    QVERIFY(!sub1.isAncestorOf(&child4));
    QVERIFY(sub2.isAncestorOf(&child3));
    QVERIFY(sub2.isAncestorOf(&child4));
    QVERIFY(!sub2.isAncestorOf(&child1));
    QVERIFY(!sub2.isAncestorOf(&child2));
    QVERIFY(!sub1.isAncestorOf(&sub1));
    QVERIFY(!sub2.isAncestorOf(&sub2));
}

QTEST_MAIN(tst_QQuickItem)

#include "tst_qquickitem.moc"
