QQuickWidget: always accept touch events and grabbed event points

A QQuickWidget contains a Quick UI, which can be expected to handle
touch events, and it handles touch events by forwarding them to the
QQuickWindow. So set the AcceptTouchEvents attribute and let the
Qt Quick delivery machinery deal with the touch-mouse synthesis
within the scene.

Also, Qt Quick's event delivery might return event points as ignored
after setting the exclusive grabber. Qt Widgets touch event delivery
logic doesn't care about exclusive grabbers, and relies on the event
points being accepted to make the widget that received the TouchBegin
an implicit grabber. QQuickWidget needs to translate those states back,
so accept all points that come back with a grabber.

Add a test that verifies that a button in a popup gets all events,
and that those events are translated correctly - without the fix,
the "clicked" test fails, as the release is delivered, but with
coordinates outside of the button.

Also test that we can have two QQuickWidgets where each gets one
touch point.

Fixes: QTBUG-101736
Pick-to: 6.5 6.4 6.2
Change-Id: I3a2bf05fd297ae4d72b6e236ecd8e5ddac37ce06
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Paul Wicking <paul.wicking@qt.io>
This commit is contained in:
Volker Hilsheimer 2022-12-14 09:52:51 +01:00
parent 4cbc25f379
commit dc8f44b145
4 changed files with 113 additions and 2 deletions

View File

@ -613,6 +613,7 @@ QQuickWidget::QQuickWidget(QWidget *parent)
{ {
setMouseTracking(true); setMouseTracking(true);
setFocusPolicy(Qt::StrongFocus); setFocusPolicy(Qt::StrongFocus);
setAttribute(Qt::WA_AcceptTouchEvents);
d_func()->init(); d_func()->init();
} }
@ -1632,9 +1633,23 @@ bool QQuickWidget::event(QEvent *e)
case QEvent::TouchBegin: case QEvent::TouchBegin:
case QEvent::TouchEnd: case QEvent::TouchEnd:
case QEvent::TouchUpdate: case QEvent::TouchUpdate:
case QEvent::TouchCancel: case QEvent::TouchCancel: {
// Touch events only have local and global positions, no need to map. // Touch events only have local and global positions, no need to map.
return QCoreApplication::sendEvent(d->offscreenWindow, e); bool res = QCoreApplication::sendEvent(d->offscreenWindow, e);
if (e->isAccepted() && e->type() == QEvent::TouchBegin) {
// If the TouchBegin got accepted, then make sure all points that have
// an exclusive grabber are also accepted so that the widget code for
// delivering touch events make this widget an implicit grabber of those
// points.
QPointerEvent *pointerEvent = static_cast<QPointerEvent *>(e);
auto deliveredPoints = pointerEvent->points();
for (auto &point : deliveredPoints) {
if (pointerEvent->exclusiveGrabber(point))
point.setAccepted(true);
}
}
return res;
}
case QEvent::FocusAboutToChange: case QEvent::FocusAboutToChange:
return QCoreApplication::sendEvent(d->offscreenWindow, e); return QCoreApplication::sendEvent(d->offscreenWindow, e);

View File

@ -22,6 +22,7 @@ qt_internal_add_test(tst_qquickwidget
Qt::GuiPrivate Qt::GuiPrivate
Qt::QmlPrivate Qt::QmlPrivate
Qt::QuickPrivate Qt::QuickPrivate
Qt::QuickTemplates2Private
Qt::QuickWidgets Qt::QuickWidgets
Qt::QuickWidgetsPrivate Qt::QuickWidgetsPrivate
Qt::WidgetsPrivate Qt::WidgetsPrivate

View File

@ -0,0 +1,27 @@
import QtQuick
import QtQuick.Controls.Basic
Item {
width: 100
height: 100
visible: true
property bool wasPressed: false
property bool wasReleased: false
property bool wasClicked: false
Popup {
closePolicy: Popup.NoAutoClose
visible: true
Button {
objectName: "button"
text: "TAP ME"
anchors.fill: parent
onPressed: wasPressed = true
onReleased: wasReleased = true
onClicked: wasClicked = true
}
}
}

View File

@ -11,6 +11,7 @@
#include <QtQuick/qquickitem.h> #include <QtQuick/qquickitem.h>
#include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquickitem_p.h>
#include <QtQuick/private/qquickmousearea_p.h> #include <QtQuick/private/qquickmousearea_p.h>
#include <QtQuickTemplates2/private/qquickbutton_p.h>
#include <QtQuickTestUtils/private/qmlutils_p.h> #include <QtQuickTestUtils/private/qmlutils_p.h>
#include <QtGui/QWindow> #include <QtGui/QWindow>
#include <QtGui/QScreen> #include <QtGui/QScreen>
@ -125,6 +126,8 @@ private slots:
void synthMouseFromTouch_data(); void synthMouseFromTouch_data();
void synthMouseFromTouch(); void synthMouseFromTouch();
void touchTapMouseArea(); void touchTapMouseArea();
void touchTapButton();
void touchMultipleWidgets();
void tabKey(); void tabKey();
void resizeOverlay(); void resizeOverlay();
void controls(); void controls();
@ -650,6 +653,71 @@ void tst_qquickwidget::touchTapMouseArea()
QVERIFY(rootItem->property("wasClicked").toBool()); QVERIFY(rootItem->property("wasClicked").toBool());
} }
void tst_qquickwidget::touchTapButton()
{
QWidget window;
QQuickWidget *quick = new QQuickWidget;
quick->setSource(testFileUrl("button.qml"));
QHBoxLayout hbox;
hbox.addWidget(quick);
window.setLayout(&hbox);
window.show();
QVERIFY(QTest::qWaitForWindowExposed(&window));
QQuickItem *rootItem = quick->rootObject();
QVERIFY(rootItem);
QQuickButton *button = rootItem->findChild<QQuickButton *>("button");
QVERIFY(button);
const QPoint point = quick->mapTo(&window, button->mapToScene(button->boundingRect().center()).toPoint());
QTest::touchEvent(&window, device).press(0, point, &window).commit();
QTRY_VERIFY(rootItem->property("wasPressed").toBool());
QTest::touchEvent(&window, device).release(0, point, &window).commit();
QTRY_VERIFY(rootItem->property("wasReleased").toBool());
QTRY_VERIFY(rootItem->property("wasClicked").toBool());
}
void tst_qquickwidget::touchMultipleWidgets()
{
QWidget window;
QQuickWidget *leftQuick = new QQuickWidget;
leftQuick->setSource(testFileUrl("button.qml"));
QQuickWidget *rightQuick = new QQuickWidget;
rightQuick->setSource(testFileUrl("button.qml"));
QHBoxLayout hbox;
hbox.addWidget(leftQuick);
hbox.addWidget(rightQuick);
window.setLayout(&hbox);
window.show();
QVERIFY(QTest::qWaitForWindowExposed(&window));
QQuickItem *leftRootItem = leftQuick->rootObject();
QQuickItem *rightRootItem = rightQuick->rootObject();
QVERIFY(leftRootItem);
QVERIFY(rightRootItem);
QQuickButton *leftButton = leftRootItem->findChild<QQuickButton *>("button");
QQuickButton *rightButton = rightRootItem->findChild<QQuickButton *>("button");
QVERIFY(leftButton);
QVERIFY(rightButton);
const QPoint leftPoint = leftQuick->mapTo(&window, leftButton->mapToScene(
leftButton->boundingRect().center()).toPoint());
const QPoint rightPoint = rightQuick->mapTo(&window, rightButton->mapToScene(
rightButton->boundingRect().center()).toPoint());
QTest::touchEvent(&window, device).press(0, leftPoint, &window).commit();
QTRY_VERIFY(leftRootItem->property("wasPressed").toBool());
QTest::touchEvent(&window, device).press(1, rightPoint, &window).commit();
QTRY_VERIFY(rightRootItem->property("wasPressed").toBool());
QTest::touchEvent(&window, device).release(1, rightPoint, &window).commit();
QTRY_VERIFY(rightRootItem->property("wasReleased").toBool());
QVERIFY(rightRootItem->property("wasClicked").toBool());
QTest::touchEvent(&window, device).release(0, leftPoint, &window).commit();
QTRY_VERIFY(leftRootItem->property("wasReleased").toBool());
QVERIFY(leftRootItem->property("wasClicked").toBool());
}
void tst_qquickwidget::tabKey() void tst_qquickwidget::tabKey()
{ {
if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls) if (QGuiApplication::styleHints()->tabFocusBehavior() != Qt::TabFocusAllControls)