409 lines
18 KiB
QML
409 lines
18 KiB
QML
import QtQuick 2.12
|
|
import QtQuick.Controls 2.12
|
|
import QtQuick.Layouts 1.12
|
|
|
|
import qt.koriolan.shipheart 1.0
|
|
|
|
// ============================================================================
|
|
// home.qml (client shipheart_wasm) - 4 domaines (circuit/batterie/capteur/
|
|
// reservoir), via l'API réelle (DataProvider). Écriture (consigne) sur
|
|
// circuit uniquement pour l'instant.
|
|
//
|
|
// Architecture : les ONGLETS et les ÉLÉMENTS ont chacun UN MODÈLE SÉPARÉ PAR
|
|
// DOMAINE (8 ListModel au total). Deux raisons distinctes :
|
|
// - Éléments : évite le piège des "rôles" QML figés (tension/intensite pour
|
|
// circuit, valeur/unite pour capteur... - cf notes historiques).
|
|
// - Onglets ET éléments : préserve les dernières données connues d'un
|
|
// domaine quand on le quitte puis qu'on y revient, y compris hors ligne -
|
|
// on ne vide JAMAIS un modèle avant d'avoir une nouvelle donnée réelle à
|
|
// afficher à la place (sinon : données qui "disparaissent" en changeant
|
|
// d'onglet, cf retours d'expérience).
|
|
// ============================================================================
|
|
|
|
Shipheart_page {
|
|
id: page_accueil
|
|
|
|
readonly property var domaines: ["circuits", "batteries", "capteurs", "reservoirs"]
|
|
readonly property var domaines_libelles: [qsTr("Circuits"), qsTr("Batteries"), qsTr("Capteurs"), qsTr("Réservoirs")]
|
|
property int domaine_actif_index: 0
|
|
readonly property string domaine_actif: domaines[domaine_actif_index]
|
|
|
|
readonly property int espace_onglets: 10
|
|
readonly property int rayon_onglets: 20
|
|
|
|
// Famille sélectionnée, conservée INDÉPENDAMMENT par domaine - permet de
|
|
// revenir sur un domaine déjà visité sans perdre sa sélection, et permet
|
|
// au minuteur de savoir, pour CHAQUE domaine, s'il a déjà une famille
|
|
// active ou doit encore en obtenir une.
|
|
property var id_groupe_par_domaine: ({"circuits": 0, "batteries": 0, "capteurs": 0, "reservoirs": 0})
|
|
readonly property int id_groupe: id_groupe_par_domaine[domaine_actif]
|
|
|
|
function definir_id_groupe(type, valeur) {
|
|
var copie = id_groupe_par_domaine;
|
|
copie[type] = valeur;
|
|
id_groupe_par_domaine = copie; // réassignation complète : nécessaire pour déclencher la notification QML sur un objet JS
|
|
}
|
|
|
|
// Un ListModel par domaine pour les onglets ET pour les éléments :
|
|
ListModel { id: modele_onglets_circuit }
|
|
ListModel { id: modele_onglets_batterie }
|
|
ListModel { id: modele_onglets_capteur }
|
|
ListModel { id: modele_onglets_reservoir }
|
|
property var modele_onglets_actuel: modele_onglets_circuit
|
|
|
|
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
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Fonction de recherche du modèle d'onglet pour un domaine donné :
|
|
// Reçoit : le nom du domaine ("circuits", "batteries", "capteurs" ou "reservoirs")
|
|
// Retour : le modèle d'onglets correspondant au domaine
|
|
//--------------------------------------------------------------------------------------------------
|
|
function modele_onglets_pour(type) {
|
|
switch (type) {
|
|
case "circuits": return modele_onglets_circuit;
|
|
case "batteries": return modele_onglets_batterie;
|
|
case "capteurs": return modele_onglets_capteur;
|
|
case "reservoirs": return modele_onglets_reservoir;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Fonction de recherche du modèle d'éléments pour un domaine donné :
|
|
// Reçoit : le nom du domaine ("circuits", "batteries", "capteurs" ou "reservoirs")
|
|
// Retour : le modèle d'onglets correspondant au domaine
|
|
//--------------------------------------------------------------------------------------------------
|
|
function modele_elements_pour(type) {
|
|
switch (type) {
|
|
case "circuits": return modele_elements_circuit;
|
|
case "batteries": return modele_elements_batterie;
|
|
case "capteurs": return modele_elements_capteur;
|
|
case "reservoirs": return modele_elements_reservoir;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
property DataProvider dataProvider: DataProvider {}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Fonction de chargement des données liées à un domaine donné :
|
|
// Reçoit : l'index du domaine
|
|
// Retour : rien
|
|
//--------------------------------------------------------------------------------------------------
|
|
function charger_domaine(index)
|
|
{
|
|
domaine_actif_index = index;
|
|
modele_onglets_actuel = modele_onglets_pour(domaine_actif);
|
|
modele_elements_actuel = modele_elements_pour(domaine_actif);
|
|
// Pas de clear() ici volontairement : si ce domaine a déjà été chargé
|
|
// avec succès plus tôt (avant une éventuelle coupure), ses dernières
|
|
// données restent affichées jusqu'à ce qu'une nouvelle récupération
|
|
// réussisse - jamais vidées juste parce qu'on regarde ailleurs puis
|
|
// qu'on revient.
|
|
dataProvider.get_familles(domaine_actif);
|
|
}
|
|
|
|
// Connexions (déclenchées sur apparition d'un signal de DataProvider) :
|
|
Connections {
|
|
target: dataProvider
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Réception du la iste des familles :
|
|
// La demande a été faite préalablement par un 'dataProvider.get_familles(domaine_actif)'
|
|
//--------------------------------------------------------------------------------------------------
|
|
function onFamillesRecues(type, familles)
|
|
{
|
|
var m = modele_onglets_pour(type);
|
|
m.clear();
|
|
for (var i = 0; i < familles.length; i++)
|
|
{
|
|
// Traitement de la première famille à afficher :
|
|
if (id_groupe_par_domaine[type] === 0)
|
|
{
|
|
definir_id_groupe(type, familles[i].id); // On sélectionne la première récupérée
|
|
dataProvider.get_elements(type, familles[i].id); // Au passage, on demande la récupération des éléments de cette famille
|
|
}
|
|
|
|
// Ajout de la famille au modèle d'onglets :
|
|
m.append({
|
|
"id": familles[i].id,
|
|
"libelle": familles[i].nom,
|
|
"etat": familles[i].etat,
|
|
"actif": (familles[i].id === id_groupe_par_domaine[type])
|
|
});
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Réception de la iste des éléments d'une famille :
|
|
// La demande a été faite préalablement par un 'dataProvider.get_elements(domaine_actif, famille_active)'
|
|
//--------------------------------------------------------------------------------------------------
|
|
function onElementsRecus(type, famille, elements)
|
|
{
|
|
if (famille !== id_groupe_par_domaine[type]) return; // Réponse obsolète (famille changée entretemps pour ce domaine)
|
|
|
|
var m = modele_elements_pour(type);
|
|
m.clear();
|
|
for (var j = 0; j < elements.length; j++) m.append(elements[j]);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Réception de la réponse à une commande de circuit :
|
|
// La demande a été faite préalablement par un 'dataProvider.set_circuit(id_circuit, nouvelle_consigne)'
|
|
//--------------------------------------------------------------------------------------------------
|
|
function onCircuitModifie(id, succes)
|
|
{
|
|
if (!succes) console.log("Échec de la commande sur le circuit " + id);
|
|
|
|
if ((domaine_actif === "circuits") && (id_groupe > 0)) dataProvider.get_elements("circuits", id_groupe); // Rafraichissement de l'affichage
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Réception d'une erreur du DataProvider :
|
|
//--------------------------------------------------------------------------------------------------
|
|
function onErreur(message) {
|
|
console.log("DataProvider erreur : " + message);
|
|
}
|
|
}
|
|
|
|
Component.onCompleted: charger_domaine(0) // A l'ouverture, chargement du premier domaine dans la liste (par défaut)
|
|
|
|
// Rafraîchissement périodique du domaine actuellement affiché.
|
|
// get_familles() est TOUJOURS retentée, même sans famille encore
|
|
// sélectionnée pour ce domaine (id_groupe===0) - c'est ce qui permet de
|
|
// se rétablir tout seul après une reconnexion, sans action de
|
|
// l'utilisateur, même pour un domaine qui n'avait encore jamais réussi
|
|
// à charger quoi que ce soit.
|
|
Timer {
|
|
interval: 1000
|
|
running: true
|
|
repeat: true
|
|
onTriggered: {
|
|
dataProvider.get_familles(domaine_actif);
|
|
if (id_groupe > 0) dataProvider.get_elements(domaine_actif, id_groupe);
|
|
}
|
|
}
|
|
|
|
|
|
//====================================================================================
|
|
// Affichage :
|
|
//====================================================================================
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
spacing: 10
|
|
|
|
//-----------------------------------------------------------
|
|
// Bandeau hors ligne - permanent, ne disparaît que sur reconnexion
|
|
// réelle (dataProvider.connecte redevient true après un succès) :
|
|
Rectangle {
|
|
Layout.fillWidth: true
|
|
height: visible ? 40 : 0
|
|
visible: !dataProvider.connecte
|
|
color: couleur_rouge_transparent
|
|
border.color: couleur_orange
|
|
border.width: 1
|
|
|
|
Text {
|
|
anchors.centerIn: parent
|
|
color: couleur_orange
|
|
font.pixelSize: 13
|
|
font.bold: true
|
|
text: dataProvider.aDejaEteConnecte
|
|
? qsTr("⚠ Bateau non connecté depuis le %1").arg(
|
|
Qt.formatDateTime(dataProvider.derniereConnexionReussie, "dd/MM/yyyy à HH:mm:ss"))
|
|
: qsTr("⚠ Bateau jamais connecté durant cette session")
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------
|
|
// 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 {
|
|
Layout.fillWidth: true
|
|
height: shipheart_hauteur_bandeau_haut
|
|
color: "transparent"
|
|
visible: modele_onglets_actuel.count > 1
|
|
|
|
GridView {
|
|
id: grille_onglets
|
|
anchors.fill: parent
|
|
cellWidth: width / Math.max(1, Math.min(modele_onglets_actuel.count, 5))
|
|
cellHeight: shipheart_hauteur_bandeau_haut
|
|
model: modele_onglets_actuel
|
|
clip: true
|
|
|
|
delegate: Rectangle {
|
|
width: grille_onglets.cellWidth - 2
|
|
height: grille_onglets.cellHeight
|
|
radius: rayon_onglets
|
|
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: model.libelle
|
|
anchors.centerIn: parent
|
|
color: couleur_blanc_casse
|
|
font.pixelSize: 14
|
|
font.bold: model.actif
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
definir_id_groupe(domaine_actif, model.id);
|
|
for (var k = 0; k < modele_onglets_actuel.count; k++) {
|
|
modele_onglets_actuel.set(k, {"actif": (modele_onglets_actuel.get(k).id === model.id)});
|
|
}
|
|
dataProvider.get_elements(domaine_actif, model.id);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------
|
|
// Éléments de la famille sélectionnée, dans le domaine actif :
|
|
// (opacité réduite quand hors ligne - les valeurs affichées sont
|
|
// les dernières connues, plus garanties à jour, cf bandeau ci-dessus)
|
|
GridView {
|
|
id: elements_grid
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
cellWidth: width / 2
|
|
cellHeight: height / 4
|
|
model: modele_elements_actuel
|
|
clip: true
|
|
opacity: dataProvider.connecte ? 1.0 : 0.45
|
|
|
|
delegate: Rectangle {
|
|
id: bouton_element
|
|
width: elements_grid.cellWidth - 4
|
|
height: elements_grid.cellHeight - 4
|
|
radius: 10
|
|
color: shipheart_couleur_fond_bouton
|
|
border.color: (model.etat === 2) ? "red" : (model.etat === 1) ? "orange" : "transparent"
|
|
border.width: 2
|
|
|
|
Column {
|
|
anchors.centerIn: parent
|
|
|
|
Rectangle {
|
|
width: bouton_element.width
|
|
height: bouton_element.height / 3
|
|
radius: 5
|
|
color: "transparent"
|
|
|
|
Text {
|
|
text: model.nom
|
|
color: couleur_blanc_casse
|
|
font.pixelSize: 16
|
|
font.bold: true
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
width: bouton_element.width
|
|
height: bouton_element.height / 3
|
|
radius: 5
|
|
color: "transparent"
|
|
|
|
Text {
|
|
text: {
|
|
switch (domaine_actif) {
|
|
case "circuits":
|
|
return model.tension.toFixed(1) + " V | " + model.intensite.toFixed(2) + " A";
|
|
case "batteries":
|
|
return model.niveau.toFixed(0) + " % | " + model.tension.toFixed(2) + " V";
|
|
case "capteurs":
|
|
return model.valeur + (model.unite ? (" " + model.unite) : "");
|
|
case "reservoirs":
|
|
return model.niveau.toFixed(0) + " % (" + model.capacite.toFixed(0) + " L)";
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
color: couleur_blanc_casse
|
|
font.pixelSize: 12
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
width: bouton_element.width
|
|
height: bouton_element.height / 3
|
|
radius: 5
|
|
color: (model.consigne > 0) ? couleur_green_yellow_transparent : "transparent"
|
|
visible: (domaine_actif === "circuits")
|
|
Text {
|
|
|
|
text: (model.consigne > 0) ? qsTr("On") : qsTr("Off")
|
|
color: (model.consigne > 0) ? couleur_green_yellow : couleur_blanc_casse
|
|
font.pixelSize: 18
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
}
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
enabled: (domaine_actif === "circuits") && dataProvider.connecte
|
|
onClicked: {
|
|
var nouvelle_consigne = (model.consigne > 0) ? 0 : 100;
|
|
dataProvider.set_circuit(model.id, nouvelle_consigne);
|
|
}
|
|
}
|
|
}
|
|
|
|
Label {
|
|
anchors.centerIn: parent
|
|
visible: elements_grid.count === 0
|
|
text: dataProvider.connecte
|
|
? qsTr("Aucun élément dans ce groupe.")
|
|
: qsTr("Hors ligne - dernières données indisponibles pour ce groupe.")
|
|
color: couleur_orange_transparent
|
|
}
|
|
}
|
|
}
|
|
}
|