diff options
-rw-r--r-- | src/quick/items/qquickflickable.cpp | 223 | ||||
-rw-r--r-- | src/quick/items/qquickflickable_p.h | 3 | ||||
-rw-r--r-- | src/quick/items/qquickflickable_p_p.h | 10 | ||||
-rw-r--r-- | src/quick/items/qquickitem.cpp | 42 | ||||
-rw-r--r-- | src/quick/items/qquickwindow.cpp | 66 | ||||
-rw-r--r-- | src/quick/items/qquickwindow_p.h | 10 | ||||
-rw-r--r-- | tests/auto/qmltest/events/tst_touch.qml | 10 | ||||
-rw-r--r-- | tests/auto/quick/pointerhandlers/flickableinterop/data/dragOnList.qml | 2 | ||||
-rw-r--r-- | tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml | 5 | ||||
-rw-r--r-- | tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp | 7 | ||||
-rw-r--r-- | tests/auto/quick/touchmouse/tst_touchmouse.cpp | 84 |
11 files changed, 297 insertions, 165 deletions
diff --git a/src/quick/items/qquickflickable.cpp b/src/quick/items/qquickflickable.cpp index 960b02ea9c..a6171ade78 100644 --- a/src/quick/items/qquickflickable.cpp +++ b/src/quick/items/qquickflickable.cpp @@ -62,6 +62,8 @@ QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcHandlerParent) +Q_LOGGING_CATEGORY(lcFilter, "qt.quick.flickable.filter") +Q_LOGGING_CATEGORY(lcReplay, "qt.quick.flickable.replay") // FlickThreshold determines how far the "mouse" must have moved // before we perform a flick. @@ -281,7 +283,7 @@ void QQuickFlickablePrivate::init() qmlobject_connect(&velocityTimeline, QQuickTimeLine, SIGNAL(completed()), q, QQuickFlickable, SLOT(velocityTimelineCompleted())); q->setAcceptedMouseButtons(Qt::LeftButton); - q->setAcceptTouchEvents(false); // rely on mouse events synthesized from touch + q->setAcceptTouchEvents(true); q->setFiltersChildMouseEvents(true); QQuickItemPrivate *viewportPrivate = QQuickItemPrivate::get(contentItem); viewportPrivate->addItemChangeListener(this, QQuickItemPrivate::Geometry); @@ -1032,7 +1034,7 @@ qreal QQuickFlickablePrivate::devicePixelRatio() const return (window ? window->effectiveDevicePixelRatio() : qApp->devicePixelRatio()); } -void QQuickFlickablePrivate::handleMousePressEvent(QMouseEvent *event) +void QQuickFlickablePrivate::handlePressEvent(QPointerEvent *event) { Q_Q(QQuickFlickable); timer.start(); @@ -1060,7 +1062,7 @@ void QQuickFlickablePrivate::handleMousePressEvent(QMouseEvent *event) } q->setKeepMouseGrab(stealMouse); - maybeBeginDrag(computeCurrentTime(event), event->position()); + maybeBeginDrag(computeCurrentTime(event), event->points().first().position()); } void QQuickFlickablePrivate::maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn) @@ -1311,34 +1313,29 @@ void QQuickFlickablePrivate::drag(qint64 currentTimestamp, QEvent::Type eventTyp lastPos = localPos; } -void QQuickFlickablePrivate::handleMouseMoveEvent(QMouseEvent *event) +void QQuickFlickablePrivate::handleMoveEvent(QPointerEvent *event) { Q_Q(QQuickFlickable); - if (!interactive || lastPosTime == -1 || event->buttons() == Qt::NoButton) + if (!interactive || lastPosTime == -1 || + (event->isSinglePointEvent() && static_cast<QSinglePointEvent *>(event)->buttons() == Qt::NoButton)) return; qint64 currentTimestamp = computeCurrentTime(event); - QVector2D deltas = QVector2D(event->position() - pressPos); + const auto &firstPoint = event->points().first(); + const auto &pos = firstPoint.position(); + QVector2D deltas = QVector2D(pos - q->mapFromGlobal(firstPoint.globalPressPosition())); bool overThreshold = false; QVector2D velocity = event->point(0).velocity(); - // TODO guarantee that events always have velocity so that it never needs to be computed here - if (!event->device()->capabilities().testFlag(QInputDevice::Capability::Velocity)) { - qint64 lastTimestamp = (lastPos.isNull() ? lastPressTime : lastPosTime); - if (currentTimestamp == lastTimestamp) - return; // events are too close together: velocity would be infinite - qreal elapsed = qreal(currentTimestamp - lastTimestamp) / 1000.; - velocity = QVector2D(event->position() - (lastPos.isNull() ? pressPos : lastPos)) / elapsed; - } if (q->yflick()) - overThreshold |= QQuickWindowPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, event); + overThreshold |= QQuickWindowPrivate::dragOverThreshold(deltas.y(), Qt::YAxis, firstPoint); if (q->xflick()) - overThreshold |= QQuickWindowPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, event); + overThreshold |= QQuickWindowPrivate::dragOverThreshold(deltas.x(), Qt::XAxis, firstPoint); - drag(currentTimestamp, event->type(), event->position(), deltas, overThreshold, false, false, velocity); + drag(currentTimestamp, event->type(), pos, deltas, overThreshold, false, false, velocity); } -void QQuickFlickablePrivate::handleMouseReleaseEvent(QMouseEvent *event) +void QQuickFlickablePrivate::handleReleaseEvent(QPointerEvent *event) { Q_Q(QQuickFlickable); stealMouse = false; @@ -1359,6 +1356,8 @@ void QQuickFlickablePrivate::handleMouseReleaseEvent(QMouseEvent *event) hData.vTime = vData.vTime = timeline.time(); bool canBoost = false; + const auto pos = event->points().first().position(); + const auto pressPos = event->points().first().pressPosition(); qreal vVelocity = 0; if (elapsed < 100 && vData.velocity != 0.) { @@ -1394,7 +1393,7 @@ void QQuickFlickablePrivate::handleMouseReleaseEvent(QMouseEvent *event) bool flickedVertically = false; vVelocity *= flickBoost; - bool isVerticalFlickAllowed = q->yflick() && qAbs(vVelocity) > MinimumFlickVelocity && qAbs(event->position().y() - pressPos.y()) > FlickThreshold; + bool isVerticalFlickAllowed = q->yflick() && qAbs(vVelocity) > MinimumFlickVelocity && qAbs(pos.y() - pressPos.y()) > FlickThreshold; if (isVerticalFlickAllowed) { velocityTimeline.reset(vData.smoothVelocity); vData.smoothVelocity.setValue(-vVelocity); @@ -1403,7 +1402,7 @@ void QQuickFlickablePrivate::handleMouseReleaseEvent(QMouseEvent *event) bool flickedHorizontally = false; hVelocity *= flickBoost; - bool isHorizontalFlickAllowed = q->xflick() && qAbs(hVelocity) > MinimumFlickVelocity && qAbs(event->position().x() - pressPos.x()) > FlickThreshold; + bool isHorizontalFlickAllowed = q->xflick() && qAbs(hVelocity) > MinimumFlickVelocity && qAbs(pos.x() - pressPos.x()) > FlickThreshold; if (isHorizontalFlickAllowed) { velocityTimeline.reset(hData.smoothVelocity); hData.smoothVelocity.setValue(-hVelocity); @@ -1424,9 +1423,9 @@ void QQuickFlickablePrivate::handleMouseReleaseEvent(QMouseEvent *event) void QQuickFlickable::mousePressEvent(QMouseEvent *event) { Q_D(QQuickFlickable); - if (d->interactive && d->wantsPointerEvent(event)) { + if (d->interactive && !d->replayingPressEvent && d->wantsPointerEvent(event)) { if (!d->pressed) - d->handleMousePressEvent(event); + d->handlePressEvent(event); event->accept(); } else { QQuickItem::mousePressEvent(event); @@ -1437,7 +1436,7 @@ void QQuickFlickable::mouseMoveEvent(QMouseEvent *event) { Q_D(QQuickFlickable); if (d->interactive && d->wantsPointerEvent(event)) { - d->handleMouseMoveEvent(event); + d->handleMoveEvent(event); event->accept(); } else { QQuickItem::mouseMoveEvent(event); @@ -1452,10 +1451,11 @@ void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event) d->replayDelayedPress(); // Now send the release - if (window() && window()->mouseGrabberItem()) { - QPointF localPos = window()->mouseGrabberItem()->mapFromScene(event->scenePosition()); - QScopedPointer<QMouseEvent> mouseEvent(QQuickWindowPrivate::cloneMouseEvent(event, &localPos)); - QCoreApplication::sendEvent(window(), mouseEvent.data()); + auto &firstPoint = event->point(0); + if (auto grabber = qmlobject_cast<QQuickItem *>(event->exclusiveGrabber(firstPoint))) { + QMouseEvent localized(*event); + QMutableEventPoint::from(firstPoint).setPosition(grabber->mapFromScene(event->scenePosition())); + QCoreApplication::sendEvent(window(), &localized); } // And the event has been consumed @@ -1464,13 +1464,69 @@ void QQuickFlickable::mouseReleaseEvent(QMouseEvent *event) return; } - d->handleMouseReleaseEvent(event); + d->handleReleaseEvent(event); event->accept(); } else { QQuickItem::mouseReleaseEvent(event); } } +void QQuickFlickable::touchEvent(QTouchEvent *event) +{ + Q_D(QQuickFlickable); + bool unhandled = false; + const auto &firstPoint = event->points().first(); + switch (firstPoint.state()) { + case QEventPoint::State::Pressed: + if (d->interactive && !d->replayingPressEvent && d->wantsPointerEvent(event)) { + if (!d->pressed) + d->handlePressEvent(event); + event->accept(); + } else { + unhandled = true; + } + break; + case QEventPoint::State::Updated: + if (d->interactive && d->wantsPointerEvent(event)) { + d->handleMoveEvent(event); + event->accept(); + } else { + unhandled = true; + } + break; + case QEventPoint::State::Released: + if (d->interactive && d->wantsPointerEvent(event)) { + if (d->delayedPressEvent) { + d->replayDelayedPress(); + + // Now send the release + auto &firstPoint = event->point(0); + if (auto grabber = qmlobject_cast<QQuickItem *>(event->exclusiveGrabber(firstPoint))) { + const auto localPos = grabber->mapFromScene(firstPoint.scenePosition()); + QScopedPointer<QPointerEvent> localizedEvent(QQuickWindowPrivate::clonePointerEvent(event, localPos)); + QCoreApplication::sendEvent(window(), localizedEvent.data()); + } + + // And the event has been consumed + d->stealMouse = false; + d->pressed = false; + return; + } + + d->handleReleaseEvent(event); + event->accept(); + } else { + unhandled = true; + } + break; + case QEventPoint::State::Stationary: + case QEventPoint::State::Unknown: + break; + } + if (unhandled) + QQuickItem::touchEvent(event); +} + #if QT_CONFIG(wheelevent) void QQuickFlickable::wheelEvent(QWheelEvent *event) { @@ -1595,7 +1651,7 @@ bool QQuickFlickablePrivate::isInnermostPressDelay(QQuickItem *i) const return false; } -void QQuickFlickablePrivate::captureDelayedPress(QQuickItem *item, QMouseEvent *event) +void QQuickFlickablePrivate::captureDelayedPress(QQuickItem *item, QPointerEvent *event) { Q_Q(QQuickFlickable); if (!q->window() || pressDelay <= 0) @@ -1606,15 +1662,17 @@ void QQuickFlickablePrivate::captureDelayedPress(QQuickItem *item, QMouseEvent * if (!isInnermostPressDelay(item)) return; - delayedPressEvent = QQuickWindowPrivate::cloneMouseEvent(event); + delayedPressEvent = QQuickWindowPrivate::clonePointerEvent(event); delayedPressEvent->setAccepted(false); delayedPressTimer.start(pressDelay, q); + qCDebug(lcReplay) << "begin press delay" << pressDelay << "ms with" << delayedPressEvent; } void QQuickFlickablePrivate::clearDelayedPress() { if (delayedPressEvent) { delayedPressTimer.stop(); + qCDebug(lcReplay) << "clear delayed press" << delayedPressEvent; delete delayedPressEvent; delayedPressEvent = nullptr; } @@ -1625,20 +1683,35 @@ void QQuickFlickablePrivate::replayDelayedPress() Q_Q(QQuickFlickable); if (delayedPressEvent) { // Losing the grab will clear the delayed press event; take control of it here - QScopedPointer<QMouseEvent> mouseEvent(delayedPressEvent); + QScopedPointer<QPointerEvent> event(delayedPressEvent); delayedPressEvent = nullptr; delayedPressTimer.stop(); // If we have the grab, release before delivering the event - if (QQuickWindow *w = q->window()) { - QQuickWindowPrivate *wpriv = QQuickWindowPrivate::get(w); + if (QQuickWindow *window = q->window()) { + QQuickWindowPrivate *wpriv = QQuickWindowPrivate::get(window); wpriv->allowChildEventFiltering = false; // don't allow re-filtering during replay replayingPressEvent = true; - if (w->mouseGrabberItem() == q) - q->ungrabMouse(); - - // Use the event handler that will take care of finding the proper item to propagate the event - QCoreApplication::sendEvent(w, mouseEvent.data()); + auto &firstPoint = event->point(0); + // At first glance, it's weird for delayedPressEvent to already have a grabber; + // but on press, filterMouseEvent() took the exclusive grab, and that's stored + // in the device-specific EventPointData instance in QPointingDevicePrivate::activePoints, + // not in the event itself. If this Flickable is still the grabber of that point on that device, + // that's the reason; but now it doesn't need that grab anymore. + if (event->exclusiveGrabber(firstPoint) == q) + event->setExclusiveGrabber(firstPoint, nullptr); + + qCDebug(lcReplay) << "replaying" << event.data(); + // Put scenePosition into position, for the sake of QQuickWindowPrivate::translateTouchEvent() + // TODO remove this if we remove QQuickWindowPrivate::translateTouchEvent() + QMutableEventPoint::from(firstPoint).setPosition(firstPoint.scenePosition()); + // Send it through like a fresh press event, and let QQuickWindow + // (more specifically, QQuickWindowPrivate::deliverPressOrReleaseEvent) + // find the item or handler that should receive it, as usual. + QCoreApplication::sendEvent(window, event.data()); + qCDebug(lcReplay) << "replay done"; + + // We're done with replay, go back to normal delivery behavior replayingPressEvent = false; wpriv->allowChildEventFiltering = true; } @@ -2372,47 +2445,53 @@ void QQuickFlickablePrivate::addPointerHandler(QQuickPointerHandler *h) QQuickItemPrivate::get(contentItem)->addPointerHandler(h); } -/*! - QQuickFlickable::filterMouseEvent checks filtered mouse events and potentially steals them. +/*! \internal + QQuickFlickable::filterPointerEvent filters pointer events intercepted on the way + to the child \a receiver, and potentially steals the exclusive grab. - This is how flickable takes over events from other items (\a receiver) that are on top of it. - It filters their events and may take over (grab) the \a event. - Return true if the mouse event will be stolen. - \internal + This is how flickable takes over the handling of events from child items. + + Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver. */ -bool QQuickFlickable::filterMouseEvent(QQuickItem *receiver, QMouseEvent *event) +bool QQuickFlickable::filterPointerEvent(QQuickItem *receiver, QPointerEvent *event) { + if (!(QQuickWindowPrivate::isMouseEvent(event) || + QQuickWindowPrivate::isTouchEvent(event) || + QQuickWindowPrivate::isTabletEvent(event))) + return false; // don't filter hover events or wheel events, for example Q_ASSERT_X(receiver != this, "", "Flickable received a filter event for itself"); + qCDebug(lcFilter) << objectName() << "filtering" << event << "for" << receiver; Q_D(QQuickFlickable); - QPointF localPos = mapFromScene(event->scenePosition()); + const auto &firstPoint = event->points().first(); + QPointF localPos = mapFromScene(firstPoint.scenePosition()); bool receiverDisabled = receiver && !receiver->isEnabled(); bool stealThisEvent = d->stealMouse; bool receiverKeepsGrab = receiver && (receiver->keepMouseGrab() || receiver->keepTouchGrab()); if ((stealThisEvent || contains(localPos)) && (!receiver || !receiverKeepsGrab || receiverDisabled)) { - QScopedPointer<QMouseEvent> mouseEvent(QQuickWindowPrivate::cloneMouseEvent(event, &localPos)); - mouseEvent->setAccepted(false); - - switch (mouseEvent->type()) { - case QEvent::MouseMove: - d->handleMouseMoveEvent(mouseEvent.data()); + QScopedPointer<QPointerEvent> localizedEvent(QQuickWindowPrivate::clonePointerEvent(event, localPos)); + localizedEvent->setAccepted(false); + switch (firstPoint.state()) { + case QEventPoint::State::Updated: + d->handleMoveEvent(localizedEvent.data()); break; - case QEvent::MouseButtonPress: - d->handleMousePressEvent(mouseEvent.data()); + case QEventPoint::State::Pressed: + d->handlePressEvent(localizedEvent.data()); d->captureDelayedPress(receiver, event); stealThisEvent = d->stealMouse; // Update stealThisEvent in case changed by function call above break; - case QEvent::MouseButtonRelease: - d->handleMouseReleaseEvent(mouseEvent.data()); + case QEventPoint::State::Released: + d->handleReleaseEvent(localizedEvent.data()); stealThisEvent = d->stealMouse; break; - default: + case QEventPoint::State::Stationary: + case QEventPoint::State::Unknown: break; } if ((receiver && stealThisEvent && !receiverKeepsGrab && receiver != this) || receiverDisabled) { d->clearDelayedPress(); - grabMouse(); + event->setExclusiveGrabber(firstPoint, this); } else if (d->delayedPressEvent) { - grabMouse(); + event->setExclusiveGrabber(firstPoint, this); } const bool filtered = stealThisEvent || d->delayedPressEvent || receiverDisabled; @@ -2424,7 +2503,7 @@ bool QQuickFlickable::filterMouseEvent(QQuickItem *receiver, QMouseEvent *event) d->lastPosTime = -1; returnToBounds(); } - if (event->type() == QEvent::MouseButtonRelease || (receiverKeepsGrab && !receiverDisabled)) { + if (firstPoint.state() == QEventPoint::State::Released || (receiverKeepsGrab && !receiverDisabled)) { // mouse released, or another item has claimed the grab d->lastPosTime = -1; d->clearDelayedPress(); @@ -2434,7 +2513,10 @@ bool QQuickFlickable::filterMouseEvent(QQuickItem *receiver, QMouseEvent *event) return false; } - +/*! \internal + Despite the name, this function filters all pointer events on their way to any child within. + Returns true if the event will be stolen and should <em>not</em> be delivered to the \a receiver. +*/ bool QQuickFlickable::childMouseEventFilter(QQuickItem *i, QEvent *e) { Q_D(QQuickFlickable); @@ -2444,19 +2526,12 @@ bool QQuickFlickable::childMouseEventFilter(QQuickItem *i, QEvent *e) return QQuickItem::childMouseEventFilter(i, e); } - switch (e->type()) { - case QEvent::MouseButtonPress: - case QEvent::MouseMove: - case QEvent::MouseButtonRelease: - return filterMouseEvent(i, static_cast<QMouseEvent *>(e)); - case QEvent::UngrabMouse: - if (d->window && d->window->mouseGrabberItem() && d->window->mouseGrabberItem() != this) { - // The grab has been taken away from a child and given to some other item. - mouseUngrabEvent(); - } - break; - default: - break; + if (e->isPointerEvent()) { + return filterPointerEvent(i, static_cast<QPointerEvent *>(e)); + } else if (e->type() == QEvent::UngrabMouse && d->window && + d->window->mouseGrabberItem() && d->window->mouseGrabberItem() != this) { + // The grab has been taken away from a child and given to some other item. + mouseUngrabEvent(); } return QQuickItem::childMouseEventFilter(i, e); diff --git a/src/quick/items/qquickflickable_p.h b/src/quick/items/qquickflickable_p.h index d0fea9d5c5..7a12f5287b 100644 --- a/src/quick/items/qquickflickable_p.h +++ b/src/quick/items/qquickflickable_p.h @@ -280,6 +280,7 @@ protected: void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; + void touchEvent(QTouchEvent *event) override; #if QT_CONFIG(wheelevent) void wheelEvent(QWheelEvent *event) override; #endif @@ -305,7 +306,7 @@ protected: virtual void viewportMoved(Qt::Orientations orient); void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry) override; void mouseUngrabEvent() override; - bool filterMouseEvent(QQuickItem *receiver, QMouseEvent *event); + bool filterPointerEvent(QQuickItem *receiver, QPointerEvent *event); bool xflick() const; bool yflick() const; diff --git a/src/quick/items/qquickflickable_p_p.h b/src/quick/items/qquickflickable_p_p.h index 4d47943702..f9f5ce7628 100644 --- a/src/quick/items/qquickflickable_p_p.h +++ b/src/quick/items/qquickflickable_p_p.h @@ -188,7 +188,7 @@ public: void updateBeginningEnd(); bool isInnermostPressDelay(QQuickItem *item) const; - void captureDelayedPress(QQuickItem *item, QMouseEvent *event); + void captureDelayedPress(QQuickItem *item, QPointerEvent *event); void clearDelayedPress(); void replayDelayedPress(); @@ -235,7 +235,7 @@ public: qreal deceleration; qreal maxVelocity; qreal reportedVelocitySmoothing; - QMouseEvent *delayedPressEvent; + QPointerEvent *delayedPressEvent; QBasicTimer delayedPressTimer; int pressDelay; int fixupDuration; @@ -259,9 +259,9 @@ public: void viewportAxisMoved(AxisData &data, qreal minExtent, qreal maxExtent, qreal vSize, QQuickTimeLineCallback::Callback fixupCallback); - void handleMousePressEvent(QMouseEvent *); - void handleMouseMoveEvent(QMouseEvent *); - void handleMouseReleaseEvent(QMouseEvent *); + void handlePressEvent(QPointerEvent *); + void handleMoveEvent(QPointerEvent *); + void handleReleaseEvent(QPointerEvent *); void maybeBeginDrag(qint64 currentTimestamp, const QPointF &pressPosn); void drag(qint64 currentTimestamp, QEvent::Type eventType, const QPointF &localPos, diff --git a/src/quick/items/qquickitem.cpp b/src/quick/items/qquickitem.cpp index 547c825502..e71ae85c54 100644 --- a/src/quick/items/qquickitem.cpp +++ b/src/quick/items/qquickitem.cpp @@ -4198,13 +4198,28 @@ void QQuickItem::dropEvent(QDropEvent *event) #endif // quick_draganddrop /*! - Reimplement this method to filter the mouse events that are received by + Reimplement this method to filter the pointer events that are received by this item's children. - This method will only be called if filtersChildMouseEvents() is true. + This method will only be called if filtersChildMouseEvents() is \c true. - Return true if the specified \a event should not be passed onto the - specified child \a item, and false otherwise. + Return \c true if the specified \a event should not be passed on to the + specified child \a item, and \c false otherwise. + + \note Despite the name, this function filters all QPointerEvent instances + during delivery to all children (typically mouse, touch, and tablet + events). When overriding this function in a subclass, we suggest writing + generic event-handling code using only the accessors found in + QPointerEvent. Alternatively you can switch on \c event->type() and/or + \c event->device()->type() to handle different event types in different ways. + + \note Filtering is just one way to share responsibility in case of gestural + ambiguity (for example on press, you don't know whether the user will tap + or drag). Another way is to call QPointerEvent::addPassiveGrabber() on + press, so as to non-exclusively monitor the progress of the QEventPoint. + In either case, the item or pointer handler that is monitoring can steal + the exclusive grab later on, when it becomes clear that the gesture fits + the pattern that it is expecting. \sa setFiltersChildMouseEvents() */ @@ -7331,8 +7346,13 @@ void QQuickItem::setAcceptedMouseButtons(Qt::MouseButtons buttons) } /*! - Returns whether mouse and touch events of this item's children should be filtered - through this item. + Returns whether pointer events intended for this item's children should be + filtered through this item. + + If both this item and a child item have acceptTouchEvents() \c true, then + when a touch interaction occurs, this item will filter the touch event. + But if either this item or the child cannot handle touch events, + childMouseEventFilter() will be called with a synthesized mouse event. \sa setFiltersChildMouseEvents(), childMouseEventFilter() */ @@ -7343,11 +7363,11 @@ bool QQuickItem::filtersChildMouseEvents() const } /*! - Sets whether mouse and touch events of this item's children should be filtered - through this item. + Sets whether pointer events intended for this item's children should be + filtered through this item. If \a filter is true, childMouseEventFilter() will be called when - a mouse event is triggered for a child item. + a pointer event is triggered for a child item. \sa filtersChildMouseEvents() */ @@ -7400,9 +7420,9 @@ void QQuickItem::setAcceptHoverEvents(bool enabled) /*! Returns whether touch events are accepted by this item. - The default value is false. + The default value is \c false. - If this is false, then the item will not receive any touch events through + If this is \c false, then the item will not receive any touch events through the touchEvent() function. \since 5.10 diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp index 419ad5bb87..b283f56ab9 100644 --- a/src/quick/items/qquickwindow.cpp +++ b/src/quick/items/qquickwindow.cpp @@ -2038,17 +2038,30 @@ void QQuickWindowPrivate::deliverKeyEvent(QKeyEvent *e) } } -// TODO can we return by value to avoid heap allocation? -// QQuickFlickablePrivate::delayedPressEvent is a ptr so that it can be null; -// but QMouseEvent::isValid() would be an alternative if we can write it -QMouseEvent *QQuickWindowPrivate::cloneMouseEvent(QMouseEvent *event, QPointF *transformedLocalPos) +/*! \internal + Make a copy of any type of QPointerEvent, and optionally localize it + by setting its first point's local position() if \a transformedLocalPos is given. + + \note some subclasses of QSinglePointEvent, such as QWheelEvent, add extra storage. + This function doesn't yet support cloning all of those; it can be extended if needed. +*/ +QPointerEvent *QQuickWindowPrivate::clonePointerEvent(QPointerEvent *event, std::optional<QPointF> transformedLocalPos) { - QMouseEvent *me = new QMouseEvent(*event); - QMutableEventPoint &point = QMutableEventPoint::from(me->point(0)); + QPointerEvent *ret = nullptr; + if (isMouseEvent(event)) { + ret = new QMouseEvent(*(static_cast<QMouseEvent *>(event))); + } else if (isTouchEvent(event)) { + ret = new QTouchEvent(*(static_cast<QTouchEvent *>(event))); + } else if (isTabletEvent(event)) { + ret = new QTabletEvent(*(static_cast<QTabletEvent *>(event))); + } + QMutableEventPoint &point = QMutableEventPoint::from(ret->point(0)); point.detach(); point.setTimestamp(event->timestamp()); - point.setPosition(transformedLocalPos ? *transformedLocalPos : event->position()); - return me; + if (transformedLocalPos) + point.setPosition(*transformedLocalPos); + + return ret; } void QQuickWindowPrivate::deliverToPassiveGrabbers(const QVector<QPointer <QObject> > &passiveGrabbers, @@ -2581,15 +2594,17 @@ void QQuickWindowPrivate::onGrabChanged(QObject *grabber, QPointingDevice::GrabT if (!filtered) item->mouseUngrabEvent(); } - if (point.device()->type() == QInputDevice::DeviceType::TouchScreen && event) { - bool allReleased = true; - for (const auto &pt : event->points()) { - if (pt.state() != QEventPoint::State::Released) { - allReleased = false; - break; + if (point.device()->type() == QInputDevice::DeviceType::TouchScreen) { + bool allReleasedOrCancelled = true; + if (transition == QPointingDevice::UngrabExclusive && event) { + for (const auto &pt : event->points()) { + if (pt.state() != QEventPoint::State::Released) { + allReleasedOrCancelled = false; + break; + } } } - if (allReleased) + if (allReleasedOrCancelled) item->touchUngrabEvent(); } } @@ -2834,6 +2849,12 @@ bool QQuickWindowPrivate::deliverPressOrReleaseEvent(QPointerEvent *event, bool // nor to any item which already had a chance to filter. if (skipDelivery.contains(item)) continue; + + // sendFilteredPointerEvent() changed the QEventPoint::accepted() state, + // but per-point acceptance is opt-in during normal delivery to items. + for (int i = 0; i < event->pointCount(); ++i) + event->point(i).setAccepted(false); + deliverMatchingPointsToItem(item, false, event, handlersOnly); if (event->allPointsAccepted()) handlersOnly = true; @@ -2935,6 +2956,8 @@ void QQuickWindowPrivate::deliverMatchingPointsToItem(QQuickItem *item, bool isG bool isPressOrRelease = pointerEvent->isBeginEvent() || pointerEvent->isEndEvent(); for (int i = 0; i < touchEvent.pointCount(); ++i) { auto &point = QMutableEventPoint::from(touchEvent.point(i)); + // legacy-style delivery: if the item doesn't reject the event, that means it handled ALL the points + point.setAccepted(); if (isPressOrRelease) pointerEvent->setExclusiveGrabber(point, item); } @@ -3220,14 +3243,19 @@ bool QQuickWindowPrivate::sendFilteredPointerEventImpl(QPointerEvent *event, QQu QTouchEvent filteringParentTouchEvent = QQuickItemPrivate::get(receiver)->localizedTouchEvent(static_cast<QTouchEvent *>(event), true); if (filteringParentTouchEvent.type() != QEvent::None) { + qCDebug(DBG_TOUCH) << "letting parent" << filteringParent << "filter for" << receiver << &filteringParentTouchEvent; if (filteringParent->childMouseEventFilter(receiver, &filteringParentTouchEvent)) { qCDebug(DBG_TOUCH) << "touch event intercepted by childMouseEventFilter of " << filteringParent; skipDelivery.append(filteringParent); for (auto point : filteringParentTouchEvent.points()) event->setExclusiveGrabber(point, filteringParent); return true; - } - else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents))) { + } else if (Q_LIKELY(QCoreApplication::testAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents) && + (!acceptsTouchEvents || !filteringParent->acceptTouchEvents()))) { + qCDebug(DBG_TOUCH) << "touch event NOT intercepted by childMouseEventFilter of " << filteringParent + << "; accepts touch?" << filteringParent->acceptTouchEvents() + << "receiver accepts touch?" << acceptsTouchEvents + << "so, letting parent filter a synth-mouse event"; // filteringParent didn't filter the touch event. Give it a chance to filter a synthetic mouse event. for (auto &tp : filteringParentTouchEvent.points()) { QEvent::Type t; @@ -3323,13 +3351,13 @@ bool QQuickWindowPrivate::dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent return overThreshold; } -bool QQuickWindowPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint *tp, int startDragThreshold) +bool QQuickWindowPrivate::dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint &tp, int startDragThreshold) { QStyleHints *styleHints = qApp->styleHints(); bool overThreshold = qAbs(d) > (startDragThreshold >= 0 ? startDragThreshold : styleHints->startDragDistance()); const bool dragVelocityLimitAvailable = (styleHints->startDragVelocity() > 0); if (!overThreshold && dragVelocityLimitAvailable) { - qreal velocity = axis == Qt::XAxis ? tp->velocity().x() : tp->velocity().y(); + qreal velocity = axis == Qt::XAxis ? tp.velocity().x() : tp.velocity().y(); overThreshold |= qAbs(velocity) > styleHints->startDragVelocity(); } return overThreshold; diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h index 62b16513c9..2928c99023 100644 --- a/src/quick/items/qquickwindow_p.h +++ b/src/quick/items/qquickwindow_p.h @@ -172,7 +172,7 @@ public: void translateTouchEvent(QTouchEvent *touchEvent); void removeGrabber(QQuickItem *grabber, bool mouse = true, bool touch = true, bool cancel = false); void onGrabChanged(QObject *grabber, QPointingDevice::GrabTransition transition, const QPointerEvent *event, const QEventPoint &point); - static QMouseEvent *cloneMouseEvent(QMouseEvent *event, QPointF *transformedLocalPos = nullptr); + static QPointerEvent *clonePointerEvent(QPointerEvent *event, std::optional<QPointF> transformedLocalPos = std::nullopt); void deliverToPassiveGrabbers(const QVector<QPointer<QObject> > &passiveGrabbers, QPointerEvent *pointerEvent); bool sendFilteredMouseEvent(QEvent *event, QQuickItem *receiver, QQuickItem *filteringParent); bool sendFilteredPointerEvent(QPointerEvent *event, QQuickItem *receiver, QQuickItem *filteringParent = nullptr); @@ -188,7 +188,7 @@ public: // utility functions that used to be in QQuickPointerEvent et al. bool allUpdatedPointsAccepted(const QPointerEvent *ev); - void localizePointerEvent(QPointerEvent *ev, const QQuickItem *dest); + static void localizePointerEvent(QPointerEvent *ev, const QQuickItem *dest); QList<QObject *> exclusiveGrabbers(QPointerEvent *ev); static bool isMouseEvent(const QPointerEvent *ev); static bool isTouchEvent(const QPointerEvent *ev); @@ -324,7 +324,11 @@ public: static bool dragOverThreshold(qreal d, Qt::Axis axis, QMouseEvent *event, int startDragThreshold = -1); - static bool dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint *tp, int startDragThreshold = -1); + static bool dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint &tp, int startDragThreshold = -1); + + // currently in use in Controls 2; TODO remove + static bool dragOverThreshold(qreal d, Qt::Axis axis, const QEventPoint *tp, int startDragThreshold = -1) + { return dragOverThreshold(d, axis, *tp, startDragThreshold); } static bool dragOverThreshold(QVector2D delta); diff --git a/tests/auto/qmltest/events/tst_touch.qml b/tests/auto/qmltest/events/tst_touch.qml index ab54eb9e09..e6b8ac755e 100644 --- a/tests/auto/qmltest/events/tst_touch.qml +++ b/tests/auto/qmltest/events/tst_touch.qml @@ -160,7 +160,7 @@ MultiPointTouchArea { sequence.stationary(first, null, 0, 0); sequence.press(second, null, 1, 0); sequence.commit(); - compare(touchUpdatedSpy.count, 3); + compare(touchUpdatedSpy.count, 2); touchPoints = touchUpdatedSpy.signalArguments[1][0]; compare(touchPoints.length, 2); verify(comparePoint(touchPoints[0], first, 0, 0)); @@ -169,15 +169,15 @@ MultiPointTouchArea { sequence.release(first, null, 0, 0); sequence.move(second, null, 1, 1); sequence.commit(); - compare(touchUpdatedSpy.count, 4); - touchPoints = touchUpdatedSpy.signalArguments[3][0]; + compare(touchUpdatedSpy.count, 3); + touchPoints = touchUpdatedSpy.signalArguments[2][0]; compare(touchPoints.length, 1); verify(comparePoint(touchPoints[0], second, 1, 1)); sequence.release(second, null, 0, 1); sequence.commit(); - compare(touchUpdatedSpy.count, 5); - touchPoints = touchUpdatedSpy.signalArguments[4][0]; + compare(touchUpdatedSpy.count, 4); + touchPoints = touchUpdatedSpy.signalArguments[3][0]; compare(touchPoints.length, 0); } diff --git a/tests/auto/quick/pointerhandlers/flickableinterop/data/dragOnList.qml b/tests/auto/quick/pointerhandlers/flickableinterop/data/dragOnList.qml index bb39c2267c..b7f4662476 100644 --- a/tests/auto/quick/pointerhandlers/flickableinterop/data/dragOnList.qml +++ b/tests/auto/quick/pointerhandlers/flickableinterop/data/dragOnList.qml @@ -62,7 +62,7 @@ ListView { } DragHandler { id: delegateDrag - objectName: "delegateDrag" + objectName: "delegateDrag " + index } } diff --git a/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml b/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml index f2d25b292f..bedfe35470 100644 --- a/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml +++ b/tests/auto/quick/qquickflickable/data/nestedStopAtBounds.qml @@ -10,6 +10,7 @@ Flickable { contentWidth: 500 contentHeight: 500 flickableDirection: inner.flickableDirection + Text { x: 100; y: 80; text: "dragging: outer " + outer.dragging + " inner " + inner.dragging } // faster rebound to speed up test runs rebound: Transition { @@ -28,6 +29,10 @@ Flickable { color: "yellow" objectName: "yellowRect" + Text { + text: "...." + y: 250 + } Flickable { id: inner diff --git a/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp b/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp index 70f5460c44..bfe09acca9 100644 --- a/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp +++ b/tests/auto/quick/qquickmultipointtoucharea/tst_qquickmultipointtoucharea.cpp @@ -121,7 +121,7 @@ void tst_QQuickMultiPointTouchArea::signalTest() QQuickTouchUtils::flush(window.data()); QCOMPARE(area->property("touchPointPressCount").toInt(), 1); - QCOMPARE(area->property("touchPointUpdateCount").toInt(), 0); + QCOMPARE(area->property("touchPointUpdateCount").toInt(), 2); QCOMPARE(area->property("touchPointReleaseCount").toInt(), 0); QCOMPARE(area->property("touchCount").toInt(), 3); QMetaObject::invokeMethod(area, "clearCounts"); @@ -132,7 +132,7 @@ void tst_QQuickMultiPointTouchArea::signalTest() QQuickTouchUtils::flush(window.data()); QCOMPARE(area->property("touchPointPressCount").toInt(), 0); - QCOMPARE(area->property("touchPointUpdateCount").toInt(), 2); + QCOMPARE(area->property("touchPointUpdateCount").toInt(), 3); QCOMPARE(area->property("touchPointReleaseCount").toInt(), 0); QCOMPARE(area->property("touchCount").toInt(), 3); QMetaObject::invokeMethod(area, "clearCounts"); @@ -677,8 +677,10 @@ void tst_QQuickMultiPointTouchArea::inFlickable() i, p1.x(), p1.y(), p2.x(), p2.y(), flickable->contentY()); } + QEXPECT_FAIL("", "currently flickable does grab the actual mouse", Continue); QCOMPARE(flickable->contentY(), qreal(0)); QCOMPARE(point11->pressed(), true); + QEXPECT_FAIL("", "currently flickable does grab the actual mouse", Continue); QCOMPARE(point12->pressed(), true); QTest::touchEvent(window.data(), device).release(0, p1).release(1, p2); @@ -783,6 +785,7 @@ void tst_QQuickMultiPointTouchArea::inFlickableWithPressDelay() // QTBUG-78818 QQuickMultiPointTouchArea *mpta = window->rootObject()->findChild<QQuickMultiPointTouchArea*>(); QVERIFY(mpta != nullptr); + mpta->setMouseEnabled(false); // don't depend on synth-mouse mpta->setMinimumTouchPoints(1); QQuickTouchPoint *point11 = window->rootObject()->findChild<QQuickTouchPoint*>("point1"); QPoint p1(20,100); diff --git a/tests/auto/quick/touchmouse/tst_touchmouse.cpp b/tests/auto/quick/touchmouse/tst_touchmouse.cpp index ac6b579d03..d06f1337c4 100644 --- a/tests/auto/quick/touchmouse/tst_touchmouse.cpp +++ b/tests/auto/quick/touchmouse/tst_touchmouse.cpp @@ -251,11 +251,12 @@ private slots: protected: bool eventFilter(QObject *, QEvent *event) { - if (event->type() == QEvent::MouseButtonPress || - event->type() == QEvent::MouseMove || - event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *me = static_cast<QMouseEvent*>(event); - filteredEventList.append(Event(me->type(), me->position().toPoint(), me->globalPosition().toPoint())); + if (event->isPointerEvent()) { + qCDebug(lcTests) << "window filtering" << event; + QPointerEvent *pe = static_cast<QPointerEvent*>(event); + filteredEventList.append(Event(pe->type(), + pe->points().first().position().toPoint(), + pe->points().first().globalPosition().toPoint())); } return false; } @@ -773,10 +774,7 @@ void tst_TouchMouse::buttonOnDelayedPressFlickable() // - eventItem2 y: 300, height 100 QFETCH(bool, scrollBeforeDelayIsOver); QFETCH(bool, releaseBeforeDelayIsOver); - -#ifdef Q_OS_MACOS - QSKIP("Deadlocks or crashes due to events changes in qtbase"); -#endif + const int threshold = qApp->styleHints()->startDragDistance(); qApp->setAttribute(Qt::AA_SynthesizeMouseForUnhandledTouchEvents, true); filteredEventList.clear(); @@ -799,6 +797,7 @@ void tst_TouchMouse::buttonOnDelayedPressFlickable() // should a mouse area button be clickable on top of flickable? yes :) EventItem *eventItem1 = window->rootObject()->findChild<EventItem*>("eventItem1"); QVERIFY(eventItem1); + eventItem1->setAcceptTouchEvents(true); // because it was that way by default in Qt 5 eventItem1->setAcceptedMouseButtons(Qt::LeftButton); eventItem1->acceptMouse = true; @@ -815,7 +814,6 @@ void tst_TouchMouse::buttonOnDelayedPressFlickable() // touch press QPoint p1 = QPoint(10, 110); - QPoint pEnd = p1; QTest::touchEvent(window.data(), device).press(0, p1, window.data()); QQuickTouchUtils::flush(window.data()); @@ -824,68 +822,66 @@ void tst_TouchMouse::buttonOnDelayedPressFlickable() QCOMPARE(eventItem1->eventList.size(), 0); } else { // wait until the button sees the press - QTRY_COMPARE(eventItem1->eventList.size(), 1); - QCOMPARE(eventItem1->eventList.at(0).type, QEvent::MouseButtonPress); - QCOMPARE(filteredEventList.count(), 1); + qCDebug(lcTests) << "expected delivered events: press(touch), press(mouse)" << eventItem1->eventList; + QTRY_COMPARE(eventItem1->eventList.size(), 2); + QCOMPARE(eventItem1->eventList.at(0).type, QEvent::TouchBegin); + QCOMPARE(eventItem1->eventList.at(1).type, QEvent::MouseButtonPress); + QCOMPARE(filteredEventList.count(), 2); // actual touch begin and replayed touch begin } if (!releaseBeforeDelayIsOver) { // move the touchpoint: try to flick - p1 += QPoint(0, -10); - QPoint p2 = p1 + QPoint(0, -10); - pEnd = p2 + QPoint(0, -10); - QQuickTouchUtils::flush(window.data()); - QTest::touchEvent(window.data(), device).move(0, p1, window.data()); - QQuickTouchUtils::flush(window.data()); - QTest::touchEvent(window.data(), device).move(0, p2, window.data()); - QQuickTouchUtils::flush(window.data()); - QTest::touchEvent(window.data(), device).move(0, pEnd, window.data()); - QQuickTouchUtils::flush(window.data()); + for (int i = 0; i < 3; ++i) { + p1 += QPoint(0, -threshold); + QTest::touchEvent(window.data(), device).move(0, p1, window.data()); + QQuickTouchUtils::flush(window.data()); + } QTRY_VERIFY(flickable->isMovingVertically()); if (scrollBeforeDelayIsOver) { QCOMPARE(eventItem1->eventList.size(), 0); - QCOMPARE(filteredEventList.count(), 0); + qCDebug(lcTests) << "expected filtered events: 1 TouchBegin and 3 TouchUpdate" << filteredEventList; + QCOMPARE(filteredEventList.count(), 4); } else { // see at least press, move and ungrab QTRY_VERIFY(eventItem1->eventList.size() > 2); - QCOMPARE(eventItem1->eventList.at(0).type, QEvent::MouseButtonPress); + QCOMPARE(eventItem1->eventList.at(0).type, QEvent::TouchBegin); QCOMPARE(eventItem1->eventList.last().type, QEvent::UngrabMouse); - QCOMPARE(filteredEventList.count(), 1); + qCDebug(lcTests) << "expected filtered events: 2 TouchBegin and 3 TouchUpdate" << filteredEventList; + QCOMPARE(filteredEventList.count(), 5); } - // flickable should have the mouse grab, and have moved the itemForTouchPointId - // for the touchMouseId to the new grabber. + // flickable should have the touchpoint grab: it no longer relies on synth-mouse QCOMPARE(grabMonitor.exclusiveGrabber, flickable); - QVERIFY(windowPriv->touchMouseId != -1); auto devPriv = QPointingDevicePrivate::get(device); QCOMPARE(devPriv->pointById(0)->exclusiveGrabber, flickable); } - QTest::touchEvent(window.data(), device).release(0, pEnd, window.data()); + QTest::touchEvent(window.data(), device).release(0, p1, window.data()); QQuickTouchUtils::flush(window.data()); if (releaseBeforeDelayIsOver) { // when the touchpoint was released, the child saw the delayed press and the release in sequence - qCDebug(lcTests) << "expected delivered events: press, release, ungrab" << eventItem1->eventList; - qCDebug(lcTests) << "expected filtered events: delayed press, release" << filteredEventList; - QSKIP("QTBUG-85607"); - QTRY_COMPARE(eventItem1->eventList.size(), 3); - QCOMPARE(eventItem1->eventList.at(0).type, QEvent::MouseButtonPress); - QCOMPARE(eventItem1->eventList.at(1).type, QEvent::MouseButtonRelease); + qCDebug(lcTests) << "expected delivered events: press(touch), press(mouse), release(touch), release(mouse), ungrab" << eventItem1->eventList; + qCDebug(lcTests) << "expected filtered events: delayed press, release (touch)" << filteredEventList; + QTRY_COMPARE(eventItem1->eventList.size(), 5); + QCOMPARE(eventItem1->eventList.at(1).type, QEvent::MouseButtonPress); + QCOMPARE(eventItem1->eventList.at(3).type, QEvent::MouseButtonRelease); QCOMPARE(eventItem1->eventList.last().type, QEvent::UngrabMouse); // QQuickWindow filters the delayed press and release - QCOMPARE(filteredEventList.count(), 2); - QCOMPARE(filteredEventList.at(0).type, QEvent::MouseButtonPress); - QCOMPARE(filteredEventList.at(1).type, QEvent::MouseButtonRelease); + QCOMPARE(filteredEventList.count(), 4); + QCOMPARE(filteredEventList.at(filteredEventList.count() - 2).type, QEvent::TouchBegin); + QCOMPARE(filteredEventList.last().type, QEvent::TouchEnd); } else { - // QQuickWindow filters the delayed press if there was one; otherwise nothing + // QQuickWindow filters the delayed press if there was one if (scrollBeforeDelayIsOver) { - QCOMPARE(filteredEventList.count(), 0); + qCDebug(lcTests) << "expected filtered events: 1 TouchBegin, 3 TouchUpdate, 1 TouchEnd" << filteredEventList; + QCOMPARE(filteredEventList.count(), 5); } else { - qCDebug(lcTests) << "expected filtered event: delayed press" << filteredEventList; - QCOMPARE(filteredEventList.count(), 1); - QCOMPARE(filteredEventList.at(0).type, QEvent::MouseButtonPress); + qCDebug(lcTests) << "expected filtered events: 2 TouchBegin, 3 TouchUpdate, 1 TouchEnd" << filteredEventList; + QCOMPARE(filteredEventList.count(), 6); + QCOMPARE(filteredEventList.at(0).type, QEvent::TouchBegin); + QCOMPARE(filteredEventList.last().type, QEvent::TouchEnd); } } } |