diff options
Diffstat (limited to 'src/quick/items/qquickevents.cpp')
-rw-r--r-- | src/quick/items/qquickevents.cpp | 549 |
1 files changed, 494 insertions, 55 deletions
diff --git a/src/quick/items/qquickevents.cpp b/src/quick/items/qquickevents.cpp index 448b63c347..1e0d268f93 100644 --- a/src/quick/items/qquickevents.cpp +++ b/src/quick/items/qquickevents.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2016 The Qt Company Ltd. +** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtQuick module of the Qt Toolkit. @@ -38,8 +38,10 @@ ****************************************************************************/ #include "qquickevents_p_p.h" +#include <QtCore/qmap.h> #include <QtGui/private/qguiapplication_p.h> #include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickpointerhandler_p.h> #include <QtQuick/private/qquickwindow_p.h> #include <private/qdebug_p.h> @@ -448,7 +450,15 @@ Item { typedef QHash<QTouchDevice *, QQuickPointerDevice *> PointerDeviceForTouchDeviceHash; Q_GLOBAL_STATIC(PointerDeviceForTouchDeviceHash, g_touchDevices) -Q_GLOBAL_STATIC_WITH_ARGS(QQuickPointerDevice, g_genericMouseDevice, +struct ConstructableQQuickPointerDevice : public QQuickPointerDevice +{ + ConstructableQQuickPointerDevice(DeviceType devType, PointerType pType, Capabilities caps, + int maxPoints, int buttonCount, const QString &name, + qint64 uniqueId = 0) + : QQuickPointerDevice(devType, pType, caps, maxPoints, buttonCount, name, uniqueId) {} + +}; +Q_GLOBAL_STATIC_WITH_ARGS(ConstructableQQuickPointerDevice, g_genericMouseDevice, (QQuickPointerDevice::Mouse, QQuickPointerDevice::GenericPointer, QQuickPointerDevice::Position | QQuickPointerDevice::Scroll | QQuickPointerDevice::Hover, @@ -457,6 +467,22 @@ Q_GLOBAL_STATIC_WITH_ARGS(QQuickPointerDevice, g_genericMouseDevice, typedef QHash<qint64, QQuickPointerDevice *> PointerDeviceForDeviceIdHash; Q_GLOBAL_STATIC(PointerDeviceForDeviceIdHash, g_tabletDevices) +// debugging helpers +static const char *pointStateString(const QQuickEventPoint *point) +{ + static const QMetaEnum stateMetaEnum = point->metaObject()->enumerator(point->metaObject()->indexOfEnumerator("State")); + return stateMetaEnum.valueToKey(point->state()); +} + +static const QString pointDeviceName(const QQuickEventPoint *point) +{ + auto device = static_cast<const QQuickPointerEvent *>(point->parent())->device(); + QString deviceName = (device ? device->name() : QLatin1String("null device")); + deviceName.resize(16, ' '); // shorten, and align in case of sequential output + return deviceName; +} + + QQuickPointerDevice *QQuickPointerDevice::touchDevice(QTouchDevice *d) { if (g_touchDevices->contains(d)) @@ -505,37 +531,241 @@ QQuickPointerDevice *QQuickPointerDevice::tabletDevice(qint64 id) return nullptr; } -void QQuickEventPoint::reset(Qt::TouchPointState state, QPointF scenePos, quint64 pointId, ulong timestamp) +void QQuickEventPoint::reset(Qt::TouchPointState state, const QPointF &scenePos, int pointId, ulong timestamp, const QVector2D &velocity) { m_scenePos = scenePos; m_pointId = pointId; - m_valid = true; m_accept = false; m_state = static_cast<QQuickEventPoint::State>(state); m_timestamp = timestamp; - if (state == Qt::TouchPointPressed) + if (state == Qt::TouchPointPressed) { m_pressTimestamp = timestamp; - // TODO calculate velocity + m_scenePressPos = scenePos; + } + m_velocity = (Q_LIKELY(velocity.isNull()) ? estimatedVelocity() : velocity); +} + +void QQuickEventPoint::localizePosition(QQuickItem *target) +{ + if (target) + m_pos = target->mapFromScene(scenePos()); + else + m_pos = QPointF(); +} + +/*! + If this point has an exclusive grabber, returns a pointer to it; else + returns null, if there is no grabber. The grabber could be either + an Item or a PointerHandler. +*/ +QObject *QQuickEventPoint::exclusiveGrabber() const +{ + return m_exclusiveGrabber.data(); +} + +/*! + Set the given Item or PointerHandler as the exclusive grabber of this point. + If there was already an exclusive grab, it will be canceled. If there + were passive grabbers, they will continue to lurk, but the exclusive grab + is a behavioral override of the passive grab as long as it remains. + If you already know whether the grabber is to be an Item or a PointerHandler, + you should instead call setGrabberItem() or setGrabberPointerHandler(), + because it is slightly more efficient. +*/ +void QQuickEventPoint::setExclusiveGrabber(QObject *grabber) +{ + if (QQuickPointerHandler *phGrabber = qmlobject_cast<QQuickPointerHandler *>(grabber)) + setGrabberPointerHandler(phGrabber, true); + else + setGrabberItem(static_cast<QQuickItem *>(grabber)); +} + +/*! + If the exclusive grabber of this point is an Item, returns a + pointer to that Item; else returns null, if there is no grabber or if + the grabber is a PointerHandler. +*/ +QQuickItem *QQuickEventPoint::grabberItem() const +{ + return (m_grabberIsHandler ? nullptr : static_cast<QQuickItem *>(m_exclusiveGrabber.data())); +} + +/*! + Set the given Item \a grabber as the exclusive grabber of this point. + If there was already an exclusive grab, it will be canceled. If there + were passive grabbers, they will continue to lurk, but the exclusive grab + is a behavioral override of the passive grab as long as it remains. +*/ +void QQuickEventPoint::setGrabberItem(QQuickItem *grabber) +{ + if (grabber != m_exclusiveGrabber.data()) { + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab" << m_exclusiveGrabber << "->" << grabber; + } + QQuickPointerHandler *oldGrabberHandler = grabberPointerHandler(); + QQuickItem *oldGrabberItem = grabberItem(); + m_exclusiveGrabber = QPointer<QObject>(grabber); + m_grabberIsHandler = false; + m_sceneGrabPos = m_scenePos; + if (oldGrabberHandler) + oldGrabberHandler->onGrabChanged(oldGrabberHandler, CancelGrabExclusive, this); + else if (oldGrabberItem && oldGrabberItem != grabber && grabber && pointerEvent()->asPointerTouchEvent()) + oldGrabberItem->touchUngrabEvent(); + for (QPointer<QQuickPointerHandler> passiveGrabber : m_passiveGrabbers) + passiveGrabber->onGrabChanged(passiveGrabber, OverrideGrabPassive, this); + } +} + +/*! + If the exclusive grabber of this point is a PointerHandler, returns a + pointer to that handler; else returns null, if there is no grabber or if + the grabber is an Item. +*/ +QQuickPointerHandler *QQuickEventPoint::grabberPointerHandler() const +{ + return (m_grabberIsHandler ? static_cast<QQuickPointerHandler *>(m_exclusiveGrabber.data()) : nullptr); +} + +/*! + Set the given PointerHandler \a grabber as grabber of this point. If \a + exclusive is true, it will override any other grabs; if false, \a grabber + will be added to the list of passive grabbers of this point. +*/ +void QQuickEventPoint::setGrabberPointerHandler(QQuickPointerHandler *grabber, bool exclusive) +{ + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + if (exclusive) { + if (m_exclusiveGrabber != grabber) + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (exclusive)" << m_exclusiveGrabber << "->" << grabber; + } else { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (passive)" << grabber; + } + } + if (exclusive) { + if (grabber != m_exclusiveGrabber.data()) { + if (grabber) { + // set variables before notifying the new grabber + m_exclusiveGrabber = QPointer<QObject>(grabber); + m_grabberIsHandler = true; + m_sceneGrabPos = m_scenePos; + grabber->onGrabChanged(grabber, GrabExclusive, this); + for (QPointer<QQuickPointerHandler> passiveGrabber : m_passiveGrabbers) { + if (passiveGrabber != grabber) + passiveGrabber->onGrabChanged(grabber, OverrideGrabPassive, this); + } + } else if (QQuickPointerHandler *oldGrabberPointerHandler = qmlobject_cast<QQuickPointerHandler *>(m_exclusiveGrabber.data())) { + oldGrabberPointerHandler->onGrabChanged(oldGrabberPointerHandler, UngrabExclusive, this); + } else if (!m_exclusiveGrabber.isNull()) { + // If there is a previous grabber and it's not a PointerHandler, it must be an Item. + QQuickItem *oldGrabberItem = static_cast<QQuickItem *>(m_exclusiveGrabber.data()); + // If this point came from a touchscreen, notify that previous grabber Item that it's losing its touch grab. + if (pointerEvent()->asPointerTouchEvent()) + oldGrabberItem->touchUngrabEvent(); + } + // set variables after notifying the old grabber + m_exclusiveGrabber = QPointer<QObject>(grabber); + m_grabberIsHandler = true; + m_sceneGrabPos = m_scenePos; + } + } else { + if (!grabber) { + qDebug() << "can't set passive grabber to null"; + return; + } + auto ptr = QPointer<QQuickPointerHandler>(grabber); + if (!m_passiveGrabbers.contains(ptr)) { + m_passiveGrabbers.append(ptr); + grabber->onGrabChanged(grabber, GrabPassive, this); + } + } +} + +/*! + If this point has an existing exclusive grabber (Item or PointerHandler), + inform the grabber that its grab is canceled, and remove it as grabber. + This normally happens when the grab is stolen by another Item. +*/ +void QQuickEventPoint::cancelExclusiveGrab() +{ + if (m_exclusiveGrabber.isNull()) + qWarning("cancelGrab: no grabber"); + else + cancelExclusiveGrabImpl(); +} + +void QQuickEventPoint::cancelExclusiveGrabImpl(QTouchEvent *cancelEvent) +{ + if (m_exclusiveGrabber.isNull()) + return; + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (exclusive)" << m_exclusiveGrabber << "-> nullptr"; + } + if (auto handler = grabberPointerHandler()) { + handler->onGrabChanged(handler, CancelGrabExclusive, this); + } else if (auto item = grabberItem()) { + if (cancelEvent) + QCoreApplication::sendEvent(item, cancelEvent); + else + item->touchUngrabEvent(); + } + m_exclusiveGrabber.clear(); +} + +/*! + If this point has the given \a handler as a passive grabber, + inform the grabber that its grab is canceled, and remove it as grabber. + This normally happens when another Item or PointerHandler does an exclusive grab. +*/ +void QQuickEventPoint::cancelPassiveGrab(QQuickPointerHandler *handler) +{ + if (removePassiveGrabber(handler)) { + if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled())) { + qCDebug(lcPointerGrab) << pointDeviceName(this) << "point" << hex << m_pointId << pointStateString(this) + << ": grab (passive)" << handler << "removed"; + } + handler->onGrabChanged(handler, CancelGrabPassive, this); + } } -QQuickItem *QQuickEventPoint::grabber() const +/*! + If this point has the given \a handler as a passive grabber, remove it as grabber. + Returns true if it was removed, false if it wasn't a grabber. +*/ +bool QQuickEventPoint::removePassiveGrabber(QQuickPointerHandler *handler) { - return m_grabber.data(); + return m_passiveGrabbers.removeOne(handler); } -void QQuickEventPoint::setGrabber(QQuickItem *grabber) +/*! + If the given \a handler is grabbing this point passively, exclusively + or both, cancel the grab and remove it as grabber. + This normally happens when the handler decides that the behavior of this + point can no longer satisfy the handler's behavioral constraints within + the remainder of the gesture which the user is performing: for example + the handler tries to detect a tap but a drag is occurring instead, or + it tries to detect a drag in one direction but the drag is going in + another direction. In such cases the handler no longer needs or wants + to be informed of any further movements of this point. +*/ +void QQuickEventPoint::cancelAllGrabs(QQuickPointerHandler *handler) { - if (Q_UNLIKELY(lcPointerGrab().isDebugEnabled()) && m_grabber.data() != grabber) { - auto device = static_cast<const QQuickPointerEvent *>(parent())->device(); - static const QMetaEnum stateMetaEnum = metaObject()->enumerator(metaObject()->indexOfEnumerator("State")); - QString deviceName = (device ? device->name() : QLatin1String("null device")); - deviceName.resize(16, ' '); // shorten, and align in case of sequential output - qCDebug(lcPointerGrab) << deviceName << "point" << hex << m_pointId << stateMetaEnum.valueToKey(state()) - << ": grab" << m_grabber << "->" << grabber; + if (m_exclusiveGrabber == handler) { + handler->onGrabChanged(handler, CancelGrabExclusive, this); + m_exclusiveGrabber.clear(); } - m_grabber = QPointer<QQuickItem>(grabber); + cancelPassiveGrab(handler); } +/*! + Set this point as \a accepted (true) or rejected (false). + Accepting a point is intended to stop event propagation. + It does not imply any kind of grab, passive or exclusive. + TODO explain further under what conditions propagation really does stop... +*/ void QQuickEventPoint::setAccepted(bool accepted) { if (m_accept != accepted) { @@ -550,12 +780,74 @@ QQuickEventTouchPoint::QQuickEventTouchPoint(QQuickPointerTouchEvent *parent) void QQuickEventTouchPoint::reset(const QTouchEvent::TouchPoint &tp, ulong timestamp) { - QQuickEventPoint::reset(tp.state(), tp.scenePos(), tp.id(), timestamp); + QQuickEventPoint::reset(tp.state(), tp.scenePos(), tp.id(), timestamp, tp.velocity()); + m_exclusiveGrabber.clear(); + m_passiveGrabbers.clear(); m_rotation = tp.rotation(); m_pressure = tp.pressure(); + m_ellipseDiameters = tp.ellipseDiameters(); m_uniqueId = tp.uniqueId(); } +struct PointVelocityData { + QVector2D velocity; + QPointF pos; + ulong timestamp; +}; + +typedef QMap<quint64, PointVelocityData*> PointDataForPointIdMap; +Q_GLOBAL_STATIC(PointDataForPointIdMap, g_previousPointData) +static const int PointVelocityAgeLimit = 500; // milliseconds + +/*! + \internal + Estimates the velocity based on a weighted average of all previous velocities. + The older the velocity is, the less significant it becomes for the estimate. +*/ +QVector2D QQuickEventPoint::estimatedVelocity() const +{ + PointVelocityData *prevPoint = g_previousPointData->value(m_pointId); + if (!prevPoint) { + // cleanup events older than PointVelocityAgeLimit + auto end = g_previousPointData->end(); + for (auto it = g_previousPointData->begin(); it != end; ) { + PointVelocityData *data = it.value(); + if (m_timestamp - data->timestamp > PointVelocityAgeLimit) { + it = g_previousPointData->erase(it); + delete data; + } else { + ++it; + } + } + // TODO optimize: stop this dynamic memory thrashing + prevPoint = new PointVelocityData; + prevPoint->velocity = QVector2D(); + prevPoint->timestamp = 0; + prevPoint->pos = QPointF(); + g_previousPointData->insert(m_pointId, prevPoint); + } + if (prevPoint) { + const ulong timeElapsed = m_timestamp - prevPoint->timestamp; + if (timeElapsed == 0) // in case we call estimatedVelocity() twice on the same QQuickEventPoint + return m_velocity; + + QVector2D newVelocity; + if (prevPoint->timestamp != 0) + newVelocity = QVector2D(m_scenePos - prevPoint->pos)/timeElapsed; + + // VERY simple kalman filter: does a weighted average + // where the older velocities get less and less significant + static const float KalmanGain = 0.7f; + QVector2D filteredVelocity = newVelocity * KalmanGain + m_velocity * (1.0f - KalmanGain); + + prevPoint->velocity = filteredVelocity; + prevPoint->pos = m_scenePos; + prevPoint->timestamp = m_timestamp; + return filteredVelocity; + } + return QVector2D(); +} + /*! \internal \class QQuickPointerEvent @@ -581,11 +873,14 @@ QQuickPointerEvent *QQuickPointerMouseEvent::reset(QEvent *event) return this; m_device = QQuickPointerDevice::genericMouseDevice(); + m_device->eventDeliveryTargets().clear(); m_button = ev->button(); m_pressedButtons = ev->buttons(); Qt::TouchPointState state = Qt::TouchPointStationary; switch (ev->type()) { case QEvent::MouseButtonPress: + m_mousePoint->clearPassiveGrabbers(); + Q_FALLTHROUGH(); case QEvent::MouseButtonDblClick: state = Qt::TouchPointPressed; break; @@ -598,10 +893,15 @@ QQuickPointerEvent *QQuickPointerMouseEvent::reset(QEvent *event) default: break; } - m_mousePoint->reset(state, ev->windowPos(), 0, ev->timestamp()); // mouse is 0 + m_mousePoint->reset(state, ev->windowPos(), quint64(1) << 24, ev->timestamp()); // mouse has device ID 1 return this; } +void QQuickPointerMouseEvent::localize(QQuickItem *target) +{ + m_mousePoint->localizePosition(target); +} + QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) { auto ev = static_cast<QTouchEvent*>(event); @@ -610,6 +910,7 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) return this; m_device = QQuickPointerDevice::touchDevice(ev->device()); + m_device->eventDeliveryTargets().clear(); m_button = Qt::NoButton; m_pressedButtons = Qt::NoButton; @@ -620,32 +921,63 @@ QQuickPointerEvent *QQuickPointerTouchEvent::reset(QEvent *event) for (int i = m_touchPoints.size(); i < newPointCount; ++i) m_touchPoints.insert(i, new QQuickEventTouchPoint(this)); - // Make sure the grabbers are right from one event to the next - QVector<QQuickItem*> grabbers; - // Copy all grabbers, because the order of points might have changed in the event. + // Make sure the grabbers and on-pressed values are right from one event to the next + struct ToPreserve { + int pointId; // just for double-checking + ulong pressTimestamp; + QPointF scenePressPos; + QPointF sceneGrabPos; + QObject * grabber; + QVector <QPointer <QQuickPointerHandler> > passiveGrabbers; + + ToPreserve() : pointId(0), pressTimestamp(0), grabber(nullptr) {} + }; + QVector<ToPreserve> preserves(newPointCount); // jar of pickled touchpoints, in order of points in the _new_ event + + // Copy stuff we need to preserve, because the order of points might have changed in the event. // The ID is all that we can rely on (release might remove the first point etc). for (int i = 0; i < newPointCount; ++i) { - QQuickItem *grabber = nullptr; - if (auto point = pointById(tps.at(i).id())) - grabber = point->grabber(); - grabbers.append(grabber); + int pid = tps.at(i).id(); + if (auto point = pointById(pid)) { + preserves[i].pointId = pid; + preserves[i].pressTimestamp = point->m_pressTimestamp; + preserves[i].scenePressPos = point->scenePressPos(); + preserves[i].sceneGrabPos = point->sceneGrabPos(); + preserves[i].grabber = point->exclusiveGrabber(); + preserves[i].passiveGrabbers = point->passiveGrabbers(); + } } for (int i = 0; i < newPointCount; ++i) { auto point = m_touchPoints.at(i); point->reset(tps.at(i), ev->timestamp()); + const auto &preserved = preserves.at(i); if (point->state() == QQuickEventPoint::Pressed) { - if (grabbers.at(i)) + if (preserved.grabber) qWarning() << "TouchPointPressed without previous release event" << point; - point->setGrabber(nullptr); + point->setGrabberItem(nullptr); + point->clearPassiveGrabbers(); } else { - point->setGrabber(grabbers.at(i)); + // Restore the grabbers without notifying (don't call onGrabChanged) + Q_ASSERT(preserved.pointId == 0 || preserved.pointId == point->pointId()); + point->m_pressTimestamp = preserved.pressTimestamp; + point->m_scenePressPos = preserved.scenePressPos; + point->m_sceneGrabPos = preserved.sceneGrabPos; + point->m_exclusiveGrabber = preserved.grabber; + point->m_grabberIsHandler = (qmlobject_cast<QQuickPointerHandler *>(point->m_exclusiveGrabber) != nullptr); + point->m_passiveGrabbers = preserved.passiveGrabbers; } } m_pointCount = newPointCount; return this; } +void QQuickPointerTouchEvent::localize(QQuickItem *target) +{ + for (auto point : qAsConst(m_touchPoints)) + point->localizePosition(target); +} + QQuickEventPoint *QQuickPointerMouseEvent::point(int i) const { if (i == 0) return m_mousePoint; @@ -659,8 +991,8 @@ QQuickEventPoint *QQuickPointerTouchEvent::point(int i) const { } QQuickEventPoint::QQuickEventPoint(QQuickPointerEvent *parent) - : QObject(parent), m_pointId(0), m_grabber(nullptr), m_timestamp(0), m_pressTimestamp(0), - m_state(QQuickEventPoint::Released), m_valid(false), m_accept(false) + : QObject(parent), m_pointId(0), m_exclusiveGrabber(nullptr), m_timestamp(0), m_pressTimestamp(0), + m_state(QQuickEventPoint::Released), m_accept(false), m_grabberIsHandler(false) { Q_UNUSED(m_reserved); } @@ -674,6 +1006,15 @@ bool QQuickPointerMouseEvent::allPointsAccepted() const { return m_mousePoint->isAccepted(); } +bool QQuickPointerMouseEvent::allUpdatedPointsAccepted() const { + return m_mousePoint->state() == QQuickEventPoint::Pressed || m_mousePoint->isAccepted(); +} + +bool QQuickPointerMouseEvent::allPointsGrabbed() const +{ + return m_mousePoint->exclusiveGrabber() != nullptr; +} + QMouseEvent *QQuickPointerMouseEvent::asMouseEvent(const QPointF &localPos) const { auto event = static_cast<QMouseEvent *>(m_event); @@ -681,16 +1022,31 @@ QMouseEvent *QQuickPointerMouseEvent::asMouseEvent(const QPointF &localPos) cons return event; } -QVector<QQuickItem *> QQuickPointerMouseEvent::grabbers() const +/*! + Returns the exclusive grabber of this event, if any, in a vector. +*/ +QVector<QObject *> QQuickPointerMouseEvent::exclusiveGrabbers() const { - QVector<QQuickItem *> result; - if (QQuickItem *grabber = m_mousePoint->grabber()) + QVector<QObject *> result; + if (QObject *grabber = m_mousePoint->exclusiveGrabber()) result << grabber; return result; } +/*! + Remove all passive and exclusive grabbers of this event, without notifying. +*/ void QQuickPointerMouseEvent::clearGrabbers() const { - m_mousePoint->setGrabber(nullptr); + m_mousePoint->setGrabberItem(nullptr); + m_mousePoint->clearPassiveGrabbers(); +} + +/*! + Returns whether the given \a handler is the exclusive grabber of this event. +*/ +bool QQuickPointerMouseEvent::hasExclusiveGrabber(const QQuickPointerHandler *handler) const +{ + return m_mousePoint->exclusiveGrabber() == handler; } bool QQuickPointerMouseEvent::isPressEvent() const @@ -700,6 +1056,24 @@ bool QQuickPointerMouseEvent::isPressEvent() const (me->buttons() & me->button()) == me->buttons()); } +bool QQuickPointerMouseEvent::isDoubleClickEvent() const +{ + auto me = static_cast<QMouseEvent*>(m_event); + return (me->type() == QEvent::MouseButtonDblClick); +} + +bool QQuickPointerMouseEvent::isUpdateEvent() const +{ + auto me = static_cast<QMouseEvent*>(m_event); + return me->type() == QEvent::MouseMove; +} + +bool QQuickPointerMouseEvent::isReleaseEvent() const +{ + auto me = static_cast<QMouseEvent*>(m_event); + return me->type() == QEvent::MouseButtonRelease; +} + bool QQuickPointerTouchEvent::allPointsAccepted() const { for (int i = 0; i < m_pointCount; ++i) { if (!m_touchPoints.at(i)->isAccepted()) @@ -708,12 +1082,32 @@ bool QQuickPointerTouchEvent::allPointsAccepted() const { return true; } -QVector<QQuickItem *> QQuickPointerTouchEvent::grabbers() const -{ - QVector<QQuickItem *> result; +bool QQuickPointerTouchEvent::allUpdatedPointsAccepted() const { for (int i = 0; i < m_pointCount; ++i) { auto point = m_touchPoints.at(i); - if (QQuickItem *grabber = point->grabber()) { + if (point->state() != QQuickEventPoint::Pressed && !point->isAccepted()) + return false; + } + return true; +} + +bool QQuickPointerTouchEvent::allPointsGrabbed() const +{ + for (int i = 0; i < m_pointCount; ++i) { + if (!m_touchPoints.at(i)->exclusiveGrabber()) + return false; + } + return true; +} + +/*! + Returns the exclusive grabbers of all points in this event, if any, in a vector. +*/ +QVector<QObject *> QQuickPointerTouchEvent::exclusiveGrabbers() const +{ + QVector<QObject *> result; + for (int i = 0; i < m_pointCount; ++i) { + if (QObject *grabber = m_touchPoints.at(i)->exclusiveGrabber()) { if (!result.contains(grabber)) result << grabber; } @@ -721,9 +1115,27 @@ QVector<QQuickItem *> QQuickPointerTouchEvent::grabbers() const return result; } +/*! + Remove all passive and exclusive grabbers of all touchpoints in this event, + without notifying. +*/ void QQuickPointerTouchEvent::clearGrabbers() const { + for (auto point: m_touchPoints) { + point->setGrabberItem(nullptr); + point->clearPassiveGrabbers(); + } +} + +/*! + Returns whether the given \a handler is the exclusive grabber of any + touchpoint within this event. +*/ +bool QQuickPointerTouchEvent::hasExclusiveGrabber(const QQuickPointerHandler *handler) const +{ for (auto point: m_touchPoints) - point->setGrabber(nullptr); + if (point->exclusiveGrabber() == handler) + return true; + return false; } bool QQuickPointerTouchEvent::isPressEvent() const @@ -731,6 +1143,16 @@ bool QQuickPointerTouchEvent::isPressEvent() const return static_cast<QTouchEvent*>(m_event)->touchPointStates() & Qt::TouchPointPressed; } +bool QQuickPointerTouchEvent::isUpdateEvent() const +{ + return static_cast<QTouchEvent*>(m_event)->touchPointStates() & (Qt::TouchPointMoved | Qt::TouchPointStationary); +} + +bool QQuickPointerTouchEvent::isReleaseEvent() const +{ + return static_cast<QTouchEvent*>(m_event)->touchPointStates() & Qt::TouchPointReleased; +} + QVector<QPointF> QQuickPointerEvent::unacceptedPressedPointScenePositions() const { QVector<QPointF> points; @@ -793,15 +1215,15 @@ QMouseEvent *QQuickPointerTouchEvent::syntheticMouseEvent(int pointID, QQuickIte \l {QQuickEventPoint::pointId}{pointId}. Returns nullptr if there is no point with that ID. - \fn QQuickPointerEvent::pointById(quint64 pointId) const + \fn QQuickPointerEvent::pointById(int pointId) const */ -QQuickEventPoint *QQuickPointerMouseEvent::pointById(quint64 pointId) const { +QQuickEventPoint *QQuickPointerMouseEvent::pointById(int pointId) const { if (m_mousePoint && pointId == m_mousePoint->pointId()) return m_mousePoint; return nullptr; } -QQuickEventPoint *QQuickPointerTouchEvent::pointById(quint64 pointId) const { +QQuickEventPoint *QQuickPointerTouchEvent::pointById(int pointId) const { auto it = std::find_if(m_touchPoints.constBegin(), m_touchPoints.constEnd(), [pointId](const QQuickEventTouchPoint *tp) { return tp->pointId() == pointId; } ); if (it != m_touchPoints.constEnd()) @@ -831,7 +1253,12 @@ const QTouchEvent::TouchPoint *QQuickPointerTouchEvent::touchPointById(int point \internal Make a new QTouchEvent, giving it a subset of the original touch points. - Returns a nullptr if all points are stationary or there are no points inside the item. + Returns a nullptr 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 isFiltering is false. When isFiltering is true, it is assumed that the item + cares about all points which are inside its bounds, because most filtering items + need to monitor eventpoint movements until a drag threshold is exceeded or the + requirements for a gesture to be recognized are met in some other way. */ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool isFiltering) const { @@ -841,19 +1268,24 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i // Or else just document that velocity is always scene-relative and is not scaled and rotated with the item // but that would require changing tst_qquickwindow::touchEvent_velocity(): it expects transformed velocity + bool anyPressOrReleaseInside = false; + bool anyGrabber = false; QMatrix4x4 transformMatrix(QQuickItemPrivate::get(item)->windowToItemTransform()); for (int i = 0; i < m_pointCount; ++i) { auto p = m_touchPoints.at(i); if (p->isAccepted()) continue; // include points where item is the grabber - bool isGrabber = p->grabber() == item; - // include newly pressed points inside the bounds - bool isPressInside = p->state() == QQuickEventPoint::Pressed && item->contains(item->mapFromScene(p->scenePos())); + bool isGrabber = p->exclusiveGrabber() == item; + if (isGrabber) + anyGrabber = true; + // include points inside the bounds if no other item is the grabber or if the item is filtering + bool isInside = item->contains(item->mapFromScene(p->scenePos())); + bool hasAnotherGrabber = p->exclusiveGrabber() && p->exclusiveGrabber() != item; // filtering: (childMouseEventFilter) include points that are grabbed by children of the target item bool grabberIsChild = false; - auto parent = p->grabber(); + auto parent = p->grabberItem(); while (isFiltering && parent) { if (parent == item) { grabberIsChild = true; @@ -862,11 +1294,11 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i parent = parent->parentItem(); } - // when filtering, send points that are grabbed by a child and points that are not grabbed but inside - bool filterRelevant = isFiltering && (grabberIsChild || (!p->grabber() && item->contains(item->mapFromScene(p->scenePos())))); - if (!(isGrabber || isPressInside || filterRelevant)) + bool filterRelevant = isFiltering && grabberIsChild; + if (!(isGrabber || (isInside && (!hasAnotherGrabber || isFiltering)) || filterRelevant)) continue; - + if ((p->state() == QQuickEventPoint::Pressed || p->state() == QQuickEventPoint::Released) && isInside) + anyPressOrReleaseInside = true; const QTouchEvent::TouchPoint *tp = touchPointById(p->pointId()); if (tp) { eventStates |= tp->state(); @@ -880,7 +1312,9 @@ QTouchEvent *QQuickPointerTouchEvent::touchEventForItem(QQuickItem *item, bool i } } - if (eventStates == Qt::TouchPointStationary || touchPoints.isEmpty()) + // Now touchPoints will have only points which are inside the item. + // But if none of them were just pressed inside, and the item has no other reason to care, ignore them anyway. + if (eventStates == Qt::TouchPointStationary || touchPoints.isEmpty() || (!anyPressOrReleaseInside && !anyGrabber && !isFiltering)) return nullptr; // if all points have the same state, set the event type accordingly @@ -942,7 +1376,12 @@ Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerDevice * Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerEvent *event) { QDebugStateSaver saver(dbg); dbg.nospace(); - dbg << "QQuickPointerEvent(dev:"; + dbg << "QQuickPointerEvent("; + if (event->isValid()) + dbg << event->timestamp(); + else + dbg << "invalid"; + dbg << " dev:"; QtDebugUtils::formatQEnum(dbg, event->device()->type()); if (event->buttons() != Qt::NoButton) { dbg << " buttons:"; @@ -959,7 +1398,7 @@ Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickPointerEvent *e Q_QUICK_PRIVATE_EXPORT QDebug operator<<(QDebug dbg, const QQuickEventPoint *event) { QDebugStateSaver saver(dbg); dbg.nospace(); - dbg << "QQuickEventPoint(valid:" << event->isValid() << " accepted:" << event->isAccepted() + dbg << "QQuickEventPoint(accepted:" << event->isAccepted() << " state:"; QtDebugUtils::formatQEnum(dbg, event->state()); dbg << " scenePos:" << event->scenePos() << " id:" << hex << event->pointId() << dec |