From: Kevin Ottens Date: Tue, 17 Jul 2012 15:18:25 +0000 (+0200) Subject: Propagate synthesized mouse events in parallel (lock-step) with touch X-Git-Tag: v5.0.0-beta1~642 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=7808ec795c5831d56dae9c4f9f7e1306489864aa;p=profile%2Fivi%2Fqtbase.git Propagate synthesized mouse events in parallel (lock-step) with touch This patch implement the equivalent of 468626e99a90d6ac21cb311cde05c658ccb3b781 in qtdeclarative but for QtWidgets. If a widget doesn't accept a touch event, then QApplication gives it another try by synthesizing a corresponding mouse event. This way QtQuick and QtWidget behave in a similar way, removing the need for platform backends to try to emulate a mouse event from a touch event unconditionally. Also add relevant unit tests and adjust old QApplication ones. Change-Id: Iddbf6d756c4b52931a9d1c314b50d7a31dbcdee9 Reviewed-by: Marc Mutz Reviewed-by: Sean Harmer Reviewed-by: Kevin Krammer Reviewed-by: Friedemann Kleint --- diff --git a/src/widgets/kernel/qapplication.cpp b/src/widgets/kernel/qapplication.cpp index 6634a2e..08cd470 100644 --- a/src/widgets/kernel/qapplication.cpp +++ b/src/widgets/kernel/qapplication.cpp @@ -3412,6 +3412,25 @@ bool QApplication::notify(QObject *receiver, QEvent *e) } break; #endif + + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + { + QWidget *widget = static_cast(receiver); + QTouchEvent *touchEvent = static_cast(e); + const bool acceptTouchEvents = widget->testAttribute(Qt::WA_AcceptTouchEvents); + + touchEvent->setTarget(widget); + touchEvent->setAccepted(acceptTouchEvents); + + res = acceptTouchEvents && d->notify_helper(widget, touchEvent); + + // If the touch event wasn't accepted, synthesize a mouse event and see if the widget wants it. + if (!touchEvent->isAccepted()) + res = d->translateTouchToMouse(widget, touchEvent); + break; + } + case QEvent::TouchBegin: // Note: TouchUpdate and TouchEnd events are never propagated { @@ -3432,6 +3451,15 @@ bool QApplication::notify(QObject *receiver, QEvent *e) touchEvent->setAccepted(acceptTouchEvents); QPointer p = widget; res = acceptTouchEvents && d->notify_helper(widget, touchEvent); + + // If the touch event wasn't accepted, synthesize a mouse event and see if the widget wants it. + if (!touchEvent->isAccepted()) { + res = d->translateTouchToMouse(widget, touchEvent); + eventAccepted = touchEvent->isAccepted(); + if (eventAccepted) + break; + } + eventAccepted = touchEvent->isAccepted(); if (p.isNull()) { // widget was deleted @@ -4357,6 +4385,68 @@ QWidget *QApplicationPrivate::findClosestTouchPointTarget(QTouchDevice *device, return static_cast(closestTarget); } +class WidgetAttributeSaver +{ +public: + explicit WidgetAttributeSaver(QWidget *widget, Qt::WidgetAttribute attribute, bool forcedValue) + : m_widget(widget), + m_attribute(attribute), + m_savedValue(widget->testAttribute(attribute)) + { + widget->setAttribute(attribute, forcedValue); + } + + ~WidgetAttributeSaver() + { + m_widget->setAttribute(m_attribute, m_savedValue); + } + +private: + QWidget * const m_widget; + const Qt::WidgetAttribute m_attribute; + const bool m_savedValue; +}; + +bool QApplicationPrivate::translateTouchToMouse(QWidget *widget, QTouchEvent *event) +{ + Q_Q(QApplication); + + Q_FOREACH (const QTouchEvent::TouchPoint &p, event->touchPoints()) { + const QEvent::Type eventType = (p.state() & Qt::TouchPointPressed) ? QEvent::MouseButtonPress + : (p.state() & Qt::TouchPointReleased) ? QEvent::MouseButtonRelease + : (p.state() & Qt::TouchPointMoved) ? QEvent::MouseMove + : QEvent::None; + + if (eventType == QEvent::None) + continue; + + const QPoint pos = widget->mapFromGlobal(p.scenePos().toPoint()); + + QMouseEvent mouseEvent(eventType, pos, + Qt::LeftButton, Qt::LeftButton, + event->modifiers()); + mouseEvent.setAccepted(true); + mouseEvent.setTimestamp(event->timestamp()); + + // Make sure our synthesized mouse event doesn't propagate + // we want to control the propagation ourself to get a chance to + // deliver a proper touch event higher up in the hierarchy if that + // widget doesn't pick up the mouse event either. + WidgetAttributeSaver saver(widget, Qt::WA_NoMousePropagation, true); + + // Note it has to be a spontaneous event if we want the focus management + // and input method support to behave properly. Quite some of the code + // related to those aspect check for the spontaneous flag. + const bool res = q->sendSpontaneousEvent(widget, &mouseEvent); + event->setAccepted(mouseEvent.isAccepted()); + + if (mouseEvent.isAccepted()) + return res; + } + + return false; +} + void QApplicationPrivate::translateRawTouchEvent(QWidget *window, QTouchDevice *device, const QList &touchPoints, diff --git a/src/widgets/kernel/qapplication_p.h b/src/widgets/kernel/qapplication_p.h index 463cccb..e53896f 100644 --- a/src/widgets/kernel/qapplication_p.h +++ b/src/widgets/kernel/qapplication_p.h @@ -290,6 +290,7 @@ public: QWidget *findClosestTouchPointTarget(QTouchDevice *device, const QPointF &screenPos); void appendTouchPoint(const QTouchEvent::TouchPoint &touchPoint); void removeTouchPoint(int touchPointId); + bool translateTouchToMouse(QWidget *widget, QTouchEvent *event); static void translateRawTouchEvent(QWidget *widget, QTouchDevice *device, const QList &touchPoints, diff --git a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp index aebe416..610c0bc 100644 --- a/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp +++ b/tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp @@ -1933,7 +1933,7 @@ void tst_QApplication::touchEventPropagation() QTest::QTouchEventSequence::touchPointList(releasedTouchPoints)); QCoreApplication::processEvents(); QVERIFY(!window.seenTouchEvent); - QVERIFY(!window.seenMouseEvent); + QVERIFY(window.seenMouseEvent); // Since QApplication transforms ignored touch events in mouse events window.reset(); window.setAttribute(Qt::WA_AcceptTouchEvents); @@ -1947,7 +1947,7 @@ void tst_QApplication::touchEventPropagation() QTest::QTouchEventSequence::touchPointList(releasedTouchPoints)); QCoreApplication::processEvents(); QVERIFY(window.seenTouchEvent); - QVERIFY(!window.seenMouseEvent); + QVERIFY(window.seenMouseEvent); window.reset(); window.acceptTouchEvent = true; @@ -1985,9 +1985,9 @@ void tst_QApplication::touchEventPropagation() QTest::QTouchEventSequence::touchPointList(releasedTouchPoints)); QCoreApplication::processEvents(); QVERIFY(!widget.seenTouchEvent); - QVERIFY(!widget.seenMouseEvent); + QVERIFY(widget.seenMouseEvent); QVERIFY(!window.seenTouchEvent); - QVERIFY(!window.seenMouseEvent); + QVERIFY(window.seenMouseEvent); window.reset(); widget.reset(); @@ -2002,9 +2002,9 @@ void tst_QApplication::touchEventPropagation() QTest::QTouchEventSequence::touchPointList(releasedTouchPoints)); QCoreApplication::processEvents(); QVERIFY(widget.seenTouchEvent); - QVERIFY(!widget.seenMouseEvent); + QVERIFY(widget.seenMouseEvent); QVERIFY(!window.seenTouchEvent); - QVERIFY(!window.seenMouseEvent); + QVERIFY(window.seenMouseEvent); window.reset(); widget.reset(); @@ -2019,7 +2019,7 @@ void tst_QApplication::touchEventPropagation() QTest::QTouchEventSequence::touchPointList(releasedTouchPoints)); QCoreApplication::processEvents(); QVERIFY(widget.seenTouchEvent); - QVERIFY(!widget.seenMouseEvent); + QVERIFY(widget.seenMouseEvent); QVERIFY(!window.seenTouchEvent); QVERIFY(!window.seenMouseEvent); @@ -2054,9 +2054,9 @@ void tst_QApplication::touchEventPropagation() QTest::QTouchEventSequence::touchPointList(releasedTouchPoints)); QCoreApplication::processEvents(); QVERIFY(!widget.seenTouchEvent); - QVERIFY(!widget.seenMouseEvent); + QVERIFY(widget.seenMouseEvent); QVERIFY(window.seenTouchEvent); - QVERIFY(!window.seenMouseEvent); + QVERIFY(window.seenMouseEvent); window.reset(); widget.reset(); @@ -2071,13 +2071,13 @@ void tst_QApplication::touchEventPropagation() QTest::QTouchEventSequence::touchPointList(releasedTouchPoints)); QCoreApplication::processEvents(); QVERIFY(!widget.seenTouchEvent); - QVERIFY(!widget.seenMouseEvent); + QVERIFY(widget.seenMouseEvent); QVERIFY(window.seenTouchEvent); QVERIFY(!window.seenMouseEvent); window.reset(); widget.reset(); - widget.acceptMouseEvent = true; // doesn't matter, touch events are propagated first + widget.acceptMouseEvent = true; // it matters, touch events are propagated in parallel to synthesized mouse events window.acceptTouchEvent = true; QWindowSystemInterface::handleTouchEvent(window.windowHandle(), 0, @@ -2089,8 +2089,8 @@ void tst_QApplication::touchEventPropagation() QTest::QTouchEventSequence::touchPointList(releasedTouchPoints)); QCoreApplication::processEvents(); QVERIFY(!widget.seenTouchEvent); - QVERIFY(!widget.seenMouseEvent); - QVERIFY(window.seenTouchEvent); + QVERIFY(widget.seenMouseEvent); + QVERIFY(!window.seenTouchEvent); QVERIFY(!window.seenMouseEvent); } } diff --git a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp index c670c5b..c9d8b31 100644 --- a/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp +++ b/tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp @@ -385,6 +385,8 @@ private slots: void nativeChildFocus(); void grab(); + void touchEventSynthesizedMouseEvent(); + private: bool ensureScreenSize(int width, int height); QWidget *testWidget; @@ -9284,5 +9286,163 @@ void tst_QWidget::grab() } } +class TouchMouseWidget : public QWidget { +public: + explicit TouchMouseWidget(QWidget *parent = 0) + : QWidget(parent), + m_touchEventCount(0), + m_acceptTouch(false), + m_mouseEventCount(0), + m_acceptMouse(true) + { + resize(200, 200); + } + + void setAcceptTouch(bool accept) + { + m_acceptTouch = accept; + setAttribute(Qt::WA_AcceptTouchEvents, accept); + } + + void setAcceptMouse(bool accept) + { + m_acceptMouse = accept; + } + +protected: + bool event(QEvent *e) + { + switch (e->type()) { + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + ++m_touchEventCount; + if (m_acceptTouch) + e->accept(); + else + e->ignore(); + return true; + + case QEvent::MouseButtonPress: + case QEvent::MouseMove: + case QEvent::MouseButtonRelease: + ++m_mouseEventCount; + if (m_acceptMouse) + e->accept(); + else + e->ignore(); + return true; + + default: + return QWidget::event(e); + } + } + +public: + int m_touchEventCount; + bool m_acceptTouch; + int m_mouseEventCount; + bool m_acceptMouse; +}; + +void tst_QWidget::touchEventSynthesizedMouseEvent() +{ + { + // Simple case, we ignore the touch events, we get mouse events instead + QTouchDevice *device = new QTouchDevice; + device->setType(QTouchDevice::TouchScreen); + QWindowSystemInterface::registerTouchDevice(device); + + TouchMouseWidget widget; + widget.show(); + QVERIFY(QTest::qWaitForWindowExposed(widget.windowHandle())); + QCOMPARE(widget.m_touchEventCount, 0); + QCOMPARE(widget.m_mouseEventCount, 0); + + QTest::touchEvent(&widget, device).press(0, QPoint(10, 10), &widget); + QCOMPARE(widget.m_touchEventCount, 0); + QCOMPARE(widget.m_mouseEventCount, 1); + QTest::touchEvent(&widget, device).move(0, QPoint(15, 15), &widget); + QCOMPARE(widget.m_touchEventCount, 0); + QCOMPARE(widget.m_mouseEventCount, 2); + QTest::touchEvent(&widget, device).release(0, QPoint(20, 20), &widget); + QCOMPARE(widget.m_touchEventCount, 0); + QCOMPARE(widget.m_mouseEventCount, 3); + } + + { + // We accept the touch events, no mouse event is generated + QTouchDevice *device = new QTouchDevice; + device->setType(QTouchDevice::TouchScreen); + QWindowSystemInterface::registerTouchDevice(device); + + TouchMouseWidget widget; + widget.setAcceptTouch(true); + widget.show(); + QVERIFY(QTest::qWaitForWindowExposed(widget.windowHandle())); + QCOMPARE(widget.m_touchEventCount, 0); + QCOMPARE(widget.m_mouseEventCount, 0); + + QTest::touchEvent(&widget, device).press(0, QPoint(10, 10), &widget); + QCOMPARE(widget.m_touchEventCount, 1); + QCOMPARE(widget.m_mouseEventCount, 0); + QTest::touchEvent(&widget, device).move(0, QPoint(15, 15), &widget); + QCOMPARE(widget.m_touchEventCount, 2); + QCOMPARE(widget.m_mouseEventCount, 0); + QTest::touchEvent(&widget, device).release(0, QPoint(20, 20), &widget); + QCOMPARE(widget.m_touchEventCount, 3); + QCOMPARE(widget.m_mouseEventCount, 0); + } + + { + // Parent accepts touch events, child ignore both mouse and touch + // We should see propagation of the TouchBegin + QTouchDevice *device = new QTouchDevice; + device->setType(QTouchDevice::TouchScreen); + QWindowSystemInterface::registerTouchDevice(device); + + TouchMouseWidget parent; + parent.setAcceptTouch(true); + TouchMouseWidget child(&parent); + child.setAcceptMouse(false); + parent.show(); + QVERIFY(QTest::qWaitForWindowExposed(parent.windowHandle())); + QCOMPARE(parent.m_touchEventCount, 0); + QCOMPARE(parent.m_mouseEventCount, 0); + QCOMPARE(child.m_touchEventCount, 0); + QCOMPARE(child.m_mouseEventCount, 0); + + QTest::touchEvent(parent.window(), device).press(0, QPoint(10, 10), &child); + QCOMPARE(parent.m_touchEventCount, 1); + QCOMPARE(parent.m_mouseEventCount, 0); + QCOMPARE(child.m_touchEventCount, 0); + QCOMPARE(child.m_mouseEventCount, 1); // Attempt at mouse event before propagation + } + + { + // Parent accepts mouse events, child ignore both mouse and touch + // We should see propagation of the TouchBegin into a MouseButtonPress + QTouchDevice *device = new QTouchDevice; + device->setType(QTouchDevice::TouchScreen); + QWindowSystemInterface::registerTouchDevice(device); + + TouchMouseWidget parent; + TouchMouseWidget child(&parent); + child.setAcceptMouse(false); + parent.show(); + QVERIFY(QTest::qWaitForWindowExposed(parent.windowHandle())); + QCOMPARE(parent.m_touchEventCount, 0); + QCOMPARE(parent.m_mouseEventCount, 0); + QCOMPARE(child.m_touchEventCount, 0); + QCOMPARE(child.m_mouseEventCount, 0); + + QTest::touchEvent(parent.window(), device).press(0, QPoint(10, 10), &child); + QCOMPARE(parent.m_touchEventCount, 0); + QCOMPARE(parent.m_mouseEventCount, 1); + QCOMPARE(child.m_touchEventCount, 0); + QCOMPARE(child.m_mouseEventCount, 1); // Attempt at mouse event before propagation + } +} + QTEST_MAIN(tst_QWidget) #include "tst_qwidget.moc"