Affichage des 4 domaines (circuit/batterie/capteur/reservoir) + commande circuit. Modèles d'éléments séparés par domaine (piège des rôles QML figés)
This commit is contained in:
+23
-4
@@ -18,7 +18,7 @@ void DataProvider::get_familles(const QString &type)
|
|||||||
url.setQuery(query);
|
url.setQuery(query);
|
||||||
|
|
||||||
auto reply = m_manager.get(QNetworkRequest(url));
|
auto reply = m_manager.get(QNetworkRequest(url));
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply, type]() {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
emit erreur(reply->errorString());
|
emit erreur(reply->errorString());
|
||||||
@@ -30,7 +30,7 @@ void DataProvider::get_familles(const QString &type)
|
|||||||
emit erreur(obj["message"].toString());
|
emit erreur(obj["message"].toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit famillesRecues(obj["familles"].toArray());
|
emit famillesRecues(type, obj["familles"].toArray());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ void DataProvider::get_elements(const QString &type, int famille)
|
|||||||
url.setQuery(query);
|
url.setQuery(query);
|
||||||
|
|
||||||
auto reply = m_manager.get(QNetworkRequest(url));
|
auto reply = m_manager.get(QNetworkRequest(url));
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
connect(reply, &QNetworkReply::finished, this, [this, reply, type, famille]() {
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
if (reply->error() != QNetworkReply::NoError) {
|
if (reply->error() != QNetworkReply::NoError) {
|
||||||
emit erreur(reply->errorString());
|
emit erreur(reply->errorString());
|
||||||
@@ -55,6 +55,25 @@ void DataProvider::get_elements(const QString &type, int famille)
|
|||||||
emit erreur(obj["message"].toString());
|
emit erreur(obj["message"].toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
emit elementsRecus(obj["elements"].toArray());
|
emit elementsRecus(type, famille, obj["elements"].toArray());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void DataProvider::set_circuit(int id, int consigne)
|
||||||
|
{
|
||||||
|
QUrl url(m_baseUrl + "/elements/circuit/" + QString::number(id));
|
||||||
|
QNetworkRequest request(url);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
QJsonObject body;
|
||||||
|
body["consigne"] = consigne;
|
||||||
|
const QByteArray data = QJsonDocument(body).toJson();
|
||||||
|
|
||||||
|
auto reply = m_manager.sendCustomRequest(request, "PATCH", data);
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, reply, id]() {
|
||||||
|
reply->deleteLater();
|
||||||
|
const bool succes = (reply->error() == QNetworkReply::NoError);
|
||||||
|
if (!succes) emit erreur(reply->errorString());
|
||||||
|
emit circuitModifie(id, succes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-6
@@ -11,8 +11,8 @@
|
|||||||
// HTTP/JSON est encapsulée ici - le QML ne voit que des signaux avec des
|
// HTTP/JSON est encapsulée ici - le QML ne voit que des signaux avec des
|
||||||
// tableaux déjà prêts à l'emploi.
|
// tableaux déjà prêts à l'emploi.
|
||||||
//
|
//
|
||||||
// Portée actuelle : uniquement le domaine "circuit" (première tranche
|
// Portée actuelle : les 4 domaines (circuit, batterie, capteur, reservoir)
|
||||||
// verticale). À généraliser aux 3 autres domaines une fois ce pattern validé.
|
// en lecture, écriture (consigne/mode auto) sur circuit uniquement.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
class DataProvider : public QObject
|
class DataProvider : public QObject
|
||||||
{
|
{
|
||||||
@@ -23,17 +23,23 @@ public:
|
|||||||
|
|
||||||
// TODO : rendre l'adresse de base configurable (IP locale du CM5 en cas 2,
|
// TODO : rendre l'adresse de base configurable (IP locale du CM5 en cas 2,
|
||||||
// domaine VPS en cas 3 - cf QR code d'accès, §"Architecture cible"/Cas 2-3
|
// domaine VPS en cas 3 - cf QR code d'accès, §"Architecture cible"/Cas 2-3
|
||||||
// de la synthèse) - en dur sur l'IP locale pour cette première tranche.
|
// de la synthèse) - en dur sur le relais VPS pour cette étape.
|
||||||
Q_INVOKABLE void get_familles(const QString &type);
|
Q_INVOKABLE void get_familles(const QString &type);
|
||||||
Q_INVOKABLE void get_elements(const QString &type, int famille);
|
Q_INVOKABLE void get_elements(const QString &type, int famille);
|
||||||
|
|
||||||
|
// Écriture rapide sur un circuit (bouton d'accueil) - PATCH /elements/circuit/<id>.
|
||||||
|
Q_INVOKABLE void set_circuit(int id, int consigne);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void famillesRecues(const QJsonArray &familles);
|
// Le type (et, pour les éléments, la famille) sont désormais inclus dans
|
||||||
void elementsRecus(const QJsonArray &elements);
|
// chaque signal - indispensable avec plusieurs domaines actifs en
|
||||||
|
// parallèle, pour que le QML sache à quelle réponse il a affaire.
|
||||||
|
void famillesRecues(const QString &type, const QJsonArray &familles);
|
||||||
|
void elementsRecus(const QString &type, int famille, const QJsonArray &elements);
|
||||||
|
void circuitModifie(int id, bool succes);
|
||||||
void erreur(const QString &message);
|
void erreur(const QString &message);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QNetworkAccessManager m_manager;
|
QNetworkAccessManager m_manager;
|
||||||
// QString m_baseUrl = QStringLiteral("http://192.168.1.220:8080");
|
|
||||||
QString m_baseUrl = QStringLiteral("https://api.ship2net.fr");
|
QString m_baseUrl = QStringLiteral("https://api.ship2net.fr");
|
||||||
};
|
};
|
||||||
|
|||||||
+156
-62
@@ -5,64 +5,94 @@ import QtQuick.Layouts 1.12
|
|||||||
import qt.koriolan.shipheart 1.0
|
import qt.koriolan.shipheart 1.0
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// home.qml (EMBRYON - POC WASM production, domaine circuit uniquement)
|
// home.qml (client shipheart_wasm) - 4 domaines (circuit/batterie/capteur/
|
||||||
// ----------------------------------------------------------------------------
|
// reservoir), via l'API réelle (DataProvider). Écriture (consigne) sur
|
||||||
// Version très réduite du vrai home.qml (~3880 lignes, tous domaines) :
|
// circuit uniquement pour l'instant.
|
||||||
// couvre uniquement l'affichage des circuits, via l'API réelle (DataProvider)
|
|
||||||
// au lieu de bdd.*. Objectif : valider la chaîne complète (HTTP réel, JSON,
|
|
||||||
// affichage QML) sur un seul domaine avant de généraliser.
|
|
||||||
//
|
//
|
||||||
// Simplifications volontaires par rapport au vrai home.qml :
|
// Architecture : les onglets (familles) partagent UN SEUL ListModel (forme
|
||||||
// - Lecture seule (pas de changement de consigne au clic - l'endpoint
|
// identique dans les 4 domaines : id/libelle/etat/actif). Les ÉLÉMENTS, en
|
||||||
// d'écriture n'est pas encore construit côté API, cf synthèse §20.7)
|
// revanche, utilisent UN MODÈLE SÉPARÉ PAR DOMAINE - piège QML rencontré en
|
||||||
// - Un seul domaine affiché (pas de batteries/réservoirs/capteurs/pages
|
// pratique : un ListModel fige les champs reconnus ("rôles") dès sa première
|
||||||
// personnalisées/messages/réveils/minuteur)
|
// utilisation, même après clear() ; réutiliser un même modèle pour des formes
|
||||||
// - Délégué d'affichage simplifié (pas Shipheart_bouton_circuit, qui
|
// JSON différentes (tension/intensite pour circuit, valeur/unite pour
|
||||||
// embarque des actions d'écriture non encore branchées)
|
// capteur...) laisse les champs propres à chaque domaine à 'undefined'.
|
||||||
// - mise_a_jour_onglets_circuits() n'existe plus : l'API renvoie déjà
|
|
||||||
// l'état agrégé par famille dans /elements/familles (cf §20.7/21.5)
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
Shipheart_page {
|
Shipheart_page {
|
||||||
id: page_accueil
|
id: page_accueil
|
||||||
|
|
||||||
readonly property int deport_haut_masquage_onglets: 20
|
readonly property var domaines: ["circuit", "batterie", "capteur", "reservoir"]
|
||||||
readonly property int rayon_onglets: 20
|
readonly property var domaines_libelles: [qsTr("Circuits"), qsTr("Batteries"), qsTr("Capteurs"), qsTr("Réservoirs")]
|
||||||
property int nombre_colonnes_affichage_circuits: 2
|
property int domaine_actif_index: 0
|
||||||
property int nombre_circuits_affiches_sur_une_colonne: 4
|
readonly property string domaine_actif: domaines[domaine_actif_index]
|
||||||
property int id_groupe_circuits: 0
|
|
||||||
|
|
||||||
ListModel { id: modele_onglets_circuits }
|
readonly property int espace_onglets: 10
|
||||||
ListModel { id: modele_circuits }
|
readonly property int rayon_onglets: 20
|
||||||
|
|
||||||
|
property int id_groupe: 0
|
||||||
|
|
||||||
|
ListModel { id: modele_onglets }
|
||||||
|
|
||||||
|
// Un ListModel par domaine, pour éviter le piège des "rôles" figés de QML :
|
||||||
|
ListModel { id: modele_elements_circuit }
|
||||||
|
ListModel { id: modele_elements_batterie }
|
||||||
|
ListModel { id: modele_elements_capteur }
|
||||||
|
ListModel { id: modele_elements_reservoir }
|
||||||
|
|
||||||
|
property var modele_elements_actuel: modele_elements_circuit
|
||||||
|
|
||||||
|
function modele_elements_pour(type) {
|
||||||
|
switch (type) {
|
||||||
|
case "circuit": return modele_elements_circuit;
|
||||||
|
case "batterie": return modele_elements_batterie;
|
||||||
|
case "capteur": return modele_elements_capteur;
|
||||||
|
case "reservoir": return modele_elements_reservoir;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
property DataProvider dataProvider: DataProvider {}
|
property DataProvider dataProvider: DataProvider {}
|
||||||
|
|
||||||
|
function charger_domaine(index) {
|
||||||
|
domaine_actif_index = index;
|
||||||
|
id_groupe = 0;
|
||||||
|
modele_onglets.clear();
|
||||||
|
modele_elements_actuel = modele_elements_pour(domaine_actif);
|
||||||
|
modele_elements_actuel.clear();
|
||||||
|
dataProvider.get_familles(domaine_actif);
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: dataProvider
|
target: dataProvider
|
||||||
|
|
||||||
function onFamillesRecues(familles) {
|
function onFamillesRecues(type, familles) {
|
||||||
modele_onglets_circuits.clear();
|
if (type !== domaine_actif) return; // Réponse d'un domaine quitté entretemps - ignorée
|
||||||
|
modele_onglets.clear();
|
||||||
for (var i = 0; i < familles.length; i++) {
|
for (var i = 0; i < familles.length; i++) {
|
||||||
var f = familles[i];
|
var f = familles[i];
|
||||||
modele_onglets_circuits.append({
|
modele_onglets.append({
|
||||||
"id": f.id,
|
"id": f.id,
|
||||||
"libelle": f.nom,
|
"libelle": f.nom,
|
||||||
"etat": f.etat,
|
"etat": f.etat,
|
||||||
"actif": (f.id === id_groupe_circuits)
|
"actif": (f.id === id_groupe)
|
||||||
});
|
});
|
||||||
// Sélectionne la première famille par défaut si aucune n'est choisie :
|
if (id_groupe === 0 && i === 0) {
|
||||||
if (id_groupe_circuits === 0 && i === 0) {
|
id_groupe = f.id;
|
||||||
id_groupe_circuits = f.id;
|
dataProvider.get_elements(domaine_actif, id_groupe);
|
||||||
dataProvider.get_elements("circuit", id_groupe_circuits);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onElementsRecus(elements) {
|
function onElementsRecus(type, famille, elements) {
|
||||||
modele_circuits.clear();
|
if (type !== domaine_actif || famille !== id_groupe) return;
|
||||||
for (var j = 0; j < elements.length; j++) {
|
var m = modele_elements_pour(type);
|
||||||
modele_circuits.append(elements[j]);
|
m.clear();
|
||||||
}
|
for (var j = 0; j < elements.length; j++) m.append(elements[j]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCircuitModifie(id, succes) {
|
||||||
|
if (!succes) console.log("Échec de la commande sur le circuit " + id);
|
||||||
|
if (domaine_actif === "circuit" && id_groupe > 0) dataProvider.get_elements("circuit", id_groupe);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onErreur(message) {
|
function onErreur(message) {
|
||||||
@@ -70,17 +100,17 @@ Shipheart_page {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: charger_domaine(0)
|
||||||
dataProvider.get_familles("circuit");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rafraîchissement périodique (remplace mise_a_jour_circuits(), appelée chaque seconde côté natif) :
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 1000
|
interval: 1000
|
||||||
running: true
|
running: true
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (id_groupe_circuits > 0) dataProvider.get_elements("circuit", id_groupe_circuits);
|
if (id_groupe > 0) {
|
||||||
|
dataProvider.get_elements(domaine_actif, id_groupe);
|
||||||
|
dataProvider.get_familles(domaine_actif);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,27 +119,59 @@ Shipheart_page {
|
|||||||
spacing: 10
|
spacing: 10
|
||||||
|
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
// Onglets (familles) :
|
// Sélecteur de domaine :
|
||||||
|
Row {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
height: shipheart_hauteur_bandeau_haut
|
||||||
|
spacing: espace_onglets
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: domaines_libelles
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: (page_accueil.width - 3 * espace_onglets) / 4
|
||||||
|
height: shipheart_hauteur_bandeau_haut
|
||||||
|
radius: rayon_onglets
|
||||||
|
color: (index === domaine_actif_index) ? shipheart_couleur_fond_bouton_actif : shipheart_couleur_fond_bouton
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: modelData
|
||||||
|
color: couleur_blanc_casse
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.bold: (index === domaine_actif_index)
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: charger_domaine(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------
|
||||||
|
// Onglets (familles du domaine actif) :
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: onglets_circuits
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
height: shipheart_hauteur_bandeau_haut
|
height: shipheart_hauteur_bandeau_haut
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
visible: modele_onglets_circuits.count > 1
|
visible: modele_onglets.count > 1
|
||||||
|
|
||||||
GridView {
|
GridView {
|
||||||
id: grille_onglets_circuits
|
id: grille_onglets
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cellWidth: width / Math.max(1, Math.min(modele_onglets_circuits.count, 5))
|
cellWidth: width / Math.max(1, Math.min(modele_onglets.count, 5))
|
||||||
cellHeight: shipheart_hauteur_bandeau_haut
|
cellHeight: shipheart_hauteur_bandeau_haut
|
||||||
model: modele_onglets_circuits
|
model: modele_onglets
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
width: grille_onglets_circuits.cellWidth - 2
|
width: grille_onglets.cellWidth - 2
|
||||||
height: grille_onglets_circuits.cellHeight
|
height: grille_onglets.cellHeight
|
||||||
radius: rayon_onglets
|
radius: rayon_onglets
|
||||||
color: model.actif ? shipheart_couleur_fond_bouton_actif : shipheart_couleur_fond_bouton
|
color: model.actif ? shipheart_couleur_fond_bouton_actif : shipheart_couleur_fond_bouton
|
||||||
|
border.color: (model.etat === 2) ? "red" : (model.etat === 1) ? "orange" : "transparent"
|
||||||
|
border.width: 2
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: model.libelle
|
text: model.libelle
|
||||||
@@ -122,11 +184,11 @@ Shipheart_page {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
onClicked: {
|
onClicked: {
|
||||||
id_groupe_circuits = model.id;
|
id_groupe = model.id;
|
||||||
for (var k = 0; k < modele_onglets_circuits.count; k++) {
|
for (var k = 0; k < modele_onglets.count; k++) {
|
||||||
modele_onglets_circuits.set(k, {"actif": (modele_onglets_circuits.get(k).id === model.id)});
|
modele_onglets.set(k, {"actif": (modele_onglets.get(k).id === model.id)});
|
||||||
}
|
}
|
||||||
dataProvider.get_elements("circuit", id_groupe_circuits);
|
dataProvider.get_elements(domaine_actif, id_groupe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,19 +196,19 @@ Shipheart_page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------
|
//-----------------------------------------------------------
|
||||||
// Circuits de la famille sélectionnée :
|
// Éléments de la famille sélectionnée, dans le domaine actif :
|
||||||
GridView {
|
GridView {
|
||||||
id: circuits_grid
|
id: elements_grid
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
cellWidth: width / nombre_colonnes_affichage_circuits
|
cellWidth: width / 2
|
||||||
cellHeight: height / nombre_circuits_affiches_sur_une_colonne
|
cellHeight: height / 4
|
||||||
model: modele_circuits
|
model: modele_elements_actuel
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
width: circuits_grid.cellWidth - 4
|
width: elements_grid.cellWidth - 4
|
||||||
height: circuits_grid.cellHeight - 4
|
height: elements_grid.cellHeight - 4
|
||||||
radius: 10
|
radius: 10
|
||||||
color: shipheart_couleur_fond_bouton
|
color: shipheart_couleur_fond_bouton
|
||||||
border.color: (model.etat === 2) ? "red" : (model.etat === 1) ? "orange" : "transparent"
|
border.color: (model.etat === 2) ? "red" : (model.etat === 1) ? "orange" : "transparent"
|
||||||
@@ -163,19 +225,51 @@ Shipheart_page {
|
|||||||
font.bold: true
|
font.bold: true
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: model.tension.toFixed(1) + " V " + model.intensite.toFixed(2) + " A"
|
text: {
|
||||||
|
switch (domaine_actif) {
|
||||||
|
case "circuit":
|
||||||
|
return model.tension.toFixed(1) + " V " + model.intensite.toFixed(2) + " A";
|
||||||
|
case "batterie":
|
||||||
|
return model.niveau.toFixed(0) + " % " + model.tension.toFixed(2) + " V";
|
||||||
|
case "capteur":
|
||||||
|
return model.valeur + (model.unite ? (" " + model.unite) : "");
|
||||||
|
case "reservoir":
|
||||||
|
return model.niveau.toFixed(0) + " % (" + model.capacite.toFixed(0) + " L)";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
color: couleur_blanc_casse
|
color: couleur_blanc_casse
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Indication visuelle discrète que l'élément est cliquable (circuits) :
|
||||||
|
Text {
|
||||||
|
visible: (domaine_actif === "circuit")
|
||||||
|
text: (model.consigne > 0) ? qsTr("● Marche (toucher pour arrêter)") : qsTr("○ Arrêt (toucher pour démarrer)")
|
||||||
|
color: (model.consigne > 0) ? "lightgreen" : couleur_blanc_casse
|
||||||
|
font.pixelSize: 10
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: (domaine_actif === "circuit")
|
||||||
|
onClicked: {
|
||||||
|
var nouvelle_consigne = (model.consigne > 0) ? 0 : 100;
|
||||||
|
dataProvider.set_circuit(model.id, nouvelle_consigne);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
visible: circuits_grid.count === 0
|
visible: elements_grid.count === 0
|
||||||
text: qsTr("Aucun circuit dans ce groupe.")
|
text: qsTr("Aucun élément dans ce groupe.")
|
||||||
color: couleur_rouge_transparent
|
color: couleur_rouge_transparent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user