How to add time limit to recognition of a gesture?
-
Hi!
Next question related to Qt's gesture API. :D
For some gestures (either provided by Qt itself or custom ones) it might be a good decision to restrict the detection with some sort of a time constraint. In my case I have a custom swipe gesture (yes, I know that Qt provides such a gesture :)) that I do with my mouse. The way it works is as follows:
- Mouse button pressed (event type:
QEvent::MouseButtonPress
) - the custom recognizer toggles a flag which is then used inside therecognize(...)
method to start gathering samples (mouse positions) - Mouse is moved while pressing a button (event type:
QEvent::MouseMove
+ the flag for pressed button is set totrue
) - the custom recognizer stores the mouse position (extracted from each move event) inside a list which will then be used to evaluate if a swipe has been triggered or not. While appending samples to the list aResultsFlag::MayBeGesture
is returned. If a different event occurs (currently I am not considering other gestures at all)ResultsFlag::Ignore
is returned - Mouse button released (event type:
QEvent::MouseButtonRelease
) - first the recognizer checks if enough samples have been gathered. If not, we cancel the gesture. If yes, then we do some math magic and check for a swipe pattern. Depending the result we cancel or returnResultsFlag::FinishGesture
)
Now the major problem here is that currently the user can press the button and then move for as long as he wants increasing the size of the samples list. I would like restrict that by setting a very specific time limit how long he can do the swipe. Let's say 500ms or so is a good time (to be determined :D). In my mind such a scenario needs to be handled by a
QTimer
howeverQGestureRecognizer
is not inheriting fromQObject
. I can of course do that but the fact that it's not there tells me that it probably doesn't belong thereQGesture
on the other hand is inheriting fromQObject
(so slots are perfectly fine here) howeverQGesture
s are handled by Qt itself and also placing a timer instance in every instance of my customMouseSwipeGesture
sounds like an overkill. Also its the recognizer that is supposed to cancel the gesture and not the gesture itself.
The only gesture I have found so far which has a timeout feature is the
QPanAndHoldGesture
however looking at its implementation there is basically nothing that I can reuse. TheQPanAndHoldGesture::timeout()
and respectivelyQPanAndHoldGesture::setTimeout()
simple return and respectively set an integer variable. My guess is that this timeout variable is read by the gesture manager. This doesn't help me much because I don't know how to add this functionality to my gesture. I can of course inherit fromQPanAndHoldGesture
but again this looks to me like a poor decision to make.Has anyone created gestures that need to be processed within a certain amount of time?
Thanks!
- Mouse button pressed (event type:
-
Hi!
Next question related to Qt's gesture API. :D
For some gestures (either provided by Qt itself or custom ones) it might be a good decision to restrict the detection with some sort of a time constraint. In my case I have a custom swipe gesture (yes, I know that Qt provides such a gesture :)) that I do with my mouse. The way it works is as follows:
- Mouse button pressed (event type:
QEvent::MouseButtonPress
) - the custom recognizer toggles a flag which is then used inside therecognize(...)
method to start gathering samples (mouse positions) - Mouse is moved while pressing a button (event type:
QEvent::MouseMove
+ the flag for pressed button is set totrue
) - the custom recognizer stores the mouse position (extracted from each move event) inside a list which will then be used to evaluate if a swipe has been triggered or not. While appending samples to the list aResultsFlag::MayBeGesture
is returned. If a different event occurs (currently I am not considering other gestures at all)ResultsFlag::Ignore
is returned - Mouse button released (event type:
QEvent::MouseButtonRelease
) - first the recognizer checks if enough samples have been gathered. If not, we cancel the gesture. If yes, then we do some math magic and check for a swipe pattern. Depending the result we cancel or returnResultsFlag::FinishGesture
)
Now the major problem here is that currently the user can press the button and then move for as long as he wants increasing the size of the samples list. I would like restrict that by setting a very specific time limit how long he can do the swipe. Let's say 500ms or so is a good time (to be determined :D). In my mind such a scenario needs to be handled by a
QTimer
howeverQGestureRecognizer
is not inheriting fromQObject
. I can of course do that but the fact that it's not there tells me that it probably doesn't belong thereQGesture
on the other hand is inheriting fromQObject
(so slots are perfectly fine here) howeverQGesture
s are handled by Qt itself and also placing a timer instance in every instance of my customMouseSwipeGesture
sounds like an overkill. Also its the recognizer that is supposed to cancel the gesture and not the gesture itself.
The only gesture I have found so far which has a timeout feature is the
QPanAndHoldGesture
however looking at its implementation there is basically nothing that I can reuse. TheQPanAndHoldGesture::timeout()
and respectivelyQPanAndHoldGesture::setTimeout()
simple return and respectively set an integer variable. My guess is that this timeout variable is read by the gesture manager. This doesn't help me much because I don't know how to add this functionality to my gesture. I can of course inherit fromQPanAndHoldGesture
but again this looks to me like a poor decision to make.Has anyone created gestures that need to be processed within a certain amount of time?
Thanks!
@Red-Baron said in How to add time limit to recognition of a gesture?:
Has anyone created gestures that need to be processed withing a certain amount of time?
I haven't even touched the gestures framework, but I'd suggest two possibilities and you can decide if one of them is a satisfactory solution.
- Derive from
QObject
as well as fromQGestureRecognizer
(your concern is unfounded, there's nothing wrong doing that) and use a timer to signal the gesture event's collection timeout. E.g. (untested scaffold code):
class MyRecognizer : public QObject, public QGestureRecognizer //< QObject always goes first in the class list { Q_OBJECT public: MyRecognizer(QObject * parent = Q_NULLPTR) : QObject(parent), timeoutTimer(this) { QObject::connect(&timeoutTimer, &QTimer::timeout, this, &MyRecognizer::timeIsUp); } Result recognize(QGesture *gesture, QObject *watched, QEvent *event) Q_DECL_OVERRIDE { if (!collectingEvents) { // Decide if you should start collecting events or we just timed out // Raise the flag and start the timeout timer timeoutTimer.start(500); } if (isValidGesture()) { // Collected events represent a gesture // Lower the internal flag and stop the timer timeoutTimer.stop(); return ResultsFlag::MayBeGesture; } } private slots: bool isValidGesture() { // ... here do the math magic and decide if the events sum up to a gesture } void timeIsUp() { if (isValidGesture()) { // Time's up, but our events are a valid gesture - handle that accordingly. } else { // Time's up and our events don't represent a gesture - discard the events and lower the internal flag (i.e. stop event collection) } } private: QTimer timeoutTimer; }
- You can forgo deriving from
QObject
and instead use aQElapsedTimer
to measure the time from receiving the first event. If you get an event that's "late" you can stop the procedure, reset the timer and return whether the gesture is valid. E.g.:
class MyRecognizer : public QGestureRecognizer { public: Result recognize(QGesture *gesture, QObject *watched, QEvent *event) Q_DECL_OVERRIDE { if (!collectingEvents) { // Decide if you should start collecting events // Raise the flag and start the timeout timer timeoutTimer.start(); } if (eventsAreAGesture || collectingEvents && timeoutTimer.isExpired(500)) { // Collected events represent a gesture or we timed out // Lower the internal flag and stop the timer timeoutTimer.invalidate(); return ResultsFlag::MayBeGesture; // Decide if you have a good gesture } } private: QElapsedTimer timeoutTimer; }
Hope that helps.
Kind regards. - Mouse button pressed (event type:
-
Bless you, mate! I have completely forgotten about the
QElapsedTimer
! This is a perfect match for this situation.As for the first solution - yes, I actually did that and it worked. Deriving from
QObject
is allowed of course. I meant that the fact that it's not done does point towards the availability of a different solution (as you have kindly pointed out with the elapsed timer). The thing isQObject
introduces not a considerable overhead which I would like to avoid if possible. I thought of using a gadget (Q_GADGET
) which alas! is worthless here since, while it supports properties etc., it doesn't have the slot-signal functionality. So it was eitherQObject
or something else. And you found that something else. ^_^ -
Btw the invalidation of the timer can be moved to the
QGestureRecognizer::reset(QGesture* state)
. The method is called whenever we finish or cancel a gesture so it's enough to simply return the respective result flag (ResultFlags::Ignore
currently isn't supposed to influence the timer since it is returned for example when an event is received which isn't mouse move/press/release and such events may have nothing to do with the gesture recognition process. In addition the reset (in my case) is also used to clean up a little bit the finished/canceled recognition (for example remove all the mouse position that I have stored for the recognition process in order for the next attempt to start anew). Checking for expiration of the timer is (as you have written in your code) enough to make the decision and either continue orreturn
someResultFlags
value.void MouseSwipeRecognizer::reset(QGesture* state) { // Invalidate the timer automatically whenever CancelGesture or FinishGesture is returned by the recognition process this->swipeTimeoutTimer.invalidate(); this->swipeProgress.clear(); this->directionTemp = MouseSwipeGesture::SwipeDirection::Undefined; MouseSwipeGesture* swipe = static_cast<MouseSwipeGesture*>(state); if (swipe) { swipe->setStart(QPointF()); swipe->setEnd(QPointF()); swipe->setDirection(); } }
Man, I might actually make a tutorial on this. LOL The suffering I have been through since I started exploring the Qt Gesture API is too much to go to waste. XD
-
Bless you, mate! I have completely forgotten about the
QElapsedTimer
! This is a perfect match for this situation.As for the first solution - yes, I actually did that and it worked. Deriving from
QObject
is allowed of course. I meant that the fact that it's not done does point towards the availability of a different solution (as you have kindly pointed out with the elapsed timer). The thing isQObject
introduces not a considerable overhead which I would like to avoid if possible. I thought of using a gadget (Q_GADGET
) which alas! is worthless here since, while it supports properties etc., it doesn't have the slot-signal functionality. So it was eitherQObject
or something else. And you found that something else. ^_^@Red-Baron said in How to add time limit to recognition of a gesture?:
The thing is QObject introduces not a considerable overhead which I would like to avoid if possible.
I generally applaud that, but here you create only a handful of recognizer objects so I think the overhead is just negligible.
Btw the invalidation of the timer can be moved to the QGestureRecognizer::reset(QGesture* state).
Absolutely, I didn't dig that deep, so my examples are rather superficial and artificial, but I'm glad it worked out in the end.
Man, I might actually make a tutorial on this.
If you can spare the time, please do. One can't have too much examples and tutorials, and it'd be appreciated! :)
Kind regards.