summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVolker Hilsheimer <volker.hilsheimer@qt.io>2021-08-18 17:33:29 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2021-08-26 14:14:32 +0000
commitc50313b7126917edeb335288153dd4a7290c8c3f (patch)
tree02e398588e425f30b6565e5211bf74ce48d2b8d8
parentaaa79cf73512a16724f084450025d223154b9f9e (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.cpp17
-rw-r--r--src/widgets/kernel/qapplication.cpp11
-rw-r--r--src/widgets/kernel/qapplication_p.h3
-rw-r--r--tests/auto/widgets/graphicsview/qgraphicsview/tst_qgraphicsview.cpp168
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()
{