Avant de commencer

Avant d'entrer dans le vif du sujet, préparons d'abord le terrain. Je proposerai pour chaque exemple le code dans ce billet, mais je vous conseille, si vous voulez tester, de télécharger l'archive jointe à ce billet contenant les sources des exemples. Une fois décompressée, déplacez vous dans le dossier qtscripting, puis ouvrez une console, et tapez :

qmake-qt4
make

Tous les exemples sont alors compilés. Bien sûr, pour que la compilation fonctionne, ils vous faut installer le paquet qt-devel. Sous Fedora, tapez :

# yum install qt-devel

Une fois les exemples compilés, vous pouvez vous déplacer dans les dossiers, et lancer les exécutables.

Notez que pour ajouter le support des scripts au projet, il a simplement suffit d'ajouter script à la directive QT (voir le QT += script dans le fichier common.pri), et d'include QtScript dans les sources.

Exemple 1 : 1 + 1

Pour le premier exemple, faisons simple ! Il s'agit ici simplement de voir comment exécuter un code JavaScript depuis du code C++, et de récupérer le résultat.

On commence direct avec le code :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    QScriptValue resultat = engine.evaluate("1+1");
    qDebug() << resultat.toString() ;
    return 0;
}

Rien de complexe. On créé d'abord un objet de type QScriptEngine, qui sera notre moteur de script, indispensable. On demande ensuite à notre moteur d'évaluer le code "1+1". Le résultat de l'évaluation est stocké dans un objet de type QScriptValue. Enfin on affiche le résultat. En parcourant la doc sur QScriptValue, vous pourrez vous rendre compte qu'il est possible d'effectuer de nombreuses vérifications afin de s'assurer que le résultat est exploitable, notamment en contrôlant son type.

Voilà pour un début, il n'y a rien d'exceptionnel bien sûr, mais les base sont posées : notre programme a affiché le résultat d'un code externe (en l'occurrence du JavaScript, même si ce n'est qu'une addition, et que le code est "en dur" dans le programme). Pour les plus sceptiques, j'affiche la sortie du programme :

[eponyme@localhost exemple1]$ ./exemple1
"2"
[eponyme@localhost exemple1]$

Surpris ? :D

Passons à la suite.

Exemple 2 : appel d'une fonction

Nous allons dans cette exemple appeler une fonction et lui passer un paramètre, puis afficher ce qu'elle nous renvoie. Le code :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    engine.evaluate("function disMoiBonjour(prenom) { return \"Bonjour \"+prenom; }");    
    QScriptValue disBonjour = engine.globalObject().property("disMoiBonjour");
    qDebug() << disBonjour.call(QScriptValue(),QScriptValueList() << "Fabien").toString() ;
    return 0;
}

Comme dans le premier exemple, on créé un moteur, mais cette fois ci au lieu de lui faire évaluer une addition, nous demandons d'évaluer la définition d'une fonction, appelée disMoiBonjour. Cette fonction retourne une phrase "disant bonjour" à la personne dont le prénom est passé en paramètre. La ligne suivante nous permet de récupérer parmi les objets du script (globalObject())  notre fonction disMoiBonjour (property()), conservée dans la variable disBonjour. Il ne nous reste plus qu'à l'appeler grâce à la méthode call(). Notez le second paramètre, qui est une liste de valeurs qui sont passées en arguments à la fonction appelée. Ici, on envoie juste la chaîne "Fabien".

Voici la sortie du programme :

[eponyme@localhost exemple2]$ ./exemple2
"Bonjour Fabien"
[eponyme@localhost exemple2]$

Cet article ayant juste pour but de découvrir des choses simples et basiques possibles avec QtScript, je ne rentre pas plus dans les le détail concernant les différentes méthodes utilisées ici, mais la documentation vous fera découvrir que l'on peut aller beaucoup plus loin.

Au passage, on voit déjà que du code JavaScript dans une chaine de caractères, ça n'est pas très pratique. Vivement qu'on puisse le mettre dans un fichier !

Exemple 3 : code JS dans un fichier et objet global

Pour le moment, nos scripts étaient "en dur" dans le code C++, ce qui n'a que très peu (aucun ?) intérêt. Voyons ici comment externaliser ce code et le placer dans un fichier. La méthode evaluate() prenant en paramètre une chaine de caractères, il sera très simple (surtout avec Qt) de récupérer le contenu d'un fichier texte pour le faire évaluer. Je profite aussi de cet exemple pour montrer comment exporter depuis le code C++ un objet global dans le script. Comme d'habitude, on attaque directement avec le code :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    QFile file("script.qs");
    file.open(QIODevice::ReadOnly);
    engine.globalObject().setProperty("name","Fabien");
    engine.evaluate(file.readAll());   
    file.close();
    return 0;
}

Comme on pouvait s'y attendre, la chose est très simple à réaliser grâce à l'objet QFile. On ouvre en lecture seule un fichier nommé script.qs, et on passe son contenu à la méthode evaluate() via la méthode readAll(). Le contenu du fichier est ainsi entièrement évalué. L'autre nouveauté ici est que l'on "envoie" dans le script l'objet "name", dont la valeur est "Fabien", avec la méthode setProperty(). Ainsi, à n'importe quel endroit du script, il sera possible d'accéder à cet objet.

Voyons d'ailleurs le contenu du script (fichier script.qs) :

print('Bonjour '+name);

Ce petit script se contentera donc d'afficher dans la console un bonjour à la personne dont le prénom est stocké dans l'objet name (et assigné depuis le code C++).

Voyons ce que ca donne à l'exécution :

[eponyme@localhost exemple3]$ ./exemple3
Bonjour Fabien
[eponyme@localhost exemple3]$

La encore, pas de grande surprise ! Mais désormais, vous pouvez stocker votre code JS dans un fichier, ce qui ouvre un grand nombre de possibilités, notamment celui de stocker différents scripts afin de charger différentes fonctions, pour un système de plugins.

Exemple 4 : envoi d'un QObject dans le script

L'exemple 3 nous a montré qu'il est possible d'envoyer un objet avec une valeur dans le script, et ce depuis le code C++. Cependant cet objet ne peut prendre qu'une valeur simple. Nous allons voir ici comment envoyer dans le script un QObject (et donc un de ses objets hérités, ce qui en fait beaucoup).

Pour illustrer cet exemple, je vais utiliser un QLabel, habituellement utilisé pour afficher un libellé dans une interface graphique. Voici le code :

#include <QtScript>
#include <QApplication>
#include <QLabel>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QLabel lbl;
    QScriptEngine engine;
    QFile file("script.qs");
    file.open(QIODevice::ReadOnly);
    lbl.setText("UN");
    QScriptValue object = engine.newQObject(&lbl);
    engine.globalObject().setProperty("label",object);   
    engine.evaluate(file.readAll());
    file.close();
    qDebug() <<"Dans le code : " <<  lbl.text();
    return 0;
}

Le QLabel ne sera pas utilisé ici dans une interface, mais on utilise ses méthodes pour le manipuler. Après l'habituelle création du moteur, on créé donc un objet QLabel, pour lequel on modifie le libellé en le passant à "UN", grâce au slot setText(). Ensuite, la méthode newQObject() du moteur est appelée, prenant en paramètre un pointeur sur notre objet de type QLabel. C'est la valeur renvoyée par newQObject qui est utilisée pour le setProperty() de la ligne suivante, déjà utilisé dans l'exemple 3. Ainsi, dans notre script, on pourra utiliser notre objet QLabel n'importe quand grâce à la variable label, dont le nom est passé en premier argument à setProperty(). Comme dans les autres exemples, on évalue le script, puis on ferme le fichier. Enfin, on affiche la valeur de notre QLabel grâce à sa méthode text(). En toute logique, elle devrait renvoyer "UN", à moins que la valeur ait été changée depuis le script ... Voyons justement le code de ce dernier (fichier script.qs) :

print("Dans le script : "+label.text);
label.setText("DEUX");

Deux lignes, très simples. La première affiche la valeur du QLabel accessible grâce à la variable label. La valeur du QLabel est obtenue grâce à la propriété text de ce dernier. Sur la seconde ligne, on change la valeur du QLabel à "DEUX", grâce à setText().

Voyons ce que donne l'exécution :

[eponyme@localhost exemple4]$ ./exemple4
Dans le script : UN
Dans le code :  "DEUX"
[eponyme@localhost exemple4]$

On voit ici l'évolution de notre QObject ! La première valeur affichée ("UN") depuis le script (avec print()) est celle qui avait été fixée dans le code C++. Après avoir affiché la valeur, le script JS a changé la valeur du QLabel, et depuis le code, la valeur affichée est bien "DEUX".

Nous pouvons donc de façon très simple passer à notre script JS des QObject et lui permettre d'en modifier les propriétés.

Exemple 5 : gestion des erreurs

Lorsque l'on débute, ou lorsque l'on commence à faire des choses compliquées, il arrive souvent que l'on bloque car le code JS n'est pas évalué. Dans la plupart des cas, il s'agit juste d'une erreur de syntaxe. Mais il est parfois difficile de savoir pourquoi est ce que notre script n'est pas évalué, et donc qu'il est impossible d'interagir avec lui ensuite. On peut aussi être parfois sûr de son code, et ne pas penser qu'il y a une erreur. Heureusement il est possible, lorsqu'une erreur est rencontrée, d'afficher un message. Voici un code d'exemple :

#include <QtScript>
#include <QApplication>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QScriptEngine engine;
    QScriptValue result = engine.evaluate("print('Bonjour !'");
    if (result.isError()) {
        qDebug() << result.property("message").toString();
    }
    return 0;
}

Dans ce code, on demande au moteur d'évaluer du JavaScript devant afficher "Bonjour !", mais la parenthèse fermante est volontairement oubliée. On se sert de la valeur renvoyée par l'évaluation pour vérifier s'il y a eu une erreur avec isError(). Si c'est le cas, on peut afficher un message explicatif en récupérant la propriété message du résultat d'évaluation.

Voyons l'exécution du programme :

[eponyme@localhost exemple5]$ ./exemple5
"Parse error"
[eponyme@localhost exemple5]$

Notre code n'est pas évalué, et le moteur nous indique une Parse error. On sait au moins ou chercher. Cette vérification vous permettra de gagner un peu de temps si vous n'obtenez pas le résultat espéré.

Exemple 6 : signaux et slots

Les signaux et slots sont l'un des avantages du développement avec la bibliothèque Qt, et n'ont pas été oubliés avec QtScript. Dans ce dernier exemple, nous allons créer une fenêtre basique comprenant deux boutons. Le premier, appelé "Bonjour", devra afficher "Bonjour !" dans la console lorsque l'on clique dessus, et le second, "Au revoir", devra afficher ... "Au revoir !" (et oui !)  lorsque l'on clique dessus. La particularité est que ces affichages dans la console se feront depuis un script JS, et non pas directement depuis des slots dans le code C++. L'intérêt ? Ici, aucun. Il s'agira simplement de montrer que l'on peut connecter un signal à un slot dont l'implémentation est faite dans un script JS, ce qui permet de personnaliser facilement une application en changeant le comportement du slot dans les scripts. Pour l'exemple, nous verrons deux façons différentes de connecter un signal à un slot implémenté dans un script, la première sera faite depuis le code C++, pour le bouton "Bonjour", et la seconde directement dans le script JS, pour le bouton "Au revoir".

Passons au code :

#include <QtScript>
#include <QApplication>
#include <QtGui>
int main(int argc, char **argv)
{
    QApplication app(argc, argv);
    QWidget fenetre;
    QVBoxLayout vbox(&fenetre);
    QPushButton bBonjour("Bonjour");
    QPushButton bAuRevoir("Au revoir");
    vbox.addWidget(&bBonjour);
    vbox.addWidget(&bAuRevoir);

    QScriptEngine engine;
    QFile file("script.qs");
    file.open(QIODevice::ReadOnly);
    QScriptValue objBonjour = engine.newQObject(&bBonjour);
    QScriptValue objAuRevoir = engine.newQObject(&bAuRevoir);
    engine.globalObject().setProperty("objAuRevoir",objAuRevoir);
    engine.evaluate(file.readAll());
    file.close();
    QScriptValue slot = engine.globalObject().property("bonjourHandler");
    qScriptConnect(&bBonjour, SIGNAL(clicked()), objBonjour, slot);

    fenetre.show();
    return app.exec();
}

C'est un peu plus long que d'habitude, certes, mais le premier pavé ne concerne que l'interface graphique, ce qui n'est pas le sujet de ce billet. On y créé les deux boutons et on les place sur la fenêtre.

Ensuite, comme d'habitude, on créé un moteur, qui évaluera du code JS placé dans un fichier. Deux "objets QtScript" sont créés à partir des deux boutons, grâce à newQObject() : objBonjour et objAuRevoir. Ils nous serviront à réaliser les connexions avec les slots du script. L'objet objAuRevoir est exporté dans le script car nous en aurons besoin pour réaliser la connexion au slot

Une fois l'évaluation du script faite, on récupère la propriété bonjourHander, qui est une fonction dans le script JS, que l'on stocke dans la variable slot. Cette variable est utilisée ensuite pour réaliser la connexion entre le signal clicked du bouton "Bonjour" et le slot bonjourHandler, grâce à la fonction qScriptConnect().

Voyons maintenant le contenu du script (fichier script.qs) :

function bonjourHandler () {
    print('Bonjour !');
}
function auRevoirHandler () {
    print('Au revoir !');
}
objAuRevoir.clicked.connect(this.auRevoirHandler);

On y trouve les deux fonctions qui seront appelées lors des clics sur les boutons. bonjourHandler est déjà connectée au bouton "Bonjour" via l'appel à qScriptConnect() dans le code C++. La fonction auRevoirHandler est elle connectée au bouton "Au revoir" à la dernière ligne du script, qui utilise l'objet objAuRevoir exporté dans le programme. Ainsi nos deux fonctions sont connectées à leur bouton respectif, l'une depuis le code C++, l'autre depuis le script JS. A vous de voir ce que vous préférez !

Une petite image pour l'exécution du programme :

tuto_qtscript_exemple6.jpg


C'est sur cet exemple que ce termine cette initiation. Je n'ai pas la prétention avec cet article de donner une vue d'ensemble des possibilités offertes par QtScript. J'en suis d'ailleurs encore au stade de la découverte. Mais ces exemples pourrons vous permettre de vous lancer, et de démystifier un peu les choses ! Je proposerai dans un prochain article un exemple concret d'utilisation de QtScript pour un système de plugins.

Hormis les liens disséminés un peu partout dans l'article, les deux sources qui m'ont le plus aidé sont :

Bon dev' !


Fabien (eponyme)