Tag Archives: dev

QtNat – Open your port with Qt

QtNat is a lightweight C++ library built with Qt 6 that simplifies NAT port mapping using UPnP (Universal Plug and Play). It is designed to help developers easily expose local services to external networks without requiring manual router configuration for users.

By leveraging UPnP, QtNat automatically communicates with compatible routers to create port forwarding rules at runtime. This makes it particularly useful for peer-to-peer applications, multiplayer games, remote access tools, and any software that needs reliable inbound connectivity behind a NAT.

QtNat provides a simplified API to do all steps automatically: discovery and mapping. This has been tested on my local device. Feel free to test it and improve it.

Use it

    UpnpNat nat;

    QObject::connect(&nat, &UpnpNat::statusChanged, [&nat, &app]() {
        switch(nat.status())
        {
        case UpnpNat::NAT_STAT::NAT_IDLE:
        case UpnpNat::NAT_STAT::NAT_DISCOVERY:
        case UpnpNat::NAT_STAT::NAT_GETDESCRIPTION:
        case UpnpNat::NAT_STAT::NAT_DESCRIPTION_FOUND:
            break;
        case UpnpNat::NAT_STAT::NAT_FOUND:
            nat.requestDescription();
            break;
        case UpnpNat::NAT_STAT::NAT_READY:
            nat.addPortMapping("UpnpTest", nat.localIp(), 6664, 6664, "TCP");
            break;
        case UpnpNat::NAT_STAT::NAT_ADD:
            qDebug() << "It worked!";
            app.quit();
            break;
        case UpnpNat::NAT_STAT::NAT_ERROR:
            qDebug() <<"Error:" <<nat.error();
            app.exit(1);
            break;
        }
    });

    nat.discovery();

  1. We create the object (l:0)
  2. We connect to statusChanged signal to get notified (l:2)
  3. When status is NAT_FOUND, we request the description (l:11)
  4. When status is NAT_READY, we request the port mapping (l:14)
  5. When status is NAT_ADD, It means the port mapping request has been added, It worked! The application quits.(l:17)
  6. When status is NAT_ERROR, Error occured and display the error text. The application exits on error. (l:21)
  7. We connect to error changed in order to detect errors. (l:14)
  8. We start the discovery. (l:28)

Technical explainations

The discovery

Basically, we need to know if there is a upnp server around. To do so, we send an M-SEARCH request on the multicast address.

Here is the code:

#define HTTPMU_HOST_ADDRESS "239.255.255.250"
#define HTTPMU_HOST_PORT 1900
#define SEARCH_REQUEST_STRING "M-SEARCH * HTTP/1.1\n"            \
                              "ST:UPnP:rootdevice\n"             \
                              "MX: 3\n"                          \
                              "Man:\"ssdp:discover\"\n"          \
                              "HOST: 239.255.255.250:1900\n"     \
                                                            "\n"
void UpnpNat::discovery()
{
    setStatus(NAT_STAT::NAT_DISCOVERY);
    m_udpSocketV4.reset(new QUdpSocket(this));

    QHostAddress broadcastIpV4(HTTPMU_HOST_ADDRESS);

    m_udpSocketV4->bind(QHostAddress(QHostAddress::AnyIPv4), 0);
    QByteArray datagram(SEARCH_REQUEST_STRING);

    connect(m_udpSocketV4.get(), &QTcpSocket::readyRead, this, [this]() {
        QByteArray datagram;
        while(m_udpSocketV4->hasPendingDatagrams())
        {
            datagram.resize(int(m_udpSocketV4->pendingDatagramSize()));
            m_udpSocketV4->readDatagram(datagram.data(), datagram.size());
        }

        QString result(datagram);
        auto start= result.indexOf("http://");

        if(start < 0)
        {
            setError(tr("Unable to read the beginning of server answer"));
            setStatus(NAT_STAT::NAT_ERROR);
            return;
        }

        auto end= result.indexOf("\r", start);
        if(end < 0)
        {
            setError(tr("Unable to read the end of server answer"));
            setStatus(NAT_STAT::NAT_ERROR);
            return;
        }

        m_describeUrl= result.sliced(start, end - start);

        setStatus(NAT_STAT::NAT_FOUND);
        m_udpSocketV4->close();
    });

    connect(m_udpSocketV4.get(), &QUdpSocket::errorOccurred, this, [this](QAbstractSocket::SocketError) {
        setError(m_udpSocketV4->errorString());
        setStatus(NAT_STAT::NAT_ERROR);
    });

    m_udpSocketV4->writeDatagram(datagram, broadcastIpV4, HTTPMU_HOST_PORT);
}

The whole goal of the discovery is to get the description file from the server with all available devices and services. The result is stored in m_describeUrl.

Request Description file

Simple request using QNetworkAccessManager.

void UpnpNat::requestDescription()
{
    setStatus(NAT_STAT::NAT_GETDESCRIPTION);
    QNetworkRequest request;
    request.setUrl(QUrl(m_describeUrl));
    m_manager.get(request);
}

Parsing Description file

Your physical network device may act as several Upnp devices. You are looking for one of these device type:

  • urn:schemas-upnp-org:device:InternetGatewayDevice
  • urn:schemas-upnp-org:device:WANDevice
  • urn:schemas-upnp-org:device:WANConnectionDevice

Those type are followed with a number (1 or 2), It is the Upnp protocol version supported by the device.

void UpnpNat::processXML(QNetworkReply* reply)
{
    auto data= reply->readAll();

    if(data.isEmpty()) {
        setError(tr("Description file is empty"));
        setStatus(NAT_STAT::NAT_ERROR);
        return;
    }

    setStatus(NAT_STAT::NAT_DESCRIPTION_FOUND);

    /*
     Boring XML&nbsp;parsing in order to find devices and services.
     Devices:
        constexpr auto deviceType1{"urn:schemas-upnp-org:device:InternetGatewayDevice"};
        constexpr auto deviceType2{"urn:schemas-upnp-org:device:WANDevice"};
        constexpr auto deviceType3{"urn:schemas-upnp-org:device:WANConnectionDevice"};

     Services:
        constexpr auto serviceTypeWanIP{"urn:schemas-upnp-org:service:WANIPConnection"};
        constexpr auto serviceTypeWANPPP{"urn:schemas-upnp-org:service:WANPPPConnection"};  
     */

     m_controlUrl = /* Most important thing to find the controlUrl of the proper service.*/

    setStatus(NAT_STAT::NAT_READY);
}

Send mapping Request

Sending a request is just sending HTTP request with the proper data.

I use inja to generate the http data properly.

This is the inja template.

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope
  xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
  s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <s:Body>
    <u:AddPortMapping
      xmlns:u="{{ service }}">
      <NewRemoteHost></NewRemoteHost>
      <NewExternalPort>{{ port }}</NewExternalPort>
      <NewProtocol>{{ protocol }}</NewProtocol>
      <NewInternalPort>{{ port }}</NewInternalPort>
      <NewInternalClient>{{ ip }}</NewInternalClient>
      <NewEnabled>1</NewEnabled>
      <NewPortMappingDescription>{{ description }}</NewPortMappingDescription>
      <NewLeaseDuration>0</NewLeaseDuration>
    </u:AddPortMapping>
  </s:Body>
</s:Envelope>

Then, let’s create a json object with all data. As final step, we need to create a request, set its data, and then post it.

void UpnpNat::addPortMapping(const QString& description, const QString& destination_ip, unsigned short int port_ex,
                             unsigned short int port_in, const QString& protocol)
{
    inja::json subdata;
    subdata["description"]= description.toStdString();
    subdata["protocol"]= protocol.toStdString();
    subdata["service"]= m_serviceType.toStdString();
    subdata["port"]= port_in;
    subdata["ip"]= destination_ip.toStdString();

    auto text= QByteArray::fromStdString(inja::render(loadFile(key::envelop).toStdString(), subdata));

    QNetworkRequest request;
    request.setUrl(QUrl(m_controlUrl));
    QHttpHeaders headers;
    headers.append(QHttpHeaders::WellKnownHeader::ContentType, "text/xml;  charset=\"utf-8\"");
    headers.append("SOAPAction", QString("\"%1#AddPortMapping\"").arg(m_serviceType));
    request.setHeaders(headers);
    m_manager.post(request, text);
}

Finally, just check the answer

The reply has no error, it worked, the status changes to NAT_ADD. Otherwise, the status changes to error.

void UpnpNat::processAnswer(QNetworkReply* reply)
{
    if(reply->error() != QNetworkReply::NoError)
    {
        setError(tr("Something went wrong: %1").arg(reply->errorString()));
        setStatus(NAT_STAT::NAT_ERROR);
        return;
    }
    setStatus(NAT_STAT::NAT_ADD);
}

Don’t hesitate to test it on your own device. Just to validate, it works everywhere. Any comment or change request, please use Github for that.

Source code

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/

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.

[pastacode lang=”cpp” manual=”%23include%20%3CQApplication%3E%0A%23include%20%3CQQmlApplicationEngine%3E%0A%23include%20%22qmlcontroler.h%22%0A%23include%20%3CQQmlContext%3E%0A%23include%20%3CQQuickTextDocument%3E%0A%0A%23include%20%22cpphighlighter.h%22%0A%0Aint%20main(int%20argc%2C%20char%20*argv%5B%5D)%0A%7B%0A%20%20%20%20QApplication%20app(argc%2C%20argv)%3B%0A%0A%20%20%20%20QQmlApplicationEngine%20engine%3B%0A%0A%20%20%20%20engine.rootContext()-%3EsetContextProperty(%22ScreenW%22%2C1280)%3B%0A%20%20%20%20engine.rootContext()-%3EsetContextProperty(%22ScreenH%22%2C720)%3B%0A%0A%20%20%20%20engine.load(QUrl(QStringLiteral(%22qrc%3A%2Fmain.qml%22)))%3B%0A%0A%20%20%20%20QmlControler%20ctr%3B%0A%20%20%20%20ctr.setEngine(%26engine)%3B%0A%0A%20%20%20%20return%20app.exec()%3B%0A%7D%0A” message=”main.cpp” highlight=”” provider=”manual”/]

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.

[pastacode lang=”cpp” manual=”void%20QmlControler%3A%3AcurrentPageHasChanged(int%20i)%0A%7B%0A%20%20%20%20m_currentScreen%20%3D%20i%3B%0A%20%20%20%20QImage%20img%20%3D%20m_window-%3EgrabWindow()%3B%0A%0A%20%20%20%20if(img.isNull())%0A%20%20%20%20%20%20%20%20return%3B%0A%0A%20%20%20%20static%20int%20count%20%3D%200%3B%0A%0A%0A%20%20%20%20img.save(tr(%22screens%2F%251_screen.png%22).arg(%2B%2Bcount%2C3%2C10%2CQChar(‘0’))%2C%22png%22)%3B%0A%20%20%20%20qDebug()%20%3C%3C%20%22screen%20shot%20save%22%20%3C%3C%20count%3B%0A%0A%20%20%20%20m_ratioImage%20%3D%20(double)img.size().width()%2Fimg.size().height()%3B%0A%20%20%20%20m_ratioImageBis%20%3D%20(double)img.size().height()%2Fimg.size().width()%3B%0A%0A%20%20%20%20m_label-%3EsetPixmap(QPixmap%3A%3AfromImage(img))%3B%0A%0A%20%20%20%20if((i%2B1%3E%3D0)%26%26(i%2B1%3Cm_commentData.size()))%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20ui-%3EtextEdit-%3EsetHtml(m_commentData.at(i%2B1))%3B%0A%20%20%20%20%7D%0A%20%20%20%20resizeLabel()%3B%0A%7D” message=”Current slide has changed” highlight=”” provider=”manual”/]

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).

[pastacode lang=”css” manual=”ListModel%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20id%3A%20panelModel%0A%20%20%20%20%20%20%20%20%20%20%20%20ListElement%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20%22Intro%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20path%3A%20%2201_intro.qml%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20time%3A%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20next%3A%20%22Pr%C3%A9sentation%20de%20Rolisteam%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D” message=”First item of the model.” highlight=”” provider=”manual”/]

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:

[pastacode lang=”css” manual=”%20%20%20%20PathView%20%7B%0A%20%20%20%20%20%20%20%20id%3A%20view%0A%20%20%20%20%20%20%20%20anchors.fill%3A%20parent%0A%20%20%20%20%20%20%20%20model%3A%20panelModel%0A%20%20%20%20%20%20%20%20highlightRangeMode%3APathView.StrictlyEnforceRange%0A%20%20%20%20%20%20%20%20snapMode%3A%20PathView.SnapOneItem%0A%20%20%20%20%20%20%20%20delegate%3A%20%20Loader%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20source%3A%20%22pages%2F%22%2Bpath%0A%20%20%20%20%20%20%20%20%7D” message=”Path View” highlight=”source: “pages/”+path” provider=”manual”/]

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:

[pastacode lang=”css” manual=”%20%20%20%20ListView%20%7B%0A%20%20%20%20%20%20%20%20id%3A%20listView1%0A%20%20%20%20%20%20%20%20x%3A%20ScreenW*0.02%0A%20%20%20%20%20%20%20%20y%3A%20ScreenH*0.3%0A%20%20%20%20%20%20%20%20width%3A%20ScreenW%2F2%0A%20%20%20%20%20%20%20%20height%3A%20ScreenH*0.2%0A%20%20%20%20%20%20%20%20delegate%3A%20Item%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3A%20ScreenW%2F2%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3A%20listView1.height%2FlistView1.count%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Text%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20color%3A%20view.currentIndex%3E%3Dindex%20%3F%20%22black%22%20%3A%20%22gray%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20text%3A%20name%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20font.pointSize%3A%20ScreenH%2F48%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20anchors.verticalCenter%3A%20parent.verticalCenter%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20font.bold%3A%20true%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20visible%3A%20view.currentIndex%3E0%20%3F%20true%20%3A%20false%0A%0A%20%20%20%20%20%20%20%20model%3A%20ListModel%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20ListElement%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20%22Concepts%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%3A1%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20ListElement%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20%22Chroniques%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%3A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20ListElement%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20%22Logiciel%22%2F%2Fsyst%C3%A8me%20de%20build%2C%20code%20sp%C3%A9cifique%20par%20OS.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%3A9%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20ListElement%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20%22Bilan%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%3A15%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D” message=”Table of contents in QML ” highlight=”” provider=”manual”/]

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.

[pastacode lang=”css” manual=”%20%20%20%20Text%20%7B%0A%20%20%20%20%20%20%20%20anchors.top%3A%20parent.top%0A%20%20%20%20%20%20%20%20anchors.right%3A%20parent.right%0A%20%20%20%20%20%20%20%20text%3A%20panelModel.get(view.currentIndex).next%2B%22%3E%22%0A%20%20%20%20%7D” message=”Next slide” highlight=”” provider=”manual”/]

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).

[pastacode lang=”css” manual=”%20%20%20%20Image%20%7B%0A%20%20%20%20%20%20%20%20id%3A%20image1%0A%20%20%20%20%20%20%20%20anchors.left%3A%20parent.left%0A%20%20%20%20%20%20%20%20anchors.top%3A%20parent.top%0A%20%20%20%20%20%20%20%20anchors.leftMargin%3A%20ScreenW*0.04%0A%20%20%20%20%20%20%20%20fillMode%3A%20Image.PreserveAspectFit%0A%20%20%20%20%20%20%20%20source%3A%20%22qrc%3A%2Frsrc%2FRolisteam.svg%22%0A%20%20%20%20%20%20%20%20width%3A%20ScreenW*0.2%0A%20%20%20%20%7D” message=”Display the logo at the right position and size.” highlight=”” provider=”manual”/]

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