From 4470801f73b86d3ee06a866fbbdafcaeb9f294a6 Mon Sep 17 00:00:00 2001 From: Thomas Zander Date: Tue, 27 Oct 2009 15:31:16 +0100 Subject: Introduce QGesture::GestureCancelPolicy, a way to auto-cancel gestures On accepting one gesture Qt can automatically cancel other gestures that belong to other targets. The policy is normally set to not cancel any other gestures and can be set to cancel all active gestures in the context. For example for all child widgets. Reviewed-By: Denis Dzyubenko --- src/gui/kernel/qgesture.cpp | 33 ++++++++++++++ src/gui/kernel/qgesture.h | 10 +++++ src/gui/kernel/qgesture_p.h | 10 +++-- src/gui/kernel/qgesturemanager.cpp | 85 ++++++++++++++++++++++++++++++++++-- src/gui/kernel/qgesturemanager_p.h | 2 + tests/auto/gestures/tst_gestures.cpp | 62 ++++++++++++++++++++++++++ 6 files changed, 195 insertions(+), 7 deletions(-) diff --git a/src/gui/kernel/qgesture.cpp b/src/gui/kernel/qgesture.cpp index a161876..c302c51 100644 --- a/src/gui/kernel/qgesture.cpp +++ b/src/gui/kernel/qgesture.cpp @@ -175,6 +175,29 @@ void QGesture::unsetHotSpot() } /*! + \enum QGesture::GestureCancelPolicy + + This enum describes how accepting a gesture can cancel other gestures + automatically. + + \value CancelNone On accepting this gesture no other gestures will be affected. + \value CancelAllInContext On accepting this gesture all gestures that are active + in the context (Qt::GestureContext) will be cancelled. +*/ + +void QGesture::setGestureCancelPolicy(GestureCancelPolicy policy) +{ + Q_D(QGesture); + d->gestureCancelPolicy = static_cast(policy); +} + +QGesture::GestureCancelPolicy QGesture::gestureCancelPolicy() const +{ + Q_D(const QGesture); + return static_cast(d->gestureCancelPolicy); +} + +/*! \class QPanGesture \since 4.6 \brief The QPanGesture class describes a panning gesture made by the user. @@ -195,6 +218,16 @@ void QGesture::unsetHotSpot() */ /*! + \property QGesture::GestureCancelPolicy + \brief the policy for deciding what happens on accepting a gesture + + On accepting one gesture Qt can automatically cancel other gestures + that belong to other targets. The policy is normally set to not cancel + any other gestures and can be set to cancel all active gestures in the + context. For example for all child widgets. +*/ + +/*! \property QPanGesture::lastOffset \brief the last offset recorded for this gesture diff --git a/src/gui/kernel/qgesture.h b/src/gui/kernel/qgesture.h index 6469959..524d26e 100644 --- a/src/gui/kernel/qgesture.h +++ b/src/gui/kernel/qgesture.h @@ -65,6 +65,7 @@ class Q_GUI_EXPORT QGesture : public QObject Q_PROPERTY(Qt::GestureState state READ state) Q_PROPERTY(Qt::GestureType gestureType READ gestureType) + Q_PROPERTY(QGesture::GestureCancelPolicy gestureCancelPolicy READ gestureCancelPolicy WRITE setGestureCancelPolicy) Q_PROPERTY(QPointF hotSpot READ hotSpot WRITE setHotSpot RESET unsetHotSpot) Q_PROPERTY(bool hasHotSpot READ hasHotSpot) @@ -81,6 +82,14 @@ public: bool hasHotSpot() const; void unsetHotSpot(); + enum GestureCancelPolicy { + CancelNone = 0, + CancelAllInContext + }; + + void setGestureCancelPolicy(GestureCancelPolicy policy); + GestureCancelPolicy gestureCancelPolicy() const; + protected: QGesture(QGesturePrivate &dd, QObject *parent); @@ -208,6 +217,7 @@ public: QT_END_NAMESPACE +Q_DECLARE_METATYPE(QGesture::GestureCancelPolicy) QT_END_HEADER #endif // QGESTURE_H diff --git a/src/gui/kernel/qgesture_p.h b/src/gui/kernel/qgesture_p.h index 975c0c9..d2ef8a7 100644 --- a/src/gui/kernel/qgesture_p.h +++ b/src/gui/kernel/qgesture_p.h @@ -67,16 +67,20 @@ class QGesturePrivate : public QObjectPrivate public: QGesturePrivate() - : gestureType(Qt::CustomGesture), state(Qt::NoGesture), isHotSpotSet(false), - targetObject(0) + : gestureType(Qt::CustomGesture), state(Qt::NoGesture), + targetObject(0), + isHotSpotSet(false), + gestureCancelPolicy(0) + { } Qt::GestureType gestureType; Qt::GestureState state; QPointF hotSpot; - bool isHotSpotSet; QObject *targetObject; + uint isHotSpotSet : 1; + uint gestureCancelPolicy : 2; }; class QPanGesturePrivate : public QGesturePrivate diff --git a/src/gui/kernel/qgesturemanager.cpp b/src/gui/kernel/qgesturemanager.cpp index 52f8eef..fc7c8b2 100644 --- a/src/gui/kernel/qgesturemanager.cpp +++ b/src/gui/kernel/qgesturemanager.cpp @@ -322,21 +322,96 @@ bool QGestureManager::filterEventThroughContexts(const QMapgestureCancelPolicy() == QGesture::CancelAllInContext) { + DEBUG() << "lets try to cancel some"; + // find gestures in context in Qt::GestureStarted or Qt::GestureUpdated state and cancel them + cancelGesturesForChildren(g); + } + } + activeGestures -= undeliveredGestures; // reset gestures that ended QSet endedGestures = finishedGestures + canceledGestures + undeliveredGestures; foreach (QGesture *gesture, endedGestures) { - if (QGestureRecognizer *recognizer = gestureToRecognizer.value(gesture, 0)) + if (QGestureRecognizer *recognizer = gestureToRecognizer.value(gesture, 0)) { + gesture->setGestureCancelPolicy(QGesture::CancelNone); recognizer->reset(gesture); - else + } else { cleanupGesturesForRemovedRecognizer(gesture); + } gestureTargets.remove(gesture); } return ret; } +// Cancel all gestures of children of the widget that original is associated with +void QGestureManager::cancelGesturesForChildren(QGesture *original) +{ + Q_ASSERT(original); + QWidget *originatingWidget = gestureTargets.value(original); + Q_ASSERT(originatingWidget); + + // iterate over all active gestures and all maybe gestures + // for each find the owner + // if the owner is part of our sub-hierarchy, cancel it. + + QSet cancelledGestures; + QSet::Iterator iter = activeGestures.begin(); + while (iter != activeGestures.end()) { + QWidget *widget = gestureTargets.value(*iter); + // note that we don't touch the gestures for our originatingWidget + if (widget != originatingWidget && originatingWidget->isAncestorOf(widget)) { + DEBUG() << " found a gesture to cancel" << (*iter); + (*iter)->d_func()->state = Qt::GestureCanceled; + cancelledGestures << *iter; + iter = activeGestures.erase(iter); + } else { + ++iter; + } + } + + // TODO handle 'maybe' gestures too + + // sort them per target widget by cherry picking from almostCanceledGestures and delivering + QSet almostCanceledGestures = cancelledGestures; + while (!almostCanceledGestures.isEmpty()) { + QWidget *target = 0; + QSet gestures; + iter = almostCanceledGestures.begin(); + // sort per target widget + while (iter != almostCanceledGestures.end()) { + QWidget *widget = gestureTargets.value(*iter); + if (target == 0) + target = widget; + if (target == widget) { + gestures << *iter; + iter = almostCanceledGestures.erase(iter); + } else { + ++iter; + } + } + Q_ASSERT(target); + + QSet undeliveredGestures; + deliverEvents(gestures, &undeliveredGestures); + } + + for (iter = cancelledGestures.begin(); iter != cancelledGestures.end(); ++iter) { + QGestureRecognizer *recognizer = gestureToRecognizer.value(*iter, 0); + if (recognizer) { + (*iter)->setGestureCancelPolicy(QGesture::CancelNone); + recognizer->reset(*iter); + } else { + cleanupGesturesForRemovedRecognizer(*iter); + } + } +} + void QGestureManager::cleanupGesturesForRemovedRecognizer(QGesture *gesture) { QGestureRecognizer *recognizer = m_deletedRecognizers.value(gesture); @@ -585,10 +660,12 @@ void QGestureManager::timerEvent(QTimerEvent *event) DEBUG() << "QGestureManager::timerEvent: gesture stopped due to timeout:" << gesture; QGestureRecognizer *recognizer = gestureToRecognizer.value(gesture, 0); - if (recognizer) + if (recognizer) { + gesture->setGestureCancelPolicy(QGesture::CancelNone); recognizer->reset(gesture); - else + } else { cleanupGesturesForRemovedRecognizer(gesture); + } } else { ++it; } diff --git a/src/gui/kernel/qgesturemanager_p.h b/src/gui/kernel/qgesturemanager_p.h index 96c2fb7..e6a1d50 100644 --- a/src/gui/kernel/qgesturemanager_p.h +++ b/src/gui/kernel/qgesturemanager_p.h @@ -135,6 +135,8 @@ private: void getGestureTargets(const QSet &gestures, QMap > *conflicts, QMap > *normal); + + void cancelGesturesForChildren(QGesture *originatingGesture); }; QT_END_NAMESPACE diff --git a/tests/auto/gestures/tst_gestures.cpp b/tests/auto/gestures/tst_gestures.cpp index 6acfe70..39cdf63 100644 --- a/tests/auto/gestures/tst_gestures.cpp +++ b/tests/auto/gestures/tst_gestures.cpp @@ -331,6 +331,7 @@ private slots: void ungrabGesture(); void consumeEventHint(); void unregisterRecognizer(); + void autoCancelGestures(); }; tst_Gestures::tst_Gestures() @@ -1292,5 +1293,66 @@ void tst_Gestures::unregisterRecognizer() // a method on QApplication } +void tst_Gestures::autoCancelGestures() +{ + class MockRecognizer : public QGestureRecognizer { + public: + QGestureRecognizer::Result filterEvent(QGesture *gesture, QObject *watched, QEvent *event) + { + Q_UNUSED(gesture); + Q_UNUSED(watched); + if (event->type() == QEvent::MouseButtonPress) + return QGestureRecognizer::GestureTriggered; + if (event->type() == QEvent::MouseButtonRelease) + return QGestureRecognizer::GestureFinished; + return QGestureRecognizer::Ignore; + } + }; + + class MockWidget : public GestureWidget { + public: + MockWidget(const char *name) : GestureWidget(name) { } + + bool event(QEvent *event) + { + if (event->type() == QEvent::Gesture) { + QGestureEvent *ge = static_cast(event); + Q_ASSERT(ge->allGestures().count() == 1); // can't use QCOMPARE here... + ge->allGestures().first()->setGestureCancelPolicy(QGesture::CancelAllInContext); + } + return GestureWidget::event(event); + } + }; + + MockWidget parent("parent"); // this one sets the cancel policy to CancelAllInContext + parent.resize(300, 100); + GestureWidget *child = new GestureWidget("child", &parent); + child->setGeometry(10, 10, 100, 80); + + Qt::GestureType type = qApp->registerGestureRecognizer(new MockRecognizer()); + parent.grabGesture(type, Qt::WidgetWithChildrenGesture); + child->grabGesture(type, Qt::WidgetWithChildrenGesture); + + /* + An event is send to both the child and the parent, when the child gets it a gesture is triggered + and send to the child. + When the parent gets the event a new gesture is triggered and delivered to the parent. When the + parent gets it he accepts it and that causes the cancel policy to activate. + The cause of that is the gesture for the child is cancelled and send to the child as such. + */ + QMouseEvent event(QEvent::MouseButtonPress, QPoint(20,20), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + QApplication::sendEvent(child, &event); + QCOMPARE(child->events.started.count(), 1); + QCOMPARE(child->events.all.count(), 1); + QCOMPARE(parent.events.all.count(), 0); + child->reset(); + QApplication::sendEvent(&parent, &event); + QCOMPARE(parent.events.all.count(), 1); + QCOMPARE(parent.events.started.count(), 1); + QCOMPARE(child->events.started.count(), 0); + QCOMPARE(child->events.all.count(), 1); + QCOMPARE(child->events.canceled.count(), 1); +} + QTEST_MAIN(tst_Gestures) #include "tst_gestures.moc" -- cgit v0.12