Custom gestures and custom gesture events - when and how to properly use the registered gesture type



  • This is a continuation of this previous post of mine and can be viewed as Part 2 of the saga "How to implement custom gestures?".


    I have successfully mapped the Swipe gesture from my device to the Qt API using the QSwipeGesture. In addition to that now I am integrating a custom gesture called Air Wheel (spinning your finger in mid-air above the device produces this type of gesture). In order to do that I've decided to use a combination of a custom QGestureRecognizer (with an implementation of the recognize(...) method) which is responsible for updating the state of the gesture and a custom QGestureEvent which delivers the gesture to both the recognizer and my widget (that updates its UI according to the state of the data inside that gesture). It took me a couple of days to get things working and there is still an issue which I am unable to figure out how to solve namely the usage of the registered type of my custom gesture (retrieved when using registerRecognizer(...)). Before I go into detail in the list below I have listed the steps I undertook to map the device's gesture to the Qt Gesture API:

    • Retrieve data from device and convert it to a more Qt C++ friendly format (the library used for communicating with the device is written in C)
    • Send data to a GestureManager via a signal. The purpose of the this manger is to have a dedicated slot for each gesture I want to map inside which it generates a built-in (example: QSwipeGesture) or generic (QGesture) gesture object, populates it with data (in case of a custom gesture the generic QGesture is populated with dynamic property(ies)) and sends a built-in or custom QGestureEvent to a given receiver
    • Inside the void GestureManager::slotAirWheelGesture(int counter)create a generic QGesture (since I'm using QGestureRecognizer::recognize(...) I don't need to subclass QGesture and implement the required methods for it) and add a dynamic property counter which contains the counter data that is part of the Air Wheel gesture (spinning your finger/hand clock-/counterclockwise increases/decreases the counter) (see code snippet below)
    • Inside the void GestureManager::slotMapAirWheelToQGesture(int counter) create a custom QAirWheelGestureEvent (its type is set to QEvent::User + 1 that is 1001 in the class declaration of the event) called airWheelEvent, attach the gesture to it and post it using QCoreApplication::postEvent(receiver, airWheelEvent) where receiver is set to the widget that will act upon the gesture event
    void GestureManager::slotMapAirWheelToQGesture(int counter) {
        if (receiver == Q_NULLPTR) {
            cout << "Receiver not set. AirWheel gestures will not be mapped and propagated" << endl;
            return;
        }
        else if (!airWheelEnabled) {
            cout << "AirWheel is not enabled." << endl;
            return;
        }
    
       cout << "GestureManager: receiver \"" << receiver->objectName().toStdString() << "\"" << endl;
    
       QGesture* gesture = new QGesture();
       gesture->setProperty("counter", counter);
      // cout << gesture->property("counter").value<int>() << endl;
       QList<QGesture*> gestures;
       gestures.append(gesture);
       QAirWheelGestureEvent *airWheelEvent = new QAirWheelGestureEvent(gestures);
       QCoreApplication::postEvent(receiver, airWheelEvent);
    }
    
    • QAirWheelGestureRecognizer processes the event as well as the widget which owns the recognizer, has registered it and accepts the gesture type the registerRecognizer(...) has returned:

      this->airWheelRecognizer = new QAirWheelGestureRecognizer(); // Create an instance of the custom recognizer
      Qt::GestureType airWheelGestureType = QGestureRecognizer::registerRecognizer(this->airWheelRecognizer); // Register the custom recognizer and retrieve the automatically generated gesture type
      this->airWheelRecognizer->setGestureType(airWheelGestureType); // Store the type inside the recognizer for further usage
      grabGesture(airWheelGestureType); // Enable grabbing of the custom gesture
      

      The custom QGestureRecognizer::Result QAirWheelGestureRecognizer::recognize(QGesture* state, QObject* watched, QEvent* event) processes a received event as follows:

    QGestureRecognizer::Result QAirWheelGestureRecognizer::recognize(QGesture* state, QObject* watched, QEvent* event) {
        // If event is not a gesture let it be
        if(event->type() != QEvent::Gesture) { return Ignore; }
    
        // Event is a gesture event so try to cast it to the QAirWheelGestureEvent
        QAirWheelGestureEvent*ge = dynamic_cast<QAirWheelGestureEvent *>(event);
        if(!ge) { return Ignore; }
    
        // Check if it's a custom gesture; NOTE: ge->gesture(this->gestureType) doesn't work!!!
        QGesture *airWheelGesture = ge->gesture(Qt::CustomGesture);
    
       if(!airWheelGesture) { return Ignore; }
    
       Qt::GestureType type = airWheelGesture->gestureType();
    
       if(type == this->gestureType-1) { // THIS IS WHAT'S BOTHERING ME!
          // ...
          // Update counter property of `state` using the data from the `event`
          return FinishGesture;
       }
       else { return Ignore; }
    }
    

    Now back to the issue. The documentation clearly states that

    User-defined gestures are registered with the QGestureRecognizer::registerRecognizer() function which generates a custom gesture ID with the Qt::CustomGesture flag set.

    I'm obvously using this here as well as in the event(QEvent *event) of my widget:

    bool Widget::event(QEvent* event) {
        if (event->type() == QEvent::Gesture) {
            bool res = gestureEvent(static_cast<QGestureEvent*>(event));
            // Do not propagate the event further in this case (don't call QWidget::event(event))
            return res;
        }
    
        return QWidget::event(event);
    }
    
    bool Widget::gestureEvent(QGestureEvent* gestureEvent) {
        QGesture* gesture = Q_NULLPTR;
    
        if ((gesture = gestureEvent->gesture(Qt::SwipeGesture))) {
            bool res = gestureSwipeTriggered(static_cast<QSwipeGesture*>(gesture));
            return res;
        }
        else if ((gesture = gestureEvent->gesture(this->airWheelRecognizer->getGestureType()))) { // THIS HERE WORKS!
            bool res = gestureAirWheelTriggered(gesture); // Handle air wheel gesture in relation to the UI
            return res;
        }
    
        return false;
    }
    

    I've added the if(type == this->gestureType-1) inside my recognizer because the type (from Qt::GestureType type = airWheelGesture->gestureType();) is set to 256 (which is 0x100 as in Qt::CustomGesture) while this->gestureType (member of the custom recognizer) is set to 257 (the return value of the registerRecognizer(...).

    The following questions pop into mind working with the code I've written:

    • Why am I unable to use the registered type inside my recognize(...) but at the same time using that very same type inside my event(...) works?
    • Is it enough that I'm casting to a QAirWheelGestureEvent and the cast doesn't fail to make a solid case that this event indeed contains my gesture with its special dynamic properties? A QGestureEvent actually contains a list of QGesture objects and from what I understand its not forbidden to have multiple different gestures in that list especially if I use the generic implementation of the QGesture and apply dynamic properties to it.
    • Do I really need to check for the type of the gesture object inside my recognize(...) then in order to make sure that the gesture's type is the same as the registered one? If yes, then how do I do that? Using this->gestureType - 1 is definitely not a solution since, depending on the order which the various recognizers are registered in, the gestureType will be different. Obviously I check for the validity of the counter property by calling if(airWheelGesture->property("counter").isValid()) which does make sure that counter is present however doesn't exclude the chance that it's another different gesture which coincidentally also contains the given property (along with others or even just that very same one).

    I'd like to make this mapping application-independent that is allow for any Qt application that can use gestures and has access to the device I'm mapping to use my custom gestures.


    One extra question handling the topic of custom gestures:

    The way the recognition for custom gestures works is somewhat of a mystery when it comes to the provided API documentation in the Qt docs. The other path one can take when handling such this particular scenario is to subclass QGesture (implementing its reset() method). This definitely provides a much better type-checking in terms of what gesture is actually stored inside the list of gestures that the custom QAirWheelGestureEvent delivers. However I have yet to discover when the create() method of the custom recognizer is called. I tried it last week and it appeared that it was called all the time even when I didn't touch the application at all.


Log in to reply
 

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