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.
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 QListQList 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 QSettingsQSettings, 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 !