Propagate synthesized mouse events in parallel (lock-step) with touch
authorKevin Ottens <kevin.ottens.qnx@kdab.com>
Tue, 17 Jul 2012 15:18:25 +0000 (17:18 +0200)
committerQt by Nokia <qt-info@nokia.com>
Tue, 24 Jul 2012 08:46:24 +0000 (10:46 +0200)
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 <marc.mutz@kdab.com>
Reviewed-by: Sean Harmer <sean.harmer@kdab.com>
Reviewed-by: Kevin Krammer <kevin.krammer@kdab.com>
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@nokia.com>
src/widgets/kernel/qapplication.cpp
src/widgets/kernel/qapplication_p.h
tests/auto/widgets/kernel/qapplication/tst_qapplication.cpp
tests/auto/widgets/kernel/qwidget/tst_qwidget.cpp

index 6634a2e..08cd470 100644 (file)
@@ -3412,6 +3412,25 @@ bool QApplication::notify(QObject *receiver, QEvent *e)
         }
         break;
 #endif
+
+    case QEvent::TouchUpdate:
+    case QEvent::TouchEnd:
+    {
+        QWidget *widget = static_cast<QWidget *>(receiver);
+        QTouchEvent *touchEvent = static_cast<QTouchEvent *>(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<QWidget> 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<QWidget *>(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<QTouchEvent::TouchPoint> &touchPoints,
index 463cccb..e53896f 100644 (file)
@@ -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<QTouchEvent::TouchPoint> &touchPoints,
index aebe416..610c0bc 100644 (file)
@@ -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);
     }
 }
index c670c5b..c9d8b31 100644 (file)
@@ -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"