// Copyright (C) 2018 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #include #include // qmlobject_cast #include #include #include Q_LOGGING_CATEGORY(lcPointerTests, "qt.quick.pointer.tests") class Event { Q_GADGET public: enum Destination { ItemFilterDestination, ItemMouseDestination, ItemTouchDestination, HandlerDestination }; Q_ENUM(Destination) Event(Destination d, QEvent::Type t, QEventPoint::State s, int grabTransition, QPointF item, QPointF scene) : destination(d), type(t), state(s), grabTransition(grabTransition), posWrtItem(item), posWrtScene(scene) {} Destination destination; QEvent::Type type; // if this represents a QEvent that was received QEventPoint::State state; // if this represents an event (pointer, touch or mouse) int grabTransition; // if this represents an onGrabChanged() notification (QPointingDevice::GrabTransition) QPointF posWrtItem; QPointF posWrtScene; }; #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const class Event &event) { QDebugStateSaver saver(dbg); dbg.nospace(); dbg << "Event("; QtDebugUtils::formatQEnum(dbg, event.destination); dbg << ' '; QtDebugUtils::formatQEnum(dbg, event.type); dbg << ' '; QtDebugUtils::formatQEnum(dbg, event.state); if (event.grabTransition) { dbg << ' '; QtDebugUtils::formatQEnum(dbg, QPointingDevice::GrabTransition(event.grabTransition)); } dbg << " @ "; QtDebugUtils::formatQPoint(dbg, event.posWrtItem); dbg << " S "; QtDebugUtils::formatQPoint(dbg, event.posWrtScene); dbg << ')'; return dbg; } #endif enum { NoGrab = 0, }; class EventItem : public QQuickItem { Q_OBJECT public: EventItem(QQuickItem *parent = nullptr) : QQuickItem(parent) {} inline int grabTransition(bool accept, QEventPoint::State state) { return (accept && (state != QEventPoint::State::Released)) ? int(QPointingDevice::GrabExclusive) : int(NoGrab); } void touchEvent(QTouchEvent *event) override { const bool wasAccepted = event->isAccepted(); for (auto &tp : event->points()) eventList.append(Event(Event::ItemTouchDestination, event->type(), tp.state(), grabTransition(!ignoreTouch, tp.state()), tp.position(), tp.scenePosition())); if (ignoreTouch) event->ignore(); qCDebug(lcPointerTests) << event << "accepted?" << wasAccepted << "->" << event->isAccepted(); } void mousePressEvent(QMouseEvent *event) override { const bool wasAccepted = event->isAccepted(); eventList.append(Event(Event::ItemMouseDestination, event->type(), QEventPoint::State::Pressed, grabTransition(!ignoreMouse, QEventPoint::State::Pressed), event->position().toPoint(), event->scenePosition())); if (ignoreMouse) event->ignore(); qCDebug(lcPointerTests) << event << "accepted?" << wasAccepted << "->" << event->isAccepted(); } void mouseMoveEvent(QMouseEvent *event) override { const bool wasAccepted = event->isAccepted(); eventList.append(Event(Event::ItemMouseDestination, event->type(), QEventPoint::State::Updated, grabTransition(!ignoreMouse, QEventPoint::State::Updated), event->position().toPoint(), event->scenePosition())); if (ignoreMouse) event->ignore(); qCDebug(lcPointerTests) << event << "accepted?" << wasAccepted << "->" << event->isAccepted(); } void mouseReleaseEvent(QMouseEvent *event) override { const bool wasAccepted = event->isAccepted(); eventList.append(Event(Event::ItemMouseDestination, event->type(), QEventPoint::State::Released, grabTransition(!ignoreMouse, QEventPoint::State::Released), event->position().toPoint(), event->scenePosition())); if (ignoreMouse) event->ignore(); qCDebug(lcPointerTests) << event << "accepted?" << wasAccepted << "->" << event->isAccepted(); } void mouseDoubleClickEvent(QMouseEvent *event) override { const bool wasAccepted = event->isAccepted(); eventList.append(Event(Event::ItemMouseDestination, event->type(), QEventPoint::State::Pressed, grabTransition(!ignoreMouse, QEventPoint::State::Pressed), event->position().toPoint(), event->scenePosition())); if (ignoreMouse) event->ignore(); qCDebug(lcPointerTests) << event << "accepted?" << wasAccepted << "->" << event->isAccepted(); } void mouseUngrabEvent() override { qCDebug(lcPointerTests); eventList.append(Event(Event::ItemMouseDestination, QEvent::UngrabMouse, QEventPoint::State::Released, QPointingDevice::UngrabExclusive, QPoint(0,0), QPoint(0,0))); } bool event(QEvent *event) override { qCDebug(lcPointerTests) << event; return QQuickItem::event(event); } QList eventList; bool ignoreMouse = true; bool ignoreTouch = true; bool filterTouch = false; // when used as event filter bool eventFilter(QObject *o, QEvent *event) override { qCDebug(lcPointerTests) << event << o; if (event->type() == QEvent::TouchBegin || event->type() == QEvent::TouchUpdate || event->type() == QEvent::TouchCancel || event->type() == QEvent::TouchEnd) { QTouchEvent *touch = static_cast(event); for (auto &tp : touch->points()) eventList.append(Event(Event::ItemFilterDestination, event->type(), tp.state(), QPointingDevice::GrabExclusive, tp.position(), tp.scenePosition())); if (filterTouch) event->accept(); return true; } return false; } }; #define QCOMPARE_EVENT(i, d, t, s, g) \ {\ const Event &event = eventItem1->eventList.at(i);\ if (event.destination != d || event.type != t || event.state != s || event.grabTransition != g)\ qCWarning(lcPointerTests) << " event " << i << ":" << event;\ QCOMPARE(event.destination, d);\ QCOMPARE(event.type, t);\ QCOMPARE(event.state, s);\ QCOMPARE(int(event.grabTransition), int(g));\ }\ class EventHandler : public QQuickPointerHandler { Q_OBJECT public: EventHandler(QQuickItem *parent = nullptr) : QQuickPointerHandler(parent) {} void handlePointerEventImpl(QPointerEvent *event) override { QQuickPointerHandler::handlePointerEventImpl(event); if (!enabled()) return; if (event->isBeginEvent()) ++pressEventCount; if (event->isUpdateEvent()) ++updateEventCount; if (event->isEndEvent()) ++releaseEventCount; EventItem *item = qmlobject_cast(target()); if (!item) { event->setExclusiveGrabber(event->point(0), this); return; } { const bool wasAccepted = event->isAccepted(); if (ignoreEvent) event->ignore(); qCDebug(lcPointerTests) << event << "accepted?" << wasAccepted << "->" << event->isAccepted(); } for (auto point : event->points()) { const bool wasAccepted = point.isAccepted(); if (acceptPoint) point.setAccepted(true); // does NOT imply a grab if (grabPoint) setExclusiveGrab(event, point, true); qCDebug(lcPointerTests) << " " << point << "accepted?" << wasAccepted << "->" << acceptPoint << "grabbed?" << (event->exclusiveGrabber(point) == this); // target item holds the eventList, which records all events to the item and handler in order item->eventList.append(Event(Event::HandlerDestination, QEvent::Pointer, static_cast(point.state()), grabPoint ? int(QPointingDevice::GrabExclusive) : int(NoGrab), eventPos(point), point.scenePosition())); } } void onGrabChanged(QQuickPointerHandler *, QPointingDevice::GrabTransition stateChange, QPointerEvent *ev, QEventPoint &point) override { Q_UNUSED(ev); EventItem *item = qmlobject_cast(target()); if (item) { // target item holds the eventList, which records all events to the item and handler in order item->eventList.append(Event(Event::HandlerDestination, QEvent::None, static_cast(point.state()), stateChange, eventPos(point), point.scenePosition())); } } bool ignoreEvent = true; bool acceptPoint = false; bool grabPoint = false; int pressEventCount = 0; int updateEventCount = 0; int releaseEventCount = 0; }; class tst_PointerHandlers : public QQmlDataTest { Q_OBJECT public: tst_PointerHandlers() : QQmlDataTest(QT_QMLTEST_DATADIR) {} private slots: void initTestCase() override; void touchEventDelivery_data(); void touchEventDelivery(); void mouseEventDelivery_data(); void mouseEventDelivery(); void touchReleaseOutside_data(); void touchReleaseOutside(); void dynamicCreation(); void handlerInWindow(); void dynamicCreationInWindow(); void cppConstruction(); void reparenting(); void grabberSceneChange_data(); void grabberSceneChange(); void clip(); protected: bool eventFilter(QObject *, QEvent *event) override { QEventPoint::State tpState; switch (event->type()) { case QEvent::MouseButtonPress: tpState = QEventPoint::State::Pressed; break; case QEvent::MouseMove: tpState = QEventPoint::State::Updated; break; case QEvent::MouseButtonRelease: tpState = QEventPoint::State::Released; break; default: // So far we aren't recording filtered touch events here - they would be quite numerous in some cases return false; } QMouseEvent *me = static_cast(event); filteredEventList.append(Event(Event::ItemFilterDestination, event->type(), tpState, 0, me->position().toPoint(), me->globalPosition().toPoint())); return false; } private: void createView(QScopedPointer &window, const char *fileName); std::unique_ptr touchscreen{QTest::createTouchDevice()}; QList filteredEventList; }; void tst_PointerHandlers::createView(QScopedPointer &window, const char *fileName) { window.reset(new QQuickView); // window->setGeometry(0,0,240,320); window->setSource(testFileUrl(fileName)); QTRY_COMPARE(window->status(), QQuickView::Ready); QQuickViewTestUtils::centerOnScreen(window.data()); QQuickViewTestUtils::moveMouseAway(window.data()); window->show(); QVERIFY(QTest::qWaitForWindowActive(window.data())); QVERIFY(window->rootObject() != nullptr); } void tst_PointerHandlers::initTestCase() { QQmlDataTest::initTestCase(); qmlRegisterType("Qt.test", 1, 0, "EventItem"); qmlRegisterType("Qt.test", 1, 0, "EventHandler"); } void tst_PointerHandlers::touchEventDelivery_data() { QTest::addColumn("synthMouse"); // AA_SynthesizeMouseForUnhandledTouchEvents QTest::newRow("no synth") << false; QTest::newRow("synth") << true; } void tst_PointerHandlers::touchEventDelivery() { QFETCH(bool, synthMouse); qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, synthMouse); QScopedPointer windowPtr; createView(windowPtr, "singleitem.qml"); QQuickView * window = windowPtr.data(); EventItem *eventItem1 = window->rootObject()->findChild(); QVERIFY(eventItem1); auto devPriv = QPointingDevicePrivate::get(touchscreen.get()); // In Qt 5, QQItem::acceptTouchEvents(true) was the default, but not in Qt 6. // This test is written to expect touch events on eventItem1. // ignoreTouch = true just tells it to reject the events as they come. eventItem1->setAcceptTouchEvents(true); // Reject incoming mouse and touch events QPoint p1 = QPoint(20, 20); QTest::touchEvent(window, touchscreen.get()).press(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events from touch press" << eventItem1->eventList; QTRY_COMPARE(eventItem1->eventList.size(), 2); QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, NoGrab); QCOMPARE_EVENT(1, Event::ItemTouchDestination, QEvent::TouchBegin, QEventPoint::State::Pressed, NoGrab); p1 += QPoint(10, 0); QTest::touchEvent(window, touchscreen.get()).move(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch move" << eventItem1->eventList; QCOMPARE(eventItem1->eventList.size(), 3); // no grabs -> only the handler gets the update QTest::touchEvent(window, touchscreen.get()).release(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch release" << eventItem1->eventList; QCOMPARE(eventItem1->eventList.size(), 4); QCOMPARE_EVENT(eventItem1->eventList.size() - 1, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Released, NoGrab); eventItem1->eventList.clear(); // Accept touch events only eventItem1->ignoreTouch = false; p1 = QPoint(20, 20); QTest::touchEvent(window, touchscreen.get()).press(0, p1, window); QQuickTouchUtils::flush(window); QCOMPARE(eventItem1->eventList.size(), 2); QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, NoGrab); QCOMPARE_EVENT(1, Event::ItemTouchDestination, QEvent::TouchBegin, QEventPoint::State::Pressed, QPointingDevice::GrabExclusive); QCOMPARE(devPriv->pointById(0)->exclusiveGrabber, eventItem1); p1 += QPoint(10, 0); QTest::touchEvent(window, touchscreen.get()).move(0, p1, window); QQuickTouchUtils::flush(window); QCOMPARE(eventItem1->eventList.size(), 4); QCOMPARE_EVENT(2, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Updated, NoGrab); QCOMPARE_EVENT(3, Event::ItemTouchDestination, QEvent::TouchUpdate, QEventPoint::State::Updated, QPointingDevice::GrabExclusive); QTest::touchEvent(window, touchscreen.get()).release(0, p1, window); QQuickTouchUtils::flush(window); QCOMPARE(eventItem1->eventList.size(), 6); QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Released, NoGrab); QCOMPARE_EVENT(5, Event::ItemTouchDestination, QEvent::TouchEnd, QEventPoint::State::Released, NoGrab); eventItem1->eventList.clear(); // wait to avoid getting a double click event QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); // Accept mouse events only eventItem1->ignoreTouch = true; eventItem1->ignoreMouse = false; eventItem1->setAcceptedMouseButtons(Qt::LeftButton); p1 = QPoint(20, 20); QTest::touchEvent(window, touchscreen.get()).press(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch press" << eventItem1->eventList; QCOMPARE(eventItem1->eventList.size(), 2); QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, NoGrab); QCOMPARE_EVENT(1, Event::ItemTouchDestination, QEvent::TouchBegin, QEventPoint::State::Pressed, NoGrab); QCOMPARE(devPriv->pointById(0)->exclusiveGrabber, nullptr); QPointF localPos = eventItem1->mapFromScene(p1); QPointF scenePos = p1; // item is at 0,0 QCOMPARE(eventItem1->eventList.at(1).posWrtItem, localPos); QCOMPARE(eventItem1->eventList.at(1).posWrtScene, scenePos); p1 += QPoint(10, 0); QTest::touchEvent(window, touchscreen.get()).move(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch move" << eventItem1->eventList; QCOMPARE(eventItem1->eventList.size(), 3); QTest::touchEvent(window, touchscreen.get()).release(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch release" << eventItem1->eventList; QCOMPARE(eventItem1->eventList.size(), 4); QCOMPARE_EVENT(3, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Released, NoGrab); eventItem1->eventList.clear(); // wait to avoid getting a double click event QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); // Accept mouse buttons but not the touch event eventItem1->ignoreTouch = true; eventItem1->ignoreMouse = true; eventItem1->setAcceptedMouseButtons(Qt::LeftButton); p1 = QPoint(20, 20); QTest::touchEvent(window, touchscreen.get()).press(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch press" << eventItem1->eventList; QCOMPARE(eventItem1->eventList.size(), 2); QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, NoGrab); QCOMPARE_EVENT(1, Event::ItemTouchDestination, QEvent::TouchBegin, QEventPoint::State::Pressed, NoGrab); QCOMPARE(devPriv->pointById(0)->exclusiveGrabber, nullptr); p1 += QPoint(10, 0); QTest::touchEvent(window, touchscreen.get()).move(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch move" << eventItem1->eventList; QCOMPARE(eventItem1->eventList.size(), 3); QTest::touchEvent(window, touchscreen.get()).release(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch release" << eventItem1->eventList; QCOMPARE(eventItem1->eventList.size(), 4); eventItem1->eventList.clear(); // wait to avoid getting a double click event QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); // Accept touch events eventItem1->ignoreTouch = false; eventItem1->setAcceptedMouseButtons(Qt::LeftButton); p1 = QPoint(20, 20); QTest::touchEvent(window, touchscreen.get()).press(0, p1, window); QQuickTouchUtils::flush(window); QCOMPARE(eventItem1->eventList.size(), 2); QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, NoGrab); QCOMPARE_EVENT(1, Event::ItemTouchDestination, QEvent::TouchBegin, QEventPoint::State::Pressed, QPointingDevice::GrabExclusive); p1 += QPoint(10, 0); QTest::touchEvent(window, touchscreen.get()).move(0, p1, window); QQuickTouchUtils::flush(window); QCOMPARE(eventItem1->eventList.size(), 4); QCOMPARE_EVENT(2, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Updated, NoGrab); QCOMPARE_EVENT(3, Event::ItemTouchDestination, QEvent::TouchUpdate, QEventPoint::State::Updated, QPointingDevice::GrabExclusive); QTest::touchEvent(window, touchscreen.get()).release(0, p1, window); QQuickTouchUtils::flush(window); QCOMPARE(eventItem1->eventList.size(), 6); QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Released, NoGrab); QCOMPARE_EVENT(5, Event::ItemTouchDestination, QEvent::TouchEnd, QEventPoint::State::Released, NoGrab); eventItem1->eventList.clear(); // Accept pointer events // TODO acceptPoint currently does nothing unique, but we could // re-enable this test if we define the best way to handle generic QPointerEvents in Item subclasses /* eventItem1->acceptPoint = true; eventItem1->grabPoint = true; p1 = QPoint(20, 20); QTest::touchEvent(window, touchscreen.get()).press(0, p1, window); QQuickTouchUtils::flush(window); QCOMPARE(eventItem1->eventList.size(), 2); QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::None, QEventPoint::State::Pressed, QPointingDevice::GrabExclusive); QCOMPARE_EVENT(1, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, QPointingDevice::GrabExclusive); p1 += QPoint(10, 0); QTest::touchEvent(window, touchscreen.get()).move(0, p1, window); QQuickTouchUtils::flush(window); QCOMPARE(eventItem1->eventList.size(), 3); QCOMPARE_EVENT(2, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Updated, QPointingDevice::GrabExclusive); QTest::touchEvent(window, touchscreen.get()).release(0, p1, window); QQuickTouchUtils::flush(window); QCOMPARE(eventItem1->eventList.size(), 5); qCDebug(lcPointerTests) << eventItem1->eventList; QCOMPARE_EVENT(3, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Released, QPointingDevice::GrabExclusive); QCOMPARE_EVENT(4, Event::HandlerDestination, QEvent::None, QEventPoint::State::Released, QPointingDevice::UngrabExclusive); eventItem1->eventList.clear(); */ } void tst_PointerHandlers::mouseEventDelivery_data() { QTest::addColumn("itemIgnoreMouse"); QTest::addColumn("handlerIgnoreMouse"); QTest::newRow("default-accepted") << false << false; QTest::newRow("handler ignore press") << false << true; QTest::newRow("item ignore press") << true << false; QTest::newRow("both ignore press") << true << true; } void tst_PointerHandlers::mouseEventDelivery() { QFETCH(bool, itemIgnoreMouse); QFETCH(bool, handlerIgnoreMouse); QScopedPointer windowPtr; createView(windowPtr, "singleitem.qml"); QQuickView * window = windowPtr.data(); EventItem *eventItem1 = window->rootObject()->findChild(); QVERIFY(eventItem1); EventHandler *handler = window->rootObject()->findChild(); QVERIFY(handler); QCOMPARE(handler->parentItem(), eventItem1); QCOMPARE(handler->target(), eventItem1); QVERIFY(QQuickItemPrivate::get(eventItem1)->extra->resourcesList.contains(handler)); eventItem1->ignoreMouse = itemIgnoreMouse; eventItem1->setAcceptedMouseButtons(Qt::LeftButton); handler->ignoreEvent = handlerIgnoreMouse; QPoint p1 = QPoint(20, 20); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); qCDebug(lcPointerTests) << "events after mouse press:" << eventItem1->eventList.size() << eventItem1->eventList; int eventCheckIndex = 0; if (eventItem1->eventList.at(eventCheckIndex).state == QEventPoint::State::Updated) ++eventCheckIndex; QPointF localPos = eventItem1->mapFromScene(p1); QPointF scenePos = p1; // item is at 0,0 QCOMPARE(eventItem1->eventList.at(eventCheckIndex).posWrtItem, localPos); QCOMPARE(eventItem1->eventList.at(eventCheckIndex).posWrtScene, scenePos); QCOMPARE_EVENT(eventCheckIndex++, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, NoGrab); QCOMPARE(eventItem1->eventList.at(eventCheckIndex).posWrtItem, localPos); QCOMPARE(eventItem1->eventList.at(eventCheckIndex).posWrtScene, scenePos); QCOMPARE_EVENT(eventCheckIndex++, Event::ItemMouseDestination, QEvent::MouseButtonPress, QEventPoint::State::Pressed, (itemIgnoreMouse ? int(NoGrab) : int(QPointingDevice::GrabExclusive))); QCOMPARE(window->mouseGrabberItem(), (itemIgnoreMouse ? nullptr : eventItem1)); p1 += QPoint(10, 0); QTest::mouseMove(window, p1); qCDebug(lcPointerTests) << "events after mouse move:" << eventItem1->eventList.size() << eventItem1->eventList; // If the item is going to get the mouse move, QQuickDeliveryAgentPrivate::deliverMatchingPointsToItem() // delivers to the handler first, even if the handler called ignore() on the press. // If the item is not going to get it (because it has no grab), it's still "relevant" in // QQuickDeliveryAgentPrivate::pointerTargets(), and therefore QQuickDeliveryAgentPrivate::deliverUpdatedPoints() // delivers to the item's handlers in the final step. So in this test, the handler always gets updates and the release. QCOMPARE_EVENT(eventCheckIndex++, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Updated, NoGrab); if (!itemIgnoreMouse) QCOMPARE_EVENT(eventCheckIndex++, Event::ItemMouseDestination, QEvent::MouseMove, QEventPoint::State::Updated, QPointingDevice::GrabExclusive); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); qCDebug(lcPointerTests) << "events after mouse release, checking from" << eventCheckIndex << "of" << eventItem1->eventList.size() << ":" << eventItem1->eventList; if (!itemIgnoreMouse) { QCOMPARE_EVENT(eventItem1->eventList.size() - 2, Event::ItemMouseDestination, QEvent::MouseButtonRelease, QEventPoint::State::Released, NoGrab); QCOMPARE_EVENT(eventItem1->eventList.size() - 1, Event::ItemMouseDestination, QEvent::UngrabMouse, QEventPoint::State::Released, QPointingDevice::UngrabExclusive); } QCOMPARE(handler->pressEventCount, 1); QCOMPARE(handler->updateEventCount, 1); // only the intentional move after press QCOMPARE(handler->releaseEventCount, 1); eventItem1->eventList.clear(); // wait to avoid getting a double click event QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); } void tst_PointerHandlers::touchReleaseOutside_data() { QTest::addColumn("acceptPoint"); QTest::addColumn("grabPoint"); QTest::addColumn("eventCount"); QTest::addColumn("endIndexToTest"); QTest::addColumn("endDestination"); // Event::Destination QTest::addColumn("endType"); // QEvent::Type QTest::addColumn("endState"); // QEventPoint::State QTest::addColumn("endGrabState"); // QEventPoint::State QTest::newRow("reject and ignore") << false << false << 6 << 5 << int(Event::ItemTouchDestination) << int(QEvent::TouchEnd) << int(QEventPoint::State::Released) << int(NoGrab); QTest::newRow("reject and grab") << false << true << 5 << 4 << int(Event::HandlerDestination) << int(QEvent::None) << int(QEventPoint::State::Released) << int(QPointingDevice::UngrabExclusive); QTest::newRow("accept and ignore") << true << false << 1 << 0 << int(Event::HandlerDestination) << int(QEvent::Pointer) << int(QEventPoint::State::Pressed) << int(NoGrab); QTest::newRow("accept and grab") << true << true << 5 << 4 << int(Event::HandlerDestination) << int(QEvent::None) << int(QEventPoint::State::Released) << int(QPointingDevice::UngrabExclusive); } void tst_PointerHandlers::touchReleaseOutside() { QScopedPointer windowPtr; createView(windowPtr, "singleitem.qml"); QQuickView * window = windowPtr.data(); QFETCH(bool, acceptPoint); QFETCH(bool, grabPoint); QFETCH(int, eventCount); QFETCH(int, endIndexToTest); QFETCH(int, endDestination); QFETCH(int, endType); QFETCH(int, endState); QFETCH(int, endGrabState); EventItem *eventItem1 = window->rootObject()->findChild(); QVERIFY(eventItem1); EventHandler *handler = window->rootObject()->findChild(); QVERIFY(handler); eventItem1->ignoreTouch = false; eventItem1->setAcceptTouchEvents(true); handler->acceptPoint = acceptPoint; handler->grabPoint = grabPoint; QPoint p1 = QPoint(20, 20); QTest::touchEvent(window, touchscreen.get()).press(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch press" << eventItem1->eventList; p1.setX(eventItem1->mapToScene(eventItem1->clipRect().bottomRight()).x() + 10); QTest::touchEvent(window, touchscreen.get()).move(0, p1, window); QTest::touchEvent(window, touchscreen.get()).release(0, p1, window); QQuickTouchUtils::flush(window); qCDebug(lcPointerTests) << "events after touch release" << eventItem1->eventList; QCOMPARE(eventItem1->eventList.size(), eventCount); QCOMPARE_EVENT(endIndexToTest, endDestination, endType, endState, endGrabState); } void tst_PointerHandlers::dynamicCreation() { QScopedPointer windowPtr; createView(windowPtr, "dynamicallyCreated.qml"); QQuickView * window = windowPtr.data(); EventItem *eventItem1 = window->rootObject()->findChild("eventItem1"); QVERIFY(eventItem1); EventHandler *handler = window->rootObject()->findChild("eventHandler"); QVERIFY(handler); QCOMPARE(handler->parentItem(), eventItem1); QCOMPARE(handler->target(), eventItem1); QVERIFY(QQuickItemPrivate::get(eventItem1)->extra->resourcesList.contains(handler)); QPoint p1(20, 20); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_COMPARE(eventItem1->eventList.size(), 2); QCOMPARE_EVENT(0, Event::HandlerDestination, QEvent::Pointer, QEventPoint::State::Pressed, NoGrab); QCOMPARE_EVENT(1, Event::ItemMouseDestination, QEvent::MouseButtonPress, QEventPoint::State::Pressed, NoGrab); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); } void tst_PointerHandlers::handlerInWindow() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("handlerInWindow.qml")); QQuickWindow *window = qobject_cast(component.create()); QScopedPointer cleanup(window); QVERIFY(window); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); EventHandler *handler = window->contentItem()->findChild("eventHandler"); QVERIFY(handler); QCOMPARE(handler->parentItem(), window->contentItem()); QCOMPARE(handler->target(), window->contentItem()); QPoint p1(20, 20); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_COMPARE(handler->pressEventCount, 1); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_COMPARE(handler->releaseEventCount, 1); } void tst_PointerHandlers::dynamicCreationInWindow() { QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("dynamicallyCreatedInWindow.qml")); QQuickWindow *window = qobject_cast(component.create()); QScopedPointer cleanup(window); QVERIFY(window); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); EventHandler *handler = window->contentItem()->findChild("eventHandler"); QVERIFY(handler); QCOMPARE(handler->parentItem(), window->contentItem()); QCOMPARE(handler->target(), window->contentItem()); QPoint p1(20, 20); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_COMPARE(handler->pressEventCount, 1); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, p1); QTRY_COMPARE(handler->releaseEventCount, 1); } void tst_PointerHandlers::cppConstruction() { QScopedPointer windowPtr; createView(windowPtr, "itemOnly.qml"); QQuickView * window = windowPtr.data(); EventItem *eventItem1 = window->rootObject()->findChild("eventItem1"); QVERIFY(eventItem1); QQuickItemPrivate *eventItemPriv = QQuickItemPrivate::get(eventItem1); EventHandler handler(eventItem1); // tell the handler to grab on press handler.grabPoint = true; QCOMPARE(handler.parentItem(), eventItem1); QCOMPARE(handler.target(), eventItem1); QCOMPARE(eventItemPriv->extra->pointerHandlers.first(), &handler); QVERIFY(eventItemPriv->extra->resourcesList.contains(&handler)); // the handler and then eventItem1 sees each event QPoint p1 = QPoint(20, 20); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); QCOMPARE(handler.pressEventCount, 1); qCDebug(lcPointerTests) << "events after mouse press" << eventItem1->eventList; QCOMPARE(eventItem1->eventList.size(), 3); QTest::mouseRelease(window, Qt::LeftButton); QCOMPARE(handler.releaseEventCount, 1); qCDebug(lcPointerTests) << "events after mouse release" << eventItem1->eventList; QCOMPARE(eventItem1->eventList.size(), 7); // wait to avoid getting a double click event QTest::qWait(qApp->styleHints()->mouseDoubleClickInterval() + 10); } void tst_PointerHandlers::reparenting() { QScopedPointer windowPtr; createView(windowPtr, "reparenting.qml"); QQuickView * window = windowPtr.data(); QQuickItem *topItem = window->rootObject()->findChild("top"); QVERIFY(topItem); QQuickItem *bottomItem = window->rootObject()->findChild("bottom"); QVERIFY(bottomItem); EventHandler *handler = window->rootObject()->findChild("eventHandler"); QVERIFY(handler); topItem->setAcceptedMouseButtons(Qt::RightButton); for (int i = 1; i < 5; ++i) { QQuickItem *expectedParentItem = (i % 2 ? topItem : bottomItem); QQuickItem *unexpectedParentItem = (i % 2 ? bottomItem : topItem); qCDebug(lcPointerTests) << "initial parent" << handler->parentItem() << "waiting for" << expectedParentItem; QTRY_COMPARE(handler->parentItem(), expectedParentItem); QCOMPARE(handler->target(), expectedParentItem); QVERIFY(QQuickItemPrivate::get(expectedParentItem)->extra.isAllocated()); QVERIFY(QQuickItemPrivate::get(expectedParentItem)->extra->resourcesList.contains(handler)); if (QQuickItemPrivate::get(unexpectedParentItem)->extra.isAllocated()) QVERIFY(!QQuickItemPrivate::get(unexpectedParentItem)->extra->resourcesList.contains(handler)); QCOMPARE(expectedParentItem->acceptedMouseButtons(), Qt::AllButtons); QCOMPARE(unexpectedParentItem->acceptedMouseButtons(), unexpectedParentItem == topItem ? Qt::RightButton : Qt::NoButton); QPoint pt = expectedParentItem->mapToScene(expectedParentItem->boundingRect().center()).toPoint(); qCDebug(lcPointerTests) << "click @" << pt; QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, pt); QTRY_COMPARE(handler->pressEventCount, i); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, pt); QTRY_COMPARE(handler->releaseEventCount, i); } } /*! Verify that removing an item that has a grabbing handler from the scene does not result in crashes in our event dispatching code. The item's window() pointer will be nullptr, so the handler must have released the grab, or never gotten the grab, depending on when the item gets removed. See QTBUG-114475. */ void tst_PointerHandlers::grabberSceneChange_data() { QTest::addColumn("useTimer"); QTest::addColumn("grabChangedCount"); QTest::addRow("Immediately") << false << 0; QTest::addRow("Delayed") << true << 2; } void tst_PointerHandlers::grabberSceneChange() { QFETCH(const bool, useTimer); QFETCH(const int, grabChangedCount); QQmlEngine engine; QQmlComponent component(&engine); component.loadUrl(testFileUrl("grabberSceneChange.qml")); QQuickWindow *window = qobject_cast(component.create()); QScopedPointer cleanup(window); QVERIFY(window); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); window->setProperty("useTimer", useTimer); QQuickItem *container = window->findChild("container"); QPoint p1 = QPoint(window->width() / 2, window->height() / 2); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, p1); // The container gets removed from this window, either immediately on // press, or through a timer. QTRY_COMPARE(container->parentItem(), nullptr); QEXPECT_FAIL("Delayed", "PointerHandlers don't release their grab when item is removed", Continue); QCOMPARE(window->property("grabChangedCounter").toInt(), grabChangedCount); // this should not crash QTest::mouseMove(window, p1 + QPoint(5, 5)); } void tst_PointerHandlers::clip() { QScopedPointer windowPtr; createView(windowPtr, "clip.qml"); QQuickView * window = windowPtr.data(); QVERIFY(window); window->show(); QVERIFY(QTest::qWaitForWindowExposed(window)); EventHandler *handler = window->contentItem()->findChild("eventHandler"); EventHandler *circleHandler = window->contentItem()->findChild("circle eventHandler"); QCOMPARE(handler->pressEventCount, 0); QCOMPARE(circleHandler->pressEventCount, 0); QCOMPARE(handler->releaseEventCount, 0); QCOMPARE(circleHandler->releaseEventCount, 0); const QPoint rectPt = QPoint(1, 1); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, rectPt); QCOMPARE(handler->pressEventCount, 1); QCOMPARE(circleHandler->pressEventCount, 0); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, rectPt); QCOMPARE(handler->releaseEventCount, 1); QCOMPARE(circleHandler->releaseEventCount, 0); handler->pressEventCount = 0; circleHandler->pressEventCount = 0; handler->releaseEventCount = 0; circleHandler->releaseEventCount = 0; const QPoint rectAndCirclePt = QPoint(49 ,49); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, rectAndCirclePt); QCOMPARE(handler->pressEventCount, 1); QCOMPARE(circleHandler->pressEventCount, 1); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, rectAndCirclePt); QCOMPARE(handler->releaseEventCount, 1); QCOMPARE(circleHandler->releaseEventCount, 1); handler->pressEventCount = 0; circleHandler->pressEventCount = 0; handler->releaseEventCount = 0; circleHandler->releaseEventCount = 0; const QPoint circlePt = QPoint(51 ,51); QTest::mousePress(window, Qt::LeftButton, Qt::NoModifier, circlePt); QCOMPARE(handler->pressEventCount, 0); QCOMPARE(circleHandler->pressEventCount, 1); QTest::mouseRelease(window, Qt::LeftButton, Qt::NoModifier, circlePt); QCOMPARE(handler->releaseEventCount, 0); QCOMPARE(circleHandler->releaseEventCount, 1); } QTEST_MAIN(tst_PointerHandlers) #include "tst_qquickpointerhandler.moc"