diff options
author | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2021-08-18 17:33:29 +0200 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2021-08-26 14:14:32 +0000 |
commit | c50313b7126917edeb335288153dd4a7290c8c3f (patch) | |
tree | 02e398588e425f30b6565e5211bf74ce48d2b8d8 | |
parent | aaa79cf73512a16724f084450025d223154b9f9e (diff) |
Forward touchEvents to children inside QGraphicsProxyWidget
Just sending the event to the embedded widget is not enough, we
have to perform hit-testing for the different touch points, and
send the event to the child widget under the point. Fortunately,
QApplicationPrivate::translateRawTouchEvent provides the logic
that generates multiple events for groups of touch points.
Since that helper always sent events spontaneously, add an
optional parameter to allow sending of non-spontaneous events.
Add a test case that simulates touch events to different widget
configurations inside a QGraphicsProxyWidget.
Fixes: QTBUG-67819
Task-number: QTBUG-45737
Change-Id: Iffd5c84c64ee2ceadc7e31863675fdf227582c81
Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
(cherry picked from commit 1ecf2212fae176b78c9951a37df9e33eb24d4f2d)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
-rw-r--r-- | src/widgets/graphicsview/qgraphicsproxywidget.cpp | 17 | ||||
-rw-r--r-- | src/widgets/kernel/qapplication.cpp | 11 | ||||
-rw-r--r-- | src/widgets/kernel/qapplication_p.h | 3 | ||||
-rw-r--r-- | tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp | 168 |
4 files changed, 187 insertions, 12 deletions
diff --git a/src/widgets/graphicsview/qgraphicsproxywidget.cpp b/src/widgets/graphicsview/qgraphicsproxywidget.cpp index aaddaa894f..2641e24b9a 100644 --- a/src/widgets/graphicsview/qgraphicsproxywidget.cpp +++ b/src/widgets/graphicsview/qgraphicsproxywidget.cpp @@ -916,16 +916,17 @@ bool QGraphicsProxyWidget::event(QEvent *event) case QEvent::TouchBegin: case QEvent::TouchUpdate: case QEvent::TouchEnd: { - if (event->spontaneous()) - qt_sendSpontaneousEvent(d->widget, event); - else - QCoreApplication::sendEvent(d->widget, event); - - if (event->isAccepted()) + QTouchEvent *touchEvent = static_cast<QTouchEvent *>(event); + auto touchPoints = touchEvent->points(); + bool res = QApplicationPrivate::translateRawTouchEvent(d->widget, touchEvent->pointingDevice(), + touchPoints, touchEvent->timestamp(), + event->spontaneous()); + if (res) { + event->accept(); return true; - + } break; - } + } default: break; } diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp index 0daa0cfcb6..b73db6d583 100644 --- a/src/widgets/kernel/qapplication.cpp +++ b/src/widgets/kernel/qapplication.cpp @@ -3928,7 +3928,8 @@ void QApplicationPrivate::activateImplicitTouchGrab(QWidget *widget, QTouchEvent bool QApplicationPrivate::translateRawTouchEvent(QWidget *window, const QPointingDevice *device, QList<QEventPoint> &touchPoints, - ulong timestamp) + ulong timestamp, + bool spontaneous) { QApplicationPrivate *d = self; // TODO get rid of this QPair @@ -4030,7 +4031,9 @@ bool QApplicationPrivate::translateRawTouchEvent(QWidget *window, { // if the TouchBegin handler recurses, we assume that means the event // has been implicitly accepted and continue to send touch events - if (QApplication::sendSpontaneousEvent(widget, &touchEvent) && touchEvent.isAccepted()) { + bool res = spontaneous ? QApplication::sendSpontaneousEvent(widget, &touchEvent) + : QApplication::sendEvent(widget, &touchEvent); + if (res && touchEvent.isAccepted()) { accepted = true; if (!widget.isNull()) widget->setAttribute(Qt::WA_WState_AcceptedTouchBeginEvent); @@ -4043,7 +4046,9 @@ bool QApplicationPrivate::translateRawTouchEvent(QWidget *window, || QGestureManager::gesturePending(widget) #endif ) { - if (QApplication::sendSpontaneousEvent(widget, &touchEvent) && touchEvent.isAccepted()) + bool res = spontaneous ? QApplication::sendSpontaneousEvent(widget, &touchEvent) + : QApplication::sendEvent(widget, &touchEvent); + if (res && touchEvent.isAccepted()) accepted = true; // widget can be deleted on TouchEnd if (touchEvent.type() == QEvent::TouchEnd && !widget.isNull()) diff --git a/src/widgets/kernel/qapplication_p.h b/src/widgets/kernel/qapplication_p.h index f690b07924..31dde00dd9 100644 --- a/src/widgets/kernel/qapplication_p.h +++ b/src/widgets/kernel/qapplication_p.h @@ -253,7 +253,8 @@ public: static bool translateRawTouchEvent(QWidget *widget, const QPointingDevice *device, QList<QEventPoint> &touchPoints, - ulong timestamp); + ulong timestamp, + bool spontaneous = true); static void translateTouchCancel(const QPointingDevice *device, ulong timestamp); QPixmap applyQIconStyleHelper(QIcon::Mode mode, const QPixmap& base) const override; diff --git a/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp b/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp index 3e3a1d18a4..d60097d2b0 100644 --- a/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp +++ b/tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp @@ -201,6 +201,7 @@ private slots: void wheelEvent(); void wheelEventPropagation(); #endif + void touchEvent(); #ifndef QT_NO_CURSOR void cursor(); void cursor2(); @@ -2393,6 +2394,173 @@ void tst_QGraphicsView::wheelEventPropagation() } #endif // QT_CONFIG(wheelevent) +void tst_QGraphicsView::touchEvent() +{ + QGraphicsScene scene(0, 0, 300, 200); + QWidget *simpleWidget = new QWidget; + simpleWidget->setObjectName("simpleWidget"); + simpleWidget->setAttribute(Qt::WA_AcceptTouchEvents, true); + QGraphicsProxyWidget *simpleProxy = scene.addWidget(simpleWidget); + simpleProxy->setAcceptTouchEvents(true); + simpleProxy->setGeometry(QRectF(0, 0, 30, 30)); + + QWidget *formWidget = new QWidget; + formWidget->setObjectName("formWidget"); + formWidget->setAttribute(Qt::WA_AcceptTouchEvents, true); + QPushButton *pushButton1 = new QPushButton("One"); + pushButton1->setObjectName("pushButton1"); + pushButton1->setAttribute(Qt::WA_AcceptTouchEvents, true); + QPushButton *pushButton2 = new QPushButton("Two"); + pushButton2->setObjectName("pushButton2"); + pushButton2->setAttribute(Qt::WA_AcceptTouchEvents, true); + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(pushButton1); + vbox->addWidget(pushButton2); + formWidget->setLayout(vbox); + QGraphicsProxyWidget *formProxy = scene.addWidget(formWidget); + formProxy->setAcceptTouchEvents(true); + formProxy->setGeometry(QRectF(50, 50, 200, 100)); + + QGraphicsView view(&scene); + view.setFixedSize(scene.width(), scene.height()); + view.verticalScrollBar()->setValue(0); + view.horizontalScrollBar()->setValue(0); + view.show(); + QVERIFY(QTest::qWaitForWindowExposed(&view)); + + class TouchEventSpy : public QObject + { + public: + using QObject::QObject; + + struct TouchRecord { + QObject *receiver; + QEvent::Type eventType; + QHash<int, QPointF> positions; + }; + QList<TouchRecord> records; + + int count() const { return records.count(); } + TouchRecord at(int i) const { return records.at(i); } + void clear() { records.clear(); } + protected: + bool eventFilter(QObject *receiver, QEvent *event) override + { + switch (event->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchCancel: + case QEvent::TouchEnd: { + QTouchEvent *touchEvent = static_cast<QTouchEvent *>(event); + // instead of detaching each QEventPoint, just store the relative positions + QHash<int, QPointF> positions; + for (const auto &touchPoint : touchEvent->points()) + positions[touchPoint.id()] = touchPoint.position(); + records << TouchRecord{receiver, event->type(), positions}; + break; + } + default: + break; + } + return QObject::eventFilter(receiver, event); + } + } eventSpy; + qApp->installEventFilter(&eventSpy); + + auto touchDevice = QTest::createTouchDevice(); + const QPointF simpleCenter = simpleProxy->geometry().center(); + + // On systems without double conversion we might get different rounding behavior. + // One pixel off in any direction is acceptable for this test. + constexpr auto closeEnough = [](QPointF exp, QPointF act) -> bool { + const QRectF expArea(exp - QPointF(1., 1.), exp + QPointF(1., 1.)); + const bool contains = expArea.contains(act); + if (!contains) + qWarning() << act << "not in" << exp; + return contains; + }; + + // verify that the embedded widget gets the correctly translated event + { + auto sequence = QTest::touchEvent(view.viewport(), touchDevice); + sequence.press(0, simpleCenter.toPoint()); + } + // window, viewport, scene, simpleProxy, simpleWidget + QCOMPARE(eventSpy.count(), 5); + auto record = eventSpy.at(eventSpy.count() - 1); + QCOMPARE(record.receiver, simpleWidget); + QCOMPARE(record.eventType, QEvent::TouchBegin); + QCOMPARE(record.positions.count(), 1); + QVERIFY(closeEnough(record.positions[0], simpleCenter)); + eventSpy.clear(); + + // verify that the layout of formWidget is how we expect it to be + QCOMPARE(formWidget->childAt(QPoint(5, 5)), nullptr); + const QPoint pb1Center = pushButton1->rect().center(); + QCOMPARE(formWidget->childAt(pushButton1->pos() + pb1Center), pushButton1); + const QPoint pb2Center = pushButton2->rect().center(); + QCOMPARE(formWidget->childAt(pushButton2->pos() + pb2Center), pushButton2); + + // single touch point to nested widget, event should bubble up and translate correctly + { + auto sequence = QTest::touchEvent(view.viewport(), touchDevice); + sequence.press(0, pushButton1->pos() + pb1Center + formProxy->pos().toPoint()); + } + // ..., formProxy, pushButton1, formWidget + QCOMPARE(eventSpy.count(), 6); + record = eventSpy.at(eventSpy.count() - 2); + QCOMPARE(record.receiver, pushButton1); + QVERIFY(closeEnough(record.positions[0], pb1Center)); + QCOMPARE(record.eventType, QEvent::TouchBegin); + // pushButton doesn't accept the point, so it propagates to parent + record = eventSpy.at(eventSpy.count() - 1); + QCOMPARE(record.receiver, formWidget); + QVERIFY(closeEnough(record.positions[0], pushButton1->pos() + pb1Center)); + QCOMPARE(record.eventType, QEvent::TouchBegin); + eventSpy.clear(); + + // multi-touch to different widgets + { + auto sequence = QTest::touchEvent(view.viewport(), touchDevice); + sequence.press(0, pushButton1->pos() + pb1Center + formProxy->pos().toPoint()); + sequence.press(1, pushButton2->pos() + pb2Center + formProxy->pos().toPoint()); + } + // window, viewport, scene, formProxy, pushButton1, formWidget, pushButton2, formWidget + QCOMPARE(eventSpy.count(), 8); + record = eventSpy.at(4); + // the order in which the two presses are delivered is not defined + const bool pb1First = record.receiver == pushButton1; + if (pb1First) { + QCOMPARE(record.receiver, pushButton1); + QVERIFY(closeEnough(record.positions[0], pb1Center)); + } else { + QCOMPARE(record.receiver, pushButton2); + QVERIFY(closeEnough(record.positions[1], pb2Center)); + } + record = eventSpy.at(5); + QCOMPARE(record.receiver, formWidget); + if (pb1First) { + QVERIFY(closeEnough(record.positions[0], pushButton1->pos() + pb1Center)); + } else { + QVERIFY(closeEnough(record.positions[1], pushButton2->pos() + pb2Center)); + } + record = eventSpy.at(6); + if (pb1First) { + QCOMPARE(record.receiver, pushButton2); + QVERIFY(closeEnough(record.positions[1], pb2Center)); + } else { + QCOMPARE(record.receiver, pushButton1); + QVERIFY(closeEnough(record.positions[0], pb1Center)); + } + record = eventSpy.at(7); + QCOMPARE(record.receiver, formWidget); + if (pb1First) { + QVERIFY(closeEnough(record.positions[1], pushButton2->pos() + pb2Center)); + } else { + QVERIFY(closeEnough(record.positions[0], pushButton1->pos() + pb1Center)); + } +} + #ifndef QT_NO_CURSOR void tst_QGraphicsView::cursor() { |