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 <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>
This commit is contained in:
Kevin Ottens 2012-07-17 17:18:25 +02:00 committed by Qt by Nokia
parent 349f16b03c
commit 7808ec795c
4 changed files with 264 additions and 13 deletions

View 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,

View 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,

View 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);
}
}

View 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"