Let passive-grabbing PointerHandlers see all point updates

even if all points are accepted or grabbed.  A passive grab isn't much
good if there are cases where the handler is prevented from monitoring.
This enables e.g. the PinchHandler to steal the grab when the right
number of touchpoints are present and have moved past the drag threshold,
and enables completion of a couple of autotests.

Change-Id: I78dc6fc585f80bfb3c13e0c6e757ef815fb94afe
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
This commit is contained in:
Shawn Rutledge 2017-05-18 11:23:25 +02:00
parent d845bdae79
commit a2e2c8a329
5 changed files with 111 additions and 96 deletions

View File

@ -349,13 +349,18 @@ void QQuickPinchHandler::handlePointerEventImpl(QQuickPointerEvent *event)
}
} else {
bool containsReleasedPoints = event->isReleaseEvent();
if (!active() && !containsReleasedPoints) {
if (!active()) {
// Verify that at least one of the points has moved beyond threshold needed to activate the handler
for (QQuickEventPoint *point : qAsConst(m_currentPoints)) {
if (QQuickWindowPrivate::dragOverThreshold(point)) {
if (grabPoints(m_currentPoints))
setActive(true);
if (!containsReleasedPoints && QQuickWindowPrivate::dragOverThreshold(point) && grabPoints(m_currentPoints)) {
setActive(true);
break;
} else {
setPassiveGrab(point);
}
if (point->state() == QQuickEventPoint::Pressed) {
point->setAccepted(false); // don't stop propagation
setPassiveGrab(point);
}
}
if (!active())

View File

@ -768,16 +768,12 @@ void QQuickWindowPrivate::setMouseGrabber(QQuickItem *grabber)
pt->cancelExclusiveGrab();
}
point->setGrabberItem(grabber);
for (auto handler : point->passiveGrabbers())
point->cancelPassiveGrab(handler);
}
} else {
QQuickPointerEvent *event = pointerEventInstance(QQuickPointerDevice::genericMouseDevice());
Q_ASSERT(event->pointCount() == 1);
auto point = event->point(0);
point->setGrabberItem(grabber);
for (auto handler : point->passiveGrabbers())
point->cancelPassiveGrab(handler);
}
@ -1745,6 +1741,7 @@ void QQuickWindowPrivate::deliverMouseEvent(QQuickPointerMouseEvent *pointerEven
if (mouseIsReleased)
point->setGrabberPointerHandler(nullptr, true);
}
deliverToPassiveGrabbers(point->passiveGrabbers(), pointerEvent);
} else {
bool delivered = false;
if (pointerEvent->isPressEvent()) {
@ -2410,6 +2407,7 @@ void QQuickWindowPrivate::deliverTouchEvent(QQuickPointerTouchEvent *event)
// Deliver touch points to existing grabbers
void QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *event)
{
bool done = false;
const auto grabbers = event->exclusiveGrabbers();
for (auto grabber : grabbers) {
// The grabber is guaranteed to be either an item or a handler.
@ -2419,51 +2417,51 @@ void QQuickWindowPrivate::deliverUpdatedTouchPoints(QQuickPointerTouchEvent *eve
QQuickPointerHandler *handler = static_cast<QQuickPointerHandler *>(grabber);
receiver = static_cast<QQuickPointerHandler *>(grabber)->parentItem();
if (sendFilteredPointerEvent(event, receiver))
return;
done = true;
event->localize(receiver);
handler->handlePointerEvent(event);
if (event->allPointsAccepted())
return;
done = true;
}
if (done)
break;
// If the grabber is an item or the grabbing handler didn't handle it,
// then deliver the event to the item (which may have multiple handlers).
deliverMatchingPointsToItem(receiver, event);
}
// If some points weren't grabbed, deliver only to non-grabber PointerHandlers
if (!event->allPointsGrabbed()) {
int pointCount = event->pointCount();
// Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once)
int pointCount = event->pointCount();
for (int i = 0; i < pointCount; ++i) {
QQuickEventPoint *point = event->point(i);
deliverToPassiveGrabbers(point->passiveGrabbers(), event);
}
// Deliver to each eventpoint's passive grabbers (but don't visit any handler more than once)
if (done)
return;
// If some points weren't grabbed, deliver only to non-grabber PointerHandlers in reverse paint order
if (!event->allPointsGrabbed()) {
QVector<QQuickItem *> targetItems;
for (int i = 0; i < pointCount; ++i) {
QQuickEventPoint *point = event->point(i);
deliverToPassiveGrabbers(point->passiveGrabbers(), event);
if (point->state() == QQuickEventPoint::Pressed)
continue; // presses were delivered earlier; not the responsibility of deliverUpdatedTouchPoints
QVector<QQuickItem *> targetItemsForPoint = pointerTargets(contentItem, point->scenePosition(), false, false);
if (targetItems.count()) {
targetItems = mergePointerTargets(targetItems, targetItemsForPoint);
} else {
targetItems = targetItemsForPoint;
}
}
// If some points weren't grabbed, deliver to non-grabber PointerHandlers in reverse paint order
if (!event->allPointsGrabbed()) {
QVector<QQuickItem *> targetItems;
for (int i = 0; i < pointCount; ++i) {
QQuickEventPoint *point = event->point(i);
if (point->state() == QQuickEventPoint::Pressed)
continue; // presses were delivered earlier; not the responsibility of deliverUpdatedTouchPoints
QVector<QQuickItem *> targetItemsForPoint = pointerTargets(contentItem, point->scenePosition(), false, false);
if (targetItems.count()) {
targetItems = mergePointerTargets(targetItems, targetItemsForPoint);
} else {
targetItems = targetItemsForPoint;
}
}
for (QQuickItem *item: targetItems) {
if (grabbers.contains(item))
continue;
QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
event->localize(item);
itemPrivate->handlePointerEvent(event, true); // avoid re-delivering to grabbers
if (event->allPointsGrabbed())
break;
}
for (QQuickItem *item : targetItems) {
if (grabbers.contains(item))
continue;
QQuickItemPrivate *itemPrivate = QQuickItemPrivate::get(item);
event->localize(item);
itemPrivate->handlePointerEvent(event, true); // avoid re-delivering to grabbers
if (event->allPointsGrabbed())
break;
}
}
}
@ -2503,7 +2501,7 @@ bool QQuickWindowPrivate::deliverPressOrReleaseEvent(QQuickPointerEvent *event,
continue;
deliverMatchingPointsToItem(item, event, handlersOnly);
if (event->allPointsAccepted())
break;
handlersOnly = true;
}
return event->allPointsAccepted();
@ -2518,8 +2516,9 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, QQuickPo
// Let the Item's handlers (if any) have the event first.
// However, double click should never be delivered to handlers.
if (!pointerEvent->isDoubleClickEvent()) {
bool wasAccepted = pointerEvent->allPointsAccepted();
itemPrivate->handlePointerEvent(pointerEvent);
allowDoubleClick = !(pointerEvent->asPointerMouseEvent() && pointerEvent->isPressEvent() && pointerEvent->allPointsAccepted());
allowDoubleClick = wasAccepted || !(pointerEvent->asPointerMouseEvent() && pointerEvent->isPressEvent() && pointerEvent->allPointsAccepted());
}
if (handlersOnly)
return;
@ -2831,17 +2830,10 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QQuickPointerEvent *event
// get a touch event customized for delivery to filteringParent
QScopedPointer<QTouchEvent> filteringParentTouchEvent(pte->touchEventForItem(receiver, true));
if (filteringParentTouchEvent) {
QVarLengthArray<QPair<QQuickPointerHandler *, QQuickEventPoint *>, 32> passiveGrabsToCancel;
if (filteringParent->childMouseEventFilter(receiver, filteringParentTouchEvent.data())) {
qCDebug(DBG_TOUCH) << "touch event intercepted by childMouseEventFilter of " << filteringParent;
skipDelivery.append(filteringParent);
for (auto point: qAsConst(filteringParentTouchEvent->touchPoints())) {
auto pointerEventPoint = pte->pointById(point.id());
for (auto handler : pointerEventPoint->passiveGrabbers()) {
QPair<QQuickPointerHandler *, QQuickEventPoint *> grab(handler, pointerEventPoint);
if (!passiveGrabsToCancel.contains(grab))
passiveGrabsToCancel.append(grab);
}
QQuickEventPoint *pt = event->pointById(point.id());
pt->setAccepted();
pt->setGrabberItem(filteringParent);
@ -2887,12 +2879,6 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QQuickPointerEvent *event
touchMouseUnset = false; // We want to leave touchMouseId and touchMouseDevice set
if (mouseEvent->isAccepted())
filteringParent->grabMouse();
auto pointerEventPoint = pte->pointById(tp.id());
for (auto handler : pointerEventPoint->passiveGrabbers()) {
QPair<QQuickPointerHandler *, QQuickEventPoint *> grab(handler, pointerEventPoint);
if (!passiveGrabsToCancel.contains(grab))
passiveGrabsToCancel.append(grab);
}
}
filtered = true;
}
@ -2908,8 +2894,6 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QQuickPointerEvent *event
}
}
}
for (auto grab : passiveGrabsToCancel)
grab.second->cancelPassiveGrab(grab.first);
}
}
}

View File

@ -111,10 +111,10 @@ void tst_MptaInterop::touchDrag()
QQuickTouchUtils::flush(window);
auto pointerEvent = QQuickWindowPrivate::get(window)->pointerEventInstance(touchPointerDevice);
QCOMPARE(tp.at(0)->property("pressed").toBool(), false);
// QCOMPARE(pointerEvent->point(0)->exclusiveGrabber(), mpta);
QTRY_VERIFY(pointerEvent->point(0)->passiveGrabbers().contains(drag));
// Start moving
// DragHandler gets keeps monitoring, due to its passive grab,
// DragHandler keeps monitoring, due to its passive grab,
// and eventually steals the exclusive grab from MPTA
int dragStoleGrab = 0;
for (int i = 0; i < 4; ++i) {
@ -123,9 +123,9 @@ void tst_MptaInterop::touchDrag()
QQuickTouchUtils::flush(window);
if (!dragStoleGrab && pointerEvent->point(0)->exclusiveGrabber() == drag)
dragStoleGrab = i;
// QCOMPARE(tp.at(0)->property("pressed").toBool(), !dragStoleGrab);
}
qCDebug(lcPointerTests, "DragHandler stole the grab after %d events", dragStoleGrab);
if (dragStoleGrab)
qCDebug(lcPointerTests, "DragHandler stole the grab after %d events", dragStoleGrab);
QVERIFY(dragStoleGrab > 1);
touch.release(1, p1).commit();
@ -150,6 +150,7 @@ void tst_MptaInterop::touchesThenPinch()
QVERIFY(tp.at(3)); // the QML declares four touchpoints
QSignalSpy mptaPressedSpy(mpta, SIGNAL(pressed(QList<QObject*>)));
QSignalSpy mptaReleasedSpy(mpta, SIGNAL(released(QList<QObject*>)));
QSignalSpy mptaCanceledSpy(mpta, SIGNAL(canceled(QList<QObject*>)));
QTest::QTouchEventSequence touch = QTest::touchEvent(window, touchDevice);
auto pointerEvent = QQuickWindowPrivate::get(window)->pointerEventInstance(touchPointerDevice);
@ -162,7 +163,6 @@ void tst_MptaInterop::touchesThenPinch()
QQuickTouchUtils::flush(window);
QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), nullptr);
QTRY_COMPARE(pointerEvent->point(0)->passiveGrabbers().first(), drag);
// QTRY_VERIFY(tp.at(0)->property("pressed").toBool());
// Press a second touchpoint: MPTA grabs it
QPoint p2 = mpta->mapToScene(QPointF(200, 30)).toPoint();
@ -190,48 +190,59 @@ void tst_MptaInterop::touchesThenPinch()
QCOMPARE(tp.at(1)->property("pressed").toBool(), false);
QCOMPARE(tp.at(2)->property("pressed").toBool(), false);
QCOMPARE(mptaPressedSpy.count(), 1);
QCOMPARE(mptaCanceledSpy.count(), 1);
QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), pinch);
QTRY_COMPARE(pointerEvent->point(1)->exclusiveGrabber(), pinch);
QTRY_COMPARE(pointerEvent->point(2)->exclusiveGrabber(), pinch);
QVERIFY(pinch->active());
// Move some more: PinchHandler reacts
// Start moving: PinchHandler steals the exclusive grab from MPTA as soon as dragThreshold is exceeded
int pinchStoleGrab = 0;
for (int i = 0; i < 8; ++i) {
p1 += QPoint(4, 4);
p2 += QPoint(4, 4);
p3 += QPoint(-4, 4);
p1 += QPoint(dragThreshold / 2, dragThreshold / 2);
p2 += QPoint(dragThreshold / 2, dragThreshold / 2);
p3 += QPoint(-dragThreshold / 2, dragThreshold / 2);
touch.move(1, p1).move(2, p2).move(3, p3).commit();
QQuickTouchUtils::flush(window);
QTRY_COMPARE(tp.at(0)->property("pressed").toBool(), false);
QCOMPARE(tp.at(1)->property("pressed").toBool(), false);
QCOMPARE(tp.at(2)->property("pressed").toBool(), false);
if (!pinchStoleGrab && pointerEvent->point(0)->exclusiveGrabber() == pinch)
pinchStoleGrab = i;
}
qCDebug(lcPointerTests) << "scale" << pinch->scale() << "rot" << pinch->rotation();
QTRY_VERIFY(pinch->rotation() > 10);
qCDebug(lcPointerTests) << "pinch started after" << pinchStoleGrab << "moves; ended with scale" << pinch->scale() << "rot" << pinch->rotation();
QTRY_VERIFY(pinch->rotation() > 8);
QVERIFY(pinch->scale() > 1);
// Press one more point (pinkie finger)
QPoint p4 = mpta->mapToScene(QPointF(300, 200)).toPoint();
touch.stationary(1).stationary(2).stationary(3).press(4, p4).commit();
// MPTA grabs p4 (which is at index 3)
// QTRY_COMPARE(pointerEvent->point(3)->exclusiveGrabber(), mpta);
// PinchHandler wantsPointerEvent declines, because it wants exactly 3 touchpoints, and there are now 4.
// Move some more... MPTA reacts, in spite of not grabbing all the points
touch.move(1, p1).move(2, p2).move(3, p3).press(4, p4).commit();
// PinchHandler gives up its grabs (only on non-stationary points at this time: see QQuickPointerHandler::handlePointerEvent())
// because it has minimum touch points 3, maximum touch points 3, and now there are 4 points.
// MPTA grabs all points which are not already grabbed
QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), mpta);
QCOMPARE(pointerEvent->point(1)->exclusiveGrabber(), mpta);
QCOMPARE(pointerEvent->point(2)->exclusiveGrabber(), mpta);
QCOMPARE(pointerEvent->point(3)->exclusiveGrabber(), mpta);
// Move some more... MPTA keeps reacting
for (int i = 0; i < 8; ++i) {
p1 += QPoint(4, 4);
p2 += QPoint(4, 4);
p3 += QPoint(-4, 4);
p4 += QPoint(-4, -4);
touch.move(1, p1).move(2, p2).move(3, p3).move(4, p4).commit();
// QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), nullptr);
// QCOMPARE(pointerEvent->point(1)->exclusiveGrabber(), nullptr);
// QCOMPARE(pointerEvent->point(2)->exclusiveGrabber(), nullptr);
// QCOMPARE(pointerEvent->point(3)->exclusiveGrabber(), mpta);
// QCOMPARE(tp.at(0)->property("pressed").toBool(), true);
// QCOMPARE(tp.at(1)->property("pressed").toBool(), true);
// QCOMPARE(tp.at(2)->property("pressed").toBool(), true);
// QCOMPARE(tp.at(3)->property("pressed").toBool(), true);
QCOMPARE(pointerEvent->point(0)->exclusiveGrabber(), mpta);
QCOMPARE(pointerEvent->point(1)->exclusiveGrabber(), mpta);
QCOMPARE(pointerEvent->point(2)->exclusiveGrabber(), mpta);
QCOMPARE(pointerEvent->point(3)->exclusiveGrabber(), mpta);
QCOMPARE(tp.at(0)->property("pressed").toBool(), true);
QCOMPARE(tp.at(1)->property("pressed").toBool(), true);
QCOMPARE(tp.at(2)->property("pressed").toBool(), true);
QCOMPARE(tp.at(3)->property("pressed").toBool(), true);
}
// Release the pinkie
touch.stationary(1).stationary(2).stationary(3).release(4, p4).commit();
// Release the pinkie: PinchHandler acquires passive grabs on the 3 remaining points
touch.move(1, p1).move(2, p2).move(3, p3).release(4, p4).commit();
// Move some more: PinchHander grabs again, and reacts
for (int i = 0; i < 8; ++i) {
p1 -= QPoint(4, 4);
@ -239,21 +250,36 @@ void tst_MptaInterop::touchesThenPinch()
p3 -= QPoint(-4, 4);
touch.move(1, p1).move(2, p2).move(3, p3).commit();
QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), pinch);
QCOMPARE(pointerEvent->point(1)->exclusiveGrabber(), pinch);
QCOMPARE(pointerEvent->point(2)->exclusiveGrabber(), pinch);
}
// Release the first finger
touch.stationary(2).stationary(3).release(1, p1).commit();
// Move some more: PinchHander isn't interested in a mere 2 points, and MPTA should react... but it doesn't (TODO?)
// Move some more: PinchHander isn't interested in a mere 2 points.
// MPTA could maybe react; but QQuickWindowPrivate::deliverTouchEvent() calls
// deliverPressOrReleaseEvent() in a way which "starts over" with event delivery
// only for handlers, not for Items; therefore MPTA is not visited at this time.
for (int i = 0; i < 8; ++i) {
p1 -= QPoint(4, 4);
p2 += QPoint(4, 4);
touch.move(1, p1).move(2, p2).commit();
QTest::qWait(100);
p2 -= QPoint(4, 4);
p3 += QPoint(4, 4);
touch.move(2, p2).move(3, p3).commit();
QQuickTouchUtils::flush(window);
}
touch.release(1, p1).release(2, p2).release(3, p3).commit();
// Release another finger
touch.stationary(2).release(3, p3).commit();
// Move some more: DragHandler reacts.
// It had a passive grab this whole time; now it activates and gets an exclusive grab.
for (int i = 0; i < 8; ++i) {
p2 += QPoint(8, -8);
touch.move(2, p2).commit();
QTRY_COMPARE(pointerEvent->point(0)->exclusiveGrabber(), drag);
}
touch.release(2, p2).commit();
QQuickTouchUtils::flush(window);
// QTRY_COMPARE(mptaReleasedSpy.count(), 1); // all points at once
QTRY_COMPARE(mptaReleasedSpy.count(), 1);
}
QTEST_MAIN(tst_MptaInterop)

View File

@ -314,11 +314,10 @@ void tst_DragHandler::touchDragMultiSliders_data()
0 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} };
QTest::newRow("Drag Knob: start on the knobs, drag diagonally downward") <<
0 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} };
// TOOD these fail
// QTest::newRow("Drag Anywhere: start on the knobs, drag down") <<
// 1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} };
// QTest::newRow("Drag Anywhere: start on the knobs, drag diagonally downward") <<
// 1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} };
QTest::newRow("Drag Anywhere: start on the knobs, drag down") <<
1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {0, 60}, {0, 60}, {0, 60} };
QTest::newRow("Drag Anywhere: start on the knobs, drag diagonally downward") <<
1 << QVector<int> { 0, 1, 2 } << QVector<int> { 0, 0, 0 } << QVector<QVector2D> { {20, 40}, {20, 60}, {20, 80} };
// TODO these next two fail because the DragHandler grabs when a finger
// drags across it from outside, but should rather start only if it is pressed inside
// QTest::newRow("Drag Knob: start above the knobs, drag down") <<

View File

@ -92,6 +92,7 @@ Rectangle {
}
Text {
anchors.bottom: parent.bottom
text: pinch3.active ? getTransformationDetails(container, pinch3) : "Pinch with 3 fingers to scale, rotate and translate"
text: pinch3.active ? getTransformationDetails(container, pinch3) :
"Pinch with 3 fingers to scale, rotate and translate\nHold down Meta to drag with one finger or mouse"
}
}