Author Archives: renaud

UI walker – UI Walk through in QML

The problem

At the first start of an application, user can be a bit confused in front of all of these features, buttons and data. In response to that, we often have a short presentation of each element on the screen. This also presents a typical workflow with the application.

First, You need to create a project or document. Then, define the name, the type
 Then add content using this or that.

This feature is often called UI walk through, or UI tour.

UI Walker

I made this library to provide an easy way to do a walkthrough in any QML application.

Include Ui Walker to your project

target_link_libraries(MyProject
PRIVATE
    Qt6::Core
    Qt6::Quick
    WalkerComponent # add UIWalker
)

Using cmake it is really easy. You can define the library to be a git submodule and then

Prepare you qml code

The whole concept is based on attached property. To highlight a item, you must define two properties:

  • WalkerItem.description: the text that will be displayed when this element is highlighted
  • WalkerItem.weight: Numeric value to define the order (ascending order).
ToolButton {
    WalkerItem.description: qsTr("Description of the element")
    WalkerItem.weight: 104
}

Add the UI walker

Currently, you have to add one item. It should have the size of the whole window. This item provides several properties in order to help you manage the output.

Properties:

namedescriptiontype
countTotal number of highlighted Itemint
currentIndex of the current highlighted itemint
currentDescDescription of the current itemQString
dimColorColor which hides the rest of the applicationQColor
dimOpacityOpacity of the dimqreal
availableRectBiggest Rectangle (where text can be displayed).QRectF
borderRectRect of the current highlighted itemQRectF
intervalDefine the time interval on each highlighted itemint
activeTrue when the Walker is displayedbool

Here is an example:

 WalkerItem {
    id: walker
    anchors.fill: parent
    visible: false
    dimOpacity: 0.8

    onActiveChanged: {
        //walker.active
    }
        
    Label {
        id: label

        
        text: walker.currentDesc // text from walker
        x: walker.availableRect.x // calculated position
        y: walker.availableRect.y // calculated position
        width: walker.availableRect.width // calculated position
        height: walker.availableRect.height // calculated position
    }
    
    Rectangle {
        x: walker.borderRect.x-2
        y: walker.borderRect.y-2
        width: walker.borderRect.width+4
        height: walker.borderRect.height+4
        border.color: "red"
        color: "transparent"
        radius: 10
        border.width: 4
    }

    ToolButton {
        icon.source: walker.current + 1 === walker.count ? "qrc:/finish.svg" : "qrc:/next.svg"
        
        onClicked:{
            if(walker.current +1 === walker.count)
                walker.skip()
            else
                walker.next()
        }   
    }
}

React on Highlight event

Highlighted items get notified through two signals: enter and exit. Defining signal handlers allow you to react. So you can show the full workflow to add new data.

TextField {
    id: nameField
    // 

    WalkerItem.description: qsTr("Set macro name.")
    WalkerItem.weight: 30
    WalkerItem.onEnter: {
        nameField.text = qsTr("Skill Roll")
    }
}

Start it !

In order, to start the UI tour, you simply have to call the function: start() of the Item. Of course, it is up to you to trigger it automatically when it’s the first start of the application or if the user asked for the tour.

Here you have an example:

Component.onCompleted: {
    if(DiceMainController.uiTour === DiceMainController.UnDone) {
        walker.start()
    }
}

I have a CPP controller with property UiTour which gives the current status of the tour. Here, I call directly the walker function. But it may be safer to call a function to reset the state of the window.

The walker provides two important function next() and previous() to navigate. Basically on the walker, you can add buttons in the available Rect to manage the navigation.

Other option, you can define an interval in milliseconds which will call the next() function.

You have to make sure the item is visible while the walker highlight it. It could be tricky to make the path from the end to be beginning. In some case, it is easier to never use the previous function.

Finish it!

Calling the function skip(), close the walker. Then the application is displayed normally. It can be called at any time.

Cheat code

Functiondescription
start()The walker becomes visible, and the first item is highligthed
next()Highlight the next item, trigger appropriated signals
previous()Highlight the previous item, trigger appropriated signals
skip()Hide the walker

How it works ?

The attached properties

In order to harvest all data from the QML, I had to define attached property.

This is the definition of QObject which will be attached, each time a QML item has defined any Walker property.

class WalkerAttachedType : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged FINAL)
    Q_PROPERTY(int weight READ weight WRITE setWeight NOTIFY weightChanged FINAL)
    QML_ANONYMOUS
public:
    explicit WalkerAttachedType(QObject* parent= nullptr);
    //

signals:
    void enter();
    void exit();
    //

};

In the WalkerItem.h, I have to create this static function.

    // 

    Q_OBJECT
    QML_ATTACHED(WalkerAttachedType)
    // 

    static WalkerAttachedType* qmlAttachedProperties(QObject* object)
    {
        QQuickItem* item= qobject_cast<QQuickItem*>(object);
        if(!item)
            qDebug() << "Walker must be attached to an Item";
        s_items.append(item);
        return new WalkerAttachedType(object);
    }

QSceneGraph and Nodes

WalkreItem defines a QML item, written in cpp to be light-weighted. I used QSGNode to draw it on screen. The item code manages the logic of the walkthrough and the update of the geometry.

To make it short, the SceneGraph is the rendering engine of QML. QSGNode defines an API to communicate with it directly.

First, I create the QML item in cpp, using QSGNode to be rendered.

//walkeritem.h
class WalkerItem : public QQuickItem
{
    Q_OBJECT
    QML_ATTACHED(WalkerAttachedType)
    QML_ELEMENT
    Q_PROPERTY(QString currentDesc READ currentDesc NOTIFY currentChanged FINAL)
    Q_PROPERTY(QColor dimColor READ dimColor WRITE setDimColor NOTIFY dimColorChanged FINAL)
    Q_PROPERTY(qreal dimOpacity READ dimOpacity WRITE setDimOpacity NOTIFY dimOpacityChanged FINAL)
    Q_PROPERTY(QRectF availableRect READ availableRect NOTIFY availableRectChanged FINAL)
    Q_PROPERTY(QRectF borderRect READ borderRect NOTIFY borderRectChanged FINAL)
    Q_PROPERTY(int interval READ interval WRITE setInterval NOTIFY intervalChanged FINAL)
    Q_PROPERTY(bool active READ active NOTIFY activeChanged FINAL)
public:
    WalkerItem();
    
    // accessors, signals, slots


protected:
    QSGNode* updatePaintNode(QSGNode*, UpdatePaintNodeData*) override;// update scenegraph
};



//walkeritem.cpp
WalkerItem::WalkerItem()// in the constructor
{
    setFlag(QQuickItem::ItemHasContents);// must be called
    connect(child, &QQuickItem::widthChanged, this, &WalkerItem::updateComputation);
    connect(child, &QQuickItem::heightChanged, this, &WalkerItem::updateComputation);
}

void WalkerItem::updateComputation()
{
    // compute geometry and list any changes that must be sync with the SceneGraph.
    m_change|= WalkerItem::ChangeType::GeometryChanged;
    update();// call to paint the item
}

QSGNode* WalkerItem::updatePaintNode(QSGNode* node, UpdatePaintNodeData*)
{
    auto wNode= static_cast<WalkerNode*>(node);
    if(!wNode)
    {
        wNode= new WalkerNode();//first time
    }

    if(m_change & WalkerItem::ChangeType::ColorChanged)
        wNode->updateColor(m_dimColor);
    if(m_change & WalkerItem::ChangeType::GeometryChanged)
        wNode->update(boundingRect(), m_targetRect);
    if(m_change & WalkerItem::ChangeType::OpacityChanged)
        wNode->updateOpacity(m_dimOpacity);

    m_change= WalkerItem::ChangeType::NoChanges;
    return wNode;
}

We have here an item with a geometry like any other item (x,y,width, height), we also have a dimColor and dimOpacity. Any time one of these properties change. I have to sync with the QSceneGraph to update either the geometry, the dimColor or the dimOpacity. Each time, one property changes, I stored the type of change in the m_change member and I call update().

The render engine will call my item with the QSGNode reprenting it on the SceneGraph side. Then I can call function on my SGNode. When sync is finished I reset the change to NoChange and return the node.

The updatePaintNode can be called with a null node. In this case, you have to create it. It will be the case, the first time. And it could happen later in some cases for optimalization reason.

Now, let see the code of the QSGNode. You have to see the QSGNode as the root item of a tree. Where each node is in charge of representing one aspect of the item: its geometry, its color and its opacity.

// header
class WalkerNode : public QSGNode
{
public:
    WalkerNode();
    virtual ~WalkerNode();
    void update(const QRectF& outRect, const QRectF& inRect);
    void updateColor(const QColor& dim);
    void updateOpacity(qreal opacity);

private:
    QSGOpacityNode m_opacity;
    QSGFlatColorMaterial m_dimMat;
    QSGGeometryNode m_dim;
};

In the constructor, I create each node, and then I define the hierarchy.

WalkerNode::WalkerNode()
{
    auto dimGeo= new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 0);
    dimGeo->setDrawingMode(QSGGeometry::DrawTriangles);
    dimGeo->allocate(triangleCount * 3);

    m_dim.setGeometry(dimGeo);
    m_dim.setMaterial(&m_dimMat);
    m_dimMat.setColor(Qt::black);

    m_opacity.setOpacity(0.6);

    m_opacity.appendChildNode(&m_dim);
    appendChildNode(&m_opacity);

    markDirty(QSGNode::DirtyMaterial | QSGNode::DirtyGeometry | QSGNode::DirtyOpacity);
}

Here the final tree:

Define or Update the geometry

void WalkerNode::update(const QRectF& out, const QRectF& in)
{
    // out is the geometry of the window
    // in is the geometry of the highlighted item
    const auto a= out.topLeft();
    const auto b= in.topLeft();
    const auto c= in.topRight();
    const auto d= out.topRight();
    const auto e= in.bottomRight();
    const auto f= out.bottomRight();
    const auto g= in.bottomLeft();
    const auto h= out.bottomLeft();

    {
        auto gem= m_dim.geometry();
        auto vertices= gem->vertexDataAsPoint2D();
        QList<std::array<QPointF, 3>> triangles{{a, b, d}, {b, d, c}, {d, c, f}, {c, f, e},
                                                {f, e, h}, {e, g, h}, {h, g, a}, {g, a, b}};
        int i= 0;
        for(auto t : triangles)
        {
            vertices[i + 0].set(t[0].x(), t[0].y());
            vertices[i + 1].set(t[1].x(), t[1].y());
            vertices[i + 2].set(t[2].x(), t[2].y());
            i+= 3;
        }

        m_dim.markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial);
    }

    markDirty(QSGNode::DirtyGeometry | QSGNode::DirtyMaterial);
}

We split the surface we have to cover in triangles.

Todo

  1. Animations: Smooth animation while transiting from one item to another.
  2. Test on bigger apps
  3. Find a logic to allow previous
  4. Use shader effect to make it better.
  5. Other


Conclusion:

UiWalker is already in production. It works like a charm. I hope to use it elsewhere. Then, I will add some new features. Contributions and comments are welcomed.

Hope you find this article interesting.

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

đŸŽČ 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/

Horloge parlante Ă  la demande

Bonjour Ă  tous!

Dans la série des projets un peu fou, je voudrais vous parler de mon horloge parlante !?

D’abord, un peu de contexte. Depuis peu, je frĂ©quente un serveur discord (The language sloth) dĂ©diĂ© Ă  l’apprentissage des langues.
Je participe Ă  ma hauteur pour aider les dĂ©butants en français. Ce n’est pas mon mĂ©tier mais l’ambiance est assez cool.

De coup, je me suis dit un peu renseigner sur les connaissances Ă  avoir pour obtenir le niveau A1 (le minimum).
Il y a comprendre l’heure. Sur ce constat, j’ai dĂ©marrĂ© un petit projet pour proposer un systĂšme capable de sortir un fichier audio pour chaque heure de la journĂ©e.
Et de proposer l’ensemble des façons de dire l’heure.

Le code:
Pour mes petits scripts, j’aime bien utiliser du python. Ici, mon petit script python permet d’enregistrer facilement les samples nĂ©cessaires pour bĂątir l’ensemble de donnĂ©es.
Ce travail construit les donnĂ©es d’une voix.
Il y a 28 mots Ă  enregistrer:

[pastacode lang=”python” manual=”unity%3D%5B%22un%22%2C%22une%22%2C%22deux%22%2C%22trois%22%2C%22quatre%22%2C%22cinq%22%2C%22six%22%2C%22sept%22%2C%22huit%22%2C%22neuf%22%5D%0Atens%3D%5B%22dix%22%2C%22onze%22%2C%22douze%22%2C%22treize%22%2C%22quatorze%22%2C%22quinze%22%2C%22seize%22%2C%22vingt%22%2C%22trente%22%2C%22quarante%22%2C%22cinquante%22%5D%0Awords%3D%5B%22midi%22%2C%22minuit%22%2C%22et%22%2C%22quart%22%2C%22moins%22%2C%20%22heure%22%2C%20%22le%22%5D” message=”Les samples Ă  enregistrer” highlight=”” provider=”manual”/]

 

Le site:
Le reste de l’application est constituĂ© d’un site web qui permet de choisir une voix, et une heure.
Suite Ă  cela, le site affiche un ou deux lecteurs audios pour permettre la lecture des sons.
J’ai recyclĂ© l’architecture php de mes sites applicatifs. Je la trouve vraiment formidable. MĂȘme si, je trouve le PHP de plus en plus dĂ©gueulasse.

Pour la suite, j’aimerai bien «gamifier» la chose. Permettre d’entendre une heure et de choisir la bonne valeur parmi trois propositions. Jouer avec ce genre d’Ă©lĂ©ments.

Si vous testez le site, pensez Ă  mettre le son pas trop fort.

 

Conclusion:
J’avais cette idĂ©e de partir des samples pour construire les messages audios qui traĂźner dans ma tĂȘte depuis un moment. J’avais envie de tester ce que cela donnerait d’enregistrer 28 samples environ pour gĂ©nĂ©rer 1560 fichiers audios. J’ai dĂ©cidĂ© de l’Ă©crire et voilĂ  le rĂ©sultat. La curiositĂ© s’attardait sur la qualitĂ© qu’on pourrait obtenir avec cette mĂ©thode. C’est audible, il y a quelques sont qui mĂ©riterait d’ĂȘtre retravailler mais dans l’ensemble, cela remplit son rĂŽle.
Je serai ravi de recevoir de nouvelles voix. Vous pouvez m’envoyer vos 28 samples et je peux m’occuper du reste.

Les liens:
Le projets en ligne: http://heures.renaudguezennec.eu/
Le code source du projet: https://github.com/obiwankennedy/french_talking_clock_game

Gérer les préférences utilisateurs en C++

Aujourd’hui, nous allons voir comment sont gĂ©rĂ©es les prĂ©fĂ©rences dans Rolisteam (et plus gĂ©nĂ©ralement dans mes dĂ©veloppements).

Quels contenus sont gérés dans les Préférences ?

Les prĂ©fĂ©rences conservent l’ensemble des Ă©lĂ©ments qu’un utilisateur peut dĂ©finir pour changer le comportement de l’application.

Dans Rolisteam, il est possible de dĂ©finir le thĂšme de l’application, la couleur du brouillard de guerre pour les cartes, la valeur de son opacitĂ©, Activer/dĂ©sactiver la recherche de mise Ă  jour, le lancement en plein Ă©cran, des alias de dĂ©s etc.

La problématique

La principale difficultĂ© est de rendre accessible en lecture et en Ă©criture les prĂ©fĂ©rences pour l’ensemble de l’application.
En parallĂšle, la fenĂȘtre de prĂ©fĂ©rences doit permettre d’afficher et de modifier toutes les valeurs.
Il est important de ne conserver qu’une version de donnĂ©es afin d’Ă©viter des incohĂ©rences. L’autre objecti est de rĂ©ussir Ă  notifier les utilisateurs d’une donnĂ©e aprĂšs son changement.

La solution

Pour rĂ©pondre Ă  ces problĂ©matiques, j’ai Ă©crit la classe PreferencesManager. Elle est fondĂ©e sur le patron de conception: Singleton.
Le but de ce patron est de s’assurer de l’unicitĂ© d’une instance de classe. Cela permet de garantir que les valeurs sera bien la mĂȘme pour tout le monde: les composants de l’application et le panneau de prĂ©fĂ©rences.

Pour réaliser un singleton, il est nécessaire de mettre les éléments suivants:

[pastacode lang=”cpp” manual=”class%20PreferencesManager%0A%7B%0Apublic%3A%0A%20%20%20%20%2F**%0A%20%20%20%20*%20%40brief%20Must%20be%20called%20instead%20of%20the%20constructor.%0A%20%20%20%20*%0A%20%20%20%20*%20%40return%20instance%20of%20PreferencesManager%0A%20%20%20%20*%2F%0A%20%20%20%20static%20PreferencesManager*%20getInstance()%3B%0A%20%20%20%20%2F**%0A%20%20%20%20*%20%40brief%20%20desturctor%0A%20%20%20%20*%2F%0A%20%20%20%20~PreferencesManager()%3B%0Aprivate%3A%0A%20%20%20%20%2F**%0A%20%20%20%20*%20%40brief%20Private%20constructor%20to%20make%20sure%20there%20is%20only%20one%20instance%20of%20this.%0A%20%20%20%20*%2F%0A%20%20%20%20PreferencesManager()%3B%0Aprivate%3A%0A%20%20%20%20%2F**%0A%20%20%20%20*%20Static%20reference%2C%20part%20of%20the%20singleton%20pattern%0A%20%20%20%20*%2F%0A%20%20%20%20static%20PreferencesManager*%20m_singleton%3B%0A%7D%3B” message=”Code d’un Singleton” highlight=”” provider=”manual”/]

CÎté implémentation, cela donne ceci:

[pastacode lang=”cpp” manual=”PreferencesManager*%20PreferencesManager%3A%3Am_singleton%20%3D%20NULL%3B%0A%0A%0APreferencesManager*%20PreferencesManager%3A%3AgetInstance()%0A%7B%0A%20%20%20%20if(m_singleton%20%3D%3D%20NULL)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20m_singleton%20%3D%20new%20PreferencesManager%3B%0A%20%20%20%20%7D%0A%20%20%20%20%0A%20%20%20%20return%20m_singleton%3B%0A%7D” message=”ImplĂ©mentation Singleton” highlight=”” provider=”manual”/]

La classe PreferencesManager conserve l’ensemble des donnĂ©es dans une map.

[pastacode lang=”cpp” manual=”QMap%3CQString%2CQVariant%3E*%20m_optionDictionary%3B” message=”structure de donnĂ©es” highlight=”” provider=”manual”/]

 

 

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/