Tag Archives: qt

rand() moi un entier !

Cet article est une synthèse de plusieurs conférences, de la documentation C++ et de mon expérience sur la génération de nombre aléatoire en C++.

Mon besoin

Je suis le développeur de Rolisteam. Un logiciel pour faire du jeu de rôle en ligne. Il intègre une solution badass (oui, je m’envoie des fleurs) de lancer de dés. J’ai passé beaucoup de temps entre juillet 2014 et février 2015 pour produire un langage de script interprété de lancement de commandes de dés. Vous pouvez y jeter un œil sur ce lien: DiceParser. Bref, j’ai crée tout un système pour lancer les dés et faire des opérations sur les résultats: tri, relance, filtre, explosion, arithmétique, gestion des priorités mathématiques et il est même capable de générer l’arbre d’exécution avec dot.

L’aléatoire

Pour la partie aléatoire dans tout le système, j’ai réutilisé le code historique de rolistik (l’ancêtre de rolisteam).
Certains utilisateurs sont venus me voir pour me demander de vérifier le système d’aléatoire car ils avaient fait des tests et avez déterminé que Rolisteam favorisait les résultats haut; ou bas selon les personnes.
J’avais droit à un tableau de statistique réalisé sur quelques dizaines de lancer de dés.
Argument qu’il est facile de détruire avec une étude statistique plus riche en lancer. DiceParser fonctionne en ligne de commande, donc très facile à «scripter». Bref, des gens avaient des doutes sans réelle preuve.
Du coup, je me suis un peu renseigné s’il n’y avait pas des méthodes plus «C++» avec le C++11, C++14 et/ou C++17.

L’existant

Quand il s’agit de générer des nombres aléatoires, la première implémentation qu’on apprend est souvent celle ci :

int resultat = std::rand()%MAX+debut;
usage classique de rand();

Je pense que tous les étudiants ont implémenté cette méthode, dans leurs premières années d’études.  Elle est facile à comprendre. Elle est disponible dans beaucoup de langages. Elle vient du C. Elle met en avant le modulo, ce qui lui donne peut-être une valeur pédagogique. Elle suffit dans de très nombreux cas.

Nous allons voir un peu ce qui cloche avec cette méthode.

Les défauts

1/ Pauvre entropie
Déjà, rand() ou std::rand() sont définis dans le “man” comme une solution pauvre pour obtenir de l’aléatoire. Il est précisé que les vieilles implémentations de rand() ou les implémentations sur d’autres systèmes fournissent un aléatoire pauvre sur les bits de poids faibles. Cela explique peut-être pourquoi certains utilisateurs sous Windows se plaignent.
Donc l’entropie de rand n’est pas bonne. Il existe une autre méthode: random() qui fonctionne normalement mieux, avec un période beaucoup plus grande.

2/ Erreur de répartition
Ces deux méthodes génèrent un nombre entre 0 et RAND_MAX. Maintenant, nous souhaitons obtenir des résultats sur un dés à 100 faces. (C’est un format de dés courant en Jeux de rôle).
Imaginons que RAND_MAX vaut 32767.

Si je découpe la valeur maximum en tranche de 100 cela donne ceci:

[0 : 99] => 100 valeurs
[100 : 199] => 100 valeurs
[200 : 299] => 100 valeurs
:
:
[32700 : 32767 ] => 67 valeurs.

Nous avons 327 tranches complètes et une dernière de 67.  L’usage du modulo vient faire la correspondance entre la valeur tirée dans l’espace de valeurs désirées. Cela favorise les résultats entre 0 et 67. Il y a une chance supplémentaire de tomber en dessous de 67. La différence est probablement négligeables mais tout de même, cela me pose problème d’avoir cela dans mon logiciel.

3/ Problème de portabilité

La valeur RAND_MAX vaut au minimum 32767 dans la norme. Elle diffèrent en fonction des implémentations, nous avons vu également que la portabilité de la méthode rand() n’est pas garantie.
Cette somme de problèmes vient conforter les utilisateurs de Rolisteam dans leur idée de problème relatif au système de dés. Deux problèmes de portabilité se cumulent.

Le constat de ces erreurs m’a poussé à chercher une solution moderne pour générer ces nombres.

Dans les API C++ est arrivé un nouveau “module” avec le C++11. Il s’agit de random.

#include <random>

Ce module présente toute une série de classes pour générer des nombres aléatoires et les répartir.
Il propose un nombre intéressant d’algorithmes ou de moteurs différents pour la génération. Je ne suis pas un expert en génération pseudo-aléatoire de nombre. Du coup, le nom des algorithmes ne me parle pas.
Cependant, il semble que deux éléments sortent du lot: le std::random_device et le std::mt19937.

Le std::random_device

Il est défini dans la documentation comme: Un générateur de nombre aléatoire non déterministe. En gros, cela signifie qu’on ne peut pas prévoir son comportement.
D’habitude, vous initialisez votre méthode aléatoire par un seed. Il peut être intéressant pour des tests automatiques ou une résolution de bug de pouvoir rejouer une séquence entière de nombre aléatoire.
Pour atteindre cet objectif, il suffit de mettre la même seed. Donc vous avez là une méthode de nombre aléatoire qui est déterministe. random_device n’a pas de seed. Il n’a pas besoin d’être initialiser. Il est donc impossible de lui faire “jouer” deux fois la même chose. Dans mon cas, cela me satisfait complètement.

C’est facile à mettre en place:

std::random_device rd;
std::uniform_int_distribution<int> dist(1, 10);
int randomNumber = dist(rd);
std::cout <<  randomNumber << std::endl;

On génère une nombre et on le distribue entre 1 et 10. Cela suffit. Il y a rien besoin de plus. C’est propre, élégant etc.
J’ai implémenté cette méthode dans rolisteam 1.8, malgré les avertissements qu’on trouve à droit à gauche. Cela donne de bons résultats sous Linux. J’ai fait une version pour Windows et là, c’est le drame.
L’entropie du module sous windows est de zéro. Cela signifie qu’il renvoie toujours le même résultat. Bref, les dés étaient toujours de la même valeur.
La solution du random_device ne marche pas. Elle n’est pas portable.

Le std::mt19937

Il reste donc la méthode du std::mt19937. Elle fait toujours partie de l’API de <random>. Voici l’implémentation finale dans rolisteam.

// dans le constructeur de ma classe dé
auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
m_rng = std::mt19937(quintptr(this)+seed);

//dans la fonction lancer (roll())
std::uniform_int_distribution<qint64> dist(m_base,m_faces);
qint64 value = dist(m_rng);
L’implementation dans rolisteam.

On peut voir qu’elle est plus proche de l’ancienne méthode car Il est nécessaire de l’initialiser avec un seed. Pour cela, il est également possible d’utiliser une API C++11: <chrono>.

 #include <chrono>
…
auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
m_rng = std::mt19937(quintptr(this)+seed);
Politique Agricole Commune

 

Autres méthodes:

Conclusion:

J’espère avoir fait le tour des nouvelles API pour la génération aléatoire de nombres entiers en C++. Il est important de garder à l’esprit que l’API <random> fournit bien sur bien plus de chose que ce que j’ai montré. Je n’ai pas fait d’étude statistique pour démontrer que c’est mieux maintenant. Dans tous les cas, j’ai implémenté le device_random dans mon lecteur audio maison, et je découvre plein de nouvelle musique de ma collection.

Les sources:

CppCon 2016: Cheinan Marks “I Just Wanted a Random Integer!”:
https://www.youtube.com/watch?v=4_QO1nm7uJs
Stephan T. Lavavej – rand() Considered Harmful:
https://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful

Distribuer une application Qt

Bonjour,

Si vous suivez ce blog, vous savez que je suis développeur C++/Qt et que je contribue beaucoup à Rolisteam (www.rolisteam.org). C’est un logiciel libre pour faire du jeu de rôle. Il vise un public assez large. Il est donc multi-plateforme. Il fonctionne sous GNU/Linux, windows et Mac Os. Je suis un linuxien convaincu mais je développe sur les autres systèmes pour Rolisteam.

L’aspect développement n’est pas vraiment un problème, grâce à Qt. Cela marche grosso modo bien. La chose devient plus périlleuse quand il s’agit de distribuer le logiciel.

Dans mon activité professionnelle, j’ai constaté,  qu’il est assez rare qu’un développeur connaisse les solutions pour distribuer facilement un logiciel. Je vais essayer ici d’expliquer comment faire. Du moins, comment je fais dans rolisteam et si vous avez d’autres astuces. N’hésitez pas à en faire part dans les commentaires.

Prédicat de base:

Vous avez un logiciel écrit en C++/Qt, il est géré par un fichier .pro. Certains systèmes de build propose des systèmes identiques, voir plus puissant mais plus complexe (e.g: cmake). Je me contente d’évoquer les outils Qt.

Pour GNU/Linux :

Pour ma part, j’ai opté pour une distribution source et la création de package Ubuntu. Grâce à Launchpad, il est possible de créer un dépôt pour son projet et de soumettre des versions. Il faut l’admettre, c’est très pratique. Il existe probablement des services pour faire cela pour d’autres distributions.

Des contributeurs ont créé des package pour d’autres distributions (fedora…). Pour arch-linux, j’ai crée un script de compilation (à tester).

L’ensemble des outils utilisées ne sont pas des outils Qt. Cela n’est donc pas le propos de cet article.  Je me suis mis de côté la tâche de jeter un coup d’œil à appimage. Cela pourrait être une solution intéressante.

Pour Windows:

Trouver tous les éléments

Une application a des dépendances. Sous windows, il est courant d’installer l’exécutable et ses dépendances dans un même dossier. Il est parfois difficile d’identifier les dépendances. Rassurez-vous un outil magique a été créée. Il s’agit de “windeployqt.exe“. C’est un outil distribué avec Qt. Son but est de copier toutes les dépendances Qt d’un binaire dans un dossier.

windeployqt  /chemin/vers/mon/executable.exe
Commande Windeployqt

Cette commande va lire le binaire et va copier l’ensemble des dépendances identifiés à côté du binaire.

Par soucis de clarté, je préfère utiliser un répertoire propre et distinct du répertoire de compilation.

windeployqt --dir /chemin/vers/un/dossier/propre /chemin/vers/mon/application.exe
Dossier séparé

L’ajout du l’argument –dir permet de définir ce «dossier de destination».

Une autre difficulté entre en jeu quand votre application utilise du QML. En effet, il convient de rajouter un argument à la commande afin de permettre à l’outil d’aller lire votre code QML pour trouver ses dépendances.

windeployqt --qmldir /chemin/vers/le/dossier/des/qml --dir /dossier/propre application.exe
Distributer du QML

Et après ?

Une fois cette étape terminée, vous aurez dans votre dossier de destination l’ensemble des dépendances Qt (et QML) de votre projets.

Je vous mets un exemple pour un logiciel utilisant Qt5 et un peu de QML le tout fut compilé avec Visual Studio.

├── accessible/
├── audio/
├── bearer/
├── designer/
├── Enginio.dll
├── iconengines/
├── icudt52.dll
├── icuin52.dll
├── icuuc52.dll
├── imageformats
├── mediaservice
├── msvcp120.dll
├── msvcr120.dll
├── platforms/
│ └── qwindows.dll
├── playlistformats/
├── position/
├── printsupport/
├── qml1tooling/
├── qmltooling/
├── Qt5Core.dll
├── Qt5Declarative.dll
├── Qt5Gui.dll
├── Qt5Network.dll
├── Qt5OpenGL.dll
├── Qt5Qml.dll
├── Qt5Quick.dll
├── Qt5WebKit.dll
├── Qt5Widgets.dll
├── Qt5Xml.dll
├── Qt5XmlPatterns.dll
├── sensorgestures/
├── sensors/
├── sqldrivers/
├── unins000.dat
└── unins000.exe

Si votre projet utilise d’autres bibliothèques (zlib …), il convient de rajouter les .dll correspondantes dans le dossier. La dernière étape est d’inclure le .exe de votre application.
En double-clickant sur le .exe, il devrait se lancer. Si ce n’est pas le cas, une dll est probablement manquante.
Cette procédure est à faire après un changement de version de Qt. Il faut vous assurer que les dll embarquées soient de la bonne version.

A ce stade, il ne reste qu’à mettre en place l’outil de publication de votre application.

 

Publication

Pour cette étape, il y a principalement deux méthodes:

La méthode de l’archive Zip, il suffit d’archiver le dossier de destination dans une archive .zip. Ce fichier peut être extrait sur n’importe quel windows, et cela fonctionnera. Il faudra créer à la main les raccourcis et il n’y pas d’autre moyen pour dés-installer le logiciel que de supprimer le dossier extrait à la main.

L’autre méthode est d’utiliser un outil qui va créer un installeur. Les plus connus sont Inno setup et NSIS.
J’utilise pour ma part: InstallForge (http://installforge.net), il est graphique et simple.
Qu’importe l’outil choisi, il faudra lui dire d’installer tous les fichiers de votre dossier de destination (en respectant la hiérarchie des fichiers) chez vos utilisateurs. Ces outils permettent de créer automatiquement des scripts de désinstallation, ils créent également les raccourcis sur le bureau et la barre de démarrage. Ils permettent aussi d’afficher du contenu à l’utilisateur (licence, logo du logiciel) et cela lui permet de prendre des décisions, au cours de l’installation (si besoin).

Pour finir, après cette étape, vous aurez un installeur soit en mode .zip, soit un .exe. Il ne reste plus qu’à mettre cet installeur accessible à vos utilisateurs et c’est bon.

Pensez à le tester quand même avant de faire des annonces publiques. On sait jamais !

 

Pour Mac OS:

Pour MacOs, il existe un outil similaire qui s’appelle: macqtdeploy.
Il utilise les mêmes paramètres que la version windows, la seule différence réside dans l’utilisation d’un .app (en lieu et place du .exe).

macdeploy application.app -qmldir=/User/dossier/vers/code/qml 
sur MacOs X

Bien sûr, cela peut être intégrer dans QtCreator comme étape de déployement.
Sur MacOs, il est possible de demander à Qt de compiler une application dans un bundle (c’est d’ailleurs la solution par défaut). Un bundle est un dossier se terminant par «.app». Dans l’environnement graphique de MacOs, un double click lance le logiciel qu’il contient. Il est également possible, d’explorer le contenu du bundle en ligne de commande (cela se comporte comme un dossier) ou par un click droit: «afficher le contenu du packet» en graphique.

Dans tous les cas, l’utilisation de la commande va venir placer l’ensemble des dépendances Qt dans le bundle. Pour être précis, les bibliothèques sont placées dans le dossier Contents/Frameworks du bundle. Les modules QML sont dans Resources/qml.

A l’instar de windows, il faut mettre également d’éventuelles autres bibliothèques dans le bundle. C’est une étape un peu technique. Pour rolisteam, j’utilise zlib, voilà le programme bash qui me permet de faire l’ajout et de bien définir les chemins.

#!/bin/sh

############################################
# Rolisteam 
# Script to deploy libz into rolisteam.
#
###########################################

echo 'Display dependancies'
otool -L rolisteam.app/Contents/MacOS/rolisteam



echo 'Create directory'
mkdir rolisteam.app/Contents/Frameworks/libz.framework/

echo 'Copy lib binary'
cp /usr/lib/libz.1.dylib rolisteam.app/Contents/Frameworks/libz.framework/

echo 'Rewrite path to the lib'
install_name_tool -change /usr/lib/libz.1.dylib @executable_path/../Frameworks/libz.framework/libz.1.dylib rolisteam.app/Contents/MacOS/rolisteam
Ajouter une dépendances à un bundle

En gros, il y a la copie du fichier en lui même puis, la modification de l’exécutable pour lui dire d’aller chercher zlib dans le bundle et non sur mon poste. Cette étape est très importante car sinon, votre bundle ne sera pas portable sur une autre machine.

Et après ?

Le fichier .app complet suffit pour être installé sur une autre machine. Cependant, il est de coutume de distribuer un logiciel par un dmg. Cela permet de le compresser pour faciliter sa distribution et surtout MacOs permet de rendre graphique l’installation par un dmg.

Pour réaliser cela, j’utilise un petit utilitaire: node-appdmg. Il suffit d’un petit fichier de configuration en json pour définir les paramètres.

{
  "title": "Rolisteam  - Mac Os X",
  "icon": "rolisteam.icns",
  "background": "1500-rolisteam.png",
  "icon-size": 80,
  "contents": [
    { "x": 448, "y": 344, "type": "link", "path": "/Applications" },
    { "x": 192, "y": 344, "type": "file", "path": "rolisteam.app" }
  ]
}
Configuration node-appdmg pour rolisteam

Vous lancez l’utilitaire avec le fichier de configuration et le nom de votre dmg pour obtenir votre dmg tout beau, tout propre.

node-appdmg dmg.json monapplication.dmg

Il ne reste qu’à distribuer votre dmg.

Conclusion

Nous venons de voir les solutions et les outils mis en place pour rolisteam.
Si vous avez des questions sur les outils,  ou si vous voulez en proposer d’autres, les commentaires sont là pour ça.

L5R présentation

Retours d’expérience d’une présentation QML

Pour préparer ma conférence à Pas Sage En Seine [FR], j’ai décidé de faire une présentation QML.
Cela offre un bien meilleur contrôle, bien plus de possibilité graphique (animations, intégration de code, 3D…) et de même de l’interactivité.
Bien sûr, une présentation QML demande plus de temps à faire. Le but de l’article est ici d’expliquer les astuces que j’ai mises en place. Espérant gagner du temps la prochaine fois.

Le contenu de la présentation QML:

Comme vous pouvez le voir, la présentation est une application C++ qui affiche du QML. Cette méthode permet de faciliter le contrôle de la partie QML. Je m’en sers pour enregistrer des captures d’écran de la présentation afin d’en faire un PDF. C’est le meilleur pour distribuer la présentation ou dans le cas ou pour éviter l’effet démo.
Tout en haut, il y a les classes C++, les fichiers de management du projet et le fichier main.qml. Dans le répertoire «pages», vous y trouverez les différentes slides de la présentation. Dans rsrc, il y a l’ensemble des images nécessaire à la présentation.

├── cpphighlighter.cpp
├── cpphighlighter.h 
├── deployment.pri 
├── LICENSE 
├── main.cpp 
├── main.qml 
├── pages 
│ ├── 01_intro.qml 
│ ├── 02_presentation.qml 
│ ├── 03_jdr_et_rolisteam.qml 
│ ├── 043_Exemple_code_1.qml 
│ ├── 04_jdr_avantages_pb.qml 
│ ├── 05_avantage_jdr_virtuel.qml 
│ ├── 06_fonctionnalites_rolisteam.qml 
│ ├── 07_rolisteam_debut.qml 
│ ├── 08_Rolistik_a_Rolisteam.qml 
│ ├── 10_frise_chronologique.qml 
│ ├── 11_son_usage.qml 
│ ├── 12_son_fonctionnement.qml 
│ ├── 13_dice_parser.qml 
│ ├── 14_themes_audio_3_pistes.qml 
│ ├── 15_nouveaute_1_8.qml 
│ ├── 16_projet_avenir.qml 
│ ├── 17_reussites.qml 
│ ├── 18_les_lecons.qml 
│ ├── 19_objectif_rolisteam_libre.qml 
│ ├── 20_FAQ.qml 
├── pasSageEnSeine.pro 
├── pasSageEnSeine.pro.user 
├── qmlcontroler.cpp 
├── qmlcontroler.h 
├── qmlcontroler.ui 
├── qml.qrc 
├── README.md 
├── rsrc 
│ ├── all.png 
│ ├── cc.png 
│ └── chat.png

L’application C++

Il peut être utile de connaître l’état de la présentation QML, et de pouvoir lire des notes sur chaque volet. Pour faire cela, j’ai écris une petite application C++.

le fichier main.cpp

Le premier objectif de l’application est d’afficher la présentation QML. Il faut donc charger le QML et établir des moyens de communication entre le monde QML et le C++. Comme vous pouvez le voir, je passe en paramètre la taille de la présentation en paramètre du QML. Ce dernier élément n’est pas obligatoire car par défaut j’utilise la taille de l’écran comme référence.

#include <QApplication>
#include <QQmlApplicationEngine>
#include "qmlcontroler.h"
#include <QQmlContext>
#include <QQuickTextDocument>

#include "cpphighlighter.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QQmlApplicationEngine engine;

    engine.rootContext()->setContextProperty("ScreenW",1280);
    engine.rootContext()->setContextProperty("ScreenH",720);

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    QmlControler ctr;
    ctr.setEngine(&engine);

    return app.exec();
}
main.cpp

Dans ce projet, la classe QmlControler est l’objet (fenêtre) C++ qui affiche l’aperçu de la présentation et les commentaires/notes.

Regardons en détails cet élément:

La fenêtre d’aperçu

C’est une simple fenêtre. Elle est découpée en deux parties.  Celle de gauche affiche dans un QLabel l’aperçu de la présentation QML. La partie de droite affiche les notes et commentaires sur la page courante dans un QTextArea.

void QmlControler::currentPageHasChanged(int i)
{
    m_currentScreen = i;
    QImage img = m_window->grabWindow();

    if(img.isNull())
        return;

    static int count = 0;


    img.save(tr("screens/%1_screen.png").arg(++count,3,10,QChar('0')),"png");
    qDebug() << "screen shot save" << count;

    m_ratioImage = (double)img.size().width()/img.size().height();
    m_ratioImageBis = (double)img.size().height()/img.size().width();

    m_label->setPixmap(QPixmap::fromImage(img));

    if((i+1>=0)&&(i+1<m_commentData.size()))
    {
        ui->textEdit->setHtml(m_commentData.at(i+1));
    }
    resizeLabel();
}
Current slide has changed

Quand la page courante change, la fenêtre C++ est informé par l’appel du slot: currentPageHasChanged alors l’application récupère une capture de la vue QML, l’affiche à l’écran (dans la partie gauche), affiche les notes à propos de la page courante et peut sauvegarder la capture dans un fichier.

Sauvegarder les captures dans des fichiers vous permet de créer un fichier PDF de votre présentation. Il s’agit là d’une solution de secours en cas de problème avec le matériel de la présentation ou une facilité pour distribuer votre présentation.

La commande qui va bien (sous linux) :

$ convert *.png mypresentation.pdf

 

L’application QML

Le système de chargement (Loader)

Par souci de clarté, je préfère avoir un fichier par page. Cela évite l’écueil d’un fichier QML long et complexe. L’application charge les pages en accord avec le modele de données principal dans le même ordre.  Pour faire cela, j’ai utilisé un ListModel. Une page est définie par un titre, un nom de fichier, un temps (non utilisé), et le nom de la page suivante.
Le fichier main.qml  affiche toutes les pages comme un «delegate» d’un pathview.  Toutes les pages sont chargés depuis le fichier de ressources de Qt (inclue dans le binaire grâce au système de ressources de Qt).

ListModel {
            id: panelModel
            ListElement {
                name: "Intro"
                path: "01_intro.qml"
                time: 1
                next: "Présentation de Rolisteam"
            }
First item of the model.

La donnée vraiment utile pour le chargement est bien sûr le nom du fichier.
Les autres sont là pour aider la rédaction ou la présentation.

Le loader fait son travail, voici les lignes importantes:

    PathView {
        id: view
        anchors.fill: parent
        model: panelModel
        highlightRangeMode:PathView.StrictlyEnforceRange
        snapMode: PathView.SnapOneItem
        delegate:  Loader {
             source: "pages/"+path
        }
Path View

Le temps était destiné à afficher un compte à rebours pour chaque slide mais je n’ai pas eu le temps de l’implémenter.

Il est important de mettre dans le main.qml tous les éléments que vous souhaitez avoir sur toutes les pages: le sommaire, l’indication de la page courante sur le total de pages, etc.

Ajouter un sommaire

Pour réaliser un sommaire, j’ai ajouté un ListView avec le modele suivant:

    ListView {
        id: listView1
        x: ScreenW*0.02
        y: ScreenH*0.3
        width: ScreenW/2
        height: ScreenH*0.2
        delegate: Item {
            width: ScreenW/2
            height: listView1.height/listView1.count
                Text {
                    color: view.currentIndex>=index ? "black" : "gray"
                    text: name
                    font.pointSize: ScreenH/48
                    anchors.verticalCenter: parent.verticalCenter
                    font.bold: true

                }
        }
        visible: view.currentIndex>0 ? true : false

        model: ListModel {
            ListElement {
                name: "Concepts"
                index:1
            }
            ListElement {
                name: "Chroniques"
                index:6
            }
            ListElement {
                name: "Logiciel"//système de build, code spécifique par OS.
                index:9
            }
            ListElement {
                name: "Bilan"
                index:15
            }
        }
    }
Table of contents in QML

Le champs index du modèle permet de connaître la page qui démarre cette section du sommaire.

Page suivante

Quand il y a beaucoup de pages il peut être intéressant de connaître la prochaine page afin de faire une petite transition vers elle. Dans mon cas, j’affiche dans le coin haut-droit le titre de la prochaine page. Ce fut assez utile pour les répétitions, je n’en ai presque pas eu besoin pendant la conférence.

    Text {
        anchors.top: parent.top
        anchors.right: parent.right
        text: panelModel.get(view.currentIndex).next+">"
    }
Next slide

L’autre possibilité est d’utiliser les notes dans la fenêtre C++.

Créer une Page de présentation QML

Chaque page est indépendante, elles peuvent être totalement différentes cependant pour des raisons de simplicité, elle utilise une conception identique. Dans mon cas, elles sont constituées d’une ListView avec un modèle. Chaque élément du modèle est un titre de section à évoquer pendant la présentation.

Les éléments ont un index. Cet index est contrôlé par le clavier (flèche bas pour augmenter l’index, haut pour réduire l’index).  Grâce à cet index, il est possible de cacher la liste et d’afficher une image, un schéma ou une vidéo.

Par exemple, dans ma présentation la fonctionnalité «Alias de dés» a pour index 10. Quand la index de la page devient 10, l’élément «Alias de dés» apparaît avec une petite animation. Puis l’index passe à 11, j’affiche une image illustrant la fonctionnalité des alias de dés. A 12, l’image disparaît et le texte revient.

Position et taille

Pour s’assurer de la lisibilité des éléments, je réalise l’ensemble des calculs en rapport à la taille de l’écran. Le positionnement est assuré par des ancres (anchors en qml).

    Image {
        id: image1
        anchors.left: parent.left
        anchors.top: parent.top
        anchors.leftMargin: ScreenW*0.04
        fillMode: Image.PreserveAspectFit
        source: "qrc:/rsrc/Rolisteam.svg"
        width: ScreenW*0.2
    }
Display the logo at the right position and size.

Autre piste

Il existe un module qui fournit des éléments QML pour faciliter la réalisation d’une présentation. Je ne l’ai pas utilisé mais il y a des choses intéressantes.

Module QML

Obtenir le code de mes présentations

Je vous invite à cloner ce code : https://github.com/obiwankennedy/pses

Autre exemple avec une image: https://github.com/obiwankennedy/l5rSummary

L5R présentation QML

Exemple de présentation QML

 

 

Rolisteam Evolution

The function evolution…

I would like to show the evolution of one function in rolisteam code.
This function is called when the user (Game Master) closes a map or image.

The function must close the map or image and send a network order to each player to close the image or plan.

At the beginning, the function looked like that.
Some code line and comments were written in French. No coding rules were respected.
Low-level code for networking. The function was very long.

 // On recupere la fenetre active (qui est forcement de type CarteFenetre ou Image, sans quoi l'action
        // ne serait pas dispo dans le menu Fichier)
        QWidget *active = workspace->activeWindow();

        // Ne devrait jamais arriver
        if (!active)
        {
                qWarning("Close map action called when no widget is active in the workspace (fermerPlanOuImage - MainWindow.h)");
                return;
        }

        // On verifie pour le principe qu'il s'agit bien d'une CarteFenetre ou d'une Image
        if (active->objectName() != "CarteFenetre" && active->objectName() != "Image")
        {
                qWarning("not expected type of windows (fermerPlanOuImage - MainWindow.h)");
                return;
        }

        // Creation de la boite d'alerte
        QMessageBox msgBox(this);
        msgBox.addButton(QMessageBox::Yes);
        msgBox.addButton(QMessageBox::Cancel);
        msgBox.setIcon(QMessageBox::Information);
        msgBox.move(QPoint(width()/2, height()/2) + QPoint(-100, -50));
        // On supprime l'icone de la barre de titre
        Qt::WindowFlags flags = msgBox.windowFlags();
        msgBox.setWindowFlags(flags ^ Qt::WindowSystemMenuHint);
        // M.a.j du titre et du message
        if (active->objectName() == "CarteFenetre")
        {
                msgBox.setWindowTitle(tr("Close Map"));
                msgBox.setText(tr("Do you want to close this map?\nIt will be closed for everybody"));
        }
        else
        {
                msgBox.setWindowTitle(tr("Close Picture"));
                msgBox.setText(tr("Do you want to close this picture?\nIt will be closed for everybody"));
        }
        msgBox.exec();

        // Si l'utilisateur n'a pas clique sur "Fermer", on quitte
        if (msgBox.result() != QMessageBox::YesRole)
                return;

        // Emission de la demande de fermeture de la carte
        if (active->objectName() == "CarteFenetre")
        {
                // Recuperation de l'identifiant de la carte
                QString idCarte = ((CarteFenetre *)active)->carte()->identifiantCarte();

                // Taille des donnees
                quint32 tailleCorps =
                        // Taille de l'identifiant de la carte
                        sizeof(quint8) + idCarte.size()*sizeof(QChar);

                // Buffer d'emission
                char *donnees = new char[tailleCorps + sizeof(enteteMessage)];

                // Creation de l'entete du message
                enteteMessage *uneEntete;
                uneEntete = (enteteMessage *) donnees;
                uneEntete->categorie = plan;
                uneEntete->action = fermerPlan;
                uneEntete->tailleDonnees = tailleCorps;

                // Creation du corps du message
                int p = sizeof(enteteMessage);
                // Ajout de l'identifiant de la carte
                quint8 tailleIdCarte = idCarte.size();
                memcpy(&(donnees[p]), &tailleIdCarte, sizeof(quint8));
                p+=sizeof(quint8);
                memcpy(&(donnees[p]), idCarte.data(), tailleIdCarte*sizeof(QChar));
                p+=tailleIdCarte*sizeof(QChar);

                // Emission de la demande de fermeture de la carte au serveur ou a l'ensemble des clients
                emettre(donnees, tailleCorps + sizeof(enteteMessage));
                // Liberation du buffer d'emission
                delete[] donnees;

                // Suppression de la CarteFenetre et de l'action associee sur l'ordinateur local
                ((CarteFenetre *)active)->~CarteFenetre();
        }

        // Emission de la demande de fermeture de l'image
        else
        {
                // Recuperation de l'identifiant de la carte
                QString idImage = ((Image *)active)->identifiantImage();

                // Taille des donnees
                quint32 tailleCorps =
                        // Taille de l'identifiant de la carte
                        sizeof(quint8) + idImage.size()*sizeof(QChar);

                // Buffer d'emission
                char *donnees = new char[tailleCorps + sizeof(enteteMessage)];

                // Creation de l'entete du message
                enteteMessage *uneEntete;
                uneEntete = (enteteMessage *) donnees;
                uneEntete->categorie = image;
                uneEntete->action = fermerImage;
                uneEntete->tailleDonnees = tailleCorps;

                // Creation du corps du message
                int p = sizeof(enteteMessage);
                // Ajout de l'identifiant de la carte
                quint8 tailleIdImage = idImage.size();
                memcpy(&(donnees[p]), &tailleIdImage, sizeof(quint8));
                p+=sizeof(quint8);
                memcpy(&(donnees[p]), idImage.data(), tailleIdImage*sizeof(QChar));
                p+=tailleIdImage*sizeof(QChar);

                // Emission de la demande de fermeture de l'image au serveur ou a l'ensemble des clients
                emettre(donnees, tailleCorps + sizeof(enteteMessage));
                // Liberation du buffer d'emission
                delete[] donnees;

                // Suppression de l'Image et de l'action associee sur l'ordinateur local
                ((Image *)active)->~Image();
        }

A big step forward, the networking code has been reworked.
So the readability is improved. It is also possible to see the beginning of polymorphism. MediaContener is used but not everywhere.

QMdiSubWindow* subactive = m_mdiArea->currentSubWindow();
QWidget* active = subactive;
MapFrame* bipMapWindow = NULL;

if (NULL!=active)
{

    QAction* action=NULL;

    Image*  imageFenetre = dynamic_cast(active);

    QString mapImageId;
    QString mapImageTitle;
    mapImageTitle = active->windowTitle();
    bool image=false;
    //it is image
    if(NULL!=imageFenetre)
    {
        m_pictureList.removeOne(imageFenetre);

        mapImageId = imageFenetre->getMediaId();
        image = true;
        action = imageFenetre->getAction();
    }
    else//it is a map
    {
        bipMapWindow= dynamic_cast(active);
        if(NULL!=bipMapWindow)
        {
            mapImageId = bipMapWindow->getMediaId();
            action = bipMapWindow->getAction();

        }
        else// it is undefined
        {
            return;
        }
    }

    QMessageBox msgBox(this);
    msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel );
    msgBox.setDefaultButton(QMessageBox::Cancel);
    msgBox.setIcon(QMessageBox::Information);
    msgBox.move(QPoint(width()/2, height()/2) + QPoint(-100, -50));
    Qt::WindowFlags flags = msgBox.windowFlags();
    msgBox.setWindowFlags(flags ^ Qt::WindowSystemMenuHint);

    if (!image)
    {
        msgBox.setWindowTitle(tr("Close Map"));
    }
    else
    {
        msgBox.setWindowTitle(tr("Close Picture"));
    }
    msgBox.setText(tr("Do you want to close %1 %2?\nIt will be closed for everybody").arg(mapImageTitle).arg(image?tr(""):tr("(Map)")));

    msgBox.exec();
    if (msgBox.result() != QMessageBox::Yes)
        return;

    if (!image)
    {
        NetworkMessageWriter msg(NetMsg::MapCategory,NetMsg::CloseMap);
        msg.string8(mapImageId);
        msg.sendAll();

        m_mapWindowMap.remove(mapImageId);
        m_playersListWidget->model()->changeMap(NULL);
        m_toolBar->changeMap(NULL);
    }
    else
    {
        NetworkMessageWriter msg(NetMsg::PictureCategory,NetMsg::DelPictureAction);
        msg.string8(mapImageId);
        msg.sendAll();
    }

    MediaContainer*  mediaContener = dynamic_cast(subactive);
    if(NULL!=mediaContener)
    {
        CleverURI* cluri = mediaContener->getCleverUri();
        cluri->setDisplayed(false);
        if(NULL!=m_sessionManager)
        {
            m_sessionManager->updateCleverUri(cluri);
        }
    }

    delete action;
    delete subactive;
}
Version Intermédiaire

Then, we implements a solution to describe map, vmap (v1.8) or image as mediacontener.
They share a large part of their code, so it becomes really easy to do it.

QMdiSubWindow* subactive = m_mdiArea->currentSubWindow();
    MediaContainer* container = dynamic_cast(subactive);
    if(NULL != container)
    {
        CleverURI::ContentType type = container->getContentType();
        if(CleverURI::VMAP == type)
        {
            removeVMapFromId(container->getMediaId());
        }
        else if(CleverURI::MAP == type)
        {
            removeMapFromId(container->getMediaId());
        }
        else if(CleverURI::PICTURE == type )
        {
            removePictureFromId(container->getMediaId());
        }
    }
Actuel

Make your own slide show presentation in QML

To prepare my conference at Pas Sage En Seine [FR], a French hacking festival, I chose to write my slide presentation in QML.
It allows me to have better control and be free to do whatever I want (such as a timeline or any kind of animation).
Of course, That comes with a price. It is longer to do it that way but now I find some solutions. So, next time will be faster.

File hierarchy:

I preferred use QML through C++ Application. It provides more helpful feature, such as: the ability to make screenshots of your presentation at any time (useful as backup plan).
At the top level, you will found all C++ classes, project files and the main qml. Then, you will have a directory with all your pages and if it is required a directory with all your images.

├── cpphighlighter.cpp
├── cpphighlighter.h
├── deployment.pri
├── LICENSE
├── main.cpp
├── main.qml
├── pages
│   ├── 01_intro.qml
│   ├── 02_presentation.qml
│   ├── 03_jdr_et_rolisteam.qml
│   ├── 043_Exemple_code_1.qml
│   ├── 04_jdr_avantages_pb.qml
│   ├── 05_avantage_jdr_virtuel.qml
│   ├── 06_fonctionnalites_rolisteam.qml
│   ├── 07_rolisteam_debut.qml
│   ├── 08_Rolistik_a_Rolisteam.qml
│   ├── 10_frise_chronologique.qml
│   ├── 11_son_usage.qml
│   ├── 12_son_fonctionnement.qml
│   ├── 13_dice_parser.qml
│   ├── 14_themes_audio_3_pistes.qml
│   ├── 15_nouveaute_1_8.qml
│   ├── 16_projet_avenir.qml
│   ├── 17_reussites.qml
│   ├── 18_les_lecons.qml
│   ├── 19_objectif_rolisteam_libre.qml
│   ├── 20_FAQ.qml
├── pasSageEnSeine.pro
├── pasSageEnSeine.pro.user
├── qmlcontroler.cpp
├── qmlcontroler.h
├── qmlcontroler.ui
├── qml.qrc
├── README.md
├── rsrc
│   ├── all.png
│   ├── cc.png
│   └── chat.png

The C++ application

The main

It can be useful to see the state of the presentation and to read some extra notes about the current slide. To manage that, I wrote a small C++ application.
The first goal is to show the QML view, then add some features and communication between the QML view and the C++ window.

#include <QApplication>
#include <QQmlApplicationEngine>
#include "qmlcontroler.h"
#include <QQmlContext>
#include <QQuickTextDocument>

#include "cpphighlighter.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QQmlApplicationEngine engine;

    engine.rootContext()->setContextProperty("ScreenW",1280);
    engine.rootContext()->setContextProperty("ScreenH",720);

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    QmlControler ctr;
    ctr.setEngine(&engine);

    return app.exec();
}
main.cpp

Really easy, it loads the main.qml from the resources management system provided by Qt. It defines the targeted resolution by setting two constant into QML word: ScreenW and ScreenH.

In this project, the QmlControler class is the C++ window which provides slide feedback and additional information.

Let’s take a look to it:

Feedback window

This window is really simple. It has two parts: On the left, there is a label which displays screenshot of the qml view, and the right part is a QTextArea which display any additional note about the current slide.

void QmlControler::currentPageHasChanged(int i)
{
    m_currentScreen = i;
    QImage img = m_window->grabWindow();

    if(img.isNull())
        return;

    static int count = 0;


    img.save(tr("screens/%1_screen.png").arg(++count,3,10,QChar('0')),"png");
    qDebug() << "screen shot save" << count;

    m_ratioImage = (double)img.size().width()/img.size().height();
    m_ratioImageBis = (double)img.size().height()/img.size().width();

    m_label->setPixmap(QPixmap::fromImage(img));

    if((i+1>=0)&&(i+1<m_commentData.size()))
    {
        ui->textEdit->setHtml(m_commentData.at(i+1));
    }
    resizeLabel();
}
Current slide has changed

When the current slide has changed, the c++ window is notified thought the slot currentPageHasChanged, The application gets screenshot of the qml view, save it as a file, display it thought the label, then it looks for data about the current slide into the model. If any, there are displayed in the textedit.

Saving screenshots into file allows you to create a pdf file as backup plan for your presentation.

$ convert *.png mypresentation.pdf

 

QML Application

Loader system.

For readability reason, it is easier to have each page into one qml file. The application has to load those pages in the right order. To reach this goal, we have to define the order. I did it thank to a data model inside the main.qml file.
The main.qml displays all pages as item of a pathview. All items are loaded from the qt resource management system.

ListModel {
            id: panelModel
            ListElement {
                name: "Intro"
                path: "01_intro.qml"
                time: 1
                next: "Présentation de Rolisteam"
            }
First item of the model.

A page is mainly defined by two data: name and path. The path is the name of the qml file.
All other data are here as help, the time has not been used.

Then, the loader does its job, the key lines are the following:

    PathView {
        id: view
        anchors.fill: parent
        model: panelModel
        highlightRangeMode:PathView.StrictlyEnforceRange
        snapMode: PathView.SnapOneItem
        delegate:  Loader {
             source: "pages/"+path
        }
Path View

 

Table of Contents

To manage the table of contents, I added a listview with a model:

    ListView {
        id: listView1
        x: ScreenW*0.02
        y: ScreenH*0.3
        width: ScreenW/2
        height: ScreenH*0.2
        delegate: Item {
            width: ScreenW/2
            height: listView1.height/listView1.count
                Text {
                    color: view.currentIndex>=index ? "black" : "gray"
                    text: name
                    font.pointSize: ScreenH/48
                    anchors.verticalCenter: parent.verticalCenter
                    font.bold: true

                }
        }
        visible: view.currentIndex>0 ? true : false

        model: ListModel {
            ListElement {
                name: "Concepts"
                index:1
            }
            ListElement {
                name: "Chroniques"
                index:6
            }
            ListElement {
                name: "Logiciel"//système de build, code spécifique par OS.
                index:9
            }
            ListElement {
                name: "Bilan"
                index:15
            }
        }
    }
Table of contents in QML

Next slide

When you have many slides it can be helpful to have indication about the next one. I chose to display the title in the top-right corner. It was the easier way.

    Text {
        anchors.top: parent.top
        anchors.right: parent.right
        text: panelModel.get(view.currentIndex).next+">"
    }
Next slide

 

Design a page

Each page are independent but they are all based on the same pattern. In my case, they have a listview with model. Each item of the model is an point I should talk about it.

Each item has a index. The index is controlled with keyboard (down to increase, up to decrease). The page manages what is shown or hidden given the value of the index.

For example, the feature of dice alias has 10 as index. When the index page value becomes 10, the «Dice Alias»  item is displayed with an animation. Then, at 11, I can show a screen shot about the dice alias. At 12, the screenshot disappears and another text is displayed.

Position and Size

To ensure that all items will be display at the proper position and size.  I have based all computation on anchor or the screen size.

    Image {
        id: image1
        anchors.left: parent.left
        anchors.top: parent.top
        anchors.leftMargin: ScreenW*0.04
        fillMode: Image.PreserveAspectFit
        source: "qrc:/rsrc/Rolisteam.svg"
        width: ScreenW*0.2
    }
Display the logo at the right position and size.

Other way

There is a module that provides Items to create QML presentation. I don’t use it for this one but it may provide interesting things.

https://github.com/qt-labs/qml-presentation-system

Get the code

You are invited to clone the code at : https://github.com/obiwankennedy/pses

Introduction à Dbus avec Qt4

Dbus

Dbus est un bus système de messagerie. C’est un moyen simple pour des applications de communiquer entre elles. En plus de la communication interprocessus, Dbus est aussi un outil aidant au cycle de vie du processus. Il peut vous permettre de n’autoriser qu’une seule instance de votre application ou de votre daemon. Il peut vous permettre de démarrer votre application ou daemon à la demande quand elles sont nécessaires. Il est possible d’établir deux types de connexions: point à point ou point d’accès (daemon central). Grâce au daemon, vos applications peuvent être prévenues du branchement d’un nouvel composant matériel. Le bus de session (un par utilisateur connecté) est le canal générique de communication. Dbus est destiné à la communication entre application d’un même ordinateur. Il est cependant possible de communiquer par TCP/IP crypté avec un dossier home partagé par NFS. Ce genre d’usage reste rare et expérimental.

Il est possible d’utiliser Dbus dans de nombreux langages: C/C++, python, ruby, perl, java, C#. Je vais me concenter sur l’implémentation de Dbus dans Qt4, dans cet article.

Dbus dans Qt4

Qt4 implement sa propre API Dbus. Le support de Dbus sur windows étant en cours (d’après le site internet), Qt a été sage de regrouper toutes les fonctionnalités dans un module. Pour des environnements linux, il suffit d’installer la bibliothèque et à l’édition des liens, ajouter

QT += dbus

Dans le fichier pro pour inclure le module dbus à votre projet.

Les deux classes importantes sont QDbusMessage et QDbusInterface. La première permet d’envoyer facilement un message vers un service Dbus. C’est un moyen rapide pour communiquer une information vers une application dont vous ne maitrisez rien. Le QBusInterface permet une communication plus transparente, car tous les signaux/slots de votre application (d’une classe, pour être précis) seront mis sur Dbus.

Monitorer Dbus

Vous ne le savez peut-être pas mais votre système GNU/Linux (s’il est assez moderne) utilise probablement DBUS en continue. Pour se donner une idée, il existe deux utilitaires assez intéressant: dbus-monitor et qdbusviewer.

capture de qdbusviewer

 

Connexion à un service : Pidgin

Nous allons maintenant implémenté une solution pour utiliser un service d’une application. Nous voulons mettre à jour la petite phrase de status de pidgin. Ainsi votre logiciel de lecture audio pourra mettre en petite phrase la chanson actuellement jouée.

La première étape est d’identifié la fonction (et son chemin) qui nous intéresse. En général, les logiciels libres fournissent dans leur documentation l’API dbus qu’ils offrent. Cependant, l’outil qdbusviewer vous permet de voir toutes ces informations.

En terme de code, cela reste très simple. Vous déclarez un message Dbus. Il faut définir les bonnes valeurs: chemin, destinataire, noms..

QDBusConnection sessionbus = QDBusConnection::sessionBus();

if ( !sessionbus.isConnected() )
{
    qDebug() << "Could not connect to session bus";
}
    QDBusMessage m = 
    QDBusMessage::createMethodCall("im.pidgin.purple.PurpleService",
                                   "/im/pidgin/purple/PurpleObject",
                                   "im.pidgin.purple.PurpleInterface",
                                   "PurpleUtilSetCurrentSong");
    QList<QVariant> args;
    args << m_p->Title << m_p->Artist << m_p->album;
    m.setArguments(args);
    QDBusMessage metadatamsg = sessionbus.call(m);
    if(metadatamsg.type() != QDBusMessage::ReplyMessage)
        qDebug() << "Error its not a message " << metadatamsg.type() << metadatamsg.errorMessage ();
Connexion à un service dbus
La première étape est de définir quel bus vous allez utiliser : le sessionBus ou le systembus? Dans notre cas, c'est le sessionBus. On verifie que tout se passe bien niveau connection au bus. Il convient ensuite de créer un message. Les paramètres du messages sont les suivants :

  • Le nom du service (la colonne de gauche de qdbusviewer)
  • Le chemin vers l'objet (concaténation de tous les noeuds de l'arbre sauf celui de l'interface).
  • Le chemin vers l'interface (noeud en italique dans qdbusviewer).
  • Une liste d'arguments peut être passée à l'appel de la méthode du service. Dans notre cas, nous passons le titre de la chanson, l'artiste et le nom de l'album.
  • Dernière étape, appel de la méthode suivit d'une vérification d'erreur.

La voie royale pour faire communiquer deux applications via dbus grâce à Qt est l'utilisation des adaptors. Ils traduisent les signaux et slots d'une classe Qt en message Dbus. C'est totalement transparent. Pour faire un exemple simple, je vais faire une application qui propose un service "dbus_example_Service", de définition de brigthness et les clients peuvent définir le nouveau de volume. Plusieurs applications peuvent envoyer le volume au service et elles peuvent également recevoir la brightness. Une seule et unique application peut faire office de service. Deux instance de l'application "dbus_example_Service" vont entrainé des comportements inconnus.

qdbuscpp2xml -S -M mainwindow.h -o org.homelinux.renaudguezennec.xml

qdbusxml2cpp -c GuiAdaptor -a guiadaptor.h:guiadaptor.cpp org.homelinux.renaudguezennec.xml

qdbusxml2cpp -c GuiAdaptor -p test.h:test.cpp org.homelinux.renaudguezennec.xml

La première commande vous permet de générer un fichier xml: org.homelinux.renaudguezennec.xml qui contiendra l'ensemble de l'API de votre fichier .h.
Le resultat est de la forme suivante:

<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.homelinux.renaudguezennec">
    <signal name="brightnessChanged">
      <arg type="i" direction="out"/>
    </signal>
    <method name="setVolumeLevel">
      <arg type="i" direction="in"/>
    </method>
  </interface>
</node>
Xml généré

La deuxième commande crée une classe GuiAdaptor (dans les fichiers: guiadaptor.h et guiadaptor.cpp) qui expose l'API d'écrire dans le xml. Cette classe sera utile coté service.

La troisième commande crée une classe GuiAdaptor (dans les fichiers: test.h et test.cpp) que sera capable de contacter l'API décrite dans le xml. Nous allons utiliser cette classe dans le client pour l'API décrite dans le xml.

L'étape suivante consiste à générer une classe Adaptor qui transformera vos signaux/Slots Qt en message dbus. Il est utilisé côté service. Dans le main de votre programme, il convient d'instancier un adaptor généré (ici: ControlPanelAdaptor), comme cela:

#include <QtGui/QApplication>
#include "mainwindow.h"
#include "guiadaptor.h"
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    new GuiAdaptor(&w);
    w.show();

    QDBusConnection connection = QDBusConnection::sessionBus();
    bool rel = connection.registerService("org.homelinux.renaudguezennec");
    rel = connection.registerObject("/",&w);

    return a.exec();
}
Creation de service

A ce stade, votre application peut-être un service dbus. Il fournit une API à travers DBus. Nous allons voir maintenant comment créer des clients qui vont utiliser cette API.

Nous allons donc construire un nouveau projet : dbus_example_client. Il contiendra la réciproque du service. Le service est capable d'envoyer le signal brightnessChanged et reçoit son volume par l'appel à setVolumeLevel. Le client lui sera capable d'appeler setVolumeLevel du service et de recevoir la valeur de brightnessChanged. Pour se faire, nous allons utilisé la class GuiAdaptor (test.h et test.cpp) dans notre application cliente.

#include <QtGui/QApplication>
#include "mainwindow.h"
#include "test.h"

int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   MainWindow w;

    GuiAdaptor* adapteur = new GuiAdaptor("org.homelinux.renaudguezennec", 
    "/",QDBusConnection::sessionBus(),0);

    QObject::connect(&w,SIGNAL(volumeChanged(int)),adapteur,
    SLOT(setVolumeLevel(int)));

    QObject::connect(adapteur,SIGNAL(brightnessChanged(int)),&w,
    SLOT(setBrightnessChanged(int)));
    w.show();

    return a.exec();
}
Client Qt

Comme vous pouvez le voir, il suffit d'instancier une classe GuiAdaptor (#include "test.h") et de connecter les signals et les slots de notre application avec cette instance. En l'occurence, notre application emet le signal volumeChanged(int), nous le lions au slot setVolumeLevel(int) de notre adapteur. Le signal émit par l'adapteur est récupéré dans un slot de notre application.

Qt4 et le design pattern Command

Bonjour,  ce petit tutorial va tenter de vous expliquer comment réaliser une opération longue tout en restant réactif dans une application Qt4.
Il existe plusieurs moyens mais je ne vais en décrire qu’un seul qui à mon sens est le plus propre. Comme cas pratique, j’ai choisi d’implémenter le Design patterns “Command”.
La première étape utilise des thread.

Communication entre un thread et une application Qt

Pas de solution miracle, pour faire “deux choses à la fois” dans une application, il faut passer par du multi-threading. Nous allons créer une classe thread qui hérite de QThread. Cette classe sera le support du traitement long à effectuer. Voyez ça comme une sorte encapsulation.

//.h de la classe

#ifndef THREAD_H
#define THREAD_H
#include <QThread>
#include "command.h"
class Thread : public QThread
{
    Q_ObJECT
        COMMAND* mycommand;
        bool undo;
public:

    Thread(COMMAND* mycommand,bool undo);

    protected:
        void run();
};

#endif // THREAD_H

Comme vous le voyez, c’est très simple. Nous héritons de QThread, nous avons deux membres dans cette classe: un pointeur vers une instance de la classe COMMAND (utile pour l’implémentation du patterns du même nom) et un booléen qui nous permet de déterminer le sens de l’action “annuler” (undo) ou “normal” (undo == false). bien entendu, il est possible d’avoir autant de paramètre que vous le souhaitez. Il ne faut pas oublié de redéfinir la fonction run().

// l’implémentation

#include "thread.h"

Thread::Thread(COMMAND* _mycommand,bool _undo)
        : mycommand(_mycommand),undo(_undo)
{

}
void Thread::run()
{
    if(undo)
        mycommand->undo();
    else
        mycommand->doCommand();
}

Dans le constructeur, je définis les données membres. La fonction run exécute la commande en fonction du paramètre undo. Il faut savoir que tout le code appelé dans run sera exécuté dans un thread différent.
À ce stade, nous avons une classe Thread qui est prête à recevoir et exécuter une “command”.

Nous allons voir maintenant comment créer un design pattern command.

Implémenter le design pattern command

Ce pattern est très utile pour gèrer l’annulation d’une action ou par exemple refaire la dernière action. Il consiste à créer une classe pour chaque action (du moins toutes les actions que vous voulez pouvoir modifier ou dans notre cas, exécuter dans un thread). Qt fournit des outils pour faire ça. Dans un cadre formateur, je préfère l’implémenter entièrement.
Il faut tout d’abord écrire la classe abstraite qui définit une commande. Cela permettra à notre classe thread de bien intéragir avec la commande.

#ifndef COMMAND_H
#define COMMAND_H
#include <QObject>
class COMMAND : public QObject
{
        Q_ObJECT

public:
    virtual void doCommand()=0;
    virtual void undo()=0;
signals:
        void Maximum(int M);
        void Minimum(int m);
        void valueChanged(int v);
        void done();
};

#endif // COMMAND_H

Il y a deux méthodes abstraites pures: l’une pour faire la commande, l’autre pour l’annuler. J’ai ajouté quelques signaux pour que la commande communique avec le thread principal pour informer l’utilisateur de l’avancée du la tâche en cours. Toutes les futures commandes de notre application doivent être des sous-classes de COMMAND. Vous l’avez deviné, il faut implémenter une commande, maintenant.
Notre commande sera vraiment basique, c’est une commande d’attente (so useless).

//wait.h

#ifndef WAIT_H
#define WAIT_H
#include "command.h"
class WAIT : public COMMAND
{
public:
    WAIT();

    virtual void doCommand();
    virtual void undo();

};

#endif // WAIT_H

Rien de particulier, juste la re-définition des fontions virtuelles pures. Voici leurs implémentations

#include "wait.h"
WAIT::WAIT()
{

}

void WAIT::doCommand()
{

int step = 10;
        emit Maximum(100);
        int i = 0;
        int k = 0;
        emit Minimum(i);
    for(int j = 0 ; j< 1000 ; j++)
    {
        sleep(0.5); //fake statement
        if(i>=step)
        {
                emit valueChanged(++k);
                i = 0;
         }
        i++;

    }
    emit valueChanged(++k);
    emit done();
}

void WAIT::undo()
{

}

Dans notre “doCommand”, nous calculons le pas de la notification de l’application principale. Ici, j’ai arbitrairement choisi 10 mais dans un contexte utile, le pas est égal à la taille de vos données à traiter divisé par le nombre de notification que vous voulez.
Nous émettons la valeur maximale. Initialisation des variables temporaires (i et k). On émet i (0). Nous faisons une bouble sur chaque élément à traiter, on fait le traitement et on calcule un peu pour savoir s’il faut ou pas prévenir l’application principale.
Il est bon de ne pas prévenir à chaque fois, car si vous travaillez sur plusieurs milliers ou millions de données le traitement des signaux ralentira un peu votre application. Comme exemple, imaginez que vous travaillez sur chaque pixel d’une grosse photo.

Il ne reste plus qu’a écrire la fenêtre principale qui affichera à l’utisateur l’avancée de notre traitement.

QProgressBar et QThread.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QtGui/QMainWindow>
#include <QProgressBar>
#include <QDockWidget>
#include "thread.h"
namespace Ui
{
    class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QProgressBar *workinprogress;
    QDockWidget *Progressdock;
    Thread* myThread;
};

#endif // MAINWINDOW_H

Rien d’original, une mainwindow avec juste trois membres. Une QProgressBar qui affichera la progression.
Un DockWidget pour afficher la progesse bar, je ne l’ai pas implémenté dans cet exemple mais il peut être amusant d’afficher le dock quand une opération est en cours et la cacher quand c’est fini. Le dernier membre est une instance de notre classe Thread.

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "wait.h"



MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    workinprogress = new QProgressBar;
    Progressdock = new QDockWidget(tr("progress panel"));
    workinprogress->setValue(0);
    Progressdock->setWidget(workinprogress);
    Progressdock->setAllowedAreas(Qt::BottomDockWidgetArea);
    addDockWidget(Qt::BottomDockWidgetArea,Progressdock);
    WAIT* mywait = new WAIT();

    myThread = new Thread(mywait,false);
    connect(mywait,SIGNAL(Maximum(int)),workinprogress,SLOT(setMaximum(int)));
    connect(mywait,SIGNAL(Minimum(int)),workinprogress,SLOT(setMinimum(int)));
    connect(mywait,SIGNAL(valueChanged(int)),workinprogress,SLOT(setValue(int)));

    connect(ui->pushButton,SIGNAL(clicked()),myThread,SLOT(start()));

}

MainWindow::~MainWindow()
{
    delete ui;
}

Nous initialisons la QProgressbar et le QDockwidget. En suite, je paramètre un peu nos deux instances. Je crée alors une instance de WAIT et j’initialise le thread avec en paramètre l’adresse de notre commande.
Je connecte maintenant les signaux de la commande au slot de la QProgressBar.
Finalement, je connecte le clique sur “pushButton” sur le démarrage du Thread.
Ainsi, à chaque clique, il demarrera la commande wait.

Aller plus loin

Il serait préférable de créer une instance de WAIT à chaque clique et de l’ajouter dans une pile (dans la mainwindow). Cela est nécessaire pour finir le design pattern command. En haut de la pile, se trouve la dernière commande, si elle est annulée alors il faut la dépiler et exécuter undo().

Je proposerai en téléchargement ce petit exemple, d’ici quelques jours. En attendant, une petite capture d’écran. Capture d'écran Design Pattern Command, QThread et Qt.