/**************************************************************************** ** ** 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 #include #include #include #include #include #include #include #include #include #include #include #include "../../shared/util.h" #include "../shared/visualtestutil.h" #include "../shared/viewtestutil.h" #include #include #include #include #include #include Q_LOGGING_CATEGORY(lcTests, "qt.quick.tests") struct TouchEventData { QEvent::Type type; QWidget *widget; QWindow *window; Qt::TouchPointStates states; QList touchPoints; }; static QTouchEvent::TouchPoint makeTouchPoint(QQuickItem *item, const QPointF &p, const QPointF &lastPoint = QPointF()) { QPointF last = lastPoint.isNull() ? p : lastPoint; QTouchEvent::TouchPoint tp; tp.setPos(p); tp.setLastPos(last); tp.setScenePos(item->mapToScene(p)); tp.setLastScenePos(item->mapToScene(last)); tp.setScreenPos(item->window()->mapToGlobal(tp.scenePos().toPoint())); tp.setLastScreenPos(item->window()->mapToGlobal(tp.lastScenePos().toPoint())); return tp; } static TouchEventData makeTouchData(QEvent::Type type, QWindow *w, Qt::TouchPointStates states = nullptr, const QList& touchPoints = QList()) { TouchEventData d = { type, nullptr, w, states, touchPoints }; return d; } static TouchEventData makeTouchData(QEvent::Type type, QWindow *w, Qt::TouchPointStates states, const QTouchEvent::TouchPoint &touchPoint) { QList points; points << touchPoint; return makeTouchData(type, w, states, points); } #define COMPARE_TOUCH_POINTS(tp1, tp2) \ { \ QCOMPARE(tp1.pos(), tp2.pos()); \ QCOMPARE(tp1.lastPos(), tp2.lastPos()); \ QCOMPARE(tp1.scenePos(), tp2.scenePos()); \ QCOMPARE(tp1.lastScenePos(), tp2.lastScenePos()); \ QCOMPARE(tp1.screenPos(), tp2.screenPos()); \ QCOMPARE(tp1.lastScreenPos(), tp2.lastScreenPos()); \ } #define COMPARE_TOUCH_DATA(d1, d2) \ { \ QCOMPARE((int)d1.type, (int)d2.type); \ QCOMPARE(d1.widget, d2.widget); \ QCOMPARE((int)d1.states, (int)d2.states); \ QCOMPARE(d1.touchPoints.count(), d2.touchPoints.count()); \ for (int i=0; icontentItem; QObject::connect(m_rootItem, SIGNAL(destroyed()), this, SLOT(rootItemDestroyed())); } return m_rootItem; } bool isRootItemDestroyed() {return m_rootItemDestroyed;} public slots: void rootItemDestroyed() { m_rootItemDestroyed = true; } private: bool m_rootItemDestroyed; QQuickItem *m_rootItem; }; class TestTouchItem : public QQuickRectangle { Q_OBJECT public: TestTouchItem(QQuickItem *parent = nullptr) : QQuickRectangle(parent), acceptTouchEvents(true), acceptMouseEvents(true), mousePressCount(0), mouseMoveCount(0), spinLoopWhenPressed(false), touchEventCount(0), mouseUngrabEventCount(0) { border()->setWidth(1); setAcceptedMouseButtons(Qt::LeftButton); setFiltersChildMouseEvents(true); } void reset() { acceptTouchEvents = acceptMouseEvents = true; setEnabled(true); setVisible(true); lastEvent = makeTouchData(QEvent::None, window(), nullptr, QList());//CHECK_VALID lastVelocity = lastVelocityFromMouseMove = QVector2D(); lastMousePos = QPointF(); lastMouseCapabilityFlags = 0; touchEventCount = 0; mouseMoveCount = 0; mouseUngrabEventCount = 0; } static void clearMouseEventCounters() { mousePressNum = mouseMoveNum = mouseReleaseNum = 0; } void clearTouchEventCounter() { touchEventCount = 0; } bool acceptTouchEvents; bool acceptMouseEvents; bool grabOnRelease = false; TouchEventData lastEvent; int mousePressCount; int mouseMoveCount; bool spinLoopWhenPressed; int touchEventCount; int mouseUngrabEventCount; QVector2D lastVelocity; QVector2D lastVelocityFromMouseMove; QPointF lastMousePos; int lastMouseCapabilityFlags; void touchEvent(QTouchEvent *event) { if (!acceptTouchEvents) { event->ignore(); return; } ++touchEventCount; lastEvent = makeTouchData(event->type(), event->window(), event->touchPointStates(), event->touchPoints()); if (event->device()->capabilities().testFlag(QTouchDevice::Velocity) && !event->touchPoints().isEmpty()) { lastVelocity = event->touchPoints().first().velocity(); } else { lastVelocity = QVector2D(); } if (spinLoopWhenPressed && event->touchPointStates().testFlag(Qt::TouchPointPressed)) { QCoreApplication::processEvents(); } } void mousePressEvent(QMouseEvent *e) { if (!acceptMouseEvents) { e->ignore(); return; } mousePressCount = ++mousePressNum; lastMousePos = e->pos(); lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e); } void mouseMoveEvent(QMouseEvent *e) { if (!acceptMouseEvents) { e->ignore(); return; } mouseMoveCount = ++mouseMoveNum; lastVelocityFromMouseMove = QGuiApplicationPrivate::mouseEventVelocity(e); lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e); lastMousePos = e->pos(); } void mouseReleaseEvent(QMouseEvent *e) { if (!acceptMouseEvents) { e->ignore(); return; } ++mouseReleaseNum; lastMousePos = e->pos(); lastMouseCapabilityFlags = QGuiApplicationPrivate::mouseEventCaps(e); } void mouseUngrabEvent() { ++mouseUngrabEventCount; } bool childMouseEventFilter(QQuickItem *item, QEvent *e) { qCDebug(lcTests) << objectName() << "filtering" << e << "ahead of delivery to" << item->metaObject()->className() << item->objectName(); switch (e->type()) { case QEvent::MouseButtonPress: mousePressCount = ++mousePressNum; break; case QEvent::MouseButtonRelease: if (grabOnRelease) grabMouse(); break; case QEvent::MouseMove: mouseMoveCount = ++mouseMoveNum; break; default: break; } return false; } static int mousePressNum, mouseMoveNum, mouseReleaseNum; }; int TestTouchItem::mousePressNum = 0; int TestTouchItem::mouseMoveNum = 0; int TestTouchItem::mouseReleaseNum = 0; class EventFilter : public QObject { public: bool eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched); events.append(event->type()); return false; } QList events; }; class ConstantUpdateItem : public QQuickItem { Q_OBJECT public: ConstantUpdateItem(QQuickItem *parent = nullptr) : QQuickItem(parent), iterations(0) {setFlag(ItemHasContents);} int iterations; protected: QSGNode* updatePaintNode(QSGNode *, UpdatePaintNodeData *){ iterations++; update(); return nullptr; } }; class MouseRecordingWindow : public QQuickWindow { public: explicit MouseRecordingWindow(QWindow *parent = nullptr) : QQuickWindow(parent) { } protected: void mousePressEvent(QMouseEvent *event) override { qCDebug(lcTests) << event; m_mouseEvents << *event; QQuickWindow::mousePressEvent(event); } void mouseMoveEvent(QMouseEvent *event) override { qCDebug(lcTests) << event; m_mouseEvents << *event; QQuickWindow::mouseMoveEvent(event); } void mouseReleaseEvent(QMouseEvent *event) override { qCDebug(lcTests) << event; m_mouseEvents << *event; QQuickWindow::mouseReleaseEvent(event); } public: QList m_mouseEvents; }; class MouseRecordingItem : public QQuickItem { public: MouseRecordingItem(bool acceptTouch, QQuickItem *parent = nullptr) : QQuickItem(parent) , m_acceptTouch(acceptTouch) { setSize(QSizeF(300, 300)); setAcceptedMouseButtons(Qt::LeftButton); } protected: void touchEvent(QTouchEvent* event) override { event->setAccepted(m_acceptTouch); m_touchEvents << *event; qCDebug(lcTests) << "accepted?" << event->isAccepted() << event; } void mousePressEvent(QMouseEvent *event) override { qCDebug(lcTests) << event; m_mouseEvents << *event; } void mouseMoveEvent(QMouseEvent *event) override { qCDebug(lcTests) << event; m_mouseEvents << *event; } void mouseReleaseEvent(QMouseEvent *event) override { qCDebug(lcTests) << event; m_mouseEvents << *event; } public: QList m_mouseEvents; QList m_touchEvents; private: bool m_acceptTouch; }; class tst_qquickwindow : public QQmlDataTest { Q_OBJECT public: tst_qquickwindow() : touchDevice(QTest::createTouchDevice()) , touchDeviceWithVelocity(QTest::createTouchDevice()) { QQuickWindow::setDefaultAlphaBuffer(true); touchDeviceWithVelocity->setCapabilities(QTouchDevice::Position | QTouchDevice::Velocity); } private slots: void cleanup(); #if QT_CONFIG(opengl) void openglContextCreatedSignal(); #endif void aboutToStopSignal(); void constantUpdates(); void constantUpdatesOnWindow_data(); void constantUpdatesOnWindow(); void mouseFiltering(); void headless(); void noUpdateWhenNothingChanges(); void touchEvent_basic(); void touchEvent_propagation(); void touchEvent_propagation_data(); void touchEvent_cancel(); void touchEvent_cancelClearsMouseGrab(); void touchEvent_reentrant(); void touchEvent_velocity(); void mergeTouchPointLists_data(); void mergeTouchPointLists(); void mouseFromTouch_basic(); void synthMouseFromTouch_data(); void synthMouseFromTouch(); void clearWindow(); void qmlCreation(); void qmlCreationWithScreen(); void clearColor(); void defaultState(); void grab_data(); void grab(); void multipleWindows(); void animationsWhileHidden(); void focusObject(); void focusReason(); void ignoreUnhandledMouseEvents(); void ownershipRootItem(); void hideThenDelete_data(); void hideThenDelete(); void showHideAnimate(); void testExpose(); void requestActivate(); void testWindowVisibilityOrder(); void blockClosing(); void blockCloseMethod(); void crashWhenHoverItemDeleted(); void unloadSubWindow(); void changeVisibilityInCompleted(); void qobjectEventFilter_touch(); void qobjectEventFilter_key(); void qobjectEventFilter_mouse(); #if QT_CONFIG(cursor) void cursor(); #endif void animatingSignal(); void contentItemSize(); void defaultSurfaceFormat(); void attachedProperty(); void testRenderJob(); void testHoverChildMouseEventFilter(); void testHoverTimestamp(); void test_circleMapItem(); void pointerEventTypeAndPointCount(); void grabContentItemToImage(); void testDragEventPropertyPropagation(); void findChild(); void testChildMouseEventFilter(); void testChildMouseEventFilter_data(); void cleanupGrabsOnRelease(); private: QTouchDevice *touchDevice; QTouchDevice *touchDeviceWithVelocity; }; #if QT_CONFIG(opengl) Q_DECLARE_METATYPE(QOpenGLContext *); #endif void tst_qquickwindow::cleanup() { QVERIFY(QGuiApplication::topLevelWindows().isEmpty()); } #if QT_CONFIG(opengl) void tst_qquickwindow::openglContextCreatedSignal() { qRegisterMetaType(); QQuickWindow window; QSignalSpy spy(&window, SIGNAL(openglContextCreated(QOpenGLContext*))); window.setTitle(QTest::currentTestFunction()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); if (window.rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) QSKIP("Skipping OpenGL context test due to not running with OpenGL"); QTRY_VERIFY(spy.size() > 0); QVariant ctx = spy.at(0).at(0); QCOMPARE(qvariant_cast(ctx), window.openglContext()); } #endif void tst_qquickwindow::aboutToStopSignal() { QQuickWindow window; window.setTitle(QTest::currentTestFunction()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QSignalSpy spy(&window, SIGNAL(sceneGraphAboutToStop())); window.hide(); QTRY_VERIFY(spy.count() > 0); } //If the item calls update inside updatePaintNode, it should schedule another sync pass void tst_qquickwindow::constantUpdates() { QQuickWindow window; window.resize(250, 250); ConstantUpdateItem item(window.contentItem()); window.setTitle(QTest::currentTestFunction()); window.show(); QSignalSpy beforeSpy(&window, SIGNAL(beforeSynchronizing())); QSignalSpy afterSpy(&window, SIGNAL(afterSynchronizing())); QTRY_VERIFY(item.iterations > 10); QTRY_VERIFY(beforeSpy.count() > 10); QTRY_VERIFY(afterSpy.count() > 10); } void tst_qquickwindow::constantUpdatesOnWindow_data() { QTest::addColumn("blockedGui"); QTest::addColumn("signal"); QQuickWindow window; window.setTitle(QTest::currentTestFunction()); window.setGeometry(100, 100, 300, 200); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); const bool threaded = QQuickWindowPrivate::get(&window)->context->thread() != QGuiApplication::instance()->thread(); if (threaded) { QTest::newRow("blocked, beforeRender") << true << QByteArray(SIGNAL(beforeRendering())); QTest::newRow("blocked, afterRender") << true << QByteArray(SIGNAL(afterRendering())); QTest::newRow("blocked, swapped") << true << QByteArray(SIGNAL(frameSwapped())); } QTest::newRow("unblocked, beforeRender") << false << QByteArray(SIGNAL(beforeRendering())); QTest::newRow("unblocked, afterRender") << false << QByteArray(SIGNAL(afterRendering())); QTest::newRow("unblocked, swapped") << false << QByteArray(SIGNAL(frameSwapped())); } class FrameCounter : public QObject { Q_OBJECT public slots: void incr() { QMutexLocker locker(&m_mutex); ++m_counter; } public: FrameCounter() : m_counter(0) {} int count() { QMutexLocker locker(&m_mutex); int x = m_counter; return x; } private: int m_counter; QMutex m_mutex; }; void tst_qquickwindow::constantUpdatesOnWindow() { QFETCH(bool, blockedGui); QFETCH(QByteArray, signal); QQuickWindow window; window.setTitle(QTest::currentTestFunction()); window.setGeometry(100, 100, 300, 200); bool ok = connect(&window, signal.constData(), &window, SLOT(update()), Qt::DirectConnection); Q_ASSERT(ok); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); FrameCounter counter; connect(&window, SIGNAL(frameSwapped()), &counter, SLOT(incr()), Qt::DirectConnection); int frameCount = 10; QElapsedTimer timer; timer.start(); if (blockedGui) { while (counter.count() < frameCount) QTest::qSleep(100); QVERIFY(counter.count() >= frameCount); } else { window.update(); QTRY_VERIFY(counter.count() > frameCount); } window.hide(); } void tst_qquickwindow::touchEvent_basic() { TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->setTitle(QTest::currentTestFunction()); window->resize(250, 250); window->setPosition(100, 100); window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); TestTouchItem *bottomItem = new TestTouchItem(window->contentItem()); bottomItem->setObjectName("Bottom Item"); bottomItem->setSize(QSizeF(150, 150)); TestTouchItem *middleItem = new TestTouchItem(bottomItem); middleItem->setObjectName("Middle Item"); middleItem->setPosition(QPointF(50, 50)); middleItem->setSize(QSizeF(150, 150)); TestTouchItem *topItem = new TestTouchItem(middleItem); topItem->setObjectName("Top Item"); topItem->setPosition(QPointF(50, 50)); topItem->setSize(QSizeF(150, 150)); QPointF pos(10, 10); QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, touchDevice, false); // press single point touchSeq.press(0, topItem->mapToScene(pos).toPoint(),window).commit(); QQuickTouchUtils::flush(window); QTRY_COMPARE(topItem->lastEvent.touchPoints.count(), 1); QVERIFY(middleItem->lastEvent.touchPoints.isEmpty()); QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty()); // At one point this was failing with kwin (KDE window manager) because window->setPosition(100, 100) // would put the decorated window at that position rather than the window itself. COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(topItem, pos))); topItem->reset(); touchSeq.release(0, topItem->mapToScene(pos).toPoint(), window).commit(); // press multiple points touchSeq.press(0, topItem->mapToScene(pos).toPoint(), window) .press(1, bottomItem->mapToScene(pos).toPoint(), window).commit(); QQuickTouchUtils::flush(window); QCOMPARE(topItem->lastEvent.touchPoints.count(), 1); QVERIFY(middleItem->lastEvent.touchPoints.isEmpty()); QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1); COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(topItem, pos))); COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos))); topItem->reset(); bottomItem->reset(); touchSeq.release(0, topItem->mapToScene(pos).toPoint(), window).release(1, bottomItem->mapToScene(pos).toPoint(), window).commit(); // touch point on top item moves to bottom item, but top item should still receive the event touchSeq.press(0, topItem->mapToScene(pos).toPoint(), window).commit(); QQuickTouchUtils::flush(window); touchSeq.move(0, bottomItem->mapToScene(pos).toPoint(), window).commit(); QQuickTouchUtils::flush(window); QCOMPARE(topItem->lastEvent.touchPoints.count(), 1); COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchUpdate, window, Qt::TouchPointMoved, makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos), pos))); topItem->reset(); touchSeq.release(0, bottomItem->mapToScene(pos).toPoint(), window).commit(); // touch point on bottom item moves to top item, but bottom item should still receive the event touchSeq.press(0, bottomItem->mapToScene(pos).toPoint(), window).commit(); QQuickTouchUtils::flush(window); touchSeq.move(0, topItem->mapToScene(pos).toPoint(), window).commit(); QQuickTouchUtils::flush(window); QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1); COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchUpdate, window, Qt::TouchPointMoved, makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos), pos))); bottomItem->reset(); touchSeq.release(0, bottomItem->mapToScene(pos).toPoint(), window).commit(); // a single stationary press on an item shouldn't cause an event touchSeq.press(0, topItem->mapToScene(pos).toPoint(), window).commit(); QQuickTouchUtils::flush(window); touchSeq.stationary(0) .press(1, bottomItem->mapToScene(pos).toPoint(), window).commit(); QQuickTouchUtils::flush(window); QCOMPARE(topItem->lastEvent.touchPoints.count(), 1); // received press only, not stationary QVERIFY(middleItem->lastEvent.touchPoints.isEmpty()); QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1); COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(topItem, pos))); COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos))); topItem->reset(); bottomItem->reset(); // cleanup: what is pressed must be released // Otherwise you will get an assertion failure: // ASSERT: "itemForTouchPointId.isEmpty()" in file items/qquickwindow.cpp touchSeq.release(0, pos.toPoint(), window).release(1, pos.toPoint(), window).commit(); QQuickTouchUtils::flush(window); // move touch point from top item to bottom, and release touchSeq.press(0, topItem->mapToScene(pos).toPoint(),window).commit(); QQuickTouchUtils::flush(window); touchSeq.release(0, bottomItem->mapToScene(pos).toPoint(),window).commit(); QQuickTouchUtils::flush(window); QCOMPARE(topItem->lastEvent.touchPoints.count(), 1); COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchEnd, window, Qt::TouchPointReleased, makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos), pos))); topItem->reset(); // release while another point is pressed touchSeq.press(0, topItem->mapToScene(pos).toPoint(),window) .press(1, bottomItem->mapToScene(pos).toPoint(), window).commit(); QQuickTouchUtils::flush(window); touchSeq.move(0, bottomItem->mapToScene(pos).toPoint(), window).commit(); QQuickTouchUtils::flush(window); touchSeq.release(0, bottomItem->mapToScene(pos).toPoint(), window) .stationary(1).commit(); QQuickTouchUtils::flush(window); QCOMPARE(topItem->lastEvent.touchPoints.count(), 1); QVERIFY(middleItem->lastEvent.touchPoints.isEmpty()); QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1); COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchEnd, window, Qt::TouchPointReleased, makeTouchPoint(topItem, topItem->mapFromItem(bottomItem, pos)))); COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(bottomItem, pos))); topItem->reset(); bottomItem->reset(); delete topItem; delete middleItem; delete bottomItem; } void tst_qquickwindow::touchEvent_propagation() { TestTouchItem::clearMouseEventCounters(); QFETCH(bool, acceptTouchEvents); QFETCH(bool, acceptMouseEvents); QFETCH(bool, enableItem); QFETCH(bool, showItem); QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->resize(250, 250); window->setPosition(100, 100); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); TestTouchItem *bottomItem = new TestTouchItem(window->contentItem()); bottomItem->setObjectName("Bottom Item"); bottomItem->setSize(QSizeF(150, 150)); TestTouchItem *middleItem = new TestTouchItem(bottomItem); middleItem->setObjectName("Middle Item"); middleItem->setPosition(QPointF(50, 50)); middleItem->setSize(QSizeF(150, 150)); TestTouchItem *topItem = new TestTouchItem(middleItem); topItem->setObjectName("Top Item"); topItem->setPosition(QPointF(50, 50)); topItem->setSize(QSizeF(150, 150)); QPointF pos(10, 10); QPoint pointInBottomItem = bottomItem->mapToScene(pos).toPoint(); // (10, 10) QPoint pointInMiddleItem = middleItem->mapToScene(pos).toPoint(); // (60, 60) overlaps with bottomItem QPoint pointInTopItem = topItem->mapToScene(pos).toPoint(); // (110, 110) overlaps with bottom & top items // disable topItem topItem->acceptTouchEvents = acceptTouchEvents; topItem->acceptMouseEvents = acceptMouseEvents; topItem->setEnabled(enableItem); topItem->setVisible(showItem); // single touch to top item, should be received by middle item QTest::touchEvent(window, touchDevice).press(0, pointInTopItem, window); QTRY_COMPARE(middleItem->lastEvent.touchPoints.count(), 1); QVERIFY(topItem->lastEvent.touchPoints.isEmpty()); QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty()); COMPARE_TOUCH_DATA(middleItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(middleItem, middleItem->mapFromItem(topItem, pos)))); QTest::touchEvent(window, touchDevice).release(0, pointInTopItem, window); // touch top and middle items, middle item should get both events QTest::touchEvent(window, touchDevice).press(0, pointInTopItem, window) .press(1, pointInMiddleItem, window); QTRY_COMPARE(middleItem->lastEvent.touchPoints.count(), 2); QVERIFY(topItem->lastEvent.touchPoints.isEmpty()); QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty()); COMPARE_TOUCH_DATA(middleItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, (QList() << makeTouchPoint(middleItem, middleItem->mapFromItem(topItem, pos)) << makeTouchPoint(middleItem, pos) ))); QTest::touchEvent(window, touchDevice).release(0, pointInTopItem, window) .release(1, pointInMiddleItem, window); middleItem->reset(); // disable middleItem as well middleItem->acceptTouchEvents = acceptTouchEvents; middleItem->acceptMouseEvents = acceptMouseEvents; middleItem->setEnabled(enableItem); middleItem->setVisible(showItem); // touch top and middle items, bottom item should get all events QTest::touchEvent(window, touchDevice).press(0, pointInTopItem, window) .press(1, pointInMiddleItem, window); QTRY_COMPARE(bottomItem->lastEvent.touchPoints.count(), 2); QVERIFY(topItem->lastEvent.touchPoints.isEmpty()); QVERIFY(middleItem->lastEvent.touchPoints.isEmpty()); COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, (QList() << makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos)) << makeTouchPoint(bottomItem, bottomItem->mapFromItem(middleItem, pos)) ))); bottomItem->reset(); // disable bottom item as well bottomItem->acceptTouchEvents = acceptTouchEvents; bottomItem->setEnabled(enableItem); bottomItem->setVisible(showItem); QTest::touchEvent(window, touchDevice).release(0, pointInTopItem, window) .release(1, pointInMiddleItem, window); // no events should be received QTest::touchEvent(window, touchDevice).press(0, pointInTopItem, window) .press(1, pointInMiddleItem, window) .press(2, pointInBottomItem, window); QTest::qWait(50); QVERIFY(topItem->lastEvent.touchPoints.isEmpty()); QVERIFY(middleItem->lastEvent.touchPoints.isEmpty()); QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty()); QTest::touchEvent(window, touchDevice).release(0, pointInTopItem, window) .release(1, pointInMiddleItem, window) .release(2, pointInBottomItem, window); topItem->reset(); middleItem->reset(); bottomItem->reset(); // disable middle item, touch on top item middleItem->acceptTouchEvents = acceptTouchEvents; middleItem->setEnabled(enableItem); middleItem->setVisible(showItem); QTest::touchEvent(window, touchDevice).press(0, pointInTopItem, window); QTest::qWait(50); if (!enableItem || !showItem) { // middle item is disabled or has 0 opacity, bottom item receives the event QVERIFY(topItem->lastEvent.touchPoints.isEmpty()); QVERIFY(middleItem->lastEvent.touchPoints.isEmpty()); QCOMPARE(bottomItem->lastEvent.touchPoints.count(), 1); COMPARE_TOUCH_DATA(bottomItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(bottomItem, bottomItem->mapFromItem(topItem, pos)))); } else { // middle item ignores event, sends it to the top item (top-most child) QCOMPARE(topItem->lastEvent.touchPoints.count(), 1); QVERIFY(middleItem->lastEvent.touchPoints.isEmpty()); QVERIFY(bottomItem->lastEvent.touchPoints.isEmpty()); COMPARE_TOUCH_DATA(topItem->lastEvent, makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(topItem, pos))); } QTest::touchEvent(window, touchDevice).release(0, pointInTopItem, window); delete topItem; delete middleItem; delete bottomItem; } void tst_qquickwindow::touchEvent_propagation_data() { QTest::addColumn("acceptTouchEvents"); QTest::addColumn("acceptMouseEvents"); QTest::addColumn("enableItem"); QTest::addColumn("showItem"); QTest::newRow("disable events") << false << false << true << true; QTest::newRow("disable item") << true << true << false << true; QTest::newRow("hide item") << true << true << true << false; } void tst_qquickwindow::touchEvent_cancel() { TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->resize(250, 250); window->setPosition(100, 100); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); TestTouchItem *item = new TestTouchItem(window->contentItem()); item->setPosition(QPointF(50, 50)); item->setSize(QSizeF(150, 150)); QPointF pos(50, 50); QTest::touchEvent(window, touchDevice).press(0, item->mapToScene(pos).toPoint(), window); QCoreApplication::processEvents(); QTRY_COMPARE(item->lastEvent.touchPoints.count(), 1); TouchEventData d = makeTouchData(QEvent::TouchBegin, window, Qt::TouchPointPressed, makeTouchPoint(item, pos)); COMPARE_TOUCH_DATA(item->lastEvent, d); item->reset(); QWindowSystemInterface::handleTouchCancelEvent(nullptr, touchDevice); QCoreApplication::processEvents(); d = makeTouchData(QEvent::TouchCancel, window); COMPARE_TOUCH_DATA(item->lastEvent, d); delete item; } void tst_qquickwindow::touchEvent_cancelClearsMouseGrab() { TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->resize(250, 250); window->setPosition(100, 100); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); TestTouchItem *item = new TestTouchItem(window->contentItem()); item->setPosition(QPointF(50, 50)); item->setSize(QSizeF(150, 150)); item->acceptMouseEvents = true; item->acceptTouchEvents = false; QPointF pos(50, 50); QTest::touchEvent(window, touchDevice).press(0, item->mapToScene(pos).toPoint(), window); QCoreApplication::processEvents(); QTRY_COMPARE(item->mousePressCount, 1); QTRY_COMPARE(item->mouseUngrabEventCount, 0); QWindowSystemInterface::handleTouchCancelEvent(nullptr, touchDevice); QCoreApplication::processEvents(); QTRY_COMPARE(item->mouseUngrabEventCount, 1); } void tst_qquickwindow::touchEvent_reentrant() { TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->resize(250, 250); window->setPosition(100, 100); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); TestTouchItem *item = new TestTouchItem(window->contentItem()); item->spinLoopWhenPressed = true; // will call processEvents() from the touch handler item->setPosition(QPointF(50, 50)); item->setSize(QSizeF(150, 150)); QPointF pos(60, 60); // None of these should commit from the dtor. QTest::QTouchEventSequence press = QTest::touchEvent(window, touchDevice, false).press(0, pos.toPoint(), window); pos += QPointF(2, 2); QTest::QTouchEventSequence move = QTest::touchEvent(window, touchDevice, false).move(0, pos.toPoint(), window); QTest::QTouchEventSequence release = QTest::touchEvent(window, touchDevice, false).release(0, pos.toPoint(), window); // Now commit (i.e. call QWindowSystemInterface::handleTouchEvent), but do not process the events yet. press.commit(false); move.commit(false); release.commit(false); QCoreApplication::processEvents(); QTRY_COMPARE(item->touchEventCount, 3); delete item; } void tst_qquickwindow::touchEvent_velocity() { TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->resize(250, 250); window->setPosition(100, 100); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); QTest::qWait(10); TestTouchItem *item = new TestTouchItem(window->contentItem()); item->setPosition(QPointF(50, 50)); item->setSize(QSizeF(150, 150)); QList points; QTouchEvent::TouchPoint tp; tp.setId(1); tp.setState(Qt::TouchPointPressed); const QPointF localPos = item->mapToScene(QPointF(10, 10)); const QPointF screenPos = window->mapToGlobal(localPos.toPoint()); tp.setPos(localPos); tp.setScreenPos(screenPos); tp.setEllipseDiameters(QSizeF(4, 4)); points << tp; QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity, QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window)); QGuiApplication::processEvents(); QQuickTouchUtils::flush(window); QCOMPARE(item->touchEventCount, 1); points[0].setState(Qt::TouchPointMoved); points[0].setPos(localPos + QPointF(5, 5)); points[0].setScreenPos(screenPos + QPointF(5, 5)); QVector2D velocity(1.5, 2.5); points[0].setVelocity(velocity); QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity, QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window)); QGuiApplication::processEvents(); QQuickTouchUtils::flush(window); QCOMPARE(item->touchEventCount, 2); QCOMPARE(item->lastEvent.touchPoints.count(), 1); QCOMPARE(item->lastVelocity, velocity); // Now have a transformation on the item and check if velocity and position are transformed accordingly. item->setRotation(90); // clockwise QMatrix4x4 transformMatrix; transformMatrix.rotate(-90, 0, 0, 1); // counterclockwise QVector2D transformedVelocity = transformMatrix.mapVector(velocity).toVector2D(); points[0].setPos(points[0].pos() + QPointF(5, 5)); points[0].setScreenPos(points[0].screenPos() + QPointF(5, 5)); QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity, QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window)); QGuiApplication::processEvents(); QQuickTouchUtils::flush(window); QCOMPARE(item->lastVelocity, transformedVelocity); QPoint itemLocalPos = item->mapFromScene(points[0].pos()).toPoint(); QPoint itemLocalPosFromEvent = item->lastEvent.touchPoints[0].pos().toPoint(); QCOMPARE(itemLocalPos, itemLocalPosFromEvent); points[0].setState(Qt::TouchPointReleased); QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity, QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window)); QGuiApplication::processEvents(); QQuickTouchUtils::flush(window); delete item; } void tst_qquickwindow::mergeTouchPointLists_data() { QTest::addColumn>("list1"); QTest::addColumn>("list2"); QTest::addColumn>("expected"); QTest::addColumn("showItem"); // FIXME: do not leak all these items auto item1 = new QQuickItem(); auto item2 = new QQuickItem(); auto item3 = new QQuickItem(); auto item4 = new QQuickItem(); auto item5 = new QQuickItem(); QTest::newRow("empty") << QVector() << QVector() << QVector(); QTest::newRow("single list left") << (QVector() << item1 << item2 << item3) << QVector() << (QVector() << item1 << item2 << item3); QTest::newRow("single list right") << QVector() << (QVector() << item1 << item2 << item3) << (QVector() << item1 << item2 << item3); QTest::newRow("two lists identical") << (QVector() << item1 << item2 << item3) << (QVector() << item1 << item2 << item3) << (QVector() << item1 << item2 << item3); QTest::newRow("two lists 1") << (QVector() << item1 << item2 << item5) << (QVector() << item3 << item4 << item5) << (QVector() << item1 << item2 << item3 << item4 << item5); QTest::newRow("two lists 2") << (QVector() << item1 << item2 << item5) << (QVector() << item3 << item4 << item5) << (QVector() << item1 << item2 << item3 << item4 << item5); QTest::newRow("two lists 3") << (QVector() << item1 << item2 << item3) << (QVector() << item1 << item4 << item5) << (QVector() << item1 << item2 << item3 << item4 << item5); QTest::newRow("two lists 4") << (QVector() << item1 << item3 << item4) << (QVector() << item2 << item3 << item5) << (QVector() << item1 << item2 << item3 << item4 << item5); QTest::newRow("two lists 5") << (QVector() << item1 << item2 << item4) << (QVector() << item1 << item3 << item4) << (QVector() << item1 << item2 << item3 << item4); } void tst_qquickwindow::mergeTouchPointLists() { QFETCH(QVector, list1); QFETCH(QVector, list2); QFETCH(QVector, expected); QQuickWindow win; auto windowPrivate = QQuickWindowPrivate::get(&win); auto targetList = windowPrivate->mergePointerTargets(list1, list2); QCOMPARE(targetList, expected); } void tst_qquickwindow::mouseFromTouch_basic() { // Turn off accepting touch events with acceptTouchEvents. This // should result in sending mouse events generated from the touch // with the new event propagation system. TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->resize(250, 250); window->setPosition(100, 100); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); QTest::qWait(10); TestTouchItem *item = new TestTouchItem(window->contentItem()); item->setPosition(QPointF(50, 50)); item->setSize(QSizeF(150, 150)); item->acceptTouchEvents = false; QList points; QTouchEvent::TouchPoint tp; tp.setId(1); tp.setState(Qt::TouchPointPressed); const QPointF localPos = item->mapToScene(QPointF(10, 10)); const QPointF screenPos = window->mapToGlobal(localPos.toPoint()); tp.setPos(localPos); tp.setScreenPos(screenPos); tp.setEllipseDiameters(QSizeF(4, 4)); points << tp; QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity, QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window)); QGuiApplication::processEvents(); QQuickTouchUtils::flush(window); points[0].setState(Qt::TouchPointMoved); points[0].setPos(localPos + QPointF(5, 5)); points[0].setScreenPos(screenPos + QPointF(5, 5)); QVector2D velocity(1.5, 2.5); points[0].setVelocity(velocity); QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity, QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window)); QGuiApplication::processEvents(); QQuickTouchUtils::flush(window); points[0].setState(Qt::TouchPointReleased); QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity, QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window)); QGuiApplication::processEvents(); QQuickTouchUtils::flush(window); // The item should have received a mouse press, move, and release. QCOMPARE(item->mousePressNum, 1); QCOMPARE(item->mouseMoveNum, 1); QCOMPARE(item->mouseReleaseNum, 1); QCOMPARE(item->lastMousePos.toPoint(), item->mapFromScene(points[0].pos()).toPoint()); QCOMPARE(item->lastVelocityFromMouseMove, velocity); QVERIFY((item->lastMouseCapabilityFlags & QTouchDevice::Velocity) != 0); // Now the same with a transformation. item->setRotation(90); // clockwise QMatrix4x4 transformMatrix; transformMatrix.rotate(-90, 0, 0, 1); // counterclockwise QVector2D transformedVelocity = transformMatrix.mapVector(velocity).toVector2D(); points[0].setState(Qt::TouchPointPressed); points[0].setVelocity(velocity); tp.setPos(localPos); tp.setScreenPos(screenPos); QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity, QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window)); QGuiApplication::processEvents(); QQuickTouchUtils::flush(window); points[0].setState(Qt::TouchPointMoved); points[0].setPos(localPos + QPointF(5, 5)); points[0].setScreenPos(screenPos + QPointF(5, 5)); QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity, QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window)); QGuiApplication::processEvents(); QQuickTouchUtils::flush(window); QCOMPARE(item->lastMousePos.toPoint(), item->mapFromScene(points[0].pos()).toPoint()); QCOMPARE(item->lastVelocityFromMouseMove, transformedVelocity); points[0].setState(Qt::TouchPointReleased); QWindowSystemInterface::handleTouchEvent(window, touchDeviceWithVelocity, QWindowSystemInterfacePrivate::toNativeTouchPoints(points, window)); QCoreApplication::processEvents(); QQuickTouchUtils::flush(window); delete item; } void tst_qquickwindow::synthMouseFromTouch_data() { QTest::addColumn("synthMouse"); // AA_SynthesizeMouseForUnhandledTouchEvents QTest::addColumn("acceptTouch"); // QQuickItem::touchEvent: setAccepted() QTest::newRow("no synth, accept") << false << true; // suitable for touch-capable UIs QTest::newRow("no synth, don't accept") << false << false; QTest::newRow("synth and accept") << true << true; QTest::newRow("synth, don't accept") << true << false; // the default } void tst_qquickwindow::synthMouseFromTouch() { QFETCH(bool, synthMouse); QFETCH(bool, acceptTouch); QCoreApplication::setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, synthMouse); QScopedPointer window(new MouseRecordingWindow); QScopedPointer item(new MouseRecordingItem(acceptTouch, nullptr)); item->setParentItem(window->contentItem()); window->resize(250, 250); window->setPosition(100, 100); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowActive(window.data())); QPoint p1 = QPoint(20, 20); QPoint p2 = QPoint(30, 30); QTest::touchEvent(window.data(), touchDevice).press(0, p1, window.data()); QTest::touchEvent(window.data(), touchDevice).move(0, p2, window.data()); QTest::touchEvent(window.data(), touchDevice).release(0, p2, window.data()); QCOMPARE(item->m_touchEvents.count(), !synthMouse && !acceptTouch ? 1 : 3); QCOMPARE(item->m_mouseEvents.count(), (acceptTouch || !synthMouse) ? 0 : 3); QCOMPARE(window->m_mouseEvents.count(), 0); for (const QMouseEvent &ev : item->m_mouseEvents) QCOMPARE(ev.source(), Qt::MouseEventSynthesizedByQt); } void tst_qquickwindow::clearWindow() { QQuickWindow *window = new QQuickWindow; window->setTitle(QTest::currentTestFunction()); QQuickItem *item = new QQuickItem; item->setParentItem(window->contentItem()); QCOMPARE(item->window(), window); delete window; QVERIFY(!item->window()); delete item; } void tst_qquickwindow::mouseFiltering() { TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->resize(250, 250); window->setPosition(100, 100); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); TestTouchItem *bottomItem = new TestTouchItem(window->contentItem()); bottomItem->setObjectName("Bottom Item"); bottomItem->setSize(QSizeF(150, 150)); TestTouchItem *siblingItem = new TestTouchItem(bottomItem); siblingItem->setObjectName("Sibling of Middle Item"); siblingItem->setPosition(QPointF(90, 25)); siblingItem->setSize(QSizeF(150, 150)); TestTouchItem *middleItem = new TestTouchItem(bottomItem); middleItem->setObjectName("Middle Item"); middleItem->setPosition(QPointF(50, 50)); middleItem->setSize(QSizeF(150, 150)); TestTouchItem *topItem = new TestTouchItem(middleItem); topItem->setObjectName("Top Item"); topItem->setPosition(QPointF(50, 50)); topItem->setSize(QSizeF(150, 150)); QPoint pos(100, 100); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, pos); // Mouse filtering propagates down the stack, so the // correct order is // 1. middleItem filters event // 2. bottomItem filters event // 3. topItem receives event QTRY_COMPARE(middleItem->mousePressCount, 1); QTRY_COMPARE(bottomItem->mousePressCount, 2); QTRY_COMPARE(topItem->mousePressCount, 3); QCOMPARE(siblingItem->mousePressCount, 0); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, pos); topItem->clearMouseEventCounters(); middleItem->clearMouseEventCounters(); bottomItem->clearMouseEventCounters(); siblingItem->clearMouseEventCounters(); // Repeat, but this time have the top item accept the press topItem->acceptMouseEvents = true; QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, pos); // Mouse filtering propagates down the stack, so the // correct order is // 1. middleItem filters event // 2. bottomItem filters event // 3. topItem receives event QTRY_COMPARE(middleItem->mousePressCount, 1); QTRY_COMPARE(bottomItem->mousePressCount, 2); QTRY_COMPARE(topItem->mousePressCount, 3); QCOMPARE(siblingItem->mousePressCount, 0); pos += QPoint(50, 50); QTest::mouseMove(window, pos); // The top item has grabbed, so the move goes there, but again // all the ancestors can filter, even when the mouse is outside their bounds QTRY_COMPARE(middleItem->mouseMoveCount, 1); QTRY_COMPARE(bottomItem->mouseMoveCount, 2); QTRY_COMPARE(topItem->mouseMoveCount, 3); QCOMPARE(siblingItem->mouseMoveCount, 0); // clean up mouse press state for the next tests QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, pos); } void tst_qquickwindow::qmlCreation() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("window.qml")); QObject *created = component.create(); QScopedPointer cleanup(created); QVERIFY(created); QQuickWindow *window = qobject_cast(created); QVERIFY(window); QCOMPARE(window->color(), QColor(Qt::green)); QQuickItem *item = window->findChild("item"); QVERIFY(item); QCOMPARE(item->window(), window); } void tst_qquickwindow::qmlCreationWithScreen() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("windowWithScreen.qml")); QObject *created = component.create(); QScopedPointer cleanup(created); QVERIFY(created); QQuickWindow *window = qobject_cast(created); QVERIFY(window); QCOMPARE(window->color(), QColor(Qt::green)); QQuickItem *item = window->findChild("item"); QVERIFY(item); QCOMPARE(item->window(), window); } void tst_qquickwindow::clearColor() { //::grab examines rendering to make sure it works visually QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->resize(250, 250); window->setPosition(100, 100); window->setColor(Qt::blue); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); QCOMPARE(window->color(), QColor(Qt::blue)); } void tst_qquickwindow::defaultState() { QQmlEngine engine; QQmlComponent component(&engine); component.setData("import QtQuick 2.0; import QtQuick.Window 2.1; Window { }", QUrl()); QObject *created = component.create(); QScopedPointer cleanup(created); QVERIFY(created); QQuickWindow *qmlWindow = qobject_cast(created); QVERIFY(qmlWindow); qmlWindow->setTitle(QTest::currentTestFunction()); QQuickWindow cppWindow; cppWindow.show(); QVERIFY(QTest::qWaitForWindowExposed(&cppWindow)); QCOMPARE(qmlWindow->windowState(), cppWindow.windowState()); } void tst_qquickwindow::grab_data() { QTest::addColumn("visible"); QTest::addColumn("alpha"); QTest::newRow("visible,opaque") << true << false; QTest::newRow("invisible,opaque") << false << false; QTest::newRow("visible,transparent") << true << true; QTest::newRow("invisible,transparent") << false << true; } void tst_qquickwindow::grab() { if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QSKIP("Skipping due to grabWindow not functional on offscreen/minimimal platforms"); QFETCH(bool, visible); QFETCH(bool, alpha); QQuickWindow window; window.setTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1Char(' ') + QLatin1String(QTest::currentDataTag())); if (alpha) { window.setColor(QColor(0, 0, 0, 0)); } else { window.setColor(Qt::red); } window.resize(250, 250); if (visible) { window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); } else { window.create(); } QImage content = window.grabWindow(); QCOMPARE(content.width(), int(window.width() * window.devicePixelRatio())); QCOMPARE(content.height(), int(window.height() * window.devicePixelRatio())); if (alpha) { QCOMPARE((uint) content.convertToFormat(QImage::Format_ARGB32_Premultiplied).pixel(0, 0), (uint) 0x00000000); } else { QCOMPARE((uint) content.convertToFormat(QImage::Format_RGB32).pixel(0, 0), (uint) 0xffff0000); } } void tst_qquickwindow::multipleWindows() { QList windows; QScopedPointer cleanup[6]; for (int i=0; i<6; ++i) { QQuickWindow *c = new QQuickWindow(); c->setTitle(QLatin1String(QTest::currentTestFunction()) + QString::number(i)); c->setColor(Qt::GlobalColor(Qt::red + i)); c->resize(300, 200); c->setPosition(100 + i * 30, 100 + i * 20); c->show(); windows << c; cleanup[i].reset(c); QVERIFY(QTest::qWaitForWindowExposed(c)); } // move them for (int i=0; isetPosition(100 + i * 30, 100 + i * 20 + 100); } // resize them for (int i=0; iresize(200, 150); } } void tst_qquickwindow::animationsWhileHidden() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("AnimationsWhileHidden.qml")); QObject *created = component.create(); QScopedPointer cleanup(created); QQuickWindow *window = qobject_cast(created); QVERIFY(window); window->setTitle(QTest::currentTestFunction()); QVERIFY(window->isVisible()); // Now hide the window and verify that it went off screen window->hide(); QTest::qWait(10); QVERIFY(!window->isVisible()); // Running animaiton should cause it to become visible again shortly. QTRY_VERIFY(window->isVisible()); } void tst_qquickwindow::headless() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("Headless.qml")); QObject *created = component.create(); QScopedPointer cleanup(created); QQuickWindow *window = qobject_cast(created); window->setPersistentOpenGLContext(false); window->setPersistentSceneGraph(false); QVERIFY(window); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); QVERIFY(window->isVisible()); const bool threaded = QQuickWindowPrivate::get(window)->context->thread() != QThread::currentThread(); QSignalSpy initialized(window, SIGNAL(sceneGraphInitialized())); QSignalSpy invalidated(window, SIGNAL(sceneGraphInvalidated())); // Verify that the window is alive and kicking QVERIFY(window->isSceneGraphInitialized()); const bool isGL = window->rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL; // Store the visual result QImage originalContent = window->grabWindow(); // Hide the window and verify signal emittion and GL context deletion window->hide(); window->releaseResources(); if (threaded) { QTRY_VERIFY(invalidated.size() >= 1); if (isGL) QVERIFY(!window->isSceneGraphInitialized()); } // Destroy the native windowing system buffers window->destroy(); QVERIFY(!window->handle()); // Show and verify that we are back and running window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); if (threaded) QTRY_COMPARE(initialized.size(), 1); QVERIFY(window->isSceneGraphInitialized()); // Verify that the visual output is the same QImage newContent = window->grabWindow(); QString errorMessage; QVERIFY2(QQuickVisualTestUtil::compareImages(newContent, originalContent, &errorMessage), qPrintable(errorMessage)); } void tst_qquickwindow::noUpdateWhenNothingChanges() { QQuickWindow window; window.setTitle(QTest::currentTestFunction()); window.setGeometry(100, 100, 300, 200); QQuickRectangle rect(window.contentItem()); window.showNormal(); QVERIFY(QTest::qWaitForWindowExposed(&window)); // Many platforms are broken in the sense that that they follow up // the initial expose with a second expose or more. Let these go // through before we let the test continue. QTest::qWait(100); if (QQuickWindowPrivate::get(&window)->context->thread() == QGuiApplication::instance()->thread()) { QSKIP("Only threaded renderloop implements this feature"); return; } QSignalSpy spy(&window, SIGNAL(frameSwapped())); rect.update(); // Wait a while and verify that no more frameSwapped come our way. QTest::qWait(100); QCOMPARE(spy.size(), 0); } void tst_qquickwindow::focusObject() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("focus.qml")); QObject *created = component.create(); QScopedPointer cleanup(created); QVERIFY(created); QQuickWindow *window = qobject_cast(created); QVERIFY(window); window->setTitle(QTest::currentTestFunction()); QSignalSpy focusObjectSpy(window, SIGNAL(focusObjectChanged(QObject*))); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); window->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(window)); QCOMPARE(window->contentItem(), window->focusObject()); QCOMPARE(focusObjectSpy.count(), 1); QQuickItem *item1 = window->findChild("item1"); QVERIFY(item1); item1->setFocus(true); QCOMPARE(item1, window->focusObject()); QCOMPARE(focusObjectSpy.count(), 2); QQuickItem *item2 = window->findChild("item2"); QVERIFY(item2); item2->setFocus(true); QCOMPARE(item2, window->focusObject()); QCOMPARE(focusObjectSpy.count(), 3); // set focus for item in non-focused focus scope and // ensure focusObject does not change and signal is not emitted QQuickItem *item3 = window->findChild("item3"); QVERIFY(item3); item3->setFocus(true); QCOMPARE(item2, window->focusObject()); QCOMPARE(focusObjectSpy.count(), 3); } void tst_qquickwindow::focusReason() { QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->resize(200, 200); window->show(); window->setTitle(QTest::currentTestFunction()); QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickItem *firstItem = new QQuickItem; firstItem->setSize(QSizeF(100, 100)); firstItem->setParentItem(window->contentItem()); QQuickItem *secondItem = new QQuickItem; secondItem->setSize(QSizeF(100, 100)); secondItem->setParentItem(window->contentItem()); firstItem->forceActiveFocus(Qt::OtherFocusReason); QCOMPARE(QQuickWindowPrivate::get(window)->lastFocusReason, Qt::OtherFocusReason); secondItem->forceActiveFocus(Qt::TabFocusReason); QCOMPARE(QQuickWindowPrivate::get(window)->lastFocusReason, Qt::TabFocusReason); firstItem->forceActiveFocus(Qt::BacktabFocusReason); QCOMPARE(QQuickWindowPrivate::get(window)->lastFocusReason, Qt::BacktabFocusReason); } void tst_qquickwindow::ignoreUnhandledMouseEvents() { QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->setTitle(QTest::currentTestFunction()); window->resize(100, 100); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); QQuickItem *item = new QQuickItem; item->setSize(QSizeF(100, 100)); item->setParentItem(window->contentItem()); { QMouseEvent me(QEvent::MouseButtonPress, QPointF(50, 50), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); me.setAccepted(true); QVERIFY(QCoreApplication::sendEvent(window, &me)); QVERIFY(!me.isAccepted()); } { QMouseEvent me(QEvent::MouseMove, QPointF(51, 51), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); me.setAccepted(true); QVERIFY(QCoreApplication::sendEvent(window, &me)); QVERIFY(!me.isAccepted()); } { QMouseEvent me(QEvent::MouseButtonRelease, QPointF(51, 51), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); me.setAccepted(true); QVERIFY(QCoreApplication::sendEvent(window, &me)); QVERIFY(!me.isAccepted()); } } void tst_qquickwindow::ownershipRootItem() { qmlRegisterType("Test", 1, 0, "RootItemAccessor"); QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("ownershipRootItem.qml")); QObject *created = component.create(); QScopedPointer cleanup(created); QQuickWindow *window = qobject_cast(created); QVERIFY(window); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); RootItemAccessor* accessor = window->findChild("accessor"); QVERIFY(accessor); engine.collectGarbage(); QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete); QCoreApplication::processEvents(); QVERIFY(!accessor->isRootItemDestroyed()); } #if QT_CONFIG(cursor) void tst_qquickwindow::cursor() { QQuickWindow window; window.setTitle(QTest::currentTestFunction()); window.setFramePosition(QGuiApplication::primaryScreen()->availableGeometry().topLeft() + QPoint(50, 50)); window.resize(320, 290); QQuickItem parentItem; parentItem.setPosition(QPointF(0, 0)); parentItem.setSize(QSizeF(180, 180)); parentItem.setParentItem(window.contentItem()); QQuickItem childItem; childItem.setPosition(QPointF(60, 90)); childItem.setSize(QSizeF(120, 120)); childItem.setParentItem(&parentItem); QQuickItem clippingItem; clippingItem.setPosition(QPointF(120, 120)); clippingItem.setSize(QSizeF(180, 180)); clippingItem.setClip(true); clippingItem.setParentItem(window.contentItem()); QQuickItem clippedItem; clippedItem.setPosition(QPointF(-30, -30)); clippedItem.setSize(QSizeF(120, 120)); clippedItem.setParentItem(&clippingItem); window.show(); QVERIFY(QTest::qWaitForWindowActive(&window)); // Position the cursor over the parent and child item and the clipped section of clippedItem. QTest::mouseMove(&window, QPoint(100, 100)); // No items cursors, window cursor is the default arrow. QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); // The section of clippedItem under the cursor is clipped, and so doesn't affect the window cursor. clippedItem.setCursor(Qt::ForbiddenCursor); QCOMPARE(clippedItem.cursor().shape(), Qt::ForbiddenCursor); QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); // parentItem is under the cursor, so the window cursor is changed. parentItem.setCursor(Qt::IBeamCursor); QCOMPARE(parentItem.cursor().shape(), Qt::IBeamCursor); QCOMPARE(window.cursor().shape(), Qt::IBeamCursor); // childItem is under the cursor and is in front of its parent, so the window cursor is changed. childItem.setCursor(Qt::WaitCursor); QCOMPARE(childItem.cursor().shape(), Qt::WaitCursor); QCOMPARE(window.cursor().shape(), Qt::WaitCursor); childItem.setCursor(Qt::PointingHandCursor); QCOMPARE(childItem.cursor().shape(), Qt::PointingHandCursor); QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor); // childItem is the current cursor item, so this has no effect on the window cursor. parentItem.unsetCursor(); QCOMPARE(parentItem.cursor().shape(), Qt::ArrowCursor); QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor); parentItem.setCursor(Qt::IBeamCursor); QCOMPARE(parentItem.cursor().shape(), Qt::IBeamCursor); QCOMPARE(window.cursor().shape(), Qt::PointingHandCursor); // With the childItem cursor cleared, parentItem is now foremost. childItem.unsetCursor(); QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor); QCOMPARE(window.cursor().shape(), Qt::IBeamCursor); // Setting the childItem cursor to the default still takes precedence over parentItem. childItem.setCursor(Qt::ArrowCursor); QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor); QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); childItem.setCursor(Qt::WaitCursor); QCOMPARE(childItem.cursor().shape(), Qt::WaitCursor); QCOMPARE(window.cursor().shape(), Qt::WaitCursor); // Move the cursor so it is over just parentItem. QTest::mouseMove(&window, QPoint(20, 20)); QCOMPARE(window.cursor().shape(), Qt::IBeamCursor); // Move the cursor so that is over all items, clippedItem wins because its a child of // clippingItem which is in from of parentItem in painting order. QTest::mouseMove(&window, QPoint(125, 125)); QCOMPARE(window.cursor().shape(), Qt::ForbiddenCursor); // Over clippingItem only, so no cursor. QTest::mouseMove(&window, QPoint(200, 280)); QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); // Over no item, so no cursor. QTest::mouseMove(&window, QPoint(10, 280)); QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); // back to the start. QTest::mouseMove(&window, QPoint(100, 100)); QCOMPARE(window.cursor().shape(), Qt::WaitCursor); // Try with the mouse pressed. QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100)); QTest::mouseMove(&window, QPoint(20, 20)); QCOMPARE(window.cursor().shape(), Qt::IBeamCursor); QTest::mouseMove(&window, QPoint(125, 125)); QCOMPARE(window.cursor().shape(), Qt::ForbiddenCursor); QTest::mouseMove(&window, QPoint(200, 280)); QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); QTest::mouseMove(&window, QPoint(10, 280)); QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); QTest::mouseMove(&window, QPoint(100, 100)); QCOMPARE(window.cursor().shape(), Qt::WaitCursor); QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 100)); // Remove the cursor item from the scene. Theoretically this should make parentItem the // cursorItem, but given the situation will correct itself after the next mouse move it // simply unsets the window cursor for now. childItem.setParentItem(nullptr); QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); parentItem.setCursor(Qt::SizeAllCursor); QCOMPARE(parentItem.cursor().shape(), Qt::SizeAllCursor); QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); // Changing the cursor of an un-parented item doesn't affect the window's cursor. childItem.setCursor(Qt::ClosedHandCursor); QCOMPARE(childItem.cursor().shape(), Qt::ClosedHandCursor); QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); childItem.unsetCursor(); QCOMPARE(childItem.cursor().shape(), Qt::ArrowCursor); QCOMPARE(window.cursor().shape(), Qt::ArrowCursor); QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, QPoint(100, 101)); QCOMPARE(window.cursor().shape(), Qt::SizeAllCursor); } #endif void tst_qquickwindow::hideThenDelete_data() { QTest::addColumn("persistentSG"); QTest::addColumn("persistentGL"); QTest::newRow("persistent:SG=false,GL=false") << false << false; QTest::newRow("persistent:SG=true,GL=false") << true << false; QTest::newRow("persistent:SG=false,GL=true") << false << true; QTest::newRow("persistent:SG=true,GL=true") << true << true; } void tst_qquickwindow::hideThenDelete() { QFETCH(bool, persistentSG); QFETCH(bool, persistentGL); QSignalSpy *openglDestroyed = nullptr; QSignalSpy *sgInvalidated = nullptr; { QQuickWindow window; window.setTitle(QLatin1String(QTest::currentTestFunction()) + QLatin1Char(' ') + QLatin1String(QTest::currentDataTag())); window.setColor(Qt::red); window.setPersistentSceneGraph(persistentSG); window.setPersistentOpenGLContext(persistentGL); window.resize(400, 300); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); const bool threaded = QQuickWindowPrivate::get(&window)->context->thread() != QGuiApplication::instance()->thread(); const bool isGL = window.rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL; #if QT_CONFIG(opengl) if (isGL) openglDestroyed = new QSignalSpy(window.openglContext(), SIGNAL(aboutToBeDestroyed())); #endif sgInvalidated = new QSignalSpy(&window, SIGNAL(sceneGraphInvalidated())); window.hide(); QTRY_VERIFY(!window.isExposed()); if (threaded) { if (!isGL) QSKIP("Skipping persistency verification due to not running with OpenGL"); if (!persistentSG) { QVERIFY(sgInvalidated->size() > 0); if (!persistentGL) QVERIFY(openglDestroyed->size() > 0); else QCOMPARE(openglDestroyed->size(), 0); } else { QCOMPARE(sgInvalidated->size(), 0); QCOMPARE(openglDestroyed->size(), 0); } } } QVERIFY(sgInvalidated->size() > 0); #if QT_CONFIG(opengl) if (openglDestroyed) QVERIFY(openglDestroyed->size() > 0); #endif } void tst_qquickwindow::showHideAnimate() { // This test tries to mimick a bug triggered in the qquickanimatedimage test // A window is shown, then removed again before it is exposed. This left // traces in the render loop which prevent other animations from running // later on. { QQuickWindow window; window.resize(400, 300); window.show(); } QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("showHideAnimate.qml")); QQuickItem* created = qobject_cast(component.create()); QVERIFY(created); QTRY_VERIFY(created->opacity() > 0.5); QTRY_VERIFY(created->opacity() < 0.5); } void tst_qquickwindow::testExpose() { QQuickWindow window; window.setTitle(QTest::currentTestFunction()); window.setGeometry(100, 100, 300, 200); window.show(); QTRY_VERIFY(window.isExposed()); QSignalSpy swapSpy(&window, SIGNAL(frameSwapped())); // exhaust pending exposes, as some platforms send us plenty // while showing the first time QTest::qWait(1000); while (swapSpy.size() != 0) { swapSpy.clear(); QTest::qWait(100); } QWindowSystemInterface::handleExposeEvent(&window, QRegion(10, 10, 20, 20)); QTRY_COMPARE(swapSpy.size(), 1); } void tst_qquickwindow::requestActivate() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("active.qml")); QScopedPointer window1(qobject_cast(component.create())); QVERIFY(!window1.isNull()); window1->setTitle(QTest::currentTestFunction()); QWindowList windows = QGuiApplication::topLevelWindows(); QCOMPARE(windows.size(), 2); for (int i = 0; i < windows.size(); ++i) { if (windows.at(i)->objectName() == window1->objectName()) { windows.removeAt(i); break; } } QCOMPARE(windows.size(), 1); QCOMPARE(windows.at(0)->objectName(), QLatin1String("window2")); window1->show(); QVERIFY(QTest::qWaitForWindowExposed(windows.at(0))); //We wait till window 2 comes up window1->requestActivate(); // and then transfer the focus to window1 QTRY_COMPARE(QGuiApplication::focusWindow(), window1.data()); QVERIFY(window1->isActive()); QQuickItem *item = QQuickVisualTestUtil::findItem(window1->contentItem(), "item1"); QVERIFY(item); //copied from src/qmltest/quicktestevent.cpp QPoint pos = item->mapToScene(QPointF(item->width()/2, item->height()/2)).toPoint(); QMouseEvent me(QEvent::MouseButtonPress, pos, window1->mapToGlobal(pos), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QSpontaneKeyEvent::setSpontaneous(&me); if (!qApp->notify(window1.data(), &me)) { QString warning = QString::fromLatin1("Mouse event MousePress not accepted by receiving window"); QWARN(warning.toLatin1().data()); } me = QMouseEvent(QEvent::MouseButtonPress, pos, window1->mapToGlobal(pos), Qt::LeftButton, nullptr, Qt::NoModifier); QSpontaneKeyEvent::setSpontaneous(&me); if (!qApp->notify(window1.data(), &me)) { QString warning = QString::fromLatin1("Mouse event MouseRelease not accepted by receiving window"); QWARN(warning.toLatin1().data()); } QTRY_COMPARE(QGuiApplication::focusWindow(), windows.at(0)); QVERIFY(windows.at(0)->isActive()); } void tst_qquickwindow::testWindowVisibilityOrder() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("windoworder.qml")); QScopedPointer window1(qobject_cast(component.create())); QVERIFY(!window1.isNull()); window1->setTitle(QTest::currentTestFunction()); QQuickWindow *window2 = window1->property("win2").value(); QQuickWindow *window3 = window1->property("win3").value(); QQuickWindow *window4 = window1->property("win4").value(); QQuickWindow *window5 = window1->property("win5").value(); QVERIFY(window2); QVERIFY(window3); QVERIFY(QTest::qWaitForWindowExposed(window3)); QWindowList windows = QGuiApplication::topLevelWindows(); QTRY_COMPARE(windows.size(), 5); if (qgetenv("XDG_CURRENT_DESKTOP") == "Unity" && QGuiApplication::focusWindow() != window3) { qDebug() << "Unity (flaky QTBUG-62604): expected window3 to have focus; actual focusWindow:" << QGuiApplication::focusWindow(); } else { QCOMPARE(window3, QGuiApplication::focusWindow()); QVERIFY(window1->isActive()); QVERIFY(window2->isActive()); QVERIFY(window3->isActive()); } //Test if window4 is shown 2 seconds after the application startup //with window4 visible window5 (transient child) should also become visible QVERIFY(!window4->isVisible()); QVERIFY(!window5->isVisible()); window4->setVisible(true); QVERIFY(QTest::qWaitForWindowExposed(window5)); QVERIFY(window4->isVisible()); QVERIFY(window5->isVisible()); } void tst_qquickwindow::blockClosing() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("ucantclosethis.qml")); QScopedPointer window(qobject_cast(component.create())); QVERIFY(!window.isNull()); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QVERIFY(window->isVisible()); QWindowSystemInterface::handleCloseEvent(window.data()); QVERIFY(window->isVisible()); QWindowSystemInterface::handleCloseEvent(window.data()); QVERIFY(window->isVisible()); window->setProperty("canCloseThis", true); QWindowSystemInterface::handleCloseEvent(window.data()); QTRY_VERIFY(!window->isVisible()); } void tst_qquickwindow::blockCloseMethod() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("ucantclosethis.qml")); QScopedPointer window(qobject_cast(component.create())); QVERIFY(!window.isNull()); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QVERIFY(window->isVisible()); QVERIFY(QMetaObject::invokeMethod(window.data(), "close", Qt::DirectConnection)); QVERIFY(window->isVisible()); QVERIFY(QMetaObject::invokeMethod(window.data(), "close", Qt::DirectConnection)); QVERIFY(window->isVisible()); window->setProperty("canCloseThis", true); QVERIFY(QMetaObject::invokeMethod(window.data(), "close", Qt::DirectConnection)); QTRY_VERIFY(!window->isVisible()); } void tst_qquickwindow::crashWhenHoverItemDeleted() { // QTBUG-32771 QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("hoverCrash.qml")); QScopedPointer window(qobject_cast(component.create())); QVERIFY(!window.isNull()); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowActive(window.data())); // Simulate a move from the first rectangle to the second. Crash will happen in here // Moving instantaneously from (0, 99) to (0, 102) does not cause the crash for (int i = 99; i < 102; ++i) { QTest::mouseMove(window.data(), QPoint(0, i)); } } // QTBUG-33436 void tst_qquickwindow::unloadSubWindow() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("unloadSubWindow.qml")); QScopedPointer window(qobject_cast(component.create())); QVERIFY(!window.isNull()); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QPointer transient; QTRY_VERIFY(transient = window->property("transientWindow").value()); QVERIFY(QTest::qWaitForWindowExposed(transient)); // Unload the inner window (in nested Loaders) and make sure it doesn't crash QQuickLoader *loader = window->property("loader1").value(); loader->setActive(false); QTRY_VERIFY(transient.isNull() || !transient->isVisible()); } // QTBUG-52573 void tst_qquickwindow::changeVisibilityInCompleted() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("changeVisibilityInCompleted.qml")); QScopedPointer window(qobject_cast(component.create())); QVERIFY(!window.isNull()); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window.data())); QPointer winVisible; QTRY_VERIFY(winVisible = window->property("winVisible").value()); QPointer winVisibility; QTRY_VERIFY(winVisibility = window->property("winVisibility").value()); QVERIFY(QTest::qWaitForWindowExposed(winVisible)); QVERIFY(QTest::qWaitForWindowExposed(winVisibility)); QVERIFY(winVisible->isVisible()); QCOMPARE(winVisibility->visibility(), QWindow::Windowed); } // QTBUG-32004 void tst_qquickwindow::qobjectEventFilter_touch() { QQuickWindow window; window.resize(250, 250); window.setPosition(100, 100); window.setTitle(QTest::currentTestFunction()); window.show(); QVERIFY(QTest::qWaitForWindowActive(&window)); TestTouchItem *item = new TestTouchItem(window.contentItem()); item->setSize(QSizeF(150, 150)); EventFilter eventFilter; item->installEventFilter(&eventFilter); QPointF pos(10, 10); // press single point QTest::touchEvent(&window, touchDevice).press(0, item->mapToScene(pos).toPoint(), &window); QCOMPARE(eventFilter.events.count(), 1); QCOMPARE(eventFilter.events.first(), (int)QEvent::TouchBegin); } // QTBUG-32004 void tst_qquickwindow::qobjectEventFilter_key() { QQuickWindow window; window.resize(250, 250); window.setPosition(100, 100); window.setTitle(QTest::currentTestFunction()); window.show(); QVERIFY(QTest::qWaitForWindowActive(&window)); TestTouchItem *item = new TestTouchItem(window.contentItem()); item->setSize(QSizeF(150, 150)); item->setFocus(true); EventFilter eventFilter; item->installEventFilter(&eventFilter); QTest::keyPress(&window, Qt::Key_A); // NB: It may also receive some QKeyEvent(ShortcutOverride) which we're not interested in QVERIFY(eventFilter.events.contains((int)QEvent::KeyPress)); eventFilter.events.clear(); QTest::keyRelease(&window, Qt::Key_A); QVERIFY(eventFilter.events.contains((int)QEvent::KeyRelease)); } // QTBUG-32004 void tst_qquickwindow::qobjectEventFilter_mouse() { QQuickWindow window; window.resize(250, 250); window.setPosition(100, 100); window.setTitle(QTest::currentTestFunction()); window.show(); QVERIFY(QTest::qWaitForWindowActive(&window)); TestTouchItem *item = new TestTouchItem(window.contentItem()); item->setSize(QSizeF(150, 150)); EventFilter eventFilter; item->installEventFilter(&eventFilter); QPoint point = item->mapToScene(QPointF(10, 10)).toPoint(); QTest::mouseMove(&window, point); QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, point); QVERIFY(eventFilter.events.contains((int)QEvent::MouseButtonPress)); // clean up mouse press state for the next tests QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, point); } void tst_qquickwindow::animatingSignal() { QQuickWindow window; window.setTitle(QTest::currentTestFunction()); window.setGeometry(100, 100, 300, 200); QSignalSpy spy(&window, SIGNAL(afterAnimating())); window.show(); QTRY_VERIFY(window.isExposed()); QTRY_VERIFY(spy.count() > 1); } // QTBUG-36938 void tst_qquickwindow::contentItemSize() { QQuickWindow window; window.setTitle(QTest::currentTestFunction()); QQuickItem *contentItem = window.contentItem(); QVERIFY(contentItem); QCOMPARE(QSize(contentItem->width(), contentItem->height()), window.size()); QSizeF size(300, 200); window.resize(size.toSize()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QCOMPARE(window.size(), size.toSize()); QCOMPARE(QSizeF(contentItem->width(), contentItem->height()), size); QQmlEngine engine; QQmlComponent component(&engine); component.setData(QByteArray("import QtQuick 2.1\n Rectangle { anchors.fill: parent }"), QUrl()); QQuickItem *rect = qobject_cast(component.create()); QVERIFY(rect); rect->setParentItem(window.contentItem()); QCOMPARE(QSizeF(rect->width(), rect->height()), size); size.transpose(); window.resize(size.toSize()); QCOMPARE(window.size(), size.toSize()); // wait for resize event QTRY_COMPARE(QSizeF(contentItem->width(), contentItem->height()), size); QCOMPARE(QSizeF(rect->width(), rect->height()), size); } void tst_qquickwindow::defaultSurfaceFormat() { // It is quite difficult to verify anything for real since the resulting format after // surface/context creation can be anything, depending on the platform and drivers, // and many options and settings may fail in various configurations, but test at // least using some harmless settings to check that the global, static format is // taken into account in the requested format. QSurfaceFormat savedDefaultFormat = QSurfaceFormat::defaultFormat(); // Verify that depth and stencil are set, as they should be, unless they are disabled // via environment variables. QSurfaceFormat format = savedDefaultFormat; format.setSwapInterval(0); format.setRedBufferSize(8); format.setGreenBufferSize(8); format.setBlueBufferSize(8); format.setProfile(QSurfaceFormat::CompatibilityProfile); format.setOption(QSurfaceFormat::DebugContext); // Will not set depth and stencil. That should be added automatically, // unless the are disabled (but they aren't). QSurfaceFormat::setDefaultFormat(format); QQuickWindow window; window.setTitle(QTest::currentTestFunction()); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); if (window.rendererInterface()->graphicsApi() != QSGRendererInterface::OpenGL) QSKIP("Skipping OpenGL context test due to not running with OpenGL"); const QSurfaceFormat reqFmt = window.requestedFormat(); QCOMPARE(format.swapInterval(), reqFmt.swapInterval()); QCOMPARE(format.redBufferSize(), reqFmt.redBufferSize()); QCOMPARE(format.greenBufferSize(), reqFmt.greenBufferSize()); QCOMPARE(format.blueBufferSize(), reqFmt.blueBufferSize()); QCOMPARE(format.profile(), reqFmt.profile()); QCOMPARE(int(format.options()), int(reqFmt.options())); #if QT_CONFIG(opengl) // Depth and stencil should be >= what has been requested. For real. But use // the context since the window's surface format is only partially updated // on most platforms. const QOpenGLContext *openglContext = nullptr; QTRY_VERIFY((openglContext = window.openglContext()) != nullptr); QVERIFY(openglContext->format().depthBufferSize() >= 16); QVERIFY(openglContext->format().stencilBufferSize() >= 8); #endif QSurfaceFormat::setDefaultFormat(savedDefaultFormat); } void tst_qquickwindow::attachedProperty() { QQuickView view(testFileUrl("windowattached.qml")); view.setTitle(QTest::currentTestFunction()); view.show(); view.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&view)); QVERIFY(view.rootObject()->property("windowActive").toBool()); QCOMPARE(view.rootObject()->property("contentItem").value(), view.contentItem()); QCOMPARE(view.rootObject()->property("windowWidth").toInt(), view.width()); QCOMPARE(view.rootObject()->property("windowHeight").toInt(), view.height()); QCOMPARE(view.rootObject()->property("window").value(), &view); QQuickWindow *innerWindow = view.rootObject()->findChild("extraWindow"); QVERIFY(innerWindow); innerWindow->show(); innerWindow->requestActivate(); QVERIFY(QTest::qWaitForWindowActive(innerWindow)); QQuickText *text = view.rootObject()->findChild("extraWindowText"); QVERIFY(text); QCOMPARE(text->text(), QLatin1String("active\nvisibility: 2")); QCOMPARE(text->property("contentItem").value(), innerWindow->contentItem()); QCOMPARE(text->property("windowWidth").toInt(), innerWindow->width()); QCOMPARE(text->property("windowHeight").toInt(), innerWindow->height()); QCOMPARE(text->property("window").value(), innerWindow); text->setParentItem(nullptr); QVERIFY(!text->property("contentItem").value()); QCOMPARE(text->property("windowWidth").toInt(), 0); QCOMPARE(text->property("windowHeight").toInt(), 0); QVERIFY(!text->property("window").value()); } class RenderJob : public QRunnable { public: RenderJob(QQuickWindow::RenderStage s, QList *l) : stage(s), list(l) { } ~RenderJob() { ++deleted; } QQuickWindow::RenderStage stage; QList *list; void run() { list->append(stage); } static int deleted; }; #if QT_CONFIG(opengl) class GlRenderJob : public QRunnable { public: GlRenderJob(GLubyte *buf) : readPixel(buf), mutex(nullptr), condition(nullptr) {} ~GlRenderJob() {} void run() { QOpenGLContext::currentContext()->functions()->glClearColor(1.0f, 0, 0, 1.0f); QOpenGLContext::currentContext()->functions()->glClear(GL_COLOR_BUFFER_BIT); QOpenGLContext::currentContext()->functions()->glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, (void *)readPixel); if (mutex) { mutex->lock(); condition->wakeOne(); mutex->unlock(); } } GLubyte *readPixel; QMutex *mutex; QWaitCondition *condition; }; #endif int RenderJob::deleted = 0; void tst_qquickwindow::testRenderJob() { QList completedJobs; QQuickWindow::RenderStage stages[] = { QQuickWindow::BeforeSynchronizingStage, QQuickWindow::AfterSynchronizingStage, QQuickWindow::BeforeRenderingStage, QQuickWindow::AfterRenderingStage, QQuickWindow::AfterSwapStage, QQuickWindow::NoStage }; const int numJobs = 6; { QQuickWindow window; window.setTitle(QTest::currentTestFunction()); RenderJob::deleted = 0; // Schedule the jobs for (int i = 0; i < numJobs; ++i) window.scheduleRenderJob(new RenderJob(stages[i], &completedJobs), stages[i]); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); // All jobs should be deleted QTRY_COMPARE(RenderJob::deleted, numJobs); // The NoStage job is not completed, if it is issued when there is no context, // but the rest will be queued and completed once relevant render stage is hit. QCOMPARE(completedJobs.size(), numJobs - 1); // Verify jobs were completed in correct order for (int i = 0; i < numJobs - 1; ++i) QCOMPARE(completedJobs.at(i), stages[i]); // Check that NoStage job gets executed if it is scheduled when window is exposed completedJobs.clear(); RenderJob::deleted = 0; window.scheduleRenderJob(new RenderJob(QQuickWindow::NoStage, &completedJobs), QQuickWindow::NoStage); QTRY_COMPARE(RenderJob::deleted, 1); if ((QGuiApplication::platformName() == QLatin1String("offscreen")) || (QGuiApplication::platformName() == QLatin1String("minimal"))) QEXPECT_FAIL("", "NoStage job fails on offscreen/minimimal platforms", Continue); QCOMPARE(completedJobs.size(), 1); #if QT_CONFIG(opengl) if (window.rendererInterface()->graphicsApi() == QSGRendererInterface::OpenGL) { // Do a synchronized GL job. GLubyte readPixel[4] = {0, 0, 0, 0}; GlRenderJob *glJob = new GlRenderJob(readPixel); if (window.openglContext()->thread() != QThread::currentThread()) { QMutex mutex; QWaitCondition condition; glJob->mutex = &mutex; glJob->condition = &condition; mutex.lock(); window.scheduleRenderJob(glJob, QQuickWindow::NoStage); condition.wait(&mutex); mutex.unlock(); } else { window.scheduleRenderJob(glJob, QQuickWindow::NoStage); } QCOMPARE(int(readPixel[0]), 255); QCOMPARE(int(readPixel[1]), 0); QCOMPARE(int(readPixel[2]), 0); QCOMPARE(int(readPixel[3]), 255); } #endif } // Verify that jobs are deleted when window is not rendered at all completedJobs.clear(); RenderJob::deleted = 0; { QQuickWindow window2; for (int i = 0; i < numJobs; ++i) { window2.scheduleRenderJob(new RenderJob(stages[i], &completedJobs), stages[i]); } } QTRY_COMPARE(RenderJob::deleted, numJobs); QCOMPARE(completedJobs.size(), 0); } class EventCounter : public QQuickRectangle { public: EventCounter(QQuickItem *parent = nullptr) : QQuickRectangle(parent) { } void addFilterEvent(QEvent::Type type) { m_returnTrueForType.append(type); } int childMouseEventFilterEventCount(QEvent::Type type) { return m_childMouseEventFilterEventCount.value(type, 0); } int eventCount(QEvent::Type type) { return m_eventCount.value(type, 0); } void reset() { m_eventCount.clear(); m_childMouseEventFilterEventCount.clear(); } protected: bool childMouseEventFilter(QQuickItem *, QEvent *event) override { m_childMouseEventFilterEventCount[event->type()]++; return m_returnTrueForType.contains(event->type()); } bool event(QEvent *event) override { m_eventCount[event->type()]++; return QQuickRectangle::event(event); } private: QList m_returnTrueForType; QMap m_childMouseEventFilterEventCount; QMap m_eventCount; }; void tst_qquickwindow::testHoverChildMouseEventFilter() { QQuickWindow window; window.resize(250, 250); window.setPosition(100, 100); window.setTitle(QTest::currentTestFunction()); window.show(); QVERIFY(QTest::qWaitForWindowActive(&window)); EventCounter *bottomItem = new EventCounter(window.contentItem()); bottomItem->setObjectName("Bottom Item"); bottomItem->setSize(QSizeF(150, 150)); bottomItem->setAcceptHoverEvents(true); EventCounter *middleItem = new EventCounter(bottomItem); middleItem->setObjectName("Middle Item"); middleItem->setPosition(QPointF(50, 50)); middleItem->setSize(QSizeF(150, 150)); middleItem->setAcceptHoverEvents(true); EventCounter *topItem = new EventCounter(middleItem); topItem->setObjectName("Top Item"); topItem->setPosition(QPointF(50, 50)); topItem->setSize(QSizeF(150, 150)); topItem->setAcceptHoverEvents(true); QPoint pos(10, 10); QTest::mouseMove(&window, pos); QTRY_VERIFY(bottomItem->eventCount(QEvent::HoverEnter) > 0); QCOMPARE(bottomItem->childMouseEventFilterEventCount(QEvent::HoverEnter), 0); QCOMPARE(middleItem->eventCount(QEvent::HoverEnter), 0); QCOMPARE(topItem->eventCount(QEvent::HoverEnter), 0); bottomItem->reset(); pos = QPoint(60, 60); QTest::mouseMove(&window, pos); QTRY_VERIFY(middleItem->eventCount(QEvent::HoverEnter) > 0); QCOMPARE(bottomItem->childMouseEventFilterEventCount(QEvent::HoverEnter), 0); middleItem->reset(); pos = QPoint(70,70); bottomItem->setFiltersChildMouseEvents(true); QTest::mouseMove(&window, pos); QTRY_VERIFY(middleItem->eventCount(QEvent::HoverMove) > 0); QVERIFY(bottomItem->childMouseEventFilterEventCount(QEvent::HoverMove) > 0); QCOMPARE(topItem->eventCount(QEvent::HoverEnter), 0); bottomItem->reset(); middleItem->reset(); pos = QPoint(110,110); bottomItem->addFilterEvent(QEvent::HoverEnter); QTest::mouseMove(&window, pos); QTRY_VERIFY(bottomItem->childMouseEventFilterEventCount(QEvent::HoverEnter) > 0); QCOMPARE(topItem->eventCount(QEvent::HoverEnter), 0); QCOMPARE(middleItem->eventCount(QEvent::HoverEnter), 0); } class HoverTimestampConsumer : public QQuickItem { Q_OBJECT public: HoverTimestampConsumer(QQuickItem *parent = nullptr) : QQuickItem(parent) { setAcceptHoverEvents(true); } void hoverEnterEvent(QHoverEvent *event) { hoverTimestamps << event->timestamp(); } void hoverLeaveEvent(QHoverEvent *event) { hoverTimestamps << event->timestamp(); } void hoverMoveEvent(QHoverEvent *event) { hoverTimestamps << event->timestamp(); } QList hoverTimestamps; }; // Checks that a QHoverEvent carries the timestamp of the QMouseEvent that caused it. // QTBUG-54600 void tst_qquickwindow::testHoverTimestamp() { QQuickWindow window; window.resize(200, 200); window.setPosition(100, 100); window.setTitle(QTest::currentTestFunction()); window.show(); QVERIFY(QTest::qWaitForWindowActive(&window)); HoverTimestampConsumer *hoverConsumer = new HoverTimestampConsumer(window.contentItem()); hoverConsumer->setWidth(100); hoverConsumer->setHeight(100); hoverConsumer->setX(50); hoverConsumer->setY(50); // First position, outside { QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(40, 40), QPointF(40, 40), QPointF(140, 140), Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized); mouseEvent.setTimestamp(10); QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent)); } // Enter { QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(50, 50), QPointF(50, 50), QPointF(150, 150), Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized); mouseEvent.setTimestamp(20); QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent)); } QCOMPARE(hoverConsumer->hoverTimestamps.size(), 1); QCOMPARE(hoverConsumer->hoverTimestamps.last(), 20UL); // Move { QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(60, 60), QPointF(60, 60), QPointF(160, 160), Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized); mouseEvent.setTimestamp(30); QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent)); } QCOMPARE(hoverConsumer->hoverTimestamps.size(), 2); QCOMPARE(hoverConsumer->hoverTimestamps.last(), 30UL); // Move { QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(100, 100), QPointF(100, 100), QPointF(200, 200), Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized); mouseEvent.setTimestamp(40); QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent)); } QCOMPARE(hoverConsumer->hoverTimestamps.size(), 3); QCOMPARE(hoverConsumer->hoverTimestamps.last(), 40UL); // Leave { QMouseEvent mouseEvent(QEvent::MouseMove, QPointF(160, 160), QPointF(160, 160), QPointF(260, 260), Qt::NoButton, Qt::NoButton, Qt::NoModifier, Qt::MouseEventNotSynthesized); mouseEvent.setTimestamp(5); QVERIFY(QCoreApplication::sendEvent(&window, &mouseEvent)); } QCOMPARE(hoverConsumer->hoverTimestamps.size(), 4); QCOMPARE(hoverConsumer->hoverTimestamps.last(), 5UL); } class CircleItem : public QQuickRectangle { public: CircleItem(QQuickItem *parent = nullptr) : QQuickRectangle(parent) { } void setRadius(qreal radius) { const qreal diameter = radius*2; setWidth(diameter); setHeight(diameter); } bool childMouseEventFilter(QQuickItem *item, QEvent *event) override { Q_UNUSED(item) if (event->type() == QEvent::MouseButtonPress && !contains(static_cast(event)->pos())) { // This is an evil hack: in case of items that are not rectangles, we never accept the event. // Instead the events are now delivered to QDeclarativeGeoMapItemBase which doesn't to anything with them. // The map below it still works since it filters events and steals the events at some point. event->setAccepted(false); return true; } return false; } bool contains(const QPointF &pos) const override { // returns true if the point is inside the the embedded circle inside the (square) rect const float radius = (float)width()/2; const QVector2D center(radius, radius); const QVector2D dx = QVector2D(pos) - center; const bool ret = dx.lengthSquared() < radius*radius; return ret; } }; void tst_qquickwindow::test_circleMapItem() { QQuickWindow window; window.resize(250, 250); window.setPosition(100, 100); window.setTitle(QTest::currentTestFunction()); QQuickItem *root = window.contentItem(); QQuickMouseArea *mab = new QQuickMouseArea(root); mab->setObjectName("Bottom MouseArea"); mab->setSize(QSizeF(100, 100)); CircleItem *topItem = new CircleItem(root); topItem->setFiltersChildMouseEvents(true); topItem->setColor(Qt::green); topItem->setObjectName("Top Item"); topItem->setPosition(QPointF(30, 30)); topItem->setRadius(20); QQuickMouseArea *mat = new QQuickMouseArea(topItem); mat->setObjectName("Top Item/MouseArea"); mat->setSize(QSizeF(40, 40)); QSignalSpy bottomSpy(mab, SIGNAL(clicked(QQuickMouseEvent *))); QSignalSpy topSpy(mat, SIGNAL(clicked(QQuickMouseEvent *))); window.show(); QVERIFY(QTest::qWaitForWindowExposed(&window)); QTest::qWait(1000); QPoint pos(50, 50); QTest::mouseClick(&window, Qt::LeftButton, Qt::KeyboardModifiers(), pos); QCOMPARE(topSpy.count(), 1); QCOMPARE(bottomSpy.count(), 0); // Outside the "Circles" "input area", but on top of the bottomItem rectangle pos = QPoint(66, 66); QTest::mouseClick(&window, Qt::LeftButton, Qt::KeyboardModifiers(), pos); QCOMPARE(bottomSpy.count(), 1); QCOMPARE(topSpy.count(), 1); } void tst_qquickwindow::pointerEventTypeAndPointCount() { QPointF localPosition(33, 66); QPointF scenePosition(133, 166); QPointF screenPosition(333, 366); QMouseEvent me(QEvent::MouseButtonPress, localPosition, scenePosition, screenPosition, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QTouchEvent te(QEvent::TouchBegin, touchDevice, Qt::NoModifier, Qt::TouchPointPressed, QList() << QTouchEvent::TouchPoint(1)); QQuickPointerMouseEvent pme; pme.reset(&me); QCOMPARE(pme.asMouseEvent(localPosition), &me); QVERIFY(pme.asPointerMouseEvent()); QVERIFY(!pme.asPointerTouchEvent()); QVERIFY(!pme.asPointerTabletEvent()); // QVERIFY(!pe->asTabletEvent()); // TODO QCOMPARE(pme.pointCount(), 1); QCOMPARE(pme.point(0)->scenePosition(), scenePosition); QCOMPARE(pme.asMouseEvent(localPosition)->localPos(), localPosition); QCOMPARE(pme.asMouseEvent(localPosition)->screenPos(), screenPosition); QQuickPointerTouchEvent pte; pte.reset(&te); QCOMPARE(pte.asTouchEvent(), &te); QVERIFY(!pte.asPointerMouseEvent()); QVERIFY(pte.asPointerTouchEvent()); QVERIFY(!pte.asPointerTabletEvent()); QVERIFY(pte.asTouchEvent()); // QVERIFY(!pte.asTabletEvent()); // TODO QCOMPARE(pte.pointCount(), 1); QCOMPARE(pte.touchPointById(1)->id(), 1); QVERIFY(!pte.touchPointById(0)); te.setTouchPoints(QList() << QTouchEvent::TouchPoint(1) << QTouchEvent::TouchPoint(2)); pte.reset(&te); QCOMPARE(pte.pointCount(), 2); QCOMPARE(pte.touchPointById(1)->id(), 1); QCOMPARE(pte.touchPointById(2)->id(), 2); QVERIFY(!pte.touchPointById(0)); te.setTouchPoints(QList() << QTouchEvent::TouchPoint(2)); pte.reset(&te); QCOMPARE(pte.pointCount(), 1); QCOMPARE(pte.touchPointById(2)->id(), 2); QVERIFY(!pte.touchPointById(1)); QVERIFY(!pte.touchPointById(0)); } void tst_qquickwindow::grabContentItemToImage() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("grabContentItemToImage.qml")); QObject *created = component.create(); QScopedPointer cleanup(created); QVERIFY(created); QQuickWindow *window = qobject_cast(created); QVERIFY(QTest::qWaitForWindowActive(window)); QMetaObject::invokeMethod(window, "grabContentItemToImage"); QTRY_COMPARE(created->property("success").toInt(), 1); } class TestDropTarget : public QQuickItem { Q_OBJECT public: TestDropTarget(QQuickItem *parent = nullptr) : QQuickItem(parent) , enterDropAction(Qt::CopyAction) , moveDropAction(Qt::CopyAction) , dropDropAction(Qt::CopyAction) , enterAccept(true) , moveAccept(true) , dropAccept(true) { setFlags(ItemAcceptsDrops); } void reset() { enterDropAction = Qt::CopyAction; moveDropAction = Qt::CopyAction; dropDropAction = Qt::CopyAction; enterAccept = true; moveAccept = true; dropAccept = true; } void dragEnterEvent(QDragEnterEvent *event) { event->setAccepted(enterAccept); event->setDropAction(enterDropAction); } void dragMoveEvent(QDragMoveEvent *event) { event->setAccepted(moveAccept); event->setDropAction(moveDropAction); } void dropEvent(QDropEvent *event) { event->setAccepted(dropAccept); event->setDropAction(dropDropAction); } Qt::DropAction enterDropAction; Qt::DropAction moveDropAction; Qt::DropAction dropDropAction; bool enterAccept; bool moveAccept; bool dropAccept; }; class DragEventTester { public: DragEventTester() : pos(60, 60) , actions(Qt::CopyAction | Qt::MoveAction | Qt::LinkAction) , buttons(Qt::LeftButton) , modifiers(Qt::NoModifier) { } ~DragEventTester() { qDeleteAll(events); events.clear(); enterEvent = nullptr; moveEvent = nullptr; dropEvent = nullptr; leaveEvent = nullptr; } void addEnterEvent() { enterEvent = new QDragEnterEvent(pos, actions, &data, buttons, modifiers); events.append(enterEvent); } void addMoveEvent() { moveEvent = new QDragMoveEvent(pos, actions, &data, buttons, modifiers, QEvent::DragMove); events.append(moveEvent); } void addDropEvent() { dropEvent = new QDropEvent(pos, actions, &data, buttons, modifiers, QEvent::Drop); events.append(dropEvent); } void addLeaveEvent() { leaveEvent = new QDragLeaveEvent(); events.append(leaveEvent); } void sendDragEventSequence(QQuickWindow *window) const { for (int i = 0; i < events.size(); ++i) { QCoreApplication::sendEvent(window, events[i]); } } // Used for building events. QMimeData data; QPoint pos; Qt::DropActions actions; Qt::MouseButtons buttons; Qt::KeyboardModifiers modifiers; // Owns events. QList events; // Non-owner pointers for easy acccess. QDragEnterEvent *enterEvent; QDragMoveEvent *moveEvent; QDropEvent *dropEvent; QDragLeaveEvent *leaveEvent; }; void tst_qquickwindow::testDragEventPropertyPropagation() { QQuickWindow window; TestDropTarget dropTarget(window.contentItem()); // Setting the size is important because the QQuickWindow checks if the drag happened inside // the drop target. dropTarget.setSize(QSizeF(100, 100)); // Test enter events property propagation. // For enter events, only isAccepted gets propagated. { DragEventTester builder; dropTarget.enterAccept = false; dropTarget.enterDropAction = Qt::IgnoreAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent(); builder.sendDragEventSequence(&window); QDragEnterEvent* enterEvent = builder.enterEvent; QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept); } { DragEventTester builder; dropTarget.enterAccept = false; dropTarget.enterDropAction = Qt::CopyAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent(); builder.sendDragEventSequence(&window); QDragEnterEvent* enterEvent = builder.enterEvent; QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept); } { DragEventTester builder; dropTarget.enterAccept = true; dropTarget.enterDropAction = Qt::IgnoreAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent(); builder.sendDragEventSequence(&window); QDragEnterEvent* enterEvent = builder.enterEvent; QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept); } { DragEventTester builder; dropTarget.enterAccept = true; dropTarget.enterDropAction = Qt::CopyAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent(); builder.sendDragEventSequence(&window); QDragEnterEvent* enterEvent = builder.enterEvent; QCOMPARE(enterEvent->isAccepted(), dropTarget.enterAccept); } // Test move events property propagation. // For move events, both isAccepted and dropAction get propagated. dropTarget.reset(); { DragEventTester builder; dropTarget.moveAccept = false; dropTarget.moveDropAction = Qt::IgnoreAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent(); builder.sendDragEventSequence(&window); QDragMoveEvent* moveEvent = builder.moveEvent; QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept); QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction); } { DragEventTester builder; dropTarget.moveAccept = false; dropTarget.moveDropAction = Qt::CopyAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent(); builder.sendDragEventSequence(&window); QDragMoveEvent* moveEvent = builder.moveEvent; QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept); QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction); } { DragEventTester builder; dropTarget.moveAccept = true; dropTarget.moveDropAction = Qt::IgnoreAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent(); builder.sendDragEventSequence(&window); QDragMoveEvent* moveEvent = builder.moveEvent; QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept); QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction); } { DragEventTester builder; dropTarget.moveAccept = true; dropTarget.moveDropAction = Qt::CopyAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addLeaveEvent(); builder.sendDragEventSequence(&window); QDragMoveEvent* moveEvent = builder.moveEvent; QCOMPARE(moveEvent->isAccepted(), dropTarget.moveAccept); QCOMPARE(moveEvent->dropAction(), dropTarget.moveDropAction); } // Test drop events property propagation. // For drop events, both isAccepted and dropAction get propagated. dropTarget.reset(); { DragEventTester builder; dropTarget.dropAccept = false; dropTarget.dropDropAction = Qt::IgnoreAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent(); builder.sendDragEventSequence(&window); QDropEvent* dropEvent = builder.dropEvent; QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept); QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction); } { DragEventTester builder; dropTarget.dropAccept = false; dropTarget.dropDropAction = Qt::CopyAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent(); builder.sendDragEventSequence(&window); QDropEvent* dropEvent = builder.dropEvent; QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept); QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction); } { DragEventTester builder; dropTarget.dropAccept = true; dropTarget.dropDropAction = Qt::IgnoreAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent(); builder.sendDragEventSequence(&window); QDropEvent* dropEvent = builder.dropEvent; QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept); QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction); } { DragEventTester builder; dropTarget.dropAccept = true; dropTarget.dropDropAction = Qt::CopyAction; builder.addEnterEvent(); builder.addMoveEvent(); builder.addDropEvent(); builder.sendDragEventSequence(&window); QDropEvent* dropEvent = builder.dropEvent; QCOMPARE(dropEvent->isAccepted(), dropTarget.dropAccept); QCOMPARE(dropEvent->dropAction(), dropTarget.dropDropAction); } } void tst_qquickwindow::findChild() { QQuickWindow window; // QQuickWindow // |_ QQuickWindow::contentItem // | |_ QObject("contentItemChild") // |_ QObject("viewChild") QObject *windowChild = new QObject(&window); windowChild->setObjectName("windowChild"); QObject *contentItemChild = new QObject(window.contentItem()); contentItemChild->setObjectName("contentItemChild"); QCOMPARE(window.findChild("windowChild"), windowChild); QCOMPARE(window.findChild("contentItemChild"), contentItemChild); QVERIFY(!window.contentItem()->findChild("viewChild")); // sibling QCOMPARE(window.contentItem()->findChild("contentItemChild"), contentItemChild); } class DeliveryRecord : public QPair { public: DeliveryRecord(const QString &filter, const QString &receiver) : QPair(filter, receiver) { } DeliveryRecord(const QString &receiver) : QPair(QString(), receiver) { } DeliveryRecord() : QPair() { } QString toString() const { if (second.isEmpty()) return QLatin1String("Delivery(no receiver)"); else if (first.isEmpty()) return QString(QLatin1String("Delivery(to '%1')")).arg(second); else return QString(QLatin1String("Delivery('%1' filtering for '%2')")).arg(first).arg(second); } }; Q_DECLARE_METATYPE(DeliveryRecord) QDebug operator<<(QDebug dbg, const DeliveryRecord &pair) { dbg << pair.toString(); return dbg; } typedef QVector DeliveryRecordVector; class EventItem : public QQuickRectangle { Q_OBJECT public: EventItem(QQuickItem *parent) : QQuickRectangle(parent) , m_eventAccepts(true) , m_filterReturns(true) , m_filterAccepts(true) , m_filterNotPreAccepted(false) { QSizeF psize(parent->width(), parent->height()); psize -= QSizeF(20, 20); setWidth(psize.width()); setHeight(psize.height()); setPosition(QPointF(10, 10)); } void setFilterReturns(bool filterReturns) { m_filterReturns = filterReturns; } void setFilterAccepts(bool accepts) { m_filterAccepts = accepts; } void setEventAccepts(bool accepts) { m_eventAccepts = accepts; } /*! * \internal * * returns false if any of the calls to childMouseEventFilter had the wrong * preconditions. If all calls had the expected precondition, returns true. */ bool testFilterPreConditions() const { return !m_filterNotPreAccepted; } static QVector &deliveryList() { return m_deliveryList; } static QSet &includedEventTypes() { if (m_includedEventTypes.isEmpty()) m_includedEventTypes << QEvent::MouseButtonPress; return m_includedEventTypes; } static void setExpectedDeliveryList(const QVector &v) { m_expectedDeliveryList = v; } protected: bool childMouseEventFilter(QQuickItem *i, QEvent *e) override { appendEvent(this, i, e); switch (e->type()) { case QEvent::MouseButtonPress: if (!e->isAccepted()) m_filterNotPreAccepted = true; e->setAccepted(m_filterAccepts); // qCDebug(lcTests) << objectName() << i->objectName(); return m_filterReturns; default: break; } return QQuickRectangle::childMouseEventFilter(i, e); } bool event(QEvent *e) override { appendEvent(nullptr, this, e); switch (e->type()) { case QEvent::MouseButtonPress: // qCDebug(lcTests) << objectName(); e->setAccepted(m_eventAccepts); return true; default: break; } return QQuickRectangle::event(e); } private: static void appendEvent(QQuickItem *filter, QQuickItem *receiver, QEvent *event) { if (includedEventTypes().contains(event->type())) { auto record = DeliveryRecord(filter ? filter->objectName() : QString(), receiver ? receiver->objectName() : QString()); int i = m_deliveryList.count(); if (m_expectedDeliveryList.count() > i && m_expectedDeliveryList[i] == record) qCDebug(lcTests).noquote().nospace() << i << ": " << record; else qCDebug(lcTests).noquote().nospace() << i << ": " << record << ", expected " << (m_expectedDeliveryList.count() > i ? m_expectedDeliveryList[i].toString() : QLatin1String("nothing")) << " <---"; m_deliveryList << record; } } bool m_eventAccepts; bool m_filterReturns; bool m_filterAccepts; bool m_filterNotPreAccepted; // list of (filtering-parent . receiver) pairs static DeliveryRecordVector m_expectedDeliveryList; static DeliveryRecordVector m_deliveryList; static QSet m_includedEventTypes; }; DeliveryRecordVector EventItem::m_expectedDeliveryList; DeliveryRecordVector EventItem::m_deliveryList; QSet EventItem::m_includedEventTypes; typedef QVector CharStarVector; Q_DECLARE_METATYPE(CharStarVector) struct InputState { struct { // event() behavior bool eventAccepts; // filterChildMouse behavior bool returns; bool accepts; bool filtersChildMouseEvent; } r[4]; }; Q_DECLARE_METATYPE(InputState) void tst_qquickwindow::testChildMouseEventFilter_data() { // HIERARCHY: // r0->r1->r2->r3 // QTest::addColumn("mousePos"); QTest::addColumn("inputState"); QTest::addColumn("expectedDeliveryOrder"); QTest::newRow("if filtered and rejected, do not deliver it to the item that filtered it") << QPoint(100, 100) << InputState({ // | event() | child mouse filter // +---------+---------+---------+--------- { // | accepts | returns | accepts | filtersChildMouseEvent { false, false, false, false}, { true, false, false, false}, { false, true, false, true}, { false, false, false, false} } }) << (DeliveryRecordVector() << DeliveryRecord("r2", "r3") //<< DeliveryRecord("r3") // it got filtered -> do not deliver // DeliveryRecord("r2") // r2 filtered it -> do not deliver << DeliveryRecord("r1") ); QTest::newRow("no filtering, no accepting") << QPoint(100, 100) << InputState({ // | event() | child mouse filter // +---------+---------+---------+--------- { // | accepts | returns | accepts | filtersChildMouseEvent { false, false, false, false}, { false , false, false, false}, { false, false, false, false}, { false, false, false, false} } }) << (DeliveryRecordVector() << DeliveryRecord("r3") << DeliveryRecord("r2") << DeliveryRecord("r1") << DeliveryRecord("r0") << DeliveryRecord("root") ); QTest::newRow("all filtering, no accepting") << QPoint(100, 100) << InputState({ // | event() | child mouse filter // +---------+---------+---------+--------- { // | accepts | returns | accepts | filtersChildMouseEvent { false, false, false, true}, { false, false, false, true}, { false, false, false, true}, { false, false, false, true} } }) << (DeliveryRecordVector() << DeliveryRecord("r2", "r3") << DeliveryRecord("r1", "r3") << DeliveryRecord("r0", "r3") << DeliveryRecord("r3") << DeliveryRecord("r1", "r2") << DeliveryRecord("r0", "r2") << DeliveryRecord("r2") << DeliveryRecord("r0", "r1") << DeliveryRecord("r1") << DeliveryRecord("r0") << DeliveryRecord("root") ); QTest::newRow("some filtering, no accepting") << QPoint(100, 100) << InputState({ // | event() | child mouse filter // +---------+---------+---------+--------- { // | accepts | returns | accepts | filtersChildMouseEvent { false, false, false, true}, { false, false, false, true}, { false, false, false, false}, { false, false, false, false} } }) << (DeliveryRecordVector() << DeliveryRecord("r1", "r3") << DeliveryRecord("r0", "r3") << DeliveryRecord("r3") << DeliveryRecord("r1", "r2") << DeliveryRecord("r0", "r2") << DeliveryRecord("r2") << DeliveryRecord("r0", "r1") << DeliveryRecord("r1") << DeliveryRecord("r0") << DeliveryRecord("root") ); QTest::newRow("r1 accepts") << QPoint(100, 100) << InputState({ // | event() | child mouse filter // +---------+---------+---------+--------- { // | accepts | returns | accepts | filtersChildMouseEvent { false, false, false, true}, { true , false, false, true}, { false, false, false, false}, { false, false, false, false} } }) << (DeliveryRecordVector() << DeliveryRecord("r1", "r3") << DeliveryRecord("r0", "r3") << DeliveryRecord("r3") << DeliveryRecord("r1", "r2") << DeliveryRecord("r0", "r2") << DeliveryRecord("r2") << DeliveryRecord("r0", "r1") << DeliveryRecord("r1") ); QTest::newRow("r1 rejects and filters") << QPoint(100, 100) << InputState({ // | event() | child mouse filter // +---------+---------+---------+--------- { // | accepts | returns | accepts | filtersChildMouseEvent { false, false, false, true}, { false , true, false, true}, { false, false, false, false}, { false, false, false, false} } }) << (DeliveryRecordVector() << DeliveryRecord("r1", "r3") << DeliveryRecord("r0", "r3") // << DeliveryRecord("r3") // since it got filtered we don't deliver to r3 << DeliveryRecord("r1", "r2") << DeliveryRecord("r0", "r2") // << DeliveryRecord("r2" // since it got filtered we don't deliver to r2 << DeliveryRecord("r0", "r1") // << DeliveryRecord("r1") // since it acted as a filter and returned true, we don't deliver to r1 << DeliveryRecord("r0") << DeliveryRecord("root") ); } void tst_qquickwindow::testChildMouseEventFilter() { QFETCH(QPoint, mousePos); QFETCH(InputState, inputState); QFETCH(DeliveryRecordVector, expectedDeliveryOrder); EventItem::setExpectedDeliveryList(expectedDeliveryOrder); QQuickWindow window; window.resize(500, 809); QQuickItem *root = window.contentItem(); root->setAcceptedMouseButtons(Qt::LeftButton); root->setObjectName("root"); EventFilter *rootFilter = new EventFilter; root->installEventFilter(rootFilter); // Create 4 items; each item a child of the previous item. EventItem *r[4]; r[0] = new EventItem(root); r[0]->setColor(QColor(0x404040)); r[0]->setWidth(200); r[0]->setHeight(200); r[1] = new EventItem(r[0]); r[1]->setColor(QColor(0x606060)); r[2] = new EventItem(r[1]); r[2]->setColor(Qt::red); r[3] = new EventItem(r[2]); r[3]->setColor(Qt::green); for (uint i = 0; i < sizeof(r)/sizeof(EventItem*); ++i) { r[i]->setEventAccepts(inputState.r[i].eventAccepts); r[i]->setFilterReturns(inputState.r[i].returns); r[i]->setFilterAccepts(inputState.r[i].accepts); r[i]->setFiltersChildMouseEvents(inputState.r[i].filtersChildMouseEvent); r[i]->setObjectName(QString::fromLatin1("r%1").arg(i)); r[i]->setAcceptedMouseButtons(Qt::LeftButton); } window.show(); window.requestActivate(); QVERIFY(QTest::qWaitForWindowActive(&window)); DeliveryRecordVector &actualDeliveryOrder = EventItem::deliveryList(); actualDeliveryOrder.clear(); QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, mousePos); // Check if event got delivered to the root item. If so, append it to the list of items the event got delivered to if (rootFilter->events.contains(QEvent::MouseButtonPress)) actualDeliveryOrder.append(DeliveryRecord("root")); for (int i = 0; i < qMax(actualDeliveryOrder.count(), expectedDeliveryOrder.count()); ++i) { const DeliveryRecord expectedNames = expectedDeliveryOrder.value(i); const DeliveryRecord actualNames = actualDeliveryOrder.value(i); QCOMPARE(actualNames.toString(), expectedNames.toString()); } for (EventItem *item : r) { QVERIFY(item->testFilterPreConditions()); } // "restore" mouse state QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, mousePos); } void tst_qquickwindow::cleanupGrabsOnRelease() { TestTouchItem::clearMouseEventCounters(); QQuickWindow *window = new QQuickWindow; QScopedPointer cleanup(window); window->resize(250, 250); window->setPosition(100, 100); window->setTitle(QTest::currentTestFunction()); window->show(); QVERIFY(QTest::qWaitForWindowActive(window)); TestTouchItem *parent = new TestTouchItem(window->contentItem()); parent->setObjectName("parent"); parent->setSize(QSizeF(150, 150)); parent->acceptMouseEvents = true; parent->grabOnRelease = true; TestTouchItem *child = new TestTouchItem(parent); child->setObjectName("child"); child->setSize(QSizeF(100, 100)); child->acceptMouseEvents = true; QPoint pos(80, 80); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, pos); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, pos); // There is an explicit parent->grabMouse on release(!). This means grab changes from child // to parent: // This will emit two ungrab events: // 1. One for the child (due to the explicit call to parent->grabMouse()) // 2. One for the parent (since the mouse button was finally released) QCOMPARE(child->mouseUngrabEventCount, 1); QCOMPARE(parent->mouseUngrabEventCount, 1); } QTEST_MAIN(tst_qquickwindow) #include "tst_qquickwindow.moc"