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:

    1. Mouse button pressed (event type: QEvent::MouseButtonPress) - the custom recognizer toggles a flag which is then used inside the recognize(...) method to start gathering samples (mouse positions)
    2. Mouse is moved while pressing a button (event type: QEvent::MouseMove + the flag for pressed button is set to true) - 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 a ResultsFlag::MayBeGesture is returned. If a different event occurs (currently I am not considering other gestures at all) ResultsFlag::Ignore is returned
    3. 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 return ResultsFlag::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 however

    • QGestureRecognizer is not inheriting from QObject. I can of course do that but the fact that it's not there tells me that it probably doesn't belong there
    • QGesture on the other hand is inheriting from QObject (so slots are perfectly fine here) however QGestures are handled by Qt itself and also placing a timer instance in every instance of my custom MouseSwipeGesture 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. The QPanAndHoldGesture::timeout() and respectively QPanAndHoldGesture::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 from QPanAndHoldGesture 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!


  • Qt Champions 2016

    @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.

    1. Derive from QObject as well as from QGestureRecognizer (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;
    }
    
    1. You can forgo deriving from QObject and instead use a QElapsedTimer 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.



  • 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 is QObject 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 either QObject 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 or return some ResultFlags 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


  • Qt Champions 2016

    @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.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.