diff options
author | Shawn Rutledge <shawn.rutledge@qt.io> | 2017-11-09 10:41:53 +0100 |
---|---|---|
committer | Shawn Rutledge <shawn.rutledge@qt.io> | 2017-11-14 05:35:54 +0000 |
commit | 3cc2148eab7f541bb8551087567b7580a2ea1822 (patch) | |
tree | 554f86030cbcf17e010579d02abd3e151e19362f /src/quick/handlers/qquickpointerhandler.cpp | |
parent | a2e2c8a329768e783b205564e44b2f486b777d74 (diff) |
PointerHandler: add grabPermissions, enforce in setExclusiveGrab
As soon as we enable the concept that PointerHandlers can use passive
grabs to lurk, monitor all movements, and then steal the passive grab,
they can fight over the grab. For example if there are two items with
PinchHandlers, and two or more touches occur within bounds for both,
then each update event can cause the other PinchHandler to steal the
grabs and become active.
So we replace stealing with negotiation: the handler which wants to
take over the grab checks its own flags to see whether that's allowed,
and the handler which is about to lose its grab also has the right
to approve or deny the takeover (just as QQuickItem has had
keepMouseGrab and keepTouchGrab for a long time.) Additionally,
if one handler wants to cancel another handler's grab without
taking over (simply set the grabber to null), it must be approved.
A single-point handler can simply call setExclusiveGrab, with the
expectation that permission may be granted or denied. A multi-point
handler only wants to grab all points if grabbing all of them will
be allowed, otherwise grab none; so it calls canGrab on each point
to check beforehand. Thus, when two handlers are competing for the
same grabs, one or both can be prevented from stealing from each other,
or from Handlers in general, or from Items, or some combination.
Change-Id: I5c733b2b8995ce686da0be42244394eeee82a268
Reviewed-by: Jan Arve Sæther <jan-arve.saether@qt.io>
Diffstat (limited to 'src/quick/handlers/qquickpointerhandler.cpp')
-rw-r--r-- | src/quick/handlers/qquickpointerhandler.cpp | 130 |
1 files changed, 121 insertions, 9 deletions
diff --git a/src/quick/handlers/qquickpointerhandler.cpp b/src/quick/handlers/qquickpointerhandler.cpp index faebdf3621..64bf1a8a8b 100644 --- a/src/quick/handlers/qquickpointerhandler.cpp +++ b/src/quick/handlers/qquickpointerhandler.cpp @@ -42,6 +42,7 @@ QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcPointerHandlerDispatch, "qt.quick.handler.dispatch") +Q_LOGGING_CATEGORY(lcPointerHandlerGrab, "qt.quick.handler.grab") Q_LOGGING_CATEGORY(lcPointerHandlerActive, "qt.quick.handler.active") /*! @@ -67,6 +68,7 @@ QQuickPointerHandler::QQuickPointerHandler(QObject *parent) , m_targetExplicitlySet(false) , m_hadKeepMouseGrab(false) , m_hadKeepTouchGrab(false) + , m_grabPermissions(CanTakeOverFromItems | CanTakeOverFromHandlersOfDifferentType | ApprovesTakeOverByAnything) { } @@ -94,7 +96,7 @@ QQuickPointerHandler::~QQuickPointerHandler() */ void QQuickPointerHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEventPoint::GrabState stateChange, QQuickEventPoint *point) { - qCDebug(lcPointerHandlerDispatch) << point << stateChange << grabber; + qCDebug(lcPointerHandlerGrab) << point << stateChange << grabber; Q_ASSERT(point); if (grabber == this) { bool wasCanceled = false; @@ -145,7 +147,7 @@ void QQuickPointerHandler::onGrabChanged(QQuickPointerHandler *grabber, QQuickEv */ void QQuickPointerHandler::setPassiveGrab(QQuickEventPoint *point, bool grab) { - qCDebug(lcPointerHandlerDispatch) << point << grab; + qCDebug(lcPointerHandlerGrab) << point << grab; if (grab) { point->setGrabberPointerHandler(this, false); } else { @@ -153,14 +155,124 @@ void QQuickPointerHandler::setPassiveGrab(QQuickEventPoint *point, bool grab) } } -void QQuickPointerHandler::setExclusiveGrab(QQuickEventPoint *point, bool grab) +/*! + Check whether it's OK to take an exclusive grab of the \a point. + + The default implementation will call approveGrabTransition() to check this + handler's \l grabPermissions. If grabbing can be done only by taking over + the exclusive grab from an Item, approveGrabTransition() checks the Item's + \l keepMouseGrab or \l keepTouchGrab flags appropriately. If grabbing can + be done only by taking over another handler's exclusive grab, canGrab() + also calls approveGrabTransition() on the handler which is about to lose + its grab. Either one can deny the takeover. +*/ +bool QQuickPointerHandler::canGrab(QQuickEventPoint *point) { - // TODO m_hadKeepMouseGrab m_hadKeepTouchGrab - qCDebug(lcPointerHandlerDispatch) << point << grab; - // Don't allow one handler to cancel another's grab, unless it is stealing it for itself - if (!grab && point->grabberPointerHandler() != this) + QQuickPointerHandler *existingPhGrabber = point->grabberPointerHandler(); + return approveGrabTransition(point, this) && + (existingPhGrabber ? existingPhGrabber->approveGrabTransition(point, this) : true); +} + +/*! + Check this handler's rules to see if \l proposedGrabber will be allowed to take + the exclusive grab. This function may be called twice: once on the instance which + will take the grab, and once on the instance which would thereby lose its grab, + in case of a takeover scenario. +*/ +bool QQuickPointerHandler::approveGrabTransition(QQuickEventPoint *point, QObject *proposedGrabber) +{ + bool allowed = false; + if (proposedGrabber == this) { + QObject* existingGrabber = point->exclusiveGrabber(); + allowed = (existingGrabber == nullptr) || ((m_grabPermissions & CanTakeOverFromAnything) == CanTakeOverFromAnything); + if (existingGrabber) { + if (QQuickPointerHandler *existingPhGrabber = point->grabberPointerHandler()) { + if (!allowed && (m_grabPermissions & CanTakeOverFromHandlersOfDifferentType) && + existingPhGrabber->metaObject()->className() != metaObject()->className()) + allowed = true; + if (!allowed && (m_grabPermissions & CanTakeOverFromHandlersOfSameType) && + existingPhGrabber->metaObject()->className() == metaObject()->className()) + allowed = true; + } else if ((m_grabPermissions & CanTakeOverFromItems)) { + QQuickItem * existingItemGrabber = point->grabberItem(); + if (existingItemGrabber && !((existingItemGrabber->keepMouseGrab() && point->pointerEvent()->asPointerMouseEvent()) || + (existingItemGrabber->keepTouchGrab() && point->pointerEvent()->asPointerTouchEvent()))) + allowed = true; + } + } + } else { + // proposedGrabber is different: that means this instance will lose its grab + if (proposedGrabber) { + if ((m_grabPermissions & ApprovesTakeOverByAnything) == ApprovesTakeOverByAnything) + allowed = true; + if (!allowed && (m_grabPermissions & ApprovesTakeOverByHandlersOfDifferentType) && + proposedGrabber->metaObject()->className() != metaObject()->className()) + allowed = true; + if (!allowed && (m_grabPermissions & ApprovesTakeOverByHandlersOfSameType) && + proposedGrabber->metaObject()->className() == metaObject()->className()) + allowed = true; + if (!allowed && (m_grabPermissions & ApprovesTakeOverByItems) && proposedGrabber->inherits("QQuickItem")) + allowed = true; + } else { + if (!allowed && (m_grabPermissions & ApprovesCancellation)) + allowed = true; + } + } + qCDebug(lcPointerHandlerGrab) << "point" << hex << point->pointId() << "permission" << + QMetaEnum::fromType<GrabPermissions>().valueToKeys(grabPermissions()) << + ':' << this << (allowed ? "approved to" : "denied to") << proposedGrabber; + return allowed; +} + +/*! + \qmlproperty bool QtQuick::PointerHandler::grabPermission + + This property specifies the permissions when this handler's logic decides + to take over the exclusive grab, or when it is asked to approve grab + takeover or cancellation by another handler. + + The default is + \c {CanTakeOverFromItems | CanTakeOverFromHandlersOfDifferentType | ApprovesTakeOverByAnything} + which allows most takeover scenarios but avoids e.g. two PinchHandlers fighting + over the same touchpoints. +*/ +void QQuickPointerHandler::setGrabPermissions(GrabPermissions grabPermission) +{ + if (m_grabPermissions == grabPermission) return; - point->setGrabberPointerHandler(grab ? this : nullptr, true); + + m_grabPermissions = grabPermission; + emit grabPermissionChanged(); +} + +/*! + \internal + Acquire or give up the exclusive grab of the given \a point, according to + the \a grab state, and subject to the rules: canGrab(), and the rule not to + relinquish another handler's grab. Returns true if permission is granted, + or if the exclusive grab has already been acquired or relinquished as + specified. Returns false if permission is denied either by this handler or + by the handler or item from which this handler would take over +*/ +bool QQuickPointerHandler::setExclusiveGrab(QQuickEventPoint *point, bool grab) +{ + if ((grab && point->exclusiveGrabber() == this) || (!grab && point->exclusiveGrabber() != this)) + return true; + // TODO m_hadKeepMouseGrab m_hadKeepTouchGrab + bool allowed = true; + if (grab) { + allowed = canGrab(point); + } else { + QQuickPointerHandler *existingPhGrabber = point->grabberPointerHandler(); + // Ask before allowing one handler to cancel another's grab + if (existingPhGrabber && existingPhGrabber != this && !existingPhGrabber->approveGrabTransition(point, nullptr)) + allowed = false; + } + qCDebug(lcPointerHandlerGrab) << point << (grab ? "grab" : "ungrab") << (allowed ? "allowed" : "forbidden") << + point->exclusiveGrabber() << "->" << (grab ? this : nullptr); + if (allowed) + point->setGrabberPointerHandler(grab ? this : nullptr, true); + return allowed; } /*! @@ -169,7 +281,7 @@ void QQuickPointerHandler::setExclusiveGrab(QQuickEventPoint *point, bool grab) */ void QQuickPointerHandler::cancelAllGrabs(QQuickEventPoint *point) { - qCDebug(lcPointerHandlerDispatch) << point; + qCDebug(lcPointerHandlerGrab) << point; point->cancelAllGrabs(this); } |