La reconnaissance des gestes de souris

| Johan Thelin | traducteur : Mikael Sans | Qt Quarterly |

Pour les dactylos rapides, il y a des raccourcis clavier. Ceux-ci sont pleinement pris en charge par Qt et facile à configurer. Il existe un équivalent orienté souris des raccourcis clavier : il s'agit des gestes de souris. Dans cet article, je présenterai quelques classes que vous pouvez employer pour utiliser la reconnaissance des gestes de souris dans votre application Qt.

Cet article est une traduction autorisée de Recognizing Mouse Gestures, de Johan Thelin.

- Titre : La reconnaissance des gestes de souris

- Auteurs : Johan Thelin, traducteur : Mikael Sans, Qt Quarterly

- Parution : 21 décembre 2009

- Mise à jour : 5 juillet 2010

- Licence : Copyright ® 2006 Johan Thelin. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Introduction

- III. L'algorithme de reconnaissance

- III-A. Étape 1 : filtrage du mouvement de la souris

- III-B. Étape 2 : limitation des directions

- III-C. Étape 3 : simplification de la liste de direction

- III-D. Étape 4 : identification et réduction

- IV. Les classes Qt

- V. Établir le lien

- Conception de gestes

- VI. L'apport des gestes à l'application

- VII. Fonctionnement de l'exemple

- VIII. Conclusions et améliorations possibles

- IX. Remerciements

I - L'article original

Qt Quarterly est une revue trimestrielle électronique proposée par Nokia à destination des développeurs et utilisateurs de Qt. Vous pouvez trouver les versions originales.

Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction de l'article Recognizing Mouse Gestures de Johan Thelin paru dans la Qt Quarterly Issue 18.

Cet article est une traduction d'un des tutoriels écrits par Nokia Corporation and/or its subsidiary(-ies) incluse dans la documentation de Qt, en anglais. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.

II - Introduction

Les gestes de souris sont de plus en plus courants, non seulement sur mais également dans les applications de bureau telles qu'Opera ou Mozilla Firefox, sans compter les jeux (comme Black & White). Le concept est simple : l'utilisateur dessine une forme tout en tenant un bouton de souris enfoncé, et si la forme est identifiée, l'action associée est exécutée.

Nous commencerons par regarder un framework de gestes basique de souris que j'ai développé en utilisant Qt 4. Nous examinerons ensuite comment utiliser le framework depuis une application Qt.

Télécharger le code source

III - L'algorithme de reconnaissance

La reconnaissance d'un geste de souris n'est pas aussi difficile qu'on pourrait le croire, parce qu'elle peut être réduite en quatre étapes assez simples : filtrage, limitation, simplification, et identification.

III-A - Étape 1 : filtrage du mouvement de la souris

Le filtrage est appliqué tant qu'un mouvement de souris (un segment dans le geste) est enregistré. Il empêche les mouvements très courts de souris d'être enregistré. Cette étape est nécessaire parce que chaque matériel rapporte des positions de souris à différents taux, et parce que le reste de l'algorithme pourrait échouer si le mouvement est trop petit.

III-B - Étape 2 : limitation des directions

L'étape de limitation est la détermination de ce qu'a voulu faire réellement l'utilisateur. Ceci est fait en limitant chaque segment du geste à une des quatre directions : haut, bas, gauche et droite. Plus précisément, nous devons comparer les composantes x et y de chaque segment et mettre à zéro le plus petit des deux.

III-C - Étape 3 : simplification de la liste de direction

La troisième étape, simplification, consiste à trouver les mouvements consécutifs de même direction et joindre ces derniers. Ceci nous donne une liste avec les directions qui composent le geste (par exemple : « droit, haut, droit, haut »). Cette liste peut alors être identifiée à des gestes prédéfinis.

III-D - Étape 4 : identification et réduction

La partie d'identification est la partie la plus difficile de l'algorithme, puisqu'il est courant que les utilisateurs changent légèrement de direction quand ils relâchent le bouton de la souris. De plus, nous devons traiter de petits problèmes engendrés sur les longs mouvements.

L'algorithme commence par essayer d'identifier le geste à partir d'une liste prédéfinie. Si cela échoue, nous retirons le segment le plus court du geste et nous retentons d'effectuer l'identification. Cela est répété jusqu'à ce qu'une identification soit trouvée ou jusqu'à ce qu?une proportion trop grande du geste original ait été enlevée.

IV - Les classes Qt

Le framework de gestes à la souris décrit dans cet article permet d'ajouter facilement le support de geste aux applications Qt existante. La partie spécifique au framework est constituée de deux classes :

- QjtMouseGestureFilter est un filtre d'événement qui récupère l'activité de la souris sur un widget Qt spécifié ;

- QjtMouseGesture représente un geste.

Commençons par la définition de la classe QjtMouseGestureFilter :

class QjtMouseGestureFilter : public QObject
{
public:
QjtMouseGestureFilter(Qt::MouseButton button = Qt::RightButton,
QObject *parent = 0);
~QjtMouseGestureFilter();  void addGesture(QjtMouseGesture *gesture);
void clearGestures(bool deleteGestures = false);protected:
bool eventFilter(QObject *obj, QEvent *event);
...
};

Pour l'utiliser, nous devons simplement créer une instance, appelée installEventFilter() sur les fenêtres ou widgets qui devraient soutenir les entrées gestuelles, et lui fournir une liste de gestes de souris. Voici la définition de la classe de geste :

class QjtMouseGesture : public QObject
{
Q_OBJECTpublic:
QjtMouseGesture(const DirectionList &directions,
QObject *parent = 0);
~QjtMouseGesture();  const DirectionList directions() const;signals:
void gestured();

private:
...
};

Un geste est essentiellement une liste de directions et un signal. Les directions disponibles sont Up, Down, Left, Right, AnyHorizontal, AnyVertical, et NoMatch. AnyHorizontal signifie « gauche ou droite », tant dis que AnyVertical signifie « haut ou bas ». La direction NoMatch est utilisée pour créer un geste, lui-même identifié dans le cas où aucun autre geste n'au pu être identifié.

L'énumération Direction, qui définit les directions disponibles, fait partie du namespace Gesture, décrit dans la prochaine partie. Pour une gestion plus simple, nous utilisons les typedef et les directives using suivants :

typedef Gesture::Direction Direction;

using Gesture::Up;
using Gesture::Down;
using Gesture::Left;
using Gesture::Right;
using Gesture::AnyHorizontal;
using Gesture::AnyVertical;
using Gesture::NoMatch;

typedef QList<Direction> DirectionList;

V - Établir le lien

Les classes actuelles de reconnaissance de gestes à la souris sont disponibles comme un ensemble de classes dans le namespace Gesture. Les classes spécifiques de Qt identifient le mécanisme de filtrage d'évènements de Qt pour un ensemble de fonctions de suivie de la souris (indépendantes du framework), et une classe de rappel à un signal de Qt. Les classes neutres correspondant à QjtMouseGesture sont montrées ci-dessous :

typedef enum { Up, Down, Left, Right, AnyHorizontal,
AnyVertical, NoMatch } Direction;
typedef std::list<Direction> DirectionList;class MouseGestureCallback
{
public:
virtual void callback() = 0;
};struct GestureDefinition
{
GestureDefinition(const DirectionList &d, MouseGestureCallback *c)
: directions(d), callbackClass(c) {}  DirectionList directions;
MouseGestureCallback *callbackClass;
};

Pour établir le lien avec Qt, nous devons faire une classe qui hérite de MouseGestureCallback et ré-implémenter la méthode callback() comme suit :

class GestureCallbackToSignal
: public Gesture::MouseGestureCallback
{
public:
GestureCallbackToSignal(QjtMouseGesture *object) {
m_object = object;
}  void callback() {
m_object->emitGestured();
}private:
QjtMouseGesture *m_object;
};

Pour permettre à la classe de transition d'émettre le signal gestured() au nom de l'objet QjtMouseGesture, nous utilisons la fonction privée emitGestured(). Ceci exige que GestureCallbackToSignal soit déclaré en tant qu?ami de QjtMouseGesture. La section privée de QjtMouseGesture ressemble à ceci :

class GestureCallbackToSignal;class QjtMouseGesture : public QObject
{
...private:
friend class GestureCallbackToSignal;
void emitGestured();  DirectionList m_directions;
};

La classe indépendante du framework correspondant à QjtMouseGestureFilter est appelée MouseGestureRecognizer :

class MouseGestureRecognizer
{
public:
MouseGestureRecognizer(int minimumMovement = 5, double minimumMatch = 0.9);
~MouseGestureRecognizer();  void addGestureDefinition(
const GestureDefinition &gesture);
void clearGestureDefinitions();  void startGesture(int x, int y);
void addPoint(int x, int y);
void endGesture(int x, int y);
void abortGesture();private:
...
class Private;
Private *d;
};

Les fonctions startGesture(), addPoint(), et endGesture() sont invoquées à partir de la partie du filtrage d'événements de la classe QjtMouseGestureFilter. La transition réelle à lieu dans addGesture() :

void QjtMouseGestureFilter::addGesture(
QjtMouseGesture *gesture)
{
Gesture::DirectionList dl = gesture->directions().toStdList();  d->bridges.append(GestureCallbackToSignal(gesture));
d->gestures.append(gesture);  d->mgr.addGestureDefinition(
Gesture::GestureDefinition(dl, &d->bridges.last()));
}

Nous copions toutes les directions d'une QList vers une liste STL, puis nous introduisons le geste dans une instance GestureCallbackToSignal. Pour finir, nous ajoutons GestureDefinition à MouseGestureRecognizer.

Les liens et les gestes ajoutés au QjtMouseGestureFilter sont détenus dans la variable membre privée d avec l'instance MouseGestureRecognizer.

Conception de gestes

Concevoir des gestes de souris consiste à définir une liste de directions comme « haut, gauche ». Lors de la conception de gestes pour une application, nous devons toujours garder le processus d'identification en tête. Par exemple, la définition de gestes qui diffère juste d'un segment est risquée, parce que l'utilisateur pourrait facilement déclencher une mauvaise action en faisant un léger mouvement involontaire

Généralement, les gestes de souris sont seulement utiles s'il est facile de se les rappeler et si l'application est orientée souris. Par exemple, il est inutile de définir ainsi l'ouverture d'une boîte de dialogue qui exige la saisie au clavier.

VI - L'apport des gestes à l'application

Du point de vue du programmeur d'application Qt, ce qui est vraiment intéressant est la manière dont nous pouvons apporter des gestes à une application. Pour illustrer ceci, nous emploierons la simple classe MainWindow affichée ci-dessous comme exemple.

Voici la définition de la classe :

class MainWindow : public QMainWindow
{
Q_OBJECTpublic:
MainWindow();public slots:
void clearAll();
void setAll();  void noMatch();private:
...
};

La fonction initiale main(), sans le support de geste, ressemble à ceci :

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mainWin;
mainWin.show();
return app.exec();
}

Pour ajouter des gestes à l'application, nous devons créer une instance de QjtMouseGestureFilter et l'installer sur le MainWindow. Nous devons également définir des gestes et les connecter a l'application. Par exemple :

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow mainWin;  QjtMouseGestureFilter filter;
mainWin.installEventFilter(&filter);  // Efface tout en effectuant trois déplacement de coté
QjtMouseGesture g1(DirectionList() << AnyHorizontal
<< AnyHorizontal << AnyHorizontal);
filter.addGesture(&g1);
connect(&g1, SIGNAL(gestured()), &mainWin, SLOT(clearAll()));  // Définit tout par un mouvement vers le haut, puis vers la gauche.
QjtMouseGesture g2(DirectionList() << Up << Left);
filter.addGesture(&g2);
connect(&g2, SIGNAL(gestured()), &mainWin, SLOT(setAll()));  mainWin.show();
return app.exec();
}

Pour rendre l'application un peu plus conviviale, nous pouvons ajouter un slot noMatch() à MainWindow et l'associer à un geste spécial « no match » (aucune correspondance) :

int main(int argc, char **argv)
{
...
// Quand rien d'autre n'est identifié
QjtMouseGesture g3(DirectionList() << NoMatch);
filter.addGesture(&g3);
connect(&g3, SIGNAL(gestured()),
&mainWin, SLOT(noMatch()));
...
}

VII - Fonctionnement de l'exemple

Jusqu'ici, nous avons parlé de comment le framework identifie les gestes, et pris de brève morceau de code pour illustrer un exemple simple. Construire cet exemple devrait juste être un cas simple d'exécution de qmake et de make.

Maintenant, lançons l'exemple. La fenêtre affiche cinq checkboxes qui seront cochées ou décochées quand un geste approprié est fait. Si vous maintenez le bouton de la souris droit (le bouton de geste par défaut dans le constructeur QjtMouseGestureFilter), les déplacements de souris seront enregistrés. Relâcher le bouton de la souris fait arrêter l'enregistrement, et le programme mettra à jour chaque checkboxe si un geste est identifié, sinon il affichera une boîte de dialogue rappelant quels gestes lui sont connus.

Un geste reconnu est le déplacement de la souris vers le haut puis vers la gauche ; un autre exige trois déplacements horizontaux. Il y a également un geste secret que vous devrez découvrir par vous-même !

VIII - Conclusions et améliorations possibles

Avec le framework de geste de souris présenté dans cet article, le support de gestes de souris peut facilement être ajouté à n'importe quelle application Qt. Le code du framework indépendant de reconnaissance gestuelle a été enveloppé dans une interface Qt, ce qui rend facile l'intégration à d'autres classes de Qt. Les améliorations spécifiques à Qt pourraient inclure un support pour le stockage des gestes, basé sur QSettings, une rétroaction visuelle ayant pour but d'aider les utilisateurs à apprendre de nouveaux gestes, et même des dispositifs d'édition de gestes.

L'algorithme de reconnaissance de geste décrit ici est simple pour commencer mais a quelques limitations. En effet, il ne supporte pas des directions en diagonales ou des longueurs relatives. Les interfaces utilisateur basées sur les stylets telles que les et les ordinateurs de tablette bénéficieraient le plus de ces améliorations.

IX - Remerciements

J'adresse ici de chaleureux remerciements à Thibaut Cuvelier pour sa relecture !

Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir autorisé la traduction de cet article !