Allow parent to filter out-of-bounds synth-mouse for grabbing handler

Consider

Flickable {
	Text {
		TapHandler { gesturePolicy: TapHandler.ReleaseWithinBounds }
	}
}

On press, TapHandler gets the exclusive grab.  Now drag vertically.
The Text is short in stature, so your finger soon strays out of bounds
of the Text, likely before you have dragged past the drag threshold.
In this case, we want Flickable to continue to filter the move events
because of the fact that TapHandler is the grabber.  If it was a
MouseArea instead of a TapHandler, it already worked that way; so this
makes behavior of handlers more consistent with that.

More specifically: QQuickPointerTouchEvent::touchEventForItem() now
generates a touch event even if the touchpoint is not within the bounds
of the given item, but is grabbed by one of that item's handlers.  Until
now, we had that exception only if it was grabbed by the item itself.

tst_FlickableInterop::touchAndDragHandlerOnFlickable now always drags
the delegate at index 2 (the third one) from its upper-right corner,
upwards and to the left.  The first drag goes outside the delegate's
bounds, but the Flickable/ListView/TableView filters and takes over
anyway (on the next drag), to prove that it is correctly depending
on the grab that the TapHandler (or DragHandler) took on press.

Pick-to: 5.15
Pick-to: 6.0
Fixes: QTBUG-75223
Change-Id: Ie4e22c87be0af9aa3ff0146067b7705949b15c40
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Shawn Rutledge 2020-11-27 08:59:20 +01:00
parent e2a7457985
commit 1e1674849a
7 changed files with 67 additions and 45 deletions

View File

@ -259,7 +259,8 @@ void QQuickTapHandler::setGesturePolicy(QQuickTapHandler::GesturePolicy gestureP
void QQuickTapHandler::setPressed(bool press, bool cancel, QPointerEvent *event, QEventPoint &point)
{
if (m_pressed != press) {
qCDebug(lcTapHandler) << objectName() << "pressed" << m_pressed << "->" << press << (cancel ? "CANCEL" : "") << point;
qCDebug(lcTapHandler) << objectName() << "pressed" << m_pressed << "->" << press
<< (cancel ? "CANCEL" : "") << point << "gp" << m_gesturePolicy;
m_pressed = press;
connectPreRenderSignal(press);
updateTimeHeld();

View File

@ -8377,9 +8377,9 @@ QQuickItemLayer *QQuickItemPrivate::layer() const
coordinate system.
Returns an invalid event with type \l QEvent::None if all points are
stationary, or there are no points inside the item, or none of the points
were pressed inside and the item was not grabbing any of them and
\a isFiltering is false.
stationary; or there are no points inside the item; or none of the points
were pressed inside, neither the item nor any of its handlers is grabbing
any of them, and \a isFiltering is false.
When \a isFiltering is true, it is assumed that the item cares about all
points which are inside its bounds, because most filtering items need to
@ -8397,11 +8397,18 @@ void QQuickItemPrivate::localizedTouchEvent(const QTouchEvent *event, bool isFil
for (auto &p : event->points()) {
if (p.isAccepted())
continue;
// include points where item is the grabber
// include points where item is the grabber, or if any of its handlers is the grabber while some parent is filtering
auto pointGrabber = event->exclusiveGrabber(p);
bool isGrabber = (pointGrabber == q);
if (!isGrabber && pointGrabber && isFiltering) {
auto handlerGrabber = qmlobject_cast<QQuickPointerHandler *>(pointGrabber);
if (handlerGrabber && handlerGrabber->parentItem() == q)
isGrabber = true;
}
if (isGrabber)
anyGrabber = true;
// include points inside the bounds if no other item is the grabber or if the item is filtering
const auto localPos = q->mapFromScene(p.scenePosition());
bool isInside = q->contains(localPos);

View File

@ -64,7 +64,7 @@ ListView {
id: buttonDrag
objectName: "buttonDrag"
}
Component.onCompleted: if (!root.buttonUnderTest) {
Component.onCompleted: if (!root.buttonUnderTest && index == 2) {
root.buttonUnderTest = this
root.delegateUnderTest = parent
}

View File

@ -68,7 +68,7 @@ TableView {
id: buttonDrag
objectName: "buttonDrag"
}
Component.onCompleted: if (!root.buttonUnderTest) {
Component.onCompleted: if (!root.buttonUnderTest && index == 2) {
root.buttonUnderTest = this
root.delegateUnderTest = parent
}

View File

@ -48,11 +48,12 @@ ListView {
}
delegate: Rectangle {
objectName: "itemview delegate"
objectName: "itemview delegate " + index
color: delegateTap.pressed ? "wheat" : "beige"
width: parent.width; height: 140
Text { text: index }
Rectangle {
objectName: "button"
objectName: "button " + index
anchors.centerIn: parent
border.color: "tomato"
border.width: 10
@ -61,16 +62,16 @@ ListView {
height: 100
TapHandler {
id: innerTap
objectName: "buttonTap"
objectName: "buttonTap " + index
}
Component.onCompleted: if (!root.buttonUnderTest) {
Component.onCompleted: if (!root.buttonUnderTest && index == 2) {
root.buttonUnderTest = this
root.delegateUnderTest = parent
}
}
TapHandler {
id: delegateTap
objectName: "delegateTap"
objectName: "delegateTap " + index
}
}

View File

@ -68,7 +68,7 @@ TableView {
id: innerTap
objectName: "buttonTap"
}
Component.onCompleted: if (!root.buttonUnderTest) {
Component.onCompleted: if (!root.buttonUnderTest && index == 2) {
root.buttonUnderTest = this
root.delegateUnderTest = parent
}

View File

@ -664,30 +664,36 @@ void tst_FlickableInterop::touchAndDragHandlerOnFlickable_data()
QTest::addColumn<QByteArray>("qmlFile");
QTest::addColumn<bool>("pressDelay");
QTest::addColumn<bool>("targetNull");
QTest::newRow("tapOnFlickable") << QByteArray("tapOnFlickable.qml") << false << false;
QTest::newRow("tapOnList") << QByteArray("tapOnList.qml") << false << false;
QTest::newRow("tapOnTable") << QByteArray("tapOnTable.qml") << false << false;
QTest::newRow("dragOnFlickable") << QByteArray("dragOnFlickable.qml") << false << false;
QTest::newRow("dragOnList") << QByteArray("dragOnList.qml") << false << false;
QTest::newRow("dragOnTable") << QByteArray("dragOnTable.qml") << false << false;
QTest::newRow("tapDelayOnFlickable") << QByteArray("tapOnFlickable.qml") << true << false;
QTest::newRow("tapDelayOnList") << QByteArray("tapOnList.qml") << true << false;
QTest::newRow("tapDelayOnTable") << QByteArray("tapOnTable.qml") << true << false;
QTest::newRow("dragDelayOnFlickable") << QByteArray("dragOnFlickable.qml") << true << false;
QTest::newRow("dragDelayOnList") << QByteArray("dragOnList.qml") << true << false;
QTest::newRow("dragDelayOnTable") << QByteArray("dragOnTable.qml") << true << false;
QTest::newRow("tapOnFlickableWithNullTargets") << QByteArray("tapOnFlickable.qml") << false << true;
QTest::newRow("tapOnListWithNullTargets") << QByteArray("tapOnList.qml") << false << true;
QTest::newRow("tapOnTableWithNullTargets") << QByteArray("tapOnTable.qml") << false << true;
QTest::newRow("dragOnFlickableWithNullTargets") << QByteArray("dragOnFlickable.qml") << false << true;
QTest::newRow("dragOnListWithNullTargets") << QByteArray("dragOnList.qml") << false << true;
QTest::newRow("dragOnTableWithNullTargets") << QByteArray("dragOnTable.qml") << false << true;
QTest::newRow("tapDelayOnFlickableWithNullTargets") << QByteArray("tapOnFlickable.qml") << true << true;
QTest::newRow("tapDelayOnListWithNullTargets") << QByteArray("tapOnList.qml") << true << true;
QTest::newRow("tapDelayOnTableWithNullTargets") << QByteArray("tapOnTable.qml") << true << true;
QTest::newRow("dragDelayOnFlickableWithNullTargets") << QByteArray("dragOnFlickable.qml") << true << true;
QTest::newRow("dragDelayOnListWithNullTargets") << QByteArray("dragOnList.qml") << true << true;
QTest::newRow("dragDelayOnTableWithNullTargets") << QByteArray("dragOnTable.qml") << true << true;
QTest::addColumn<QQuickTapHandler::GesturePolicy>("tapGesturePolicy");
QTest::newRow("tapOnFlickable") << QByteArray("tapOnFlickable.qml") << false << false << QQuickTapHandler::DragThreshold;
QTest::newRow("tapOnFlickable-excl") << QByteArray("tapOnFlickable.qml") << false << false << QQuickTapHandler::ReleaseWithinBounds;
QTest::newRow("tapOnList") << QByteArray("tapOnList.qml") << false << false << QQuickTapHandler::DragThreshold;
// QTBUG-75223
QTest::newRow("tapOnList-excl") << QByteArray("tapOnList.qml") << false << false << QQuickTapHandler::ReleaseWithinBounds;
QTest::newRow("tapOnTable") << QByteArray("tapOnTable.qml") << false << false << QQuickTapHandler::DragThreshold;
QTest::newRow("dragOnFlickable") << QByteArray("dragOnFlickable.qml") << false << false << QQuickTapHandler::DragThreshold;
QTest::newRow("dragOnList") << QByteArray("dragOnList.qml") << false << false << QQuickTapHandler::DragThreshold;
QTest::newRow("dragOnTable") << QByteArray("dragOnTable.qml") << false << false << QQuickTapHandler::DragThreshold;
QTest::newRow("tapDelayOnFlickable") << QByteArray("tapOnFlickable.qml") << true << false << QQuickTapHandler::DragThreshold;
QTest::newRow("tapDelayOnFlickable-excl") << QByteArray("tapOnFlickable.qml") << true << false << QQuickTapHandler::ReleaseWithinBounds;
QTest::newRow("tapDelayOnList") << QByteArray("tapOnList.qml") << true << false << QQuickTapHandler::DragThreshold;
QTest::newRow("tapDelayOnList-excl") << QByteArray("tapOnList.qml") << true << false << QQuickTapHandler::ReleaseWithinBounds;
QTest::newRow("tapDelayOnTable") << QByteArray("tapOnTable.qml") << true << false << QQuickTapHandler::DragThreshold;
QTest::newRow("dragDelayOnFlickable") << QByteArray("dragOnFlickable.qml") << true << false << QQuickTapHandler::DragThreshold;
QTest::newRow("dragDelayOnList") << QByteArray("dragOnList.qml") << true << false << QQuickTapHandler::DragThreshold;
QTest::newRow("dragDelayOnTable") << QByteArray("dragOnTable.qml") << true << false << QQuickTapHandler::DragThreshold;
QTest::newRow("tapOnFlickableWithNullTargets") << QByteArray("tapOnFlickable.qml") << false << true << QQuickTapHandler::DragThreshold;
QTest::newRow("tapOnListWithNullTargets") << QByteArray("tapOnList.qml") << false << true << QQuickTapHandler::DragThreshold;
QTest::newRow("tapOnTableWithNullTargets") << QByteArray("tapOnTable.qml") << false << true << QQuickTapHandler::DragThreshold;
QTest::newRow("dragOnFlickableWithNullTargets") << QByteArray("dragOnFlickable.qml") << false << true << QQuickTapHandler::DragThreshold;
QTest::newRow("dragOnListWithNullTargets") << QByteArray("dragOnList.qml") << false << true << QQuickTapHandler::DragThreshold;
QTest::newRow("dragOnTableWithNullTargets") << QByteArray("dragOnTable.qml") << false << true << QQuickTapHandler::DragThreshold;
QTest::newRow("tapDelayOnFlickableWithNullTargets") << QByteArray("tapOnFlickable.qml") << true << true << QQuickTapHandler::DragThreshold;
QTest::newRow("tapDelayOnListWithNullTargets") << QByteArray("tapOnList.qml") << true << true << QQuickTapHandler::DragThreshold;
QTest::newRow("tapDelayOnTableWithNullTargets") << QByteArray("tapOnTable.qml") << true << true << QQuickTapHandler::DragThreshold;
QTest::newRow("dragDelayOnFlickableWithNullTargets") << QByteArray("dragOnFlickable.qml") << true << true << QQuickTapHandler::DragThreshold;
QTest::newRow("dragDelayOnListWithNullTargets") << QByteArray("dragOnList.qml") << true << true << QQuickTapHandler::DragThreshold;
QTest::newRow("dragDelayOnTableWithNullTargets") << QByteArray("dragOnTable.qml") << true << true << QQuickTapHandler::DragThreshold;
}
void tst_FlickableInterop::touchAndDragHandlerOnFlickable()
@ -695,6 +701,7 @@ void tst_FlickableInterop::touchAndDragHandlerOnFlickable()
QFETCH(QByteArray, qmlFile);
QFETCH(bool, pressDelay);
QFETCH(bool, targetNull);
QFETCH(QQuickTapHandler::GesturePolicy, tapGesturePolicy);
const int dragThreshold = QGuiApplication::styleHints()->startDragDistance();
QScopedPointer<QQuickView> windowPtr;
@ -703,11 +710,7 @@ void tst_FlickableInterop::touchAndDragHandlerOnFlickable()
QQuickFlickable *flickable = qmlobject_cast<QQuickFlickable*>(window->rootObject());
QVERIFY(flickable);
flickable->setPressDelay(pressDelay ? 5000 : 0);
QQuickItem *delegate = nullptr;
if (QQuickItemView *itemView = qmlobject_cast<QQuickItemView *>(flickable))
delegate = itemView->currentItem();
if (!delegate)
delegate = flickable->property("delegateUnderTest").value<QQuickItem*>();
QQuickItem *delegate = flickable->property("delegateUnderTest").value<QQuickItem*>();
QQuickItem *button = delegate ? delegate->findChild<QQuickItem*>("button")
: flickable->findChild<QQuickItem*>("button");
if (!button)
@ -716,8 +719,13 @@ void tst_FlickableInterop::touchAndDragHandlerOnFlickable()
QQuickPointerHandler *buttonHandler = button->findChild<QQuickPointerHandler*>();
QVERIFY(buttonHandler);
QQuickTapHandler *buttonTapHandler = qmlobject_cast<QQuickTapHandler *>(buttonHandler);
if (buttonTapHandler)
buttonTapHandler->setGesturePolicy(tapGesturePolicy);
QQuickDragHandler *buttonDragHandler = qmlobject_cast<QQuickDragHandler *>(buttonHandler);
QQuickPointerHandler *delegateHandler = delegate ? delegate->findChild<QQuickPointerHandler*>() : nullptr;
QQuickTapHandler *delegateTapHandler = qmlobject_cast<QQuickTapHandler *>(delegateHandler);
if (delegateTapHandler)
delegateTapHandler->setGesturePolicy(tapGesturePolicy);
QQuickPointerHandler *contentItemHandler = flickable->findChild<QQuickPointerHandler*>();
QVERIFY(contentItemHandler);
// a handler declared directly in a Flickable (or item view) must actually be a child of the contentItem,
@ -730,9 +738,12 @@ void tst_FlickableInterop::touchAndDragHandlerOnFlickable()
contentItemHandler->setTarget(nullptr);
}
// Drag one finger on the Flickable and make sure it flicks
// Drag one finger on the Flickable (between delegates) and make sure it flicks
QTest::QTouchEventSequence touchSeq = QTest::touchEvent(window, touchDevice, false);
QPoint p1(780, 460);
if (delegate)
p1 = delegate->mapToScene(delegate->clipRect().bottomRight()).toPoint() + QPoint(-1, 1);
qCDebug(lcPointerTests) << "drag between delegates starting @" << p1;
touchSeq.press(1, p1, window).commit();
QQuickTouchUtils::flush(window);
for (int i = 0; i < 4; ++i) {
@ -752,8 +763,8 @@ void tst_FlickableInterop::touchAndDragHandlerOnFlickable()
flickable->setContentY(0);
QTRY_COMPARE(flickable->isMoving(), false);
QVERIFY(delegateHandler);
QQuickTapHandler *delegateTapHandler = qmlobject_cast<QQuickTapHandler *>(delegateHandler);
p1 = button->mapToScene(button->clipRect().bottomRight()).toPoint() + QPoint(10, 0);
p1 = delegate->mapToScene(delegate->clipRect().topRight()).toPoint() + QPoint(-2, 2);
qCDebug(lcPointerTests) << "drag on delegate 2 starting @" << p1;
touchSeq.press(1, p1, window).commit();
QQuickTouchUtils::flush(window);
if (delegateTapHandler && !pressDelay)
@ -762,6 +773,8 @@ void tst_FlickableInterop::touchAndDragHandlerOnFlickable()
p1 -= QPoint(dragThreshold, dragThreshold);
touchSeq.move(1, p1, window).commit();
QQuickTouchUtils::flush(window);
qCDebug(lcPointerTests) << i << p1 << delegateHandler->objectName()
<< "active" << delegateHandler->active() << "flickable moving" << flickable->isMoving();
if (i > 1)
QTRY_VERIFY(delegateHandler->active() || flickable->isMoving());
}