aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorShawn Rutledge <shawn.rutledge@qt.io>2016-12-28 20:27:18 +0100
committerShawn Rutledge <shawn.rutledge@qt.io>2017-01-27 09:36:03 +0000
commit9b5fc80af28580e9672792dd511d876a93947882 (patch)
treeabaccc5d10bbb5bf8aba52e0e4cdbc08378bacf4
parent9225ac7348c9023093b6ef8d4519087c7dddeaa2 (diff)
build a vector of child-filtering parents before delivery of pointer event
Formerly during normal mouse or touch event delivery, sending it to the Item needed to be done via QQuickWindow::sendEvent, which would then call sendFilteredMouseEvent, which is a recursive function to visit all the item's parents, check whether filtersChildMouseEvents() returns true, if so then return early if childMouseEventFilter() returns true. This is the mechanism by which Flickable (for example) can monitor the movements of an eventpoint even while one of its children has an exclusive grab, and can steal the grab away. Now, we do this by building a vector of such parents first, then visiting them in order. It might be more efficient, it eliminates the recursion, and should eliminate the need for a QSet to ensure that we don't visit the same parent more than once. We can't change the behavior of QQuickWindow::sendEvent() because it's public API, but now we don't use it as much internally. Change-Id: I686fc5612c66eac09ec05c381a648ec65dec3923 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
-rw-r--r--src/quick/items/qquickflickable.cpp3
-rw-r--r--src/quick/items/qquickwindow.cpp84
-rw-r--r--src/quick/items/qquickwindow_p.h5
-rw-r--r--tests/auto/qmltest/events/tst_touch.qml10
-rw-r--r--tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml3
5 files changed, 102 insertions, 3 deletions
diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp
index 45bb2a367d..a09ffff816 100644
--- a/src/quick/items/qquickflickable.cpp
+++ b/src/quick/items/qquickflickable.cpp
@@ -1557,6 +1557,8 @@ void QQuickFlickablePrivate::replayDelayedPress()
// If we have the grab, release before delivering the event
if (QQuickWindow *w = q->window()) {
+ QQuickWindowPrivate *wpriv = QQuickWindowPrivate::get(w);
+ wpriv->allowChildEventFiltering = false; // don't allow re-filtering during replay
replayingPressEvent = true;
if (w->mouseGrabberItem() == q)
q->ungrabMouse();
@@ -1564,6 +1566,7 @@ void QQuickFlickablePrivate::replayDelayedPress()
// Use the event handler that will take care of finding the proper item to propagate the event
QCoreApplication::sendEvent(w, mouseEvent.data());
replayingPressEvent = false;
+ wpriv->allowChildEventFiltering = true;
}
}
}
diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp
index fa3093c245..83aff5e07e 100644
--- a/src/quick/items/qquickwindow.cpp
+++ b/src/quick/items/qquickwindow.cpp
@@ -496,6 +496,7 @@ QQuickWindowPrivate::QQuickWindowPrivate()
, persistentSceneGraph(true)
, lastWheelEventAccepted(false)
, componentCompleted(true)
+ , allowChildEventFiltering(true)
, lastFocusReason(Qt::OtherFocusReason)
, renderTarget(0)
, renderTargetId(0)
@@ -1663,11 +1664,16 @@ void QQuickWindowPrivate::deliverMouseEvent(QQuickPointerMouseEvent *pointerEven
return;
}
+ if (allowChildEventFiltering) {
+ if (sendFilteredPointerEvent(pointerEvent, grabber))
+ return;
+ }
+
// send update
QPointF localPos = grabber->mapFromScene(lastMousePosition);
auto me = pointerEvent->asMouseEvent(localPos);
me->accept();
- q->sendEvent(grabber, me);
+ QCoreApplication::sendEvent(grabber, me);
point->setAccepted(me->isAccepted());
// release event, make sure to ungrab if there still is a grabber
@@ -1887,7 +1893,7 @@ bool QQuickWindowPrivate::deliverTouchCancelEvent(QTouchEvent *event)
for (QObject *grabber: qAsConst(grabbers)) {
if (QQuickItem *grabberItem = qmlobject_cast<QQuickItem *>(grabber))
- q->sendEvent(grabberItem, event);
+ QCoreApplication::sendEvent(grabberItem, event);
else //if (QQuickPointerHandler *grabberHandler = qmlobject_cast<QQuickPointerHandler *>(grabber))
// grabberHandler->handlePointerEvent()
qWarning("unexpected: can't deliver touch cancel to a PointerHandler (yet?)");
@@ -2255,6 +2261,8 @@ bool QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *eve
QQuickItem *receiver = qmlobject_cast<QQuickItem *>(grabber);
if (!receiver)
receiver = static_cast<QQuickPointerHandler *>(grabber)->parentItem();
+ if (allowChildEventFiltering && sendFilteredPointerEvent(event, receiver))
+ return true;
deliverMatchingPointsToItem(receiver, event, hasFiltered);
}
@@ -2275,6 +2283,12 @@ bool QQuickWindowPrivate::deliverPressEvent(QQuickPointerEvent *event, QSet<QQui
}
}
+ if (allowChildEventFiltering) {
+ updateFilteringParentItems(targetItems);
+ if (sendFilteredPointerEvent(event, nullptr))
+ return true;
+ }
+
for (QQuickItem *item: targetItems) {
deliverMatchingPointsToItem(item, event, hasFiltered);
if (event->allPointsAccepted())
@@ -2308,7 +2322,7 @@ bool QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo
Q_ASSERT(item->contains(localPos)); // transform is checked already
QMouseEvent *me = event->asMouseEvent(localPos);
me->accept();
- q->sendEvent(item, me);
+ QCoreApplication::sendEvent(item, me);
if (me->isAccepted()) {
auto mouseGrabber = q->mouseGrabberItem();
if (mouseGrabber && mouseGrabber != item && mouseGrabber != oldMouseGrabber) {
@@ -2553,6 +2567,7 @@ QQuickItem *QQuickWindowPrivate::findCursorItem(QQuickItem *item, const QPointF
}
#endif
+// TODO assimilate this logic and remove this function
bool QQuickWindowPrivate::sendFilteredTouchEvent(QQuickItem *target, QQuickItem *item, QQuickPointerTouchEvent *event, QSet<QQuickItem *> *hasFiltered)
{
Q_Q(QQuickWindow);
@@ -2628,6 +2643,69 @@ bool QQuickWindowPrivate::sendFilteredTouchEvent(QQuickItem *target, QQuickItem
return sendFilteredTouchEvent(target->parentItem(), item, event, hasFiltered) || filtered;
}
+void QQuickWindowPrivate::updateFilteringParentItems(const QVector<QQuickItem *> &targetItems)
+{
+ if (Q_UNLIKELY(DBG_MOUSE_TARGET().isDebugEnabled())) {
+ // qDebug() << map(&objectName, targetItems) but done the hard way because C++ is still that primitive
+ QStringList targetNames;
+ for (QQuickItem *t : targetItems)
+ targetNames << (QLatin1String(t->metaObject()->className()) + QLatin1Char(' ') + t->objectName());
+ qCDebug(DBG_MOUSE_TARGET) << "finding potential filtering parents of" << targetNames;
+ }
+ filteringParentItems.clear();
+ for (QQuickItem *item : targetItems) {
+ QQuickItemPrivate *itemPriv = QQuickItemPrivate::get(item);
+ // If the item neither handles events nor has handlers which do, then it will never be a receiver, so filtering is irrelevant
+ if (!item->acceptedMouseButtons() && !(itemPriv->extra.isAllocated() && !itemPriv->extra->pointerHandlers.isEmpty()))
+ continue; // TODO there's no acceptTouchEvents, so it's hard to avoid skipping any items which handle only touch
+ QQuickItem *parent = item->parentItem();
+ while (parent) {
+ if (parent->filtersChildMouseEvents()) {
+ bool foundParent = false;
+ for (const QPair<QQuickItem*,QQuickItem*> existingItemAndParent : filteringParentItems)
+ if (existingItemAndParent.second == parent)
+ foundParent = true;
+ if (!foundParent)
+ filteringParentItems.append(QPair<QQuickItem*,QQuickItem*>(item, parent));
+ }
+ parent = parent->parentItem();
+ }
+ }
+}
+
+bool QQuickWindowPrivate::sendFilteredPointerEvent(QQuickPointerEvent *event, QQuickItem *receiver)
+{
+ bool ret = false;
+ if (QQuickPointerMouseEvent *pme = event->asPointerMouseEvent()) {
+ for (QPair<QQuickItem *,QQuickItem *> itemAndParent : filteringParentItems) {
+ QQuickItem *item = receiver ? receiver : itemAndParent.first;
+ QQuickItem *filteringParent = itemAndParent.second;
+ if (item == filteringParent)
+ continue; // a filtering item never needs to filter for itself
+ QPointF localPos = item->mapFromScene(pme->point(0)->scenePos());
+ QMouseEvent *me = pme->asMouseEvent(localPos);
+ if (filteringParent->childMouseEventFilter(item, me)) {
+ event->setAccepted(true);
+ ret = true;
+ }
+ }
+ } else if (QQuickPointerTouchEvent *pte = event->asPointerTouchEvent()) {
+ QTouchEvent *te = pte->asTouchEvent();
+ for (QPair<QQuickItem *,QQuickItem *> itemAndParent : filteringParentItems) {
+ QQuickItem *item = receiver ? receiver : itemAndParent.first;
+ QQuickItem *filteringParent = itemAndParent.second;
+ if (item == filteringParent)
+ continue; // a filtering item never needs to filter for itself
+ if (filteringParent->childMouseEventFilter(item, te)) {
+ for (auto point: qAsConst(te->touchPoints()))
+ event->pointById(point.id())->setAccepted();
+ return true;
+ }
+ }
+ }
+ return ret;
+}
+
bool QQuickWindowPrivate::sendFilteredMouseEvent(QQuickItem *target, QQuickItem *item, QEvent *event, QSet<QQuickItem *> *hasFiltered)
{
if (!target)
diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h
index 30e3b71d0a..38c1b0a4d4 100644
--- a/src/quick/items/qquickwindow_p.h
+++ b/src/quick/items/qquickwindow_p.h
@@ -148,6 +148,7 @@ public:
static QMouseEvent *cloneMouseEvent(QMouseEvent *event, QPointF *transformedLocalPos = 0);
void deliverMouseEvent(QQuickPointerMouseEvent *pointerEvent);
bool sendFilteredMouseEvent(QQuickItem *, QQuickItem *, QEvent *, QSet<QQuickItem *> *);
+ bool sendFilteredPointerEvent(QQuickPointerEvent *event, QQuickItem *receiver);
#if QT_CONFIG(wheelevent)
bool deliverWheelEvent(QQuickItem *, QWheelEvent *);
#endif
@@ -174,6 +175,7 @@ public:
QVector<QQuickItem *> pointerTargets(QQuickItem *, const QPointF &, bool checkMouseButtons) const;
QVector<QQuickItem *> mergePointerTargets(const QVector<QQuickItem *> &list1, const QVector<QQuickItem *> &list2) const;
+ void updateFilteringParentItems(const QVector<QQuickItem *> &targetItems);
// hover delivery
bool deliverHoverEvent(QQuickItem *, const QPointF &scenePos, const QPointF &lastScenePos, Qt::KeyboardModifiers modifiers, ulong timestamp, bool &accepted);
@@ -222,6 +224,7 @@ public:
QList<QSGNode *> cleanupNodeList;
QVector<QQuickItem *> itemsToPolish;
+ QVector<QPair<QQuickItem *,QQuickItem *> > filteringParentItems; // item:parent pairs
qreal devicePixelRatio;
QMetaObject::Connection physicalDpiChangedConnection;
@@ -259,6 +262,8 @@ public:
uint lastWheelEventAccepted : 1;
bool componentCompleted : 1;
+ bool allowChildEventFiltering : 1;
+
Qt::FocusReason lastFocusReason;
QOpenGLFramebufferObject *renderTarget;
diff --git a/tests/auto/qmltest/events/tst_touch.qml b/tests/auto/qmltest/events/tst_touch.qml
index 5b209a6d0b..fd603e5a71 100644
--- a/tests/auto/qmltest/events/tst_touch.qml
+++ b/tests/auto/qmltest/events/tst_touch.qml
@@ -35,6 +35,16 @@ MultiPointTouchArea {
width: 100
height: 100
+ // touchUpdatedSpy stores the QQuickTouchPoint, and in some cases
+ // MultiPointTouchArea can delete it out from under us.
+ // (test_simpleChain was failing because touchUpdatedSpy.signalArguments[0][0][0]
+ // ended up as an empty object somehow.) If we declare
+ // all the touchpoints that this test will use, that won't happen.
+ touchPoints: [
+ TouchPoint { },
+ TouchPoint { }
+ ]
+
SignalSpy {
id: touchUpdatedSpy
target: touchArea
diff --git a/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml b/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml
index 81187f3c2f..902920babc 100644
--- a/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml
+++ b/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml
@@ -18,6 +18,8 @@ Flickable {
height: 300
color: "yellow"
+ objectName: "yellowRect"
+
Flickable {
id: inner
objectName: "innerFlickable"
@@ -30,6 +32,7 @@ Flickable {
Rectangle {
anchors.fill: parent
anchors.margins: 100
+ objectName: "blueRect"
color: "blue"
}
MouseArea {