aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRichard Moe Gustavsen <richard.gustavsen@qt.io>2023-12-20 10:13:06 +0100
committerRichard Moe Gustavsen <richard.gustavsen@qt.io>2024-01-24 17:12:57 +0100
commit571c407ea67d120c6735f728c59a464cce43ec8d (patch)
treed48ac8c2b64d741b38039fe99acfcd191aa479c9
parentf932f8c86d35cb5ab52f21ec810893a429a3622c (diff)
DA: align delivery of mouse, touch, and synthesized mouse events to childMouseEventFilter
Currently the delivery of pointer events to childMouseEventFilters differs depending on if the event is a mouse, touch, or synthesized mouse event. If case of a mouse event, it will be sent to all the filters up the parent chain, even if one of the filters along the way returns true. If it's a touch event, propagation will stop as soon as a filter returns true. What does it mean that childMouseEventFilter returns true? According to tst_QQuickWindow::testChildMouseEventFilter(), if a childMouseEventFilter returns true, the event should be stopped from being sent to the receiver, and only the receiver. It should not be stopped from propagating to other childMouseEventFilters up the parent chain. This is explicitly tested with data row "r1 rejects and filters". Therefore, in order to make testChildMouseEventFilter() pass, not only for mouse events, but also for touch and synthesized mouse events, this patch will make the following changes: 1) Remove the early 'return' statement after a touch event was filtered by a childMouseEventFilter. This will make sure that the touch event will continue to propagate to parent childMouseEventFilters, equal to how it works for mouse and synthesized mouse events. 2) For both touch-, and synthesized mouse events, we deliver a (localized) copy of the original touch event to childMouseEventFilter(). The filter can then choose to accept or ignore this copy. But as it stood, we would never sync back the accept state from the copy to the original event. The result was that the original event would continue to propagate, regardless of accepted state set by the filter. This patch will therefore sync the accepted state from the copy back to the original event, when the event is filtered. This will make sure that if a filter e.g ignores the event, the receiver will not receive the event (since it was filtered), and the event will propagate to the parent (since it was ignored). Which is equal to how it works for mouse events. 3) For both touch and synthesized mouse events, we used to always set an exclusive grab on the affected event points if a childMouseEventFilter filtered an event. This is different from mouse event delivery, where we only set a grab when the event is also accepted. And the latter is also (most likely) the correct thing to do; If the event is ignored, it means that the filter says (on behalf of the receiver) that it doesn't want the event. And it doesn't make sense then (AFAICS) to still grab the event points. This patch will therefore, equal to mouse event delivery, ensure that we only give a filter an exclusive grab on the touch points when the event was actually accepted. With these changes applied, we then also change the tst_qquickwindow::testChildMouseEventFilter() to run three times, once for mouse event, touch events, and synthesized mouse events, to verify that they're all aligned. Fixes: QTBUG-115953 Pick-to: 6.7 6.6 6.5 Change-Id: I8b5b1faadc907e804b7e21c667888db7cfe28872 Reviewed-by: Shawn Rutledge <shawn.rutledge@qt.io>
-rw-r--r--src/quick/items/qquickitem.cpp6
-rw-r--r--src/quick/util/qquickdeliveryagent.cpp25
-rw-r--r--tests/auto/quick/qquickwindow/tst_qquickwindow.cpp109
3 files changed, 81 insertions, 59 deletions
diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp
index 58fb51e98a..6e71f64b9c 100644
--- a/src/quick/items/qquickitem.cpp
+++ b/src/quick/items/qquickitem.cpp
@@ -4358,7 +4358,11 @@ void QQuickItem::dropEvent(QDropEvent *event)
This method will only be called if filtersChildMouseEvents() is \c true.
Return \c true if the specified \a event should not be passed on to the
- specified child \a item, and \c false otherwise.
+ specified child \a item, and \c false otherwise. If you return \c true, you
+ should also \l {QEvent::accept()}{accept} or \l {QEvent::ignore()}{ignore}
+ the \a event, to signal if event propagation should stop or continue.
+ The \a event will, however, always be sent to all childMouseEventFilters
+ up the parent chain.
\note Despite the name, this function filters all QPointerEvent instances
during delivery to all children (typically mouse, touch, and tablet
diff --git a/src/quick/util/qquickdeliveryagent.cpp b/src/quick/util/qquickdeliveryagent.cpp
index e2bb83e638..4672a16aec 100644
--- a/src/quick/util/qquickdeliveryagent.cpp
+++ b/src/quick/util/qquickdeliveryagent.cpp
@@ -2609,15 +2609,18 @@ bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *eve
QQuickItemPrivate::get(receiver)->localizedTouchEvent(static_cast<QTouchEvent *>(event), true, &filteringParentTouchEvent);
if (filteringParentTouchEvent.type() != QEvent::None) {
qCDebug(lcTouch) << "letting parent" << filteringParent << "filter for" << receiver << &filteringParentTouchEvent;
- if (filteringParent->childMouseEventFilter(receiver, &filteringParentTouchEvent)) {
+ filtered = filteringParent->childMouseEventFilter(receiver, &filteringParentTouchEvent);
+ if (filtered) {
qCDebug(lcTouch) << "touch event intercepted by childMouseEventFilter of " << filteringParent;
+ event->setAccepted(filteringParentTouchEvent.isAccepted());
skipDelivery.append(filteringParent);
- for (auto point : filteringParentTouchEvent.points()) {
- const QQuickItem *exclusiveGrabber = qobject_cast<const QQuickItem *>(event->exclusiveGrabber(point));
- if (!exclusiveGrabber || !exclusiveGrabber->keepTouchGrab())
- event->setExclusiveGrabber(point, filteringParent);
+ if (event->isAccepted()) {
+ for (auto point : filteringParentTouchEvent.points()) {
+ const QQuickItem *exclusiveGrabber = qobject_cast<const QQuickItem *>(event->exclusiveGrabber(point));
+ if (!exclusiveGrabber || !exclusiveGrabber->keepTouchGrab())
+ event->setExclusiveGrabber(point, filteringParent);
+ }
}
- return true;
} else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents)) &&
!filteringParent->acceptTouchEvents()) {
qCDebug(lcTouch) << "touch event NOT intercepted by childMouseEventFilter of " << filteringParent
@@ -2653,17 +2656,17 @@ bool QQuickDeliveryAgentPrivate::sendFilteredPointerEventImpl(QPointerEvent *eve
// touchMouseId and touchMouseDevice must be set, even if it's only temporarily and isn't grabbed.
touchMouseId = tp.id();
touchMouseDevice = event->pointingDevice();
- if (filteringParent->childMouseEventFilter(receiver, &mouseEvent)) {
+ filtered = filteringParent->childMouseEventFilter(receiver, &mouseEvent);
+ if (filtered) {
qCDebug(lcTouch) << "touch event intercepted as synth mouse event by childMouseEventFilter of " << filteringParent;
+ event->setAccepted(mouseEvent.isAccepted());
skipDelivery.append(filteringParent);
- if (t != QEvent::MouseButtonRelease) {
+ if (event->isAccepted() && event->isBeginEvent()) {
qCDebug(lcTouchTarget) << "TP (mouse)" << Qt::hex << tp.id() << "->" << filteringParent;
filteringParentTouchEvent.setExclusiveGrabber(tp, filteringParent);
touchMouseUnset = false; // We want to leave touchMouseId and touchMouseDevice set
- if (mouseEvent.isAccepted())
- filteringParent->grabMouse();
+ filteringParent->grabMouse();
}
- filtered = true;
}
if (touchMouseUnset)
// Now that we're done sending a synth mouse event, and it wasn't grabbed,
diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
index 0df13c5087..cb59b275a5 100644
--- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
+++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
@@ -3299,57 +3299,48 @@ public:
*/
bool testFilterPreConditions() const { return !m_filterNotPreAccepted; }
static QVector<DeliveryRecord> &deliveryList() { return m_deliveryList; }
- static QSet<QEvent::Type> &includedEventTypes()
+ static void setExpectedDeliveryList(const QVector<DeliveryRecord> &v) { m_expectedDeliveryList = v; }
+
+ bool isRelevant(QQuickItem *receiver, QEvent *e)
{
- if (m_includedEventTypes.isEmpty())
- m_includedEventTypes << QEvent::MouseButtonPress;
- return m_includedEventTypes;
+ if (receiver->acceptTouchEvents())
+ return e->type() == QEvent::TouchBegin;
+ return e->type() == QEvent::MouseButtonPress;
}
- static void setExpectedDeliveryList(const QVector<DeliveryRecord> &v) { m_expectedDeliveryList = v; }
protected:
bool childMouseEventFilter(QQuickItem *i, QEvent *e) override
{
+ if (!isRelevant(i, e))
+ return QQuickRectangle::childMouseEventFilter(i, e);
+
appendEvent(this, i, e);
- switch (e->type()) {
- case QEvent::MouseButtonPress:
- if (!e->isAccepted())
- m_filterNotPreAccepted = true;
- e->setAccepted(m_filterAccepts);
- // qCDebug(lcTests) << objectName() << i->objectName();
- return m_filterReturns;
- default:
- break;
- }
- return QQuickRectangle::childMouseEventFilter(i, e);
+ if (!e->isAccepted())
+ m_filterNotPreAccepted = true;
+ e->setAccepted(m_filterAccepts);
+ return m_filterReturns;
}
bool event(QEvent *e) override
{
+ if (!isRelevant(this, e))
+ QQuickRectangle::event(e);
+
appendEvent(nullptr, this, e);
- switch (e->type()) {
- case QEvent::MouseButtonPress:
- // qCDebug(lcTests) << objectName();
- e->setAccepted(m_eventAccepts);
- return true;
- default:
- break;
- }
- return QQuickRectangle::event(e);
+ e->setAccepted(m_eventAccepts);
+ return true;
}
private:
static void appendEvent(QQuickItem *filter, QQuickItem *receiver, QEvent *event) {
- if (includedEventTypes().contains(event->type())) {
- auto record = DeliveryRecord(filter ? filter->objectName() : QString(), receiver ? receiver->objectName() : QString());
- int i = m_deliveryList.size();
- if (m_expectedDeliveryList.size() > i && m_expectedDeliveryList[i] == record)
- qCDebug(lcTests).noquote().nospace() << i << ": " << record;
- else
- qCDebug(lcTests).noquote().nospace() << i << ": " << record
- << ", expected " << (m_expectedDeliveryList.size() > i ? m_expectedDeliveryList[i].toString() : QLatin1String("nothing")) << " <---";
- m_deliveryList << record;
- }
+ auto record = DeliveryRecord(filter ? filter->objectName() : QString(), receiver ? receiver->objectName() : QString());
+ int i = m_deliveryList.size();
+ if (m_expectedDeliveryList.size() > i && m_expectedDeliveryList[i] == record)
+ qCDebug(lcTests).noquote().nospace() << i << ": " << record;
+ else
+ qCDebug(lcTests).noquote().nospace() << i << ": " << record
+ << ", expected " << (m_expectedDeliveryList.size() > i ? m_expectedDeliveryList[i].toString() : QLatin1String("nothing")) << " <---";
+ m_deliveryList << record;
}
bool m_eventAccepts;
bool m_filterReturns;
@@ -3359,12 +3350,10 @@ private:
// list of (filtering-parent . receiver) pairs
static DeliveryRecordVector m_expectedDeliveryList;
static DeliveryRecordVector m_deliveryList;
- static QSet<QEvent::Type> m_includedEventTypes;
};
DeliveryRecordVector EventItem::m_expectedDeliveryList;
DeliveryRecordVector EventItem::m_deliveryList;
-QSet<QEvent::Type> EventItem::m_includedEventTypes;
typedef QVector<const char*> CharStarVector;
@@ -3389,11 +3378,17 @@ void tst_qquickwindow::testChildMouseEventFilter_data()
// r0->r1->r2->r3
//
QTest::addColumn<QPoint>("mousePos");
+ QTest::addColumn<QString>("eventMode");
QTest::addColumn<InputState>("inputState");
QTest::addColumn<DeliveryRecordVector>("expectedDeliveryOrder");
- QTest::newRow("if filtered and rejected, do not deliver it to the item that filtered it")
+ for (const QString &eventMode : {"mouse", "touch", "touchToMouse"}) {
+
+ #define desc(txt) qPrintable(QString("%1 events, ").arg(eventMode) + txt)
+
+ QTest::newRow(desc("if filtered and rejected, do not deliver it to the item that filtered it"))
<< QPoint(100, 100)
+ << eventMode
<< InputState({
// | event() | child mouse filter
// +---------+---------+---------+---------
@@ -3411,8 +3406,9 @@ void tst_qquickwindow::testChildMouseEventFilter_data()
<< DeliveryRecord("r1")
);
- QTest::newRow("no filtering, no accepting")
+ QTest::newRow(desc("no filtering, no accepting"))
<< QPoint(100, 100)
+ << eventMode
<< InputState({
// | event() | child mouse filter
// +---------+---------+---------+---------
@@ -3431,8 +3427,9 @@ void tst_qquickwindow::testChildMouseEventFilter_data()
<< DeliveryRecord("root")
);
- QTest::newRow("all filtering, no accepting")
+ QTest::newRow(desc("all filtering, no accepting"))
<< QPoint(100, 100)
+ << eventMode
<< InputState({
// | event() | child mouse filter
// +---------+---------+---------+---------
@@ -3458,8 +3455,9 @@ void tst_qquickwindow::testChildMouseEventFilter_data()
);
- QTest::newRow("some filtering, no accepting")
+ QTest::newRow(desc("some filtering, no accepting"))
<< QPoint(100, 100)
+ << eventMode
<< InputState({
// | event() | child mouse filter
// +---------+---------+---------+---------
@@ -3483,8 +3481,9 @@ void tst_qquickwindow::testChildMouseEventFilter_data()
<< DeliveryRecord("root")
);
- QTest::newRow("r1 accepts")
+ QTest::newRow(desc("r1 accepts"))
<< QPoint(100, 100)
+ << eventMode
<< InputState({
// | event() | child mouse filter
// +---------+---------+---------+---------
@@ -3506,8 +3505,9 @@ void tst_qquickwindow::testChildMouseEventFilter_data()
<< DeliveryRecord("r1")
);
- QTest::newRow("r1 rejects and filters")
+ QTest::newRow(desc("r1 rejects and filters"))
<< QPoint(100, 100)
+ << eventMode
<< InputState({
// | event() | child mouse filter
// +---------+---------+---------+---------
@@ -3530,12 +3530,13 @@ void tst_qquickwindow::testChildMouseEventFilter_data()
<< DeliveryRecord("r0")
<< DeliveryRecord("root")
);
-
+ }
}
void tst_qquickwindow::testChildMouseEventFilter()
{
QFETCH(QPoint, mousePos);
+ QFETCH(QString, eventMode);
QFETCH(InputState, inputState);
QFETCH(DeliveryRecordVector, expectedDeliveryOrder);
@@ -3550,21 +3551,28 @@ void tst_qquickwindow::testChildMouseEventFilter()
QScopedPointer<EventFilter> rootFilter(new EventFilter);
root->installEventFilter(rootFilter.data());
+ const bool useMouseEvents = eventMode == "mouse";
+ const bool acceptTouchEvents = eventMode == "touch";
+
// Create 4 items; each item a child of the previous item.
EventItem *r[4];
r[0] = new EventItem(root);
r[0]->setColor(QColor(0x404040));
r[0]->setWidth(200);
r[0]->setHeight(200);
+ r[0]->setAcceptTouchEvents(acceptTouchEvents);
r[1] = new EventItem(r[0]);
r[1]->setColor(QColor(0x606060));
+ r[1]->setAcceptTouchEvents(acceptTouchEvents);
r[2] = new EventItem(r[1]);
r[2]->setColor(Qt::red);
+ r[2]->setAcceptTouchEvents(acceptTouchEvents);
r[3] = new EventItem(r[2]);
r[3]->setColor(Qt::green);
+ r[3]->setAcceptTouchEvents(acceptTouchEvents);
for (uint i = 0; i < sizeof(r)/sizeof(EventItem*); ++i) {
r[i]->setEventAccepts(inputState.r[i].eventAccepts);
@@ -3581,7 +3589,11 @@ void tst_qquickwindow::testChildMouseEventFilter()
DeliveryRecordVector &actualDeliveryOrder = EventItem::deliveryList();
actualDeliveryOrder.clear();
- QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, mousePos);
+
+ if (useMouseEvents)
+ QTest::mousePress(&window, Qt::LeftButton, Qt::NoModifier, mousePos);
+ else
+ QTest::touchEvent(&window, touchDevice).press(0, mousePos, &window);
// Check if event got delivered to the root item. If so, append it to the list of items the event got delivered to
if (rootFilter->events.contains(QEvent::MouseButtonPress))
@@ -3597,8 +3609,11 @@ void tst_qquickwindow::testChildMouseEventFilter()
QVERIFY(item->testFilterPreConditions());
}
- // "restore" mouse state
- QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, mousePos);
+ // "restore" mouse/touch state
+ if (useMouseEvents)
+ QTest::mouseRelease(&window, Qt::LeftButton, Qt::NoModifier, mousePos);
+ else
+ QTest::touchEvent(&window, touchDevice).release(0, mousePos, &window);
}
void tst_qquickwindow::cleanupGrabsOnRelease()