Tag Archives: qt

đŸŽČ DicelyVerse – Your Ultimate Dice Companion for RPGs!

Level up your tabletop RPG experience with Dice Roller 3D!
Whether you’re deep into D&D, Pathfinder, or your favorite homebrew system, DicelyVerse gives you all the power of physical dice and more—right in your pocket.

https://play.google.com/store/apps/details?id=org.rolisteam.dicelyverse


🚀 Core Features

🌀 3D Dice Rolling Engine
Feel the thrill of real dice physics with stunning 3D visuals. Roll multiple dice with satisfying animations.

💬 Roll Dice with Commands
Type d20+5 or any custom command to get your results instantly. Supports advanced syntax for complex rolls!
The command engine is DiceParser.
See the documentation here.

🔁 Reroll with Ease
Need a second chance? Reroll instantly without retyping your command or resetting your dice.

📩 Macros – One Tap Commands
Save your favorite or frequently used dice rolls as macros for quick access. Perfect for initiative rolls, attack rolls, and spell damage!

✍ Aliases – Shortcuts for Long Commands
Tired of long roll strings? Set up aliases to keep your gameplay fast and your input clean.

📖 Character Sheet Integration
Store your character’s stats, modifiers, and abilities directly in the app. Pull values into rolls on the fly.

đŸ‘„ Multiple Profiles
Play multiple campaigns or characters? No problem. Create separate profiles to keep everything organized.

🌘 Dark Mode
Change the UI to dark mode or light mode on the fly (no need to restart).

🌐 Translation
DicelyVerse
has translations available now: English, German, French, Italian, Spanish and Portuguese.


đŸŽ„ Watch it in Action!

đŸ“ș Check out our YouTube demo video showcasing the app’s features and real-time gameplay experience:

 

 


đŸ“± Download Now on Android!

Simplify your tabletop experience. Make every roll count—with flair.

https://play.google.com/store/apps/details?id=org.rolisteam.dicelyverse


Qt Creator plugin : Simple all-in-one QML editor

Hello all,

I would like to show you my first QtCreator plugin. It is an all-in-one window to test Qml Code.

How often do you run into issue about QML code; not doing exactly what you want ?

Demo:

 

 

Examples demo:

 

Here it a simple tool to test quickly some QML code. No need to create a new Test Project.
Open the plugin, type or copy/paste your code, do some changes until it does what you want.

It is mostly inspired from some Qml online site, but it is available directly into QtCreator.

 

Git repo: https://github.com/obiwankennedy/QmlSampleEditor

Download: https://github.com/obiwankennedy/QmlSampleEditor/actions/runs/

QML – Tutoriel 4 – Bindings et les mĂ©thodes de travail

Bindings + Méthode de travail:
Notions abordées:
-Les syntaxes pour le Binding

Le code de la présentation: https://github.com/obiwankennedy/Learn-Qt
Le code de l’exercice: 04_exercice_binding
La correction sera dans le prochain épisode.

 

Lien youtube: https://youtu.be/A-SMHKkH19U

Supporter le logiciel et la chaĂźne: https://liberapay.com/Rolisteam/donate
Rolisteam: http://www.rolisteam.org/

QML – Tutoriel 3 – Les attributs

Les attributs:
Notions abordées:
-id
-Propriétés
-Signaux
-Gestionnaire de Signaux
-Gestionnaires et propriétés groupés.
-méthodes
-Enumération

Fichier à télécharger: 03_attributs

Le code de la présentation: https://github.com/obiwankennedy/Learn-Qt

 

Lien youtube: https://youtu.be/tDSVlrsQJU0

QML – Tutoriel 2 – Le positionnement

Le positionnement:
-Apprenez à positionner vos éléments.
Notions abordées:
-Positionnement absolue
-Ancres
-Les positionneurs
-Les layouts
Le code de la présentation: https://github.com/obiwankennedy/Learn-Qt

Ressources du tutoriel: 03_positionnement

 

Lien youtube: https://youtu.be/QDbn1CDjc14

Supporter le logiciel et la chaĂźne: https://liberapay.com/Rolisteam/donate
Rolisteam: http://www.rolisteam.org/

QML – Tutoriel 1 – Introduction

Introduction:
-Apprenez Ă  installer les outils pour programmer en QML.
-CrĂ©ation d’un projet QML.
-Découverte du premier programme.
-Introduction des types de base du QML (Item, Rectangle, Image, Text, TextInput, TextArea, MouseArea, Timer, Flickable)

 

 

TĂ©lĂ©chargez les ressources de l’épisode

Liens Utiles:
Qt: https://www.qt.io/download
RCSE: http://www.rolisteam.org/download.html (Lire attentivement en fonction de vos plate formes)
Le code de la présentation: https://github.com/obiwankennedy/Learn-Qt

Exemple QML:
https://www.youtube.com/results?search_query=qml+example

Lien youtube: https://youtu.be/8moq00ofQPY

Supporter le logiciel et la chaĂźne: https://liberapay.com/Rolisteam/donate
Rolisteam: http://www.rolisteam.org/

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