//======================================================================================================================================================================== // CONFIG_ORDRE_ELEMENTS.QML // Ce fichier traite l'ordre d'affichage d'éléments sur la page d'accueil (batteries, circuits, réservoirs, capteurs). La page permet modifier l'ordre d'affichage // en déplaçant (glisser/déposer) éléments configurés. // L'appel se fait en passant la propriété 'table' qui permet de connaitre le type d'éléments à traiter. // La page est appelée depuis la page CONFIG_XXXXXX_1, en cliquant sur l'icone des deux flèches inversés en bas de l'écran //======================================================================================================================================================================== import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQml.Models 2.1 import qt.koriolan.shipheart 1.0 Shipheart_page { id: page_config_ordre_elements // Mis en forme : readonly property double affichage_hauteur_utile: shipheart_hauteur_page - shipheart_hauteur_bandeau_haut - shipheart_hauteur_bandeau_bas readonly property int periode_init_raffraichissement_parametres: 1 // En ms readonly property int periode_raffraichissement_parametres: 1000 // En ms // Affichage : property string texte_titre: "" property string texte_aucun_groupe: "" property string texte_aucun_element: "" property bool bouton_bandeau_bas_zone_retour_actif: false property bool flag_affichage_zone_groupe: false property double pourcentage_largeur_zone_groupes: 0.4 property int nombre_groupes_affichees_sur_une_colonne: 8 property int espace_zones: 10 property int nombre_colonnes_affichage: 1 property int nombre_elements_affichees_sur_une_colonne: 8 property int espace_lignes_affichage: 10 property int espace_colonnes_affichage: 300 // Données : property string table: "" property int type_source_groupe: shipheart_type_source_inconnu // 0 property int id_groupe: 0 // Cle_primaire du groupe en cours (par défaut, 0 = sans famille) // Modèles : // Note : les modèles chargés dans 'main.qml' n'ont pas à être rechargés dans les pages suivantes //=========================================================== // Timer periode raffraichissement parametres : //=========================================================== Timer { id: timer_raffraichissement_parametres interval: periode_init_raffraichissement_parametres triggeredOnStart: false running: true repeat: true onTriggered: { if (interval == periode_init_raffraichissement_parametres) // Premier passage, 1ms après le premier chargement { interval = periode_raffraichissement_parametres; init_page(); } else if (!bouton_bandeau_bas_zone_retour_actif) { } } } //=========================================================== // Lorsque la page est sur le point d'être affichée dans la pile : //=========================================================== StackView.onActivating: { mise_a_jour_liste_groupes(); mise_a_jour_liste_elements_groupe(id_groupe); } // Titre de la page : Shipheart_titre_1 {titre:texte_titre; hauteur: shipheart_hauteur_bandeau_haut} //------------------------------------------------------- // Ecran Principal : //------------------------------------------------------- Rectangle { id: zone_affichage_principale x: 0 y: shipheart_hauteur_bandeau_haut width: shipheart_largeur_page height: affichage_hauteur_utile color: "transparent" //------------------------------------------------- // Liste des groupes Rectangle { id: zone_affichage_groupes x: 0 y: 0 width: (shipheart_largeur_page * pourcentage_largeur_zone_groupes) - (espace_zones / 2) height: affichage_hauteur_utile visible: flag_affichage_zone_groupe color: "transparent" //------------------------------------------------------------------- // Liste : ListView { property bool deplacement_en_cours: false // Indique si un déplacement (au global) est en cours ou pas property int save_current_index: 0 // Permet de garder le Current_index du début du déplacement et de compenser les changements qui sont fait automatiquement, notamment en début et fin de liste. Le principe est qu'une fois le mouvement déclenché, ON NE CHANGE PAS de CurrentIndex !! id: grille_groupes x: 0 y: 0 width: parent.width height: parent.height clip: true // Limite l'affichage des délégués dans la zone de ListView (sinon, on affiche au moins un délégué au-dessus et un en dessous...) highlightRangeMode: ListView.StrictlyEnforceRange // Permet de faire défiler la liste jusqu'à ne garder que le dernier élément affiché model: modele_liste_groupes delegate: bouton_groupe displaced: Transition { // Gère la transition lors d'un déplacement NumberAnimation{ properties: "x,y" // Ici, on devrait pouvoir limiter à Y duration: 500 // Vitesse à laquelle se déplacent les boutons lorsqu'on les déplace } } Component { id: bouton_groupe MouseArea { id: dragArea_groupe // Zone correspondant à un seul bouton (on est dans le Delegate) width: grille_groupes.width height: grille_groupes.height / nombre_groupes_affichees_sur_une_colonne property bool bouton_appuye: false // On ne peut pas utiliser la propriété globale "deplacement_en_cours" car il faut une propriété par délégué pressAndHoldInterval: 500; // Temporise avant de passer en mode "déplacement" drag.target: bouton_appuye ? contenu_bouton_groupe : undefined drag.axis: Drag.YAxis // On ne se déplace que sur l'axe Y onClicked: { shipheart_clic(); mise_a_jour_ordre(); id_groupe = modele_liste_groupes.get(index).id; mise_a_jour_liste_groupes(); mise_a_jour_liste_elements_groupe(id_groupe); } onDoubleClicked: { shipheart_clic(); stackView.push("config_groupe_elements.qml", {"table": type_source_groupe, "id_groupe":modele_liste_groupes.get(index).id}) } onPressAndHold: { shipheart_clic(); bouton_appuye = true // Indication (au niveau du bouton seul) du début de l'appui long donc du déplacement grille_groupes.deplacement_en_cours = true // Indication (au niveau de ListView) du début du déplacement grille_groupes.save_current_index = grille_groupes.currentIndex; // Sauvegarde du CurrentIndex au début du déplacement } onReleased: { bouton_appuye = false // Indication (au niveau du bouton seul) de la fin de l'appui long donc du déplacement grille_groupes.deplacement_en_cours = false // Indication (au niveau de ListView) de la fin du déplacement grille_groupes.currentIndex = grille_groupes.save_current_index; // Restitution de la valeur initiale du CurrentIndex (a priori, pas nécessaire car mis à jour au fur et à mesure des changements dans "onCurrentIndexChanged") } Rectangle { id: contenu_bouton_groupe anchors { horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter } width: dragArea_groupe.width - 10 height: dragArea_groupe.height - 10 radius: shipheart_rayon_bouton color: (id_groupe === model.id) ? shipheart_couleur_fond_bouton_actif : shipheart_couleur_fond_bouton // SDIR #245 opacity: dragArea_groupe.bouton_appuye ? 0.6 : 1.0 Text { anchors.verticalCenter: contenu_bouton_groupe.verticalCenter anchors.left: contenu_bouton_groupe.left leftPadding: 20 rightPadding: 20 text: model.nom color: couleur_blanc_casse font.pixelSize: 18 } Drag.active: dragArea_groupe.bouton_appuye Drag.source: dragArea_groupe Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 states: State{ when: dragArea_groupe.bouton_appuye ParentChange { target: contenu_bouton_groupe parent: grille_groupes } AnchorChanges { target: contenu_bouton_groupe anchors { // Necessaire pour autoriser le mouvement horizontalCenter: undefined verticalCenter: undefined } } } } DropArea { anchors.fill: parent onEntered: { if (drag.source.DelegateModel.itemsIndex !== dragArea_groupe.DelegateModel.itemsIndex) { //console.log("\tMouvement de Source index = " + drag.source.DelegateModel.itemsIndex + " vers Dest index = " + dragArea.DelegateModel.itemsIndex) modele_liste_groupes.move(drag.source.DelegateModel.itemsIndex, dragArea_groupe.DelegateModel.itemsIndex, 1) } } } } } footer: Rectangle { // Ce premier rectangle sert juste à créer un espace un peu plus grand pour le bouton '+' et le décaler de 'spacing' vers le bas... width: grille_groupes.width height: grille_groupes.height / nombre_groupes_affichees_sur_une_colonne color: "transparent" Rectangle { // c'est le bouton proprement dit... id: bouton_footer_plus width: parent.width - 10 height: parent.height - 10 x: 5 color: shipheart_couleur_fond_bouton radius: shipheart_rayon_bouton anchors.verticalCenter: parent.verticalCenter Text { anchors { verticalCenter: parent.verticalCenter horizontalCenter: parent.horizontalCenter } text: "+" font.pixelSize: 40 font.bold: true color: couleur_gris_moyen } MouseArea{ anchors.fill : parent onClicked: { shipheart_clic(); stackView.push("config_groupe_elements.qml", {"table": type_source_groupe, "id_groupe":0}) } } } Rectangle { // Texte d'info en-dessous du '+' width: parent.width - 10 height: parent.height - 10 x: 5 y: bouton_footer_plus.height color: "transparent" anchors.top: parent.bottom anchors.topMargin: 30 Text { anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter horizontalAlignment: Text.AlignHCenter font.pixelSize: 12 color: couleur_gris_moyen text: qsTr("Pour déplacer un groupe, le maintenir appuyé puis le faire glisser.
Pour modifier ou supprimer un groupe, double-cliquer sur le groupe.
Pour ajouter un groupe, cliquer sur '+'.

Note : si un seul groupe, aucun onglet n'est affiché sur la page concernée...") } } } // Fin de footer ListModel { id: modele_liste_groupes } onCurrentIndexChanged: { if (grille_groupes.deplacement_en_cours === false) { // grille_modules_physiques.positionViewAtIndex(currentIndex, ListView.Beginning); // Permet de faire défiler la liste des modules physiques "en face" des modules configurés. Mais uniquement en dehors de déplacements puisque, pendant les déplacements, on force CurrentIndex à rester fixe. } else { grille_groupes.currentIndex = grille_groupes.save_current_index; // Permet de compenser les changements de CurrentIndex 'imposés' par le ListView. Notamment lors des déplacements en début et fin de liste. } } } // Fin de Listview des Groupes // Texte si liste de groupes est vide : Label { anchors.fill: parent horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter visible: parent.count === 0 text: texte_aucun_groupe color: couleur_rouge_transparent font.pixelSize: 15 font.bold: true wrapMode: Label.Wrap } } //--------------------------------------------------------------------- // Ligne verticale de séparation entre groupes et éléments du groupe : Rectangle { x: zone_affichage_groupes.width + (espace_zones/2) y: 0 width: 3 height: affichage_hauteur_utile color: couleur_gris_tres_pale_transparent visible: flag_affichage_zone_groupe } //------------------------------------------------- // Liste des éléments du groupe : Rectangle { id: zone_affichage_elements x: flag_affichage_zone_groupe ? (zone_affichage_groupes.width + espace_zones) : 0 y: 0 width: flag_affichage_zone_groupe ? (shipheart_largeur_page * (1 - pourcentage_largeur_zone_groupes)) - (espace_zones / 2) : shipheart_largeur_page height: affichage_hauteur_utile color: "transparent" GridView { id: grille_elements property int save_current_index: 0 // Permet de garder le Current_index du début du déplacement et de compenser les changements qui sont fait automatiquement, notamment en début et fin de liste. Le principe est qu'une fois le mouvement déclenché, ON NE CHANGE PAS de CurrentIndex !! anchors.fill: parent cellWidth: (width / nombre_colonnes_affichage) cellHeight: parent.height / nombre_elements_affichees_sur_une_colonne displaced: Transition { // Gère la transition lors d'un déplacement NumberAnimation{ properties: "x,y" // Ici, on devrait pouvoir limiter à Y duration: 500 // Vitesse à laquelle se déplacent les boutons lorsqu'on les déplace } } model: modele_liste_elements delegate: bouton_element Component { id: bouton_element MouseArea { id: dragArea_element // Zone correspondant à un seul bouton (on est dans le Delegate) property bool bouton_appuye: false // On ne peut pas utiliser la propriété globale "deplacement_en_cours" car il faut une propriété par délégué width: grille_elements.cellWidth height: grille_elements.cellHeight pressAndHoldInterval: 500; // Temporise avant de passer en mode "déplacement" drag.target: bouton_appuye ? contenu_bouton_element : undefined drag.axis: Drag.XandYAxis // On se déplace sur les 2 axes onPressAndHold: { bouton_appuye = true // Indication (au niveau du bouton seul) du début de l'appui long donc du déplacement grille_elements.save_current_index = grille_elements.currentIndex; // Sauvegarde du CurrentIndex au début du déplacement } onReleased: { bouton_appuye = false // Indication (au niveau du bouton seul) de la fin de l'appui long donc du déplacement grille_elements.currentIndex = grille_elements.save_current_index; // Restitution de la valeur initiale du CurrentIndex (a priori, pas nécessaire car mis à jour au fur et à mesure des changements dans "onCurrentIndexChanged") } Rectangle { id: contenu_bouton_element anchors { horizontalCenter: parent.horizontalCenter verticalCenter: parent.verticalCenter } width: dragArea_element.width - 10 height: dragArea_element.height - 10 radius: shipheart_rayon_bouton color: shipheart_couleur_fond_bouton opacity: dragArea_element.bouton_appuye ? 0.6 : 1.0 Text { anchors.verticalCenter: contenu_bouton_element.verticalCenter anchors.left: contenu_bouton_element.left leftPadding: 20 rightPadding: 20 text: model.nom color: couleur_blanc_casse font.pixelSize: 18 } Drag.active: dragArea_element.bouton_appuye Drag.source: dragArea_element Drag.hotSpot.x: width / 2 Drag.hotSpot.y: height / 2 states: State{ when: dragArea_element.bouton_appuye ParentChange { target: contenu_bouton_element parent: grille_elements } AnchorChanges { target: contenu_bouton_element anchors { // Necessaire pour autoriser le mouvement horizontalCenter: undefined verticalCenter: undefined } } } } DropArea { anchors.fill: parent onEntered: { if (drag.source.DelegateModel.itemsIndex !== dragArea_element.DelegateModel.itemsIndex) { //console.log("\tMouvement de Source index = " + drag.source.DelegateModel.itemsIndex + " vers Dest index = " + dragArea_element.DelegateModel.itemsIndex) modele_liste_elements.move(drag.source.DelegateModel.itemsIndex, dragArea_element.DelegateModel.itemsIndex, 1) } } } } } ListModel { id: modele_liste_elements } onCurrentIndexChanged: { grille_elements.currentIndex = grille_elements.save_current_index; // Permet de compenser les changements de CurrentIndex 'imposés' par le ListView. Notamment lors des déplacements en début et fin de liste. } } // Fin de Gridview // Texte si groupe vide : Label { anchors.fill: parent horizontalAlignment: Qt.AlignHCenter verticalAlignment: Qt.AlignVCenter visible: modele_liste_elements.count === 0 text: texte_aucun_element color: couleur_rouge_transparent font.pixelSize: 15 font.bold: true wrapMode: Label.Wrap } } // Fin de zone affichage elements } // Fin de Ecran Principal //------------------------------------------------------- // Bandeau bas : //------------------------------------------------------- Row { x: 0 y: shipheart_hauteur_page - shipheart_hauteur_bandeau_bas; width: shipheart_largeur_page; height: shipheart_hauteur_bandeau_bas; spacing: 5 padding: 5 //................................................. // Bouton retour //................................................. Rectangle { id: bandeau_bas_zone_retour width: parent.width - (2 * parent.padding) height: shipheart_hauteur_bandeau_bas - parent.topPadding - parent.bottomPadding; radius: 5 color: shipheart_couleur_fond_bouton Image { source: "/images/icone_retour.png" sourceSize.height: 30 anchors { verticalCenter: parent.verticalCenter horizontalCenter: parent.horizontalCenter } } MouseArea { // Zone d'interaction. On est obligé de la mettre dans la grille pour que Rectangle et MouseArea soient frères ou parent/enfant ET qu'on n'occupe qu'un espace dans la grille. id: mouse_area_bandeau_bas_zone_retour anchors.fill: parent onClicked: { shipheart_clic(); mise_a_jour_ordre(); stackView.pop() } } } } // Note : il est inutile de prévoir un Mousearea pour sortir de l'économiseur sur cette page puisqu'il // est systématiquement bloqué pour toutes les pages lancées depuis 'Paramètres Avancés'. //============================================================================================================= // Init de la page //============================================================================================================= function init_page() { var req, res; var i; var nb; //console.log("Table = "+ table); // Paramètres par défaut : flag_affichage_zone_groupe = false; nombre_colonnes_affichage = 1; nombre_elements_affichees_sur_une_colonne = 8; espace_lignes_affichage = 8; espace_colonnes_affichage = 300; type_source_groupe = shipheart_type_source_inconnu; texte_aucun_groupe = qsTr("Aucun groupe n'est actuellement configuré..."); texte_aucun_element = qsTr("Aucun élément n'est actuellement configuré..."); // Paramètres spécifiques : switch(table) { //---------------------------------------------------------------------------------- case "TB_Batteries": nombre_colonnes_affichage = 1; espace_colonnes_affichage = 20; texte_aucun_groupe = qsTr("Aucun groupe de batteries n'est actuellement configuré..."); texte_aucun_element = qsTr("Aucune batterie n'est actuellement affectée à ce groupe..."); texte_titre = qsTr("Affichage des batteries sur la page d'accueil"); type_source_groupe = shipheart_type_source_batteries; break; //---------------------------------------------------------------------------------- case "TB_Capteurs": nombre_colonnes_affichage = 3; espace_colonnes_affichage = 20; texte_aucun_groupe = qsTr("Aucun groupe de capteurs n'est actuellement configuré..."); texte_aucun_element = qsTr("Aucun capteur n'est actuellement affecté ce groupe..."); texte_titre = qsTr("Affichage des capteurs sur la page d'accueil"); type_source_groupe = shipheart_type_source_capteurs; break; //---------------------------------------------------------------------------------- case "TB_Circuits": nombre_colonnes_affichage = 2; espace_colonnes_affichage = 80; texte_aucun_groupe = qsTr("Aucun groupe de circuits n'est actuellement configuré..."); texte_aucun_element = qsTr("Aucun circuit n'est actuellement affecté à ce groupe..."); texte_titre = qsTr("Affichage des circuits sur la page d'accueil"); type_source_groupe = shipheart_type_source_circuits; break; //---------------------------------------------------------------------------------- case "TB_Reservoirs": nombre_colonnes_affichage = 1; nombre_elements_affichees_sur_une_colonne = 8; espace_colonnes_affichage = 20; texte_aucun_groupe = qsTr("Aucun groupe de réservoirs n'est actuellement configuré..."); texte_aucun_element = qsTr("Aucun réservoir n'est actuellement affecté à ce groupe..."); texte_titre = qsTr("Affichage des réservoirs sur la page d'accueil"); type_source_groupe = shipheart_type_source_reservoirs; break; //---------------------------------------------------------------------------------- default: } // Traitement spécifique quand on traite les groupes : if (type_source_groupe !== shipheart_type_source_inconnu) { flag_affichage_zone_groupe = true; // Plus facile pour gérer l'affichage mise_a_jour_liste_groupes(); // Après la rcherche du premier groupe sinon il n'est pas sélectionné... } mise_a_jour_liste_elements_groupe(id_groupe); } //============================================================================================================= // Mise à jour des groupes //============================================================================================================= function mise_a_jour_liste_groupes() { var req, res, nb; var i; var actif; //console.log("Table groupe = " +table_groupe); modele_liste_groupes.clear(); // Vérif que l'id froupe existe bien : req = 'SELECT Cle_primaire FROM TB_Familles WHERE Type_source=' + type_source_groupe + ' AND Cle_primaire=' + id_groupe + ' '; //console.log("Requête = [ " + req + "; ]") nb = bdd.requete(req); if (nb === 0) id_groupe = 0; // Sinon, on positionne l'ID à 0 pour que la première valeur soit enregistrée dans la boucle suivante. // Recherche des groupes : if (type_source_groupe !== shipheart_type_source_inconnu) { req = 'SELECT Nom, Cle_primaire, Priorite_affichage FROM TB_Familles WHERE Type_source=' + type_source_groupe + ' ORDER BY Priorite_affichage'; //console.log("Requête = [ " + req + "; ]") nb = bdd.requete(req); for (i = 0; i < nb; i ++) { res = bdd.lecture_ligne(); if (!res.isEmpty) { if (id_groupe === 0) id_groupe = Number(res[1]); // Utile en retour de suppression d'un groupe qui n'est plus disponible => on prend le premier if (id_groupe === Number(res[1])) actif = true; else actif = false; modele_liste_groupes.append({"nom": res[0], "id": Number(res[1]), "actif": actif}); } } } } //============================================================================================================= // Mise à jour des éléments d'un groupe //============================================================================================================= function mise_a_jour_liste_elements_groupe(id_groupe) { var req, res, nb; var i; var filtre; modele_liste_elements.clear(); // Filtre de sélection en fonction du fait qu'on gère les groupes ou pas : if (type_source_groupe !== shipheart_type_source_inconnu) filtre = ' AND ID_Famille=' + id_groupe + ' '; else filtre = ""; // Récupération des données correspondant au type dans la BDD + mise à jour du modèle : req = 'SELECT Nom, Cle_primaire FROM TB_Familles WHERE Type_source=' + type_source_groupe + ' ' + filtre + ' AND Priorite_affichage_accueil>0 ORDER BY Priorite_affichage_accueil'; //console.log("Requête = [ " + req + "; ]") nb = bdd.requete(req); for (i = 0; i < nb; i ++) { res = bdd.lecture_ligne(); if (!res.isEmpty) { modele_liste_elements.append({"nom": res[0], "id_element": Number(res[1])}); } } } //============================================================================================================= // Enregistrement des modifications dans la BDD //============================================================================================================= function mise_a_jour_ordre() { var req, res; var i; var nb; //------------------------------------------------------------ // Mise à jour de l'ordre d'affichage des familles (groupes) : if (type_source_groupe !== shipheart_type_source_inconnu) { for(i = 0; i < modele_liste_groupes.count; i++) { if (modele_liste_groupes.get(i).id === 0) continue; // C'est le groupe par défaut qui n'est pas stocké dans la BDD... req = 'UPDATE TB_Familles SET Priorite_affichage=' + (i+1) + ' WHERE Type_source=' + type_source_groupe + ' AND Cle_primaire=' + modele_liste_groupes.get(i).id + ' '; //console.log("Requête = " + req) bdd.requete_unique(req); } } //-------------------------------------------------------------------------- // Mise à jour de l'ordre des éléments dans le groupe actuellement affiché : for(i = 0; i < modele_liste_elements.count; i++) { // Important : il faut bien commencer la numérotation à 1 car si 0 (ou négatif), l'élément est considéré comme masqué sur la page d'accueil (il faut alors le démasquer dans sa page de config). req = 'UPDATE ' + table + ' SET Priorite_affichage_accueil=' + (i+1) + ' WHERE Cle_primaire=' + modele_liste_elements.get(i).id_element + ' '; //console.log("Requête = [ " + req + "; ]") bdd.requete_unique(req); } // NB : il n'est pas utile de demander une synchro (2) car les éléments modifiés ne concernent que des sujets d'affichage qui n'influent en rien le fonctionnement en dehors de QT. } }