Le projet

Pour commencer, assurez vous que le package qt-devel est installé sur votre poste. Si ce n'est pas le cas, sous Fedora, tapez :

# yum install qt-devel

Il faudra ensuite télécharger les sources du projet, dont vous trouverez l'archive jointe à ce billet. Une fois cette dernière décompressée, déplacez vous dans le dossier qtexcalc puis tapez :

$ qmake-qt4
$ make

Le projet est alors compilé. Vous pouvez l'exécuter :

$ ./qtexcalc

L'application se lance :

capture1.jpg

Elle est très simple. Choisissez un type d'opération proposé dans la liste, saisissez deux nombre, puis cliquez sur le bouton pour afficher le résultat. Tout l'intérêt de ce programme est donc qu'il est modulable, grâce à des scripts JS. A son lancement, elle charge des plugins placés dans le sous-répertoire plugins. Chaque plugin fourni trois informations :

  • Le libellé de son opération, qui sera affiché dans la liste ("Addition" par exemple)
  • Le signe de son opération, qui sera affiché sur le bouton ("+" par exemple)
  • Une procédure de traitement, prenant deux chiffres en paramètre, et renvoyant un résultat. Le traitement effectué est évidemment propre à chaque plugin

Le code

Les sources sont composées d'une classe Dialog comprennant la fenêtre principale, ainsi que les différents traitements associés aux actions de l'utilisateur, une classe Plugin, dont chaque instance "représentera" un des plugins, et qui sera chargée de dialoguer avec. Enfin, un fichier main, simplement chargé de créer la fenêtre principale et de l'afficher. Passons au code.

main.cpp

C'est le seul fichier dont je copierai intégralement le contenu ici, tant il est petit :

#include <QtGui/QApplication>
#include "dialog.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Dialog w;
    w.show();
    return a.exec();
}

Comme expliqué plus tôt, on se contente ici d'afficher la fenêtre du projet.

Classe Plugin

Cette classe a en charge de dialoguer avec le plugin. Elle contient trois méthodes dont voici l'implémentation :

Plugin::Plugin(QScriptEngine* eng)
{
    this->engine = eng;
    this->sign =this->engine->globalObject().property("getOperationSign").call(QScriptValue()).toString() ;
}

QString Plugin::getSign() {
    return this->sign;
}

double Plugin::compute(double num1, double num2) {
    return this->engine->globalObject().property("compute").call(QScriptValue(),QScriptValueList() << num1<<num2).toNumber();
}

Le constructeur prend en paramètre un pointeur sur un moteur de script, qui permettra de dialoguer avec le plugin, et récupère ensuite le signe de l'opération du plugin en appelant sa fonction getOperationSign()

La méthode getSign() renvoie le signe de l'opération du plugin qui a été stocké dans le constructeur.

Enfin, la méthode compute() se charge d'effectuer l'opération. Elle attend deux paramètres qui seront passés à la fonction compute() du plugin. Elle renvoie ensuite le résultat de l'opération.

Classe Dialog

Voici la principale classe du projet. Je laisse de coté ici tout ce qui concerne la gui, pour ne se concentrer que sur QtScript. Commençons par la méthode loadPlugins() qui est appelée lors de la construction de l'objet et qui permet de "charger" les différents plugins :

void Dialog::loadPlugins() {
    QDir dirPlugins("plugins");
    QString fName;
    QScriptEngine * engine;
    QComboBox * cbPlugins = this->ui->cbChoosePlugin;
    cbPlugins->addItem(QString::fromUtf8 ("Choisissez une opération ..."),"");
    foreach(fName,dirPlugins.entryList(QStringList() << "*.qs")) {
        engine = new QScriptEngine();
        QFile file("plugins/"+fName);
        file.open(QIODevice::ReadOnly);
        engine->evaluate(file.readAll());
        file.close();
        cbPlugins->addItem(engine->globalObject().property("getOperationName").call(QScriptValue()).toString(),fName);
        this->plugins[fName] = new Plugin(engine) ;
    }
}

Tous les fichiers dont l'extension est qs (pour QtScript, mais on peut bien sûr utiliser ce qu'on veut) sont listés grâce à la méthode entryList(). Pour chacun des fichiers, on créé un moteur de script puis on évalue le contenu. Ensuite, on ajoute à la liste (addItem()) le nom de l'opération, en appelant la fonction getOperationName du plugin, et on donne le nom du fichier (fName) comme data. Enfin, on construit un nouvel objet Plugin auquel on passe en paramètre le moteur et que l'on stocke dans une QMap, avec le nom de fichier (fName toujours) comme clé. Ainsi, lorsqu'une opération sera choisie dans la liste, on récupèrera le data (correspondant au nom de fichier) grâce auquel on pourra accéder à l'objet correspondant de la QMap.

Passons maintenant au slot choosedPlugin appelé à chaque fois que l'on sélectionne un item dans la liste des opérations :

void Dialog::choosedPlugin(int index) {
    if(index>0) {
        this->ui->bProcess->setText(this->plugins[this->ui->cbChoosePlugin->itemData(index).toString()]->getSign());
        this->ui->bProcess->setEnabled(true);
    }
    else {
        this->ui->bProcess->setText("");
        this->ui->bProcess->setEnabled(false);
    }
}

On vérifie tout d'abord que l'on a pas sélectionné le premier élément, qui est celui demandant de choisir une opération. Si on a bien sélectionné une opération, on récupère la donnée de l'élément (itemData()) sélectionné, qui nous permet d'accéder à l'objet Plugin correspondant, depuis lequel on appelle la méthode getSign(). Le signe de l'opération est appliqué au bouton, qui est ensuite activé.

Passons au dernier slot, compute, appelé lorsque l'utilisateur clique sur le bouton :

void Dialog::compute() {
    if(!this->ui->leNumber1->text().isEmpty() && ! this->ui->leNumber2->text().isEmpty() ) {
        Plugin * selectedPlugin = this->plugins[this->ui->cbChoosePlugin->itemData(this->ui->cbChoosePlugin->currentIndex()).toString()];
        this->ui->lResult->setText(QString::number(selectedPlugin->compute(this->ui->leNumber1->text().toDouble(),this->ui->leNumber2->text().toDouble())));
    }
}

Si les deux champs de saisie ont été remplis, on récupère l'objet Plugin de l'opération sélectionnée. On appelle la méthode compute() du Plugin, en lui passant en paramètre la valeur des champs de saisie, qui nous retourne le résultat de l'opération, résultat que l'on affiche dans le libellé correspondant.

Voici pour terminer, le code du plugin "addition", je ne donne pas le code des 3 autres, qui se devine facilement, et qui est dispo dans les sources :

function getOperationName() {
    return "Addition";
}

function getOperationSign() {
    return "+";
}

function compute(num1,num2) {
    return num1+num2;
}

On y voit donc les trois fonctions nécessaires, donnant le libellé de l'opération, son signe, et enfin le résultat de l'opération.

Voilà pour l'explication de ce petit projet, qui j'espère vous permettra de voir de quelle façon on peut facilement avec Qt gérer des plugins écrits en JavaScript. Si vous avez des questions sur le code, n'hésitez pas à les poser dans les commentaires.


Bon dev'

Fabien (eponyme)