/**************************************************************************** ** ** Copyright (C) 2020 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 #include #include #include #include "private/qquickfocusscope_p.h" #include "private/qquickitem_p.h" #include #include #ifdef Q_OS_WIN #include #endif #include #include #include #include "../../shared/util.h" #include "../shared/viewtestutil.h" #include #include #include #ifdef TEST_QTBUG_60123 #include #include #endif Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") class TestItem : public QQuickItem { Q_OBJECT public: TestItem(QQuickItem *parent = nullptr) : QQuickItem(parent) { } bool focused = false; int pressCount = 0; int releaseCount = 0; int wheelCount = 0; bool acceptIncomingTouchEvents = true; bool touchEventReached = false; ulong timestamp = 0; QPoint lastWheelEventPos; QPoint lastWheelEventGlobalPos; int languageChangeEventCount = 0; protected: void focusInEvent(QFocusEvent *event) override { qCDebug(lcTests) << objectName() << event; Q_ASSERT(!focused); focused = true; } void focusOutEvent(QFocusEvent *event) override { qCDebug(lcTests) << objectName() << event; Q_ASSERT(focused); focused = false; } void mousePressEvent(QMouseEvent *event) override { qCDebug(lcTests) << objectName() << event; // Tradition holds that an item or widget does not need to accept a mouse event: // it arrives pre-accepted. ++pressCount; } void mouseReleaseEvent(QMouseEvent *event) override { qCDebug(lcTests) << objectName() << event; ++releaseCount; } void touchEvent(QTouchEvent *event) override { qCDebug(lcTests) << objectName() << event; touchEventReached = true; event->setAccepted(acceptIncomingTouchEvents); } void wheelEvent(QWheelEvent *event) override { qCDebug(lcTests) << objectName() << event; event->accept(); ++wheelCount; timestamp = event->timestamp(); lastWheelEventPos = event->position().toPoint(); lastWheelEventGlobalPos = event->globalPosition().toPoint(); } bool event(QEvent *e) override { Q_ASSERT(e->isAccepted()); // every event is constructed with the accept flag initialized to true if (e->type() == QEvent::LanguageChange) languageChangeEventCount++; return QQuickItem::event(e); // default dispatch } }; class TestWindow: public QQuickWindow { public: TestWindow() : QQuickWindow() {} virtual bool event(QEvent *event) { return QQuickWindow::event(event); } }; class TestPolishItem : public QQuickItem { Q_OBJECT public: TestPolishItem(QQuickItem *parent = nullptr) : QQuickItem(parent), wasPolished(false) { } bool wasPolished; int repolishLoopCount = 0; protected: virtual void updatePolish() { wasPolished = true; if (repolishLoopCount > 0) { --repolishLoopCount; polish(); } } public slots: void doPolish() { polish(); } }; class TestFocusScope : public QQuickFocusScope { Q_OBJECT public: TestFocusScope(QQuickItem *parent = nullptr) : QQuickFocusScope(parent), focused(false) {} bool focused; protected: virtual void focusInEvent(QFocusEvent *) { Q_ASSERT(!focused); focused = true; } virtual void focusOutEvent(QFocusEvent *) { Q_ASSERT(focused); focused = false; } }; class tst_qquickitem : public QQmlDataTest { Q_OBJECT public: private slots: void initTestCase(); void noWindow(); void simpleFocus(); void scopedFocus(); void addedToWindow(); void changeParent(); void multipleFocusClears(); void focusSubItemInNonFocusScope(); void parentItemWithFocus(); void reparentFocusedItem(); void constructor(); void setParentItem(); void visible(); void enabled(); void enabledFocus(); void mouseGrab(); void mousePropagationToParent(); void touchEventAcceptIgnore_data(); void touchEventAcceptIgnore(); void polishOutsideAnimation(); void polishOnCompleted(); void wheelEvent_data(); void wheelEvent(); void hoverEvent_data(); void hoverEvent(); void hoverEventInParent(); void paintOrder_data(); void paintOrder(); void acceptedMouseButtons(); void visualParentOwnership(); void visualParentOwnershipWindow(); void testSGInvalidate(); void objectChildTransform(); void contains_data(); void contains(); void childAt(); void ignoreButtonPressNotInAcceptedMouseButtons(); void shortcutOverride(); #ifdef TEST_QTBUG_60123 void qtBug60123(); #endif void setParentCalledInOnWindowChanged(); void receivesLanguageChangeEvent(); void polishLoopDetection_data(); void polishLoopDetection(); private: enum PaintOrderOp { NoOp, Append, Remove, StackBefore, StackAfter, SetZ }; bool ensureFocus(QWindow *w) { if (w->width() <=0 || w->height() <= 0) w->setGeometry(100, 100, 400, 300); w->show(); w->requestActivate(); return QTest::qWaitForWindowActive(w); } }; void tst_qquickitem::initTestCase() { QQmlDataTest::initTestCase(); qmlRegisterType("Qt.test", 1, 0, "TestPolishItem"); } // Focus still updates when outside a window void tst_qquickitem::noWindow() { QQuickItem *root = new TestItem; QQuickItem *child = new TestItem(root); QQuickItem *scope = new TestItem(root); QQuickFocusScope *scopedChild = new TestFocusScope(scope); QQuickFocusScope *scopedChild2 = new TestFocusScope(scope); QCOMPARE(root->hasFocus(), false); QCOMPARE(child->hasFocus(), false); QCOMPARE(scope->hasFocus(), false); QCOMPARE(scopedChild->hasFocus(), false); QCOMPARE(scopedChild2->hasFocus(), false); root->setFocus(true); scope->setFocus(true); scopedChild2->setFocus(true); QCOMPARE(root->hasFocus(), false); QCOMPARE(child->hasFocus(), false); QCOMPARE(scope->hasFocus(), false); QCOMPARE(scopedChild->hasFocus(), false); QCOMPARE(scopedChild2->hasFocus(), true); root->setFocus(false); child->setFocus(true); scopedChild->setFocus(true); scope->setFocus(false); QCOMPARE(root->hasFocus(), false); QCOMPARE(child->hasFocus(), false); QCOMPARE(scope->hasFocus(), false); QCOMPARE(scopedChild->hasFocus(), true); QCOMPARE(scopedChild2->hasFocus(), false); delete root; } struct FocusData { FocusData() : focus(false), activeFocus(false) {} void set(bool f, bool af) { focus = f; activeFocus = af; } bool focus; bool activeFocus; }; struct FocusState : public QHash { FocusState() : activeFocusItem(nullptr) {} FocusState &operator<<(QQuickItem *item) { insert(item, FocusData()); return *this; } void active(QQuickItem *i) { activeFocusItem = i; } QQuickItem *activeFocusItem; }; #define FVERIFY() \ do { \ if (focusState.activeFocusItem) { \ QCOMPARE(window.activeFocusItem(), focusState.activeFocusItem); \ if (qobject_cast(window.activeFocusItem())) \ QCOMPARE(qobject_cast(window.activeFocusItem())->focused, true); \ else if (qobject_cast(window.activeFocusItem())) \ QCOMPARE(qobject_cast(window.activeFocusItem())->focused, true); \ } else { \ QCOMPARE(window.activeFocusItem(), window.contentItem()); \ } \ for (QHash::Iterator iter = focusState.begin(); \ iter != focusState.end(); \ iter++) { \ QCOMPARE(iter.key()->hasFocus(), iter.value().focus); \ QCOMPARE(iter.key()->hasActiveFocus(), iter.value().activeFocus); \ } \ } while (false) // Tests a simple set of top-level scoped items void tst_qquickitem::simpleFocus() { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *l1c1 = new TestItem(window.contentItem()); QQuickItem *l1c2 = new TestItem(window.contentItem()); QQuickItem *l1c3 = new TestItem(window.contentItem()); QQuickItem *l2c1 = new TestItem(l1c1); QQuickItem *l2c2 = new TestItem(l1c1); QQuickItem *l2c3 = new TestItem(l1c3); FocusState focusState; focusState << l1c1 << l1c2 << l1c3 << l2c1 << l2c2 << l2c3; FVERIFY(); l1c1->setFocus(true); focusState[l1c1].set(true, true); focusState.active(l1c1); FVERIFY(); l2c3->setFocus(true); focusState[l1c1].set(false, false); focusState[l2c3].set(true, true); focusState.active(l2c3); FVERIFY(); l1c3->setFocus(true); focusState[l2c3].set(false, false); focusState[l1c3].set(true, true); focusState.active(l1c3); FVERIFY(); l1c2->setFocus(false); FVERIFY(); l1c3->setFocus(false); focusState[l1c3].set(false, false); focusState.active(nullptr); FVERIFY(); l2c1->setFocus(true); focusState[l2c1].set(true, true); focusState.active(l2c1); FVERIFY(); } // Items with a focus scope void tst_qquickitem::scopedFocus() { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *l1c1 = new TestItem(window.contentItem()); QQuickItem *l1c2 = new TestItem(window.contentItem()); QQuickItem *l1c3 = new TestItem(window.contentItem()); QQuickItem *l2c1 = new TestItem(l1c1); QQuickItem *l2c2 = new TestItem(l1c1); QQuickItem *l2c3 = new TestFocusScope(l1c3); QQuickItem *l3c1 = new TestItem(l2c3); QQuickItem *l3c2 = new TestFocusScope(l2c3); QQuickItem *l4c1 = new TestItem(l3c2); QQuickItem *l4c2 = new TestItem(l3c2); FocusState focusState; focusState << l1c1 << l1c2 << l1c3 << l2c1 << l2c2 << l2c3 << l3c1 << l3c2 << l4c1 << l4c2; FVERIFY(); l4c2->setFocus(true); focusState[l4c2].set(true, false); FVERIFY(); l4c1->setFocus(true); focusState[l4c2].set(false, false); focusState[l4c1].set(true, false); FVERIFY(); l1c1->setFocus(true); focusState[l1c1].set(true, true); focusState.active(l1c1); FVERIFY(); l3c2->setFocus(true); focusState[l3c2].set(true, false); FVERIFY(); l2c3->setFocus(true); focusState[l1c1].set(false, false); focusState[l2c3].set(true, true); focusState[l3c2].set(true, true); focusState[l4c1].set(true, true); focusState.active(l4c1); FVERIFY(); l3c2->setFocus(false); focusState[l3c2].set(false, false); focusState[l4c1].set(true, false); focusState.active(l2c3); FVERIFY(); l3c2->setFocus(true); focusState[l3c2].set(true, true); focusState[l4c1].set(true, true); focusState.active(l4c1); FVERIFY(); l4c1->setFocus(false); focusState[l4c1].set(false, false); focusState.active(l3c2); FVERIFY(); l1c3->setFocus(true); focusState[l1c3].set(true, true); focusState[l2c3].set(false, false); focusState[l3c2].set(true, false); focusState.active(l1c3); FVERIFY(); } // Tests focus corrects itself when a tree is added to a window for the first time void tst_qquickitem::addedToWindow() { { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *item = new TestItem; FocusState focusState; focusState << item; item->setFocus(true); focusState[item].set(true, false); FVERIFY(); item->setParentItem(window.contentItem()); focusState[item].set(true, true); focusState.active(item); FVERIFY(); } { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *item = new TestItem(window.contentItem()); QQuickItem *tree = new TestItem; QQuickItem *c1 = new TestItem(tree); QQuickItem *c2 = new TestItem(tree); FocusState focusState; focusState << item << tree << c1 << c2; item->setFocus(true); c1->setFocus(true); c2->setFocus(true); focusState[item].set(true, true); focusState[c1].set(false, false); focusState[c2].set(true, false); focusState.active(item); FVERIFY(); tree->setParentItem(item); focusState[c1].set(false, false); focusState[c2].set(false, false); FVERIFY(); } { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *tree = new TestItem; QQuickItem *c1 = new TestItem(tree); QQuickItem *c2 = new TestItem(tree); FocusState focusState; focusState << tree << c1 << c2; c1->setFocus(true); c2->setFocus(true); focusState[c1].set(false, false); focusState[c2].set(true, false); FVERIFY(); tree->setParentItem(window.contentItem()); focusState[c1].set(false, false); focusState[c2].set(true, true); focusState.active(c2); FVERIFY(); } { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *tree = new TestFocusScope; QQuickItem *c1 = new TestItem(tree); QQuickItem *c2 = new TestItem(tree); FocusState focusState; focusState << tree << c1 << c2; c1->setFocus(true); c2->setFocus(true); focusState[c1].set(false, false); focusState[c2].set(true, false); FVERIFY(); tree->setParentItem(window.contentItem()); focusState[c1].set(false, false); focusState[c2].set(true, false); FVERIFY(); tree->setFocus(true); focusState[tree].set(true, true); focusState[c2].set(true, true); focusState.active(c2); FVERIFY(); } { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *tree = new TestFocusScope; QQuickItem *c1 = new TestItem(tree); QQuickItem *c2 = new TestItem(tree); FocusState focusState; focusState << tree << c1 << c2; tree->setFocus(true); c1->setFocus(true); c2->setFocus(true); focusState[tree].set(true, false); focusState[c1].set(false, false); focusState[c2].set(true, false); FVERIFY(); tree->setParentItem(window.contentItem()); focusState[tree].set(true, true); focusState[c1].set(false, false); focusState[c2].set(true, true); focusState.active(c2); FVERIFY(); } { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *child = new TestItem(window.contentItem()); QQuickItem *tree = new TestFocusScope; QQuickItem *c1 = new TestItem(tree); QQuickItem *c2 = new TestItem(tree); FocusState focusState; focusState << child << tree << c1 << c2; child->setFocus(true); tree->setFocus(true); c1->setFocus(true); c2->setFocus(true); focusState[child].set(true, true); focusState[tree].set(true, false); focusState[c1].set(false, false); focusState[c2].set(true, false); focusState.active(child); FVERIFY(); tree->setParentItem(window.contentItem()); focusState[tree].set(false, false); focusState[c1].set(false, false); focusState[c2].set(true, false); FVERIFY(); tree->setFocus(true); focusState[child].set(false, false); focusState[tree].set(true, true); focusState[c2].set(true, true); focusState.active(c2); FVERIFY(); } } void tst_qquickitem::changeParent() { // Parent to no parent { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *child = new TestItem(window.contentItem()); FocusState focusState; focusState << child; FVERIFY(); child->setFocus(true); focusState[child].set(true, true); focusState.active(child); FVERIFY(); child->setParentItem(nullptr); focusState[child].set(true, false); focusState.active(nullptr); FVERIFY(); } // Different parent, same focus scope { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *child = new TestItem(window.contentItem()); QQuickItem *child2 = new TestItem(window.contentItem()); FocusState focusState; focusState << child << child2; FVERIFY(); child->setFocus(true); focusState[child].set(true, true); focusState.active(child); FVERIFY(); child->setParentItem(child2); FVERIFY(); } // Different parent, different focus scope { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *child = new TestItem(window.contentItem()); QQuickItem *child2 = new TestFocusScope(window.contentItem()); QQuickItem *item = new TestItem(child); FocusState focusState; focusState << child << child2 << item; FVERIFY(); item->setFocus(true); focusState[item].set(true, true); focusState.active(item); FVERIFY(); item->setParentItem(child2); focusState[item].set(true, false); focusState.active(nullptr); FVERIFY(); } { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *child = new TestItem(window.contentItem()); QQuickItem *child2 = new TestFocusScope(window.contentItem()); QQuickItem *item = new TestItem(child2); FocusState focusState; focusState << child << child2 << item; FVERIFY(); item->setFocus(true); focusState[item].set(true, false); focusState.active(nullptr); FVERIFY(); item->setParentItem(child); focusState[item].set(true, true); focusState.active(item); FVERIFY(); } { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *child = new TestItem(window.contentItem()); QQuickItem *child2 = new TestFocusScope(window.contentItem()); QQuickItem *item = new TestItem(child2); FocusState focusState; focusState << child << child2 << item; FVERIFY(); child->setFocus(true); item->setFocus(true); focusState[child].set(true, true); focusState[item].set(true, false); focusState.active(child); FVERIFY(); item->setParentItem(child); focusState[item].set(false, false); FVERIFY(); } // child has active focus, then its fs parent changes parent to 0, then // child is deleted, then its parent changes again to a valid parent { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem *item = new TestFocusScope(window.contentItem()); QQuickItem *child = new TestItem(item); QQuickItem *child2 = new TestItem; FocusState focusState; focusState << item << child; FVERIFY(); item->setFocus(true); child->setFocus(true); focusState[child].set(true, true); focusState[item].set(true, true); focusState.active(child); FVERIFY(); item->setParentItem(nullptr); focusState[child].set(true, false); focusState[item].set(true, false); focusState.active(nullptr); FVERIFY(); focusState.remove(child); delete child; item->setParentItem(window.contentItem()); focusState[item].set(true, true); focusState.active(item); FVERIFY(); delete child2; } } void tst_qquickitem::multipleFocusClears() { //Multiple clears of focus inside a focus scope shouldn't crash. QTBUG-24714 QQuickView view; view.setSource(testFileUrl("multipleFocusClears.qml")); view.show(); QVERIFY(ensureFocus(&view)); QTRY_COMPARE(QGuiApplication::focusWindow(), &view); } void tst_qquickitem::focusSubItemInNonFocusScope() { QQuickView view; view.setSource(testFileUrl("focusSubItemInNonFocusScope.qml")); view.show(); QVERIFY(QTest::qWaitForWindowActive(&view)); QQuickItem *dummyItem = view.rootObject()->findChild("dummyItem"); QVERIFY(dummyItem); QQuickItem *textInput = view.rootObject()->findChild("textInput"); QVERIFY(textInput); QVERIFY(dummyItem->hasFocus()); QVERIFY(!textInput->hasFocus()); QVERIFY(dummyItem->hasActiveFocus()); QVERIFY(QMetaObject::invokeMethod(textInput, "forceActiveFocus")); QVERIFY(!dummyItem->hasFocus()); QVERIFY(textInput->hasFocus()); QVERIFY(textInput->hasActiveFocus()); } void tst_qquickitem::parentItemWithFocus() { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); { QQuickItem parent; QQuickItem child; FocusState focusState; focusState << &parent << &child; FVERIFY(); parent.setFocus(true); child.setFocus(true); focusState[&parent].set(true, false); focusState[&child].set(true, false); FVERIFY(); child.setParentItem(&parent); focusState[&parent].set(true, false); focusState[&child].set(false, false); FVERIFY(); parent.setParentItem(window.contentItem()); focusState[&parent].set(true, true); focusState[&child].set(false, false); focusState.active(&parent); FVERIFY(); child.forceActiveFocus(); focusState[&parent].set(false, false); focusState[&child].set(true, true); focusState.active(&child); FVERIFY(); } { QQuickItem parent; QQuickItem child; QQuickItem grandchild(&child); FocusState focusState; focusState << &parent << &child << &grandchild; FVERIFY(); parent.setFocus(true); grandchild.setFocus(true); focusState[&parent].set(true, false); focusState[&child].set(false, false); focusState[&grandchild].set(true, false); FVERIFY(); child.setParentItem(&parent); focusState[&parent].set(true, false); focusState[&child].set(false, false); focusState[&grandchild].set(false, false); FVERIFY(); parent.setParentItem(window.contentItem()); focusState[&parent].set(true, true); focusState[&child].set(false, false); focusState[&grandchild].set(false, false); focusState.active(&parent); FVERIFY(); grandchild.forceActiveFocus(); focusState[&parent].set(false, false); focusState[&child].set(false, false); focusState[&grandchild].set(true, true); focusState.active(&grandchild); FVERIFY(); } { QQuickItem parent; QQuickItem child1; QQuickItem child2; FocusState focusState; focusState << &parent << &child1 << &child2; parent.setFocus(true); child1.setParentItem(&parent); child2.setParentItem(&parent); focusState[&parent].set(true, false); focusState[&child1].set(false, false); focusState[&child2].set(false, false); FVERIFY(); child1.setFocus(true); focusState[&parent].set(false, false); focusState[&child1].set(true, false); FVERIFY(); parent.setFocus(true); focusState[&parent].set(true, false); focusState[&child1].set(false, false); FVERIFY(); } } void tst_qquickitem::reparentFocusedItem() { QQuickWindow window; QVERIFY(ensureFocus(&window)); QTRY_COMPARE(QGuiApplication::focusWindow(), &window); QQuickItem parent(window.contentItem()); QQuickItem child(&parent); QQuickItem sibling(&parent); QQuickItem grandchild(&child); FocusState focusState; focusState << &parent << &child << &sibling << &grandchild; FVERIFY(); grandchild.setFocus(true); focusState[&parent].set(false, false); focusState[&child].set(false, false); focusState[&sibling].set(false, false); focusState[&grandchild].set(true, true); focusState.active(&grandchild); FVERIFY(); // Parenting the item to another item within the same focus scope shouldn't change it's focus. child.setParentItem(&sibling); FVERIFY(); } void tst_qquickitem::constructor() { QScopedPointer root(new QQuickItem); QVERIFY(!root->parent()); QVERIFY(!root->parentItem()); QQuickItem *child1 = new QQuickItem(root.data()); QCOMPARE(child1->parent(), root.data()); QCOMPARE(child1->parentItem(), root.data()); QCOMPARE(root->childItems().count(), 1); QCOMPARE(root->childItems().at(0), child1); QQuickItem *child2 = new QQuickItem(root.data()); QCOMPARE(child2->parent(), root.data()); QCOMPARE(child2->parentItem(), root.data()); QCOMPARE(root->childItems().count(), 2); QCOMPARE(root->childItems().at(0), child1); QCOMPARE(root->childItems().at(1), child2); } void tst_qquickitem::setParentItem() { QQuickItem *root = new QQuickItem; QVERIFY(!root->parent()); QVERIFY(!root->parentItem()); QQuickItem *child1 = new QQuickItem; QVERIFY(!child1->parent()); QVERIFY(!child1->parentItem()); child1->setParentItem(root); QVERIFY(!child1->parent()); QCOMPARE(child1->parentItem(), root); QCOMPARE(root->childItems().count(), 1); QCOMPARE(root->childItems().at(0), child1); QQuickItem *child2 = new QQuickItem; QVERIFY(!child2->parent()); QVERIFY(!child2->parentItem()); child2->setParentItem(root); QVERIFY(!child2->parent()); QCOMPARE(child2->parentItem(), root); QCOMPARE(root->childItems().count(), 2); QCOMPARE(root->childItems().at(0), child1); QCOMPARE(root->childItems().at(1), child2); child1->setParentItem(nullptr); QVERIFY(!child1->parent()); QVERIFY(!child1->parentItem()); QCOMPARE(root->childItems().count(), 1); QCOMPARE(root->childItems().at(0), child2); delete root; QVERIFY(!child1->parent()); QVERIFY(!child1->parentItem()); QVERIFY(!child2->parent()); QVERIFY(!child2->parentItem()); delete child1; delete child2; } void tst_qquickitem::visible() { QQuickItem *root = new QQuickItem; QQuickItem *child1 = new QQuickItem; child1->setParentItem(root); QQuickItem *child2 = new QQuickItem; child2->setParentItem(root); QVERIFY(child1->isVisible()); QVERIFY(child2->isVisible()); root->setVisible(false); QVERIFY(!child1->isVisible()); QVERIFY(!child2->isVisible()); root->setVisible(true); QVERIFY(child1->isVisible()); QVERIFY(child2->isVisible()); child1->setVisible(false); QVERIFY(!child1->isVisible()); QVERIFY(child2->isVisible()); child2->setParentItem(child1); QVERIFY(!child1->isVisible()); QVERIFY(!child2->isVisible()); child2->setParentItem(root); QVERIFY(!child1->isVisible()); QVERIFY(child2->isVisible()); delete root; delete child1; delete child2; } void tst_qquickitem::enabled() { QQuickItem *root = new QQuickItem; QQuickItem *child1 = new QQuickItem; child1->setParentItem(root); QQuickItem *child2 = new QQuickItem; child2->setParentItem(root); QVERIFY(child1->isEnabled()); QVERIFY(child2->isEnabled()); root->setEnabled(false); QVERIFY(!child1->isEnabled()); QVERIFY(!child2->isEnabled()); root->setEnabled(true); QVERIFY(child1->isEnabled()); QVERIFY(child2->isEnabled()); child1->setEnabled(false); QVERIFY(!child1->isEnabled()); QVERIFY(child2->isEnabled()); child2->setParentItem(child1); QVERIFY(!child1->isEnabled()); QVERIFY(!child2->isEnabled()); child2->setParentItem(root); QVERIFY(!child1->isEnabled()); QVERIFY(child2->isEnabled()); delete root; delete child1; delete child2; } void tst_qquickitem::enabledFocus() { QQuickWindow window; QVERIFY(ensureFocus(&window)); QQuickFocusScope root; root.setFocus(true); root.setEnabled(false); QCOMPARE(root.isEnabled(), false); QCOMPARE(root.hasFocus(), true); QCOMPARE(root.hasActiveFocus(), false); root.setParentItem(window.contentItem()); QCOMPARE(root.isEnabled(), false); QCOMPARE(root.hasFocus(), true); QCOMPARE(root.hasActiveFocus(), false); QCOMPARE(window.activeFocusItem(), window.contentItem()); root.setEnabled(true); QCOMPARE(root.isEnabled(), true); QCOMPARE(root.hasFocus(), true); QCOMPARE(root.hasActiveFocus(), true); QCOMPARE(window.activeFocusItem(), static_cast(&root)); QQuickItem child1; child1.setParentItem(&root); QCOMPARE(child1.isEnabled(), true); QCOMPARE(child1.hasFocus(), false); QCOMPARE(child1.hasActiveFocus(), false); QCOMPARE(window.activeFocusItem(), static_cast(&root)); QQuickItem child2; child2.setFocus(true); child2.setParentItem(&root); QCOMPARE(root.isEnabled(), true); QCOMPARE(root.hasFocus(), true); QCOMPARE(root.hasActiveFocus(), true); QCOMPARE(child2.isEnabled(), true); QCOMPARE(child2.hasFocus(), true); QCOMPARE(child2.hasActiveFocus(), true); QCOMPARE(window.activeFocusItem(), &child2); child2.setEnabled(false); QCOMPARE(root.isEnabled(), true); QCOMPARE(root.hasFocus(), true); QCOMPARE(root.hasActiveFocus(), true); QCOMPARE(child1.isEnabled(), true); QCOMPARE(child1.hasFocus(), false); QCOMPARE(child1.hasActiveFocus(), false); QCOMPARE(child2.isEnabled(), false); QCOMPARE(child2.hasFocus(), true); QCOMPARE(child2.hasActiveFocus(), false); QCOMPARE(window.activeFocusItem(), static_cast(&root)); child1.setEnabled(false); QCOMPARE(child1.isEnabled(), false); QCOMPARE(child1.hasFocus(), false); QCOMPARE(child1.hasActiveFocus(), false); child1.setFocus(true); QCOMPARE(child1.isEnabled(), false); QCOMPARE(child1.hasFocus(), true); QCOMPARE(child1.hasActiveFocus(), false); QCOMPARE(child2.isEnabled(), false); QCOMPARE(child2.hasFocus(), false); QCOMPARE(child2.hasActiveFocus(), false); QCOMPARE(window.activeFocusItem(), static_cast(&root)); child1.setEnabled(true); QCOMPARE(child1.isEnabled(), true); QCOMPARE(child1.hasFocus(), true); QCOMPARE(child1.hasActiveFocus(), true); QCOMPARE(window.activeFocusItem(), static_cast(&child1)); root.setFocus(false); QCOMPARE(root.isEnabled(), true); QCOMPARE(root.hasFocus(), false); QCOMPARE(root.hasActiveFocus(), false); QCOMPARE(child1.isEnabled(), true); QCOMPARE(child1.hasFocus(), true); QCOMPARE(child1.hasActiveFocus(), false); QCOMPARE(window.activeFocusItem(), window.contentItem()); child2.forceActiveFocus(); QCOMPARE(root.isEnabled(), true); QCOMPARE(root.hasFocus(), true); QCOMPARE(root.hasActiveFocus(), true); QCOMPARE(child1.isEnabled(), true); QCOMPARE(child1.hasFocus(), false); QCOMPARE(child1.hasActiveFocus(), false); QCOMPARE(child2.isEnabled(), false); QCOMPARE(child2.hasFocus(), true); QCOMPARE(child2.hasActiveFocus(), false); QCOMPARE(window.activeFocusItem(), static_cast(&root)); root.setEnabled(false); QCOMPARE(root.isEnabled(), false); QCOMPARE(root.hasFocus(), true); QCOMPARE(root.hasActiveFocus(), false); QCOMPARE(child1.isEnabled(), false); QCOMPARE(child1.hasFocus(), false); QCOMPARE(child1.hasActiveFocus(), false); QCOMPARE(child2.isEnabled(), false); QCOMPARE(child2.hasFocus(), true); QCOMPARE(child2.hasActiveFocus(), false); QCOMPARE(window.activeFocusItem(), window.contentItem()); child1.forceActiveFocus(); QCOMPARE(root.isEnabled(), false); QCOMPARE(root.hasFocus(), true); QCOMPARE(root.hasActiveFocus(), false); QCOMPARE(child1.isEnabled(), false); QCOMPARE(child1.hasFocus(), true); QCOMPARE(child1.hasActiveFocus(), false); QCOMPARE(child2.isEnabled(), false); QCOMPARE(child2.hasFocus(), false); QCOMPARE(child2.hasActiveFocus(), false); QCOMPARE(window.activeFocusItem(), window.contentItem()); root.setEnabled(true); QCOMPARE(root.isEnabled(), true); QCOMPARE(root.hasFocus(), true); QCOMPARE(root.hasActiveFocus(), true); QCOMPARE(child1.isEnabled(), true); QCOMPARE(child1.hasFocus(), true); QCOMPARE(child1.hasActiveFocus(), true); QCOMPARE(child2.isEnabled(), false); QCOMPARE(child2.hasFocus(), false); QCOMPARE(child2.hasActiveFocus(), false); QCOMPARE(window.activeFocusItem(), static_cast(&child1)); child2.setFocus(true); QCOMPARE(root.isEnabled(), true); QCOMPARE(root.hasFocus(), true); QCOMPARE(root.hasActiveFocus(), true); QCOMPARE(child1.isEnabled(), true); QCOMPARE(child1.hasFocus(), false); QCOMPARE(child1.hasActiveFocus(), false); QCOMPARE(child2.isEnabled(), false); QCOMPARE(child2.hasFocus(), true); QCOMPARE(child2.hasActiveFocus(), false); QCOMPARE(window.activeFocusItem(), static_cast(&root)); root.setEnabled(false); QCOMPARE(root.isEnabled(), false); QCOMPARE(root.hasFocus(), true); QCOMPARE(root.hasActiveFocus(), false); QCOMPARE(child1.isEnabled(), false); QCOMPARE(child1.hasFocus(), false); QCOMPARE(child1.hasActiveFocus(), false); QCOMPARE(child2.isEnabled(), false); QCOMPARE(child2.hasFocus(), true); QCOMPARE(child2.hasActiveFocus(), false); QCOMPARE(window.activeFocusItem(), window.contentItem()); } void tst_qquickitem::mouseGrab() { QQuickWindow window; window.resize(200, 200); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); auto devPriv = QPointingDevicePrivate::get(QPointingDevice::primaryPointingDevice()); auto child1 = new TestItem(window.contentItem()); child1->setObjectName(QStringLiteral("child1")); child1->setAcceptedMouseButtons(Qt::LeftButton); child1->setSize(QSizeF(200, 100)); auto child2 = new TestItem(window.contentItem()); child2->setObjectName(QStringLiteral("child2")); child2->setAcceptedMouseButtons(Qt::LeftButton); child2->setY(51); child2->setSize(QSizeF(200, 100)); // click over child1 QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50,50)); QTRY_COMPARE(devPriv->firstPointExclusiveGrabber(), child1); QCOMPARE(child1->pressCount, 1); QCOMPARE(child2->pressCount, 0); QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50,50)); QCOMPARE(devPriv->firstPointExclusiveGrabber(), nullptr); QCOMPARE(child1->releaseCount, 1); QCOMPARE(child2->releaseCount, 0); // press over child1 and then disable it: it doesn't get the release QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50,50)); QTRY_COMPARE(devPriv->firstPointExclusiveGrabber(), child1); QCOMPARE(child1->pressCount, 2); QCOMPARE(child2->pressCount, 0); child1->setEnabled(false); QCOMPARE(devPriv->firstPointExclusiveGrabber(), nullptr); QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50,50)); QCOMPARE(child1->releaseCount, 1); QCOMPARE(child2->releaseCount, 0); child1->setEnabled(true); // press over child1 and then hide it: it doesn't get the release QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50,50)); QTRY_COMPARE(devPriv->firstPointExclusiveGrabber(), child1); QCOMPARE(child1->pressCount, 3); QCOMPARE(child2->pressCount, 0); child1->setVisible(false); QCOMPARE(devPriv->firstPointExclusiveGrabber(), nullptr); QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50,50)); QCOMPARE(child1->releaseCount, 1); QCOMPARE(child2->releaseCount, 0); child1->setVisible(true); // click in a position over both children: only the top one gets it, // because the event is pre-accepted, which implies that the event // stops propagating and the top item gets the mouse grab. QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(75,75)); QCOMPARE(child1->pressCount, 3); QCOMPARE(child2->pressCount, 1); QCOMPARE(devPriv->firstPointExclusiveGrabber(), child2); QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(75,75)); QCOMPARE(devPriv->firstPointExclusiveGrabber(), nullptr); QCOMPARE(child1->releaseCount, 1); QCOMPARE(child2->releaseCount, 1); } void tst_qquickitem::mousePropagationToParent() { QQuickWindow window; window.resize(200, 200); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); auto devPriv = QPointingDevicePrivate::get(QPointingDevice::primaryPointingDevice()); auto child1 = new TestItem(window.contentItem()); child1->setObjectName(QStringLiteral("child1")); child1->setAcceptedMouseButtons(Qt::LeftButton); child1->setSize(QSizeF(200, 100)); auto child2 = new TestItem(child1); child2->setObjectName(QStringLiteral("child2")); child2->setAcceptedMouseButtons(Qt::LeftButton); child2->setSize(QSizeF(200, 100)); QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50,50)); // The event is pre-accepted; child2 gets it first because it's on top, // and it does NOT call QPointerEvent::accept(), but by tradition, // the event stops there anyway: child1 does not see it. QTRY_COMPARE(child2->pressCount, 1); QCOMPARE(child1->pressCount, 0); // child1 also does not explicitly grab, but the grab happens because the event is accepted. QCOMPARE(devPriv->firstPointExclusiveGrabber(), child2); QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50,50)); QTRY_COMPARE(child2->releaseCount, 1); QTRY_COMPARE(child1->releaseCount, 0); } void tst_qquickitem::touchEventAcceptIgnore_data() { QTest::addColumn("itemAcceptsTouch"); QTest::addColumn("itemAcceptsTouchEvents"); QTest::newRow("accepts touch, accepts events") << true << true; QTest::newRow("accepts touch, ignores events") << true << false; QTest::newRow("doesn't accept touch, gets no events") << false << false; } void tst_qquickitem::touchEventAcceptIgnore() { QFETCH(bool, itemAcceptsTouch); QFETCH(bool, itemAcceptsTouchEvents); TestWindow window; window.resize(100, 100); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QScopedPointer item(new TestItem); item->setSize(QSizeF(100, 100)); item->setParentItem(window.contentItem()); if (itemAcceptsTouch) item->setAcceptTouchEvents(itemAcceptsTouch); // it's false by default in Qt 6 item->acceptIncomingTouchEvents = itemAcceptsTouchEvents; static QPointingDevice* device = QTest::createTouchDevice(); // Send Begin, Update & End touch sequence item->touchEventReached = false; QTest::touchEvent(&window, device).press(1, QPoint(50, 50), &window); QQuickTouchUtils::flush(&window); QTRY_COMPARE(item->touchEventReached, itemAcceptsTouch); item->touchEventReached = false; QTest::touchEvent(&window, device).move(1, QPoint(60, 60), &window); QQuickTouchUtils::flush(&window); QTRY_COMPARE(item->touchEventReached, itemAcceptsTouchEvents); item->touchEventReached = false; QTest::touchEvent(&window, device).release(1, QPoint(60, 60), &window); QQuickTouchUtils::flush(&window); QTRY_COMPARE(item->touchEventReached, itemAcceptsTouchEvents); } void tst_qquickitem::polishOutsideAnimation() { QQuickWindow *window = new QQuickWindow; window->resize(200, 200); window->show(); TestPolishItem *item = new TestPolishItem(window->contentItem()); item->setSize(QSizeF(200, 100)); QTest::qWait(50); QTimer::singleShot(10, item, SLOT(doPolish())); QTRY_VERIFY(item->wasPolished); delete item; delete window; } void tst_qquickitem::polishOnCompleted() { QQuickView view; view.setSource(testFileUrl("polishOnCompleted.qml")); view.show(); QVERIFY(QTest::qWaitForWindowExposed(&view)); TestPolishItem *item = qobject_cast(view.rootObject()); QVERIFY(item); QTRY_VERIFY(item->wasPolished); } struct PolishItemSpan { int itemCount; // Number of items... int repolishCount; // ...repolishing 'repolishCount' times }; /* * For instance, two consecutive spans {99,0} and {1,2000} } instructs to * construct 99 items with no repolish, and 1 item with 2000 repolishes (in that sibling order) */ typedef QVector PolishItemSpans; Q_DECLARE_METATYPE(PolishItemSpan) Q_DECLARE_METATYPE(PolishItemSpans) void tst_qquickitem::polishLoopDetection_data() { QTest::addColumn("listOfItemsToPolish"); QTest::addColumn("expectedNumberOfWarnings"); QTest::newRow("test1.100") << PolishItemSpans({ {1, 100} }) << 0; QTest::newRow("test1.1002") << PolishItemSpans({ {1, 1002} }) << 3; QTest::newRow("test1.2020") << PolishItemSpans({ {1, 2020} }) << 10; QTest::newRow("test5.1") << PolishItemSpans({ {5, 1} }) << 0; QTest::newRow("test5.10") << PolishItemSpans({ {5, 10} }) << 0; QTest::newRow("test5.100") << PolishItemSpans({ {5, 100} }) << 0; QTest::newRow("test5.1000") << PolishItemSpans({ {5, 1000} }) << 5; QTest::newRow("test1000.1") << PolishItemSpans({ {1000,1} }) << 0; QTest::newRow("test2000.1") << PolishItemSpans({ {2000,1} }) << 0; QTest::newRow("test99.0-1.1100") << PolishItemSpans({ {99,0},{1,1100} }) << 5; QTest::newRow("test98.0-2.1100") << PolishItemSpans({ {98,0},{2,1100} }) << 5+5; // reverse the two above QTest::newRow("test1.1100-99.0") << PolishItemSpans({ {1,1100},{99,0} }) << 5; QTest::newRow("test2.1100-98.0") << PolishItemSpans({ {2,1100},{98,0} }) << 5+5; } void tst_qquickitem::polishLoopDetection() { QFETCH(PolishItemSpans, listOfItemsToPolish); QFETCH(int, expectedNumberOfWarnings); QQuickWindow window; window.resize(200, 200); window.show(); TestPolishItem *item = nullptr; int count = 0; for (PolishItemSpan s : listOfItemsToPolish) { for (int i = 0; i < s.itemCount; ++i) { item = new TestPolishItem(window.contentItem()); item->setSize(QSizeF(200, 100)); item->repolishLoopCount = s.repolishCount; item->setObjectName(QString::fromLatin1("obj%1").arg(count++)); } } for (int i = 0; i < expectedNumberOfWarnings; ++i) { QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*possible QQuickItem..polish.. loop.*")); QTest::ignoreMessage(QtWarningMsg, QRegularExpression(".*TestPolishItem.* called polish.. inside updatePolish.. of TestPolishItem.*")); } QList items = window.contentItem()->childItems(); for (int i = 0; i < items.count(); ++i) { static_cast(items.at(i))->doPolish(); } item = static_cast(items.first()); // item is the last item, so we wait until the last item reached 0 QVERIFY(QTest::qWaitFor([=](){return item->repolishLoopCount == 0 && item->wasPolished;})); } void tst_qquickitem::wheelEvent_data() { QTest::addColumn("visible"); QTest::addColumn("enabled"); QTest::newRow("visible and enabled") << true << true; QTest::newRow("visible and disabled") << true << false; QTest::newRow("invisible and enabled") << false << true; QTest::newRow("invisible and disabled") << false << false; } void tst_qquickitem::wheelEvent() { QFETCH(bool, visible); QFETCH(bool, enabled); const bool shouldReceiveWheelEvents = visible && enabled; const int width = 200; const int height = 200; QQuickWindow window; window.resize(width, height); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); TestItem *item = new TestItem; item->setSize(QSizeF(width, height)); item->setParentItem(window.contentItem()); item->setEnabled(enabled); item->setVisible(visible); QPoint localPoint(width / 2, height / 2); QPoint globalPoint = window.mapToGlobal(localPoint); QWheelEvent event(localPoint, globalPoint, QPoint(0, 0), QPoint(0, -120), Qt::NoButton, Qt::NoModifier, Qt::NoScrollPhase, false); event.setTimestamp(123456UL); event.setAccepted(false); QGuiApplication::sendEvent(&window, &event); if (shouldReceiveWheelEvents) { QVERIFY(event.isAccepted()); QCOMPARE(item->wheelCount, 1); QCOMPARE(item->timestamp, 123456UL); QCOMPARE(item->lastWheelEventPos, localPoint); QCOMPARE(item->lastWheelEventGlobalPos, globalPoint); } else { QVERIFY(!event.isAccepted()); QCOMPARE(item->wheelCount, 0); } } class HoverItem : public QQuickItem { Q_OBJECT public: HoverItem(QQuickItem *parent = nullptr) : QQuickItem(parent), hoverEnterCount(0), hoverMoveCount(0), hoverLeaveCount(0) { } void resetCounters() { hoverEnterCount = 0; hoverMoveCount = 0; hoverLeaveCount = 0; } int hoverEnterCount; int hoverMoveCount; int hoverLeaveCount; protected: virtual void hoverEnterEvent(QHoverEvent *event) { event->accept(); ++hoverEnterCount; } virtual void hoverMoveEvent(QHoverEvent *event) { event->accept(); ++hoverMoveCount; } virtual void hoverLeaveEvent(QHoverEvent *event) { event->accept(); ++hoverLeaveCount; } }; void tst_qquickitem::hoverEvent_data() { QTest::addColumn("visible"); QTest::addColumn("enabled"); QTest::addColumn("acceptHoverEvents"); QTest::newRow("visible, enabled, accept hover") << true << true << true; QTest::newRow("visible, disabled, accept hover") << true << false << true; QTest::newRow("invisible, enabled, accept hover") << false << true << true; QTest::newRow("invisible, disabled, accept hover") << false << false << true; QTest::newRow("visible, enabled, not accept hover") << true << true << false; QTest::newRow("visible, disabled, not accept hover") << true << false << false; QTest::newRow("invisible, enabled, not accept hover") << false << true << false; QTest::newRow("invisible, disabled, not accept hover") << false << false << false; } void tst_qquickitem::hoverEvent() { QFETCH(bool, visible); QFETCH(bool, enabled); QFETCH(bool, acceptHoverEvents); QQuickWindow *window = new QQuickWindow(); window->resize(200, 200); window->show(); HoverItem *item = new HoverItem; item->setSize(QSizeF(100, 100)); item->setParentItem(window->contentItem()); item->setEnabled(enabled); item->setVisible(visible); item->setAcceptHoverEvents(acceptHoverEvents); const QPoint outside(150, 150); const QPoint inside(50, 50); const QPoint anotherInside(51, 51); QTest::mouseMove(window, outside); item->resetCounters(); // Enter, then move twice inside, then leave. QTest::mouseMove(window, inside); QTest::mouseMove(window, anotherInside); QTest::mouseMove(window, inside); QTest::mouseMove(window, outside); const bool shouldReceiveHoverEvents = visible && acceptHoverEvents; if (shouldReceiveHoverEvents) { QCOMPARE(item->hoverEnterCount, 1); QCOMPARE(item->hoverMoveCount, 2); QCOMPARE(item->hoverLeaveCount, 1); } else { QCOMPARE(item->hoverEnterCount, 0); QCOMPARE(item->hoverMoveCount, 0); QCOMPARE(item->hoverLeaveCount, 0); } delete window; } void tst_qquickitem::hoverEventInParent() { QQuickWindow window; window.resize(200, 200); window.show(); HoverItem *parentItem = new HoverItem(window.contentItem()); parentItem->setSize(QSizeF(200, 200)); parentItem->setAcceptHoverEvents(true); HoverItem *leftItem = new HoverItem(parentItem); leftItem->setSize(QSizeF(100, 200)); leftItem->setAcceptHoverEvents(true); HoverItem *rightItem = new HoverItem(parentItem); rightItem->setSize(QSizeF(100, 200)); rightItem->setPosition(QPointF(100, 0)); rightItem->setAcceptHoverEvents(true); const QPoint insideLeft(50, 100); const QPoint insideRight(150, 100); QTest::mouseMove(&window, insideLeft); parentItem->resetCounters(); leftItem->resetCounters(); rightItem->resetCounters(); QTest::mouseMove(&window, insideRight); QCOMPARE(parentItem->hoverEnterCount, 0); QCOMPARE(parentItem->hoverLeaveCount, 0); QCOMPARE(leftItem->hoverEnterCount, 0); QCOMPARE(leftItem->hoverLeaveCount, 1); QCOMPARE(rightItem->hoverEnterCount, 1); QCOMPARE(rightItem->hoverLeaveCount, 0); QTest::mouseMove(&window, insideLeft); QCOMPARE(parentItem->hoverEnterCount, 0); QCOMPARE(parentItem->hoverLeaveCount, 0); QCOMPARE(leftItem->hoverEnterCount, 1); QCOMPARE(leftItem->hoverLeaveCount, 1); QCOMPARE(rightItem->hoverEnterCount, 1); QCOMPARE(rightItem->hoverLeaveCount, 1); } void tst_qquickitem::paintOrder_data() { const QUrl order1Url = testFileUrl("order.1.qml"); const QUrl order2Url = testFileUrl("order.2.qml"); QTest::addColumn("source"); QTest::addColumn("op"); QTest::addColumn("param1"); QTest::addColumn("param2"); QTest::addColumn("expected"); QTest::newRow("test 1 noop") << order1Url << int(NoOp) << QVariant() << QVariant() << (QStringList() << "1" << "2" << "3"); QTest::newRow("test 1 add") << order1Url << int(Append) << QVariant("new") << QVariant() << (QStringList() << "1" << "2" << "3" << "new"); QTest::newRow("test 1 remove") << order1Url << int(Remove) << QVariant(1) << QVariant() << (QStringList() << "1" << "3"); QTest::newRow("test 1 stack before") << order1Url << int(StackBefore) << QVariant(2) << QVariant(1) << (QStringList() << "1" << "3" << "2"); QTest::newRow("test 1 stack after") << order1Url << int(StackAfter) << QVariant(0) << QVariant(1) << (QStringList() << "2" << "1" << "3"); QTest::newRow("test 1 set z") << order1Url << int(SetZ) << QVariant(1) << QVariant(qreal(1.)) << (QStringList() << "1" << "3" << "2"); QTest::newRow("test 2 noop") << order2Url << int(NoOp) << QVariant() << QVariant() << (QStringList() << "1" << "3" << "2"); QTest::newRow("test 2 add") << order2Url << int(Append) << QVariant("new") << QVariant() << (QStringList() << "1" << "3" << "new" << "2"); QTest::newRow("test 2 remove 1") << order2Url << int(Remove) << QVariant(1) << QVariant() << (QStringList() << "1" << "3"); QTest::newRow("test 2 remove 2") << order2Url << int(Remove) << QVariant(2) << QVariant() << (QStringList() << "1" << "2"); QTest::newRow("test 2 stack before 1") << order2Url << int(StackBefore) << QVariant(1) << QVariant(0) << (QStringList() << "1" << "3" << "2"); QTest::newRow("test 2 stack before 2") << order2Url << int(StackBefore) << QVariant(2) << QVariant(0) << (QStringList() << "3" << "1" << "2"); QTest::newRow("test 2 stack after 1") << order2Url << int(StackAfter) << QVariant(0) << QVariant(1) << (QStringList() << "1" << "3" << "2"); QTest::newRow("test 2 stack after 2") << order2Url << int(StackAfter) << QVariant(0) << QVariant(2) << (QStringList() << "3" << "1" << "2"); QTest::newRow("test 1 set z") << order1Url << int(SetZ) << QVariant(2) << QVariant(qreal(2.)) << (QStringList() << "1" << "2" << "3"); } void tst_qquickitem::paintOrder() { QFETCH(QUrl, source); QFETCH(int, op); QFETCH(QVariant, param1); QFETCH(QVariant, param2); QFETCH(QStringList, expected); QQuickView view; view.setSource(source); QQuickItem *root = qobject_cast(view.rootObject()); QVERIFY(root); switch (op) { case Append: { QQuickItem *item = new QQuickItem(root); item->setObjectName(param1.toString()); } break; case Remove: { QQuickItem *item = root->childItems().at(param1.toInt()); delete item; } break; case StackBefore: { QQuickItem *item1 = root->childItems().at(param1.toInt()); QQuickItem *item2 = root->childItems().at(param2.toInt()); item1->stackBefore(item2); } break; case StackAfter: { QQuickItem *item1 = root->childItems().at(param1.toInt()); QQuickItem *item2 = root->childItems().at(param2.toInt()); item1->stackAfter(item2); } break; case SetZ: { QQuickItem *item = root->childItems().at(param1.toInt()); item->setZ(param2.toReal()); } break; default: break; } QList list = QQuickItemPrivate::get(root)->paintOrderChildItems(); QStringList items; for (int i = 0; i < list.count(); ++i) items << list.at(i)->objectName(); QCOMPARE(items, expected); } void tst_qquickitem::acceptedMouseButtons() { TestItem item; QCOMPARE(item.acceptedMouseButtons(), Qt::MouseButtons(Qt::NoButton)); QQuickWindow window; item.setSize(QSizeF(200,100)); item.setParentItem(window.contentItem()); QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50)); QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50)); QCOMPARE(item.pressCount, 0); QCOMPARE(item.releaseCount, 0); QTest::mousePress(&window, Qt::RightButton, Qt::NoModifier, QPoint(50, 50)); QTest::mouseRelease(&window, Qt::RightButton, Qt::NoModifier, QPoint(50, 50)); QCOMPARE(item.pressCount, 0); QCOMPARE(item.releaseCount, 0); QTest::mousePress(&window, Qt::MiddleButton, Qt::NoModifier, QPoint(50, 50)); QTest::mouseRelease(&window, Qt::MiddleButton, Qt::NoModifier, QPoint(50, 50)); QCOMPARE(item.pressCount, 0); QCOMPARE(item.releaseCount, 0); item.setAcceptedMouseButtons(Qt::LeftButton); QCOMPARE(item.acceptedMouseButtons(), Qt::MouseButtons(Qt::LeftButton)); QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50)); QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50)); QCOMPARE(item.pressCount, 1); QCOMPARE(item.releaseCount, 1); QTest::mousePress(&window, Qt::RightButton, Qt::NoModifier, QPoint(50, 50)); QTest::mouseRelease(&window, Qt::RightButton, Qt::NoModifier, QPoint(50, 50)); QCOMPARE(item.pressCount, 1); QCOMPARE(item.releaseCount, 1); QTest::mousePress(&window, Qt::MiddleButton, Qt::NoModifier, QPoint(50, 50)); QTest::mouseRelease(&window, Qt::MiddleButton, Qt::NoModifier, QPoint(50, 50)); QCOMPARE(item.pressCount, 1); QCOMPARE(item.releaseCount, 1); item.setAcceptedMouseButtons(Qt::RightButton | Qt::MiddleButton); QCOMPARE(item.acceptedMouseButtons(), Qt::MouseButtons(Qt::RightButton | Qt::MiddleButton)); QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50)); QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50)); QCOMPARE(item.pressCount, 1); QCOMPARE(item.releaseCount, 1); QTest::mousePress(&window, Qt::RightButton, Qt::NoModifier, QPoint(50, 50)); QTest::mouseRelease(&window, Qt::RightButton, Qt::NoModifier, QPoint(50, 50)); QCOMPARE(item.pressCount, 2); QCOMPARE(item.releaseCount, 2); QTest::mousePress(&window, Qt::MiddleButton, Qt::NoModifier, QPoint(50, 50)); QTest::mouseRelease(&window, Qt::MiddleButton, Qt::NoModifier, QPoint(50, 50)); QCOMPARE(item.pressCount, 3); QCOMPARE(item.releaseCount, 3); } static void gc(QQmlEngine &engine) { engine.collectGarbage(); QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); QCoreApplication::processEvents(); } void tst_qquickitem::visualParentOwnership() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("visualParentOwnership.qml")); QScopedPointer root(qobject_cast(component.create())); QVERIFY(root); QVariant newObject; { QVERIFY(QMetaObject::invokeMethod(root.data(), "createItemWithoutParent", Q_RETURN_ARG(QVariant, newObject))); QPointer newItem = qvariant_cast(newObject); QVERIFY(!newItem.isNull()); QVERIFY(!newItem->parent()); QVERIFY(!newItem->parentItem()); newItem->setParentItem(root.data()); gc(engine); QVERIFY(!newItem.isNull()); newItem->setParentItem(nullptr); gc(engine); QVERIFY(newItem.isNull()); } { QVERIFY(QMetaObject::invokeMethod(root.data(), "createItemWithoutParent", Q_RETURN_ARG(QVariant, newObject))); QPointer firstItem = qvariant_cast(newObject); QVERIFY(!firstItem.isNull()); firstItem->setParentItem(root.data()); QVERIFY(QMetaObject::invokeMethod(root.data(), "createItemWithoutParent", Q_RETURN_ARG(QVariant, newObject))); QPointer secondItem = qvariant_cast(newObject); QVERIFY(!firstItem.isNull()); secondItem->setParentItem(firstItem); gc(engine); delete firstItem; root->setProperty("keepAliveProperty", newObject); gc(engine); QVERIFY(!secondItem.isNull()); root->setProperty("keepAliveProperty", QVariant()); gc(engine); QVERIFY(secondItem.isNull()); } } void tst_qquickitem::visualParentOwnershipWindow() { QQmlEngine engine; QQmlComponent component(&engine, testFileUrl("visualParentOwnershipWindow.qml")); QQuickWindow *window = qobject_cast(component.create()); QVERIFY(window); QQuickItem *root = window->contentItem(); QVariant newObject; { QVERIFY(QMetaObject::invokeMethod(window, "createItemWithoutParent", Q_RETURN_ARG(QVariant, newObject))); QPointer newItem = qvariant_cast(newObject); QVERIFY(!newItem.isNull()); QVERIFY(!newItem->parent()); QVERIFY(!newItem->parentItem()); newItem->setParentItem(root); gc(engine); QVERIFY(!newItem.isNull()); newItem->setParentItem(nullptr); gc(engine); QVERIFY(newItem.isNull()); } { QVERIFY(QMetaObject::invokeMethod(window, "createItemWithoutParent", Q_RETURN_ARG(QVariant, newObject))); QPointer firstItem = qvariant_cast(newObject); QVERIFY(!firstItem.isNull()); firstItem->setParentItem(root); QVERIFY(QMetaObject::invokeMethod(window, "createItemWithoutParent", Q_RETURN_ARG(QVariant, newObject))); QPointer secondItem = qvariant_cast(newObject); QVERIFY(!firstItem.isNull()); secondItem->setParentItem(firstItem); gc(engine); delete firstItem; window->setProperty("keepAliveProperty", newObject); gc(engine); QVERIFY(!secondItem.isNull()); window->setProperty("keepAliveProperty", QVariant()); gc(engine); QVERIFY(secondItem.isNull()); } } class InvalidatedItem : public QQuickItem { Q_OBJECT signals: void invalidated(); public slots: void invalidateSceneGraph() { emit invalidated(); } }; void tst_qquickitem::testSGInvalidate() { for (int i=0; i<2; ++i) { QScopedPointer view(new QQuickView()); InvalidatedItem *item = new InvalidatedItem(); int expected = 0; if (i == 0) { // First iteration, item has contents and should get signals expected = 1; item->setFlag(QQuickItem::ItemHasContents, true); } else { // Second iteration, item does not have content and will not get signals } QSignalSpy invalidateSpy(item, SIGNAL(invalidated())); item->setParentItem(view->contentItem()); view->show(); QVERIFY(QTest::qWaitForWindowExposed(view.data())); delete view.take(); QCOMPARE(invalidateSpy.size(), expected); } } void tst_qquickitem::objectChildTransform() { QQuickView view; view.setSource(testFileUrl("objectChildTransform.qml")); QQuickItem *root = qobject_cast(view.rootObject()); QVERIFY(root); root->setProperty("source", QString()); // Shouldn't crash. } void tst_qquickitem::contains_data() { QTest::addColumn("x"); QTest::addColumn("y"); QTest::addColumn("contains"); QTest::newRow("(0, 0) = false") << 0 << 0 << false; QTest::newRow("(50, 0) = false") << 50 << 0 << false; QTest::newRow("(0, 50) = false") << 0 << 50 << false; QTest::newRow("(50, 50) = true") << 50 << 50 << true; QTest::newRow("(100, 100) = true") << 100 << 100 << true; QTest::newRow("(150, 150) = false") << 150 << 150 << false; } void tst_qquickitem::contains() { // Tests that contains works, but also checks that mapToItem/mapFromItem // return the correct type (point or rect, not a JS object with those properties), // as this is a common combination of calls. QFETCH(int, x); QFETCH(int, y); QFETCH(bool, contains); QQuickView view; view.setSource(testFileUrl("contains.qml")); QQuickItem *root = qobject_cast(view.rootObject()); QVERIFY(root); QVariant result = false; QVERIFY(QMetaObject::invokeMethod(root, "childContainsViaMapToItem", Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, qreal(x)), Q_ARG(QVariant, qreal(y)))); QCOMPARE(result.toBool(), contains); result = false; QVERIFY(QMetaObject::invokeMethod(root, "childContainsViaMapFromItem", Q_RETURN_ARG(QVariant, result), Q_ARG(QVariant, qreal(x)), Q_ARG(QVariant, qreal(y)))); QCOMPARE(result.toBool(), contains); } void tst_qquickitem::childAt() { QQuickView view; view.setSource(testFileUrl("childAtRectangle.qml")); QQuickItem *root = qobject_cast(view.rootObject()); int found = 0; for (int i = 0; i < 16; i++) { if (root->childAt(i, 0)) found++; } QCOMPARE(found, 16); found = 0; for (int i = 0; i < 16; i++) { if (root->childAt(0, i)) found++; } QCOMPARE(found, 16); found = 0; for (int i = 0; i < 2; i++) { if (root->childAt(18 + i, 0)) found++; } QCOMPARE(found, 1); found = 0; for (int i = 0; i < 16; i++) { if (root->childAt(18, i)) found++; } QCOMPARE(found, 1); QVERIFY(!root->childAt(19,19)); } void tst_qquickitem::ignoreButtonPressNotInAcceptedMouseButtons() { // Verify the fix for QTBUG-31861 TestItem item; QCOMPARE(item.acceptedMouseButtons(), Qt::MouseButtons(Qt::NoButton)); QQuickWindow window; item.setSize(QSizeF(200,100)); item.setParentItem(window.contentItem()); item.setAcceptedMouseButtons(Qt::LeftButton); QCOMPARE(item.acceptedMouseButtons(), Qt::MouseButtons(Qt::LeftButton)); QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50)); QTest::mousePress(&window, Qt::RightButton, Qt::NoModifier, QPoint(50, 50)); // ignored because it's not LeftButton QTest::mouseRelease(&window, Qt::RightButton, Qt::NoModifier, QPoint(50, 50)); // ignored because it didn't grab the RightButton press QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(50, 50)); QCOMPARE(item.pressCount, 1); QCOMPARE(item.releaseCount, 1); } void tst_qquickitem::shortcutOverride() { QQuickView view; view.setSource(testFileUrl("shortcutOverride.qml")); QVERIFY(ensureFocus(&view)); QCOMPARE(view.rootObject()->property("escapeHandlerActivationCount").toInt(), 0); QCOMPARE(view.rootObject()->property("shortcutActivationCount").toInt(), 0); QQuickItem *escapeItem = view.rootObject()->property("escapeItem").value(); QVERIFY(escapeItem); QVERIFY(escapeItem->hasActiveFocus()); // escapeItem's onEscapePressed handler should accept the first escape press event. QTest::keyPress(&view, Qt::Key_Escape); QCOMPARE(view.rootObject()->property("escapeHandlerActivationCount").toInt(), 1); QCOMPARE(view.rootObject()->property("shortcutActivationCount").toInt(), 0); // Now it shouldn't have focus, so it can't handle the next escape press event. QVERIFY(!escapeItem->hasActiveFocus()); QTest::keyRelease(&view, Qt::Key_Escape); QCOMPARE(view.rootObject()->property("escapeHandlerActivationCount").toInt(), 1); QCOMPARE(view.rootObject()->property("shortcutActivationCount").toInt(), 0); QTest::keyPress(&view, Qt::Key_Escape); QCOMPARE(view.rootObject()->property("escapeHandlerActivationCount").toInt(), 1); QCOMPARE(view.rootObject()->property("shortcutActivationCount").toInt(), 1); QTest::keyRelease(&view, Qt::Key_Escape); QCOMPARE(view.rootObject()->property("escapeHandlerActivationCount").toInt(), 1); QCOMPARE(view.rootObject()->property("shortcutActivationCount").toInt(), 1); } #ifdef TEST_QTBUG_60123 void tst_qquickitem::qtBug60123() { QMainWindow main; main.resize(400, 200); QQuickView window; QQuickView window2; window.setSource(testFileUrl("mainWindowQtBug60123.qml")); window2.setSource(testFileUrl("mainWindowQtBug60123.qml")); // Create central widget for the main window QWidget *baseWidget = new QWidget(&main); baseWidget->resize(400, 200); baseWidget->setMaximumHeight(200); baseWidget->setMaximumWidth(400); main.setCentralWidget(baseWidget); // Create container widgets for both windows QWidget *containers = QWidget::createWindowContainer(&window, baseWidget); QWidget *containers2 = QWidget::createWindowContainer(&window2, baseWidget); containers->setGeometry(0, 0, 100, 100); containers2->setGeometry(100, 100, 100, 100); // Show and activate the main window main.show(); QVERIFY(QTest::qWaitForWindowExposed(&main)); // Activate window, test press and release events auto activateWindowAndTestPress = [] (QQuickView* testWindow) { testWindow->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(testWindow)); QTest::mousePress(testWindow, Qt::LeftButton, Qt::NoModifier, QPoint(10, 10)); QCOMPARE(testWindow->rootObject()->property("lastEvent").toString(), QString("pressed")); QTest::mouseRelease(testWindow, Qt::LeftButton, Qt::NoModifier, QPoint(10, 10)); QCOMPARE(testWindow->rootObject()->property("lastEvent").toString(), QString("released")); }; // First press after switching focus window resulted in cancelled event activateWindowAndTestPress(&window); activateWindowAndTestPress(&window2); activateWindowAndTestPress(&window); } #endif void tst_qquickitem::setParentCalledInOnWindowChanged() { QQuickView view; view.setSource(testFileUrl("setParentInWindowChange.qml")); QVERIFY(ensureFocus(&view)); // should not crash } void tst_qquickitem::receivesLanguageChangeEvent() { QQuickWindow window; window.setFramePosition(QPoint(100, 100)); window.resize(200, 200); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QScopedPointer child1(new TestItem); child1->setObjectName(QStringLiteral("child1")); child1->setSize(QSizeF(200, 100)); child1->setParentItem(window.contentItem()); QScopedPointer child2(new TestItem); child2->setObjectName(QStringLiteral("child2")); child2->setSize(QSizeF(50, 50)); child2->setParentItem(child1.data()); QTranslator t; QVERIFY(t.load("hellotr_la.qm", dataDirectory())); QVERIFY(QCoreApplication::installTranslator(&t)); QTRY_COMPARE(child1->languageChangeEventCount, 1); QCOMPARE(child2->languageChangeEventCount, 1); } QTEST_MAIN(tst_qquickitem) #include "tst_qquickitem.moc"