Files
shipheart-qt/config_pages.qml

529 lines
25 KiB
QML

//========================================================================================================================================================================
// CONFIG_PAGES.QML
// Ce fichier traite la configuration des pages. La page permet notamment de :
// - Afficher, pour toutes les pages déjà configurées (et donc d'accéder à la configuration détaillée de l'une d'elle ; ou sa création)
// - valider/dévalider l'affichage d'une page via un bouton en bout de ligne basculable par clic-droit
// - Changer l'ordre d'affichage sur la page d'accueil
// La page est appelée depuis la page PARAMETRES_AVANCES, en cliquant sur "Batteries"
//========================================================================================================================================================================
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_pages
// Mis en forme :
readonly property double affichage_hauteur_utile: shipheart_hauteur_page - shipheart_hauteur_bandeau_haut - shipheart_hauteur_bandeau_bas
readonly property int hauteur_bouton_droit: 80
readonly property double pourcentage_largeur_colonne_gauche: 0.75
readonly property double pourcentage_bandeau_bas_largeur_zone_retour: 1.0
readonly property int espace_colonnes_principales: 20
readonly property double pourcentage_largeur_bouton_affichage_dans_colonne_gauche: 0.15
readonly property int nombre_pages_affiches_sur_une_colonne: 8
readonly property int periode_init_raffraichissement_parametres: 1 // En ms
readonly property int periode_raffraichissement_parametres: 100 // En ms
// Affichage :
property int niveau_page: 0
// 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;
}
else {
}
}
}
//===========================================================
// Lorsque la page est sur le point d'être affichée dans la pile :
//===========================================================
StackView.onActivating: {
init_page(); // Permet que lors du retour sur la page, l'état des différentes variables soit mise à jour avant l'affichage proprement dit. Y compris lors de l'ouverture initiale...
}
// Titre de la page :
Shipheart_titre_1 {titre:qsTr("Configuration des pages"); hauteur: shipheart_hauteur_bandeau_haut}
//-------------------------------------------------------
// Ecran de gauche (liste des pages) :
//-------------------------------------------------------
Rectangle {
id: zone_affichage_gauche
x: 0
y: shipheart_hauteur_bandeau_haut;
width: (shipheart_largeur_page * pourcentage_largeur_colonne_gauche) - (espace_colonnes_principales / 2)
height: affichage_hauteur_utile
color: "transparent"
//-------------------------------------------------------------------
// Grille pour afficher les pages :
ListView {
id: grille_principale
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 !!
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é
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_grille_principale
delegate: bouton_page
Component {
id: bouton_page
MouseArea {
id: dragArea_groupe // Zone correspondant à un seul bouton (on est dans le Delegate)
width: grille_principale.width
height: grille_principale.height / nombre_pages_affiches_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_page : undefined
drag.axis: Drag.YAxis // On ne se déplace que sur l'axe Y
onClicked: {
shipheart_clic();
if (model.configurable) stackView.push("config_page_edition.qml", {"id_page": model.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_principale.deplacement_en_cours = true // Indication (au niveau de ListView) du début du déplacement
grille_principale.save_current_index = grille_principale.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_principale.deplacement_en_cours = false // Indication (au niveau de ListView) de la fin du déplacement
grille_principale.currentIndex = grille_principale.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_page
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: parent.verticalCenter
}
width: dragArea_groupe.width - 5
height: dragArea_groupe.height - 5
color: "transparent"
opacity: dragArea_groupe.bouton_appuye ? 0.6 : 1.0
//------------------------------------
// Bouton avec le nom de la page :
//------------------------------------
Rectangle {
height: parent.height - 5
width: (model.masquable) ?
((parent.width * (1 - pourcentage_largeur_bouton_affichage_dans_colonne_gauche)) - 10) :
parent.width
color: model.configurable ?
shipheart_couleur_fond_bouton :
"transparent"
border.width: 2
border.color: shipheart_couleur_fond_bouton
radius: shipheart_rayon_bouton
anchors {
left: parent.left
verticalCenter: parent.verticalCenter
}
//--------------------------------------------------------------------
// Icone du bouton :
Rectangle { // Le passage par un rectangle avant l'image permet de centrer l'image sur le rectangle et pas juste caler les images à gauche (elles n'ont pas la même largeur donc elles n'étaient pas centrées les unes par rapport aux autres)
width: 35
height: parent.height
x: 20
color: "transparent"
visible: (model.type < shipheart_type_page_personnalisee_non_masquable)
Image {
source: (model.icone !== "") ? ("/images/" + model.icone) : "" // Sans le test, on génère un warning lorsque 'icone' est vide
sourceSize.height: parent.height * 0.5
sourceSize.width: parent.width
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
}
visible: (model.icone !== "")
}
}
//--------------------------------------------------------------------
// Texte du bouton :
Text {
visible: (model.type >= shipheart_type_page_personnalisee_non_masquable)
anchors.fill: parent
horizontalAlignment: Qt.AlignLeft
verticalAlignment: Qt.AlignVCenter
leftPadding: 20
text: model.nom
color: couleur_blanc_casse
font.pixelSize: 18
wrapMode: Label.Wrap
}
}
//------------------------------------
// Bouton Visible :
//------------------------------------
Rectangle {
x: (parent.width * (1 - pourcentage_largeur_bouton_affichage_dans_colonne_gauche))
height: parent.height - 10
width:(parent.width * pourcentage_largeur_bouton_affichage_dans_colonne_gauche)
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
}
radius: shipheart_rayon_bouton
color: shipheart_couleur_fond_bouton
visible: model.masquable
//----------------------------
// Coche verte ou croix rouge :
Image {
source: (model.priorite_affichage <= 0) ? "/images/faux.png" : "/images/vrai.png"
sourceSize.height: parent.height * 0.5
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
}
}
//----------------------------
MouseArea {
id: mouse_area_cdes_auto
x: 0
y: 0
width: parent.width
height: parent.height
pressAndHoldInterval: shipheart_mouse_press_and_hold_interval_long
onPressAndHold: {
shipheart_clic();
toggle_validite(model.index);
}
}
}
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_page
parent: grille_principale
}
AnchorChanges {
target: contenu_bouton_page
anchors { // Necessaire pour autoriser le mouvement
horizontalCenter: undefined
verticalCenter: undefined
}
}
}
}
DropArea {
enabled: (index !== 0) // On interdit de bouger la première ligne (Ephéméride)
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_grille_principale.move(drag.source.DelegateModel.itemsIndex, dragArea_groupe.DelegateModel.itemsIndex, 1)
mise_a_jour_ordre_pages();
}
}
}
}
}
ListModel {
id: modele_grille_principale
}
Label {
anchors.fill: parent
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
visible: parent.count === 0
text: qsTr("Aucune page n'est encore configurée...")
color: couleur_rouge_transparent
font.pixelSize: 15
font.bold: true
wrapMode: Label.Wrap
}
}
}
//-------------------------------------------------------
// Ecran Droite :
//-------------------------------------------------------
Rectangle {
id: zone_affichage_droite
x: zone_affichage_gauche.width + espace_colonnes_principales
y: shipheart_hauteur_bandeau_haut + 5; // Le +5 permet d'aligner le haut du premier bouton de droite avec le haut du premier bouton de la liste de gauche (
width: (shipheart_largeur_page * (1 - pourcentage_largeur_colonne_gauche)) - (espace_colonnes_principales / 2)
height: affichage_hauteur_utile
color: "transparent"
Column {
anchors.fill: parent
spacing: 10
//----------------------
// Bouton + :
Rectangle {
width: parent.width
height: hauteur_bouton_droit
color: shipheart_couleur_fond_bouton
radius: shipheart_rayon_bouton
visible: !clavier.active
Text {
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
}
font.pixelSize: 40
text: qsTr("+")
color: couleur_blanc_casse
}
MouseArea {
anchors.fill: parent
onClicked: {
shipheart_clic();
stackView.push("config_page_edition.qml", {"id_page": 0})
}
}
}
}
}
//-------------------------------------------------------
// Bandeau bas :
//-------------------------------------------------------
Row {
x: 0
y: shipheart_hauteur_page - shipheart_hauteur_bandeau_bas;
width: shipheart_largeur_page;
height: shipheart_hauteur_bandeau_bas;
padding: 5
spacing: 5
//.................................................
// Bouton retour
//.................................................
Rectangle {
id: bandeau_bas_zone_retour
width: (parent.width * pourcentage_bandeau_bas_largeur_zone_retour) - parent.leftPadding - (parent.spacing / 2)
height: shipheart_hauteur_bandeau_bas - parent.topPadding - parent.bottomPadding;
radius: shipheart_rayon_bouton
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();
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()
{
niveau_page = stackView.depth - 1; // Permet de stocker le niveau de la page dans la Stackview
mise_a_jour_donnees();
}
//=============================================================================================================
// Mise à jour des données
//=============================================================================================================
function mise_a_jour_donnees()
{
var req, res;
var i;
var nb_pages;
var type;
var configurable;
var masquable;
modele_grille_principale.clear(); // RAZ des valeurs existantes
req = 'SELECT Cle_primaire, ';
req += ' Nom, ';
req += ' Type, ';
req += ' Fichier, ';
req += ' Priorite_affichage ';
req += ' FROM TB_Pages ORDER BY ABS(Priorite_affichage), Cle_primaire';
//console.log("Requête = [ " + req + "; ]");
nb_pages = bdd.requete(req);
for (i = 0; i < nb_pages; i ++)
{
res = bdd.lecture_ligne();
if (!res.isEmpty)
{
// Recherche du caractère configurable et/ou masquable de la page :
type = Number(res[2]);
switch(type)
{
//-------------------------------------------------
case shipheart_type_page_preconfiguree_ephemeride:
configurable = false;
masquable = false;
break;
//-------------------------------------------------
case shipheart_type_page_preconfiguree_chronometre:
case shipheart_type_page_personnalisee_minuteur:
case shipheart_type_page_preconfiguree_reveil:
configurable = false;
masquable = true;
break;
//-------------------------------------------------
case shipheart_type_page_personnalisee_non_masquable:
configurable = true;
masquable = false;
break;
//-------------------------------------------------
case shipheart_type_page_personnalisee_masquable:
configurable = true;
masquable = true;
break;
//-------------------------------------------------
case shipheart_type_page_inconnu:
continue;
}
// Mise à jour du modèle :
modele_grille_principale.append({
"id": Number(res[0]),
"nom": res[1],
"type": type,
"configurable": configurable,
"masquable": masquable,
"icone": (Number(res[2]) >= shipheart_type_page_personnalisee_non_masquable) ? "" : res[3], // On n'utilise les icones que pour les pages préconfigurées (et il faut bien faire la distinctions car les fichiers icones sont stockées dans 'images' alors que les fichiers silhouette sont stockées dans 'docs/images_silhouette')
"priorite_affichage": Number(res[4]) });
}
else break; // Ne devrait pas arriver....
}
}
//=============================================================================================================
// Mise à jour de l'ordre des pages
//=============================================================================================================
function mise_a_jour_ordre_pages()
{
var req;
var i;
var new_priorite_affichage;
for (i = 0; i < modele_grille_principale.count; i++)
{
new_priorite_affichage = i + 1; // Nouvelle valeur arbitraire
if (modele_grille_principale.get(i).priorite_affichage < 0) new_priorite_affichage *= (-1); // Passage en négatif pour les pages déjà dévalidées
// Mise à jour dans le modele :
modele_grille_principale.set(i, { "priorite_affichage": new_priorite_affichage } );
// Mis à jour de la nouvelle valeur dans la BDD :
req = 'UPDATE TB_Pages SET Priorite_affichage=' + new_priorite_affichage + ' WHERE Cle_primaire=' + modele_grille_principale.get(i).id + ' ';
//console.log("Requête = [ " + req + "; ]");
bdd.requete_unique(req);
}
}
//=============================================================================================================
// Bascule de la visibilité d'une page
//=============================================================================================================
function toggle_validite(index)
{
var req;
var priorite_affichage;
// Recherche de la valeur actuelle et calcul de la nouvelle :
priorite_affichage = modele_grille_principale.get(index).priorite_affichage * (-1);
// Mise à jour de la nouvelle valeur dans le modèle :
modele_grille_principale.set(index, {"priorite_affichage": priorite_affichage});
// Mis à jour de la nouvelle valeur dans la BDD :
req = 'UPDATE TB_Pages SET Priorite_affichage=' + priorite_affichage + ' WHERE Cle_primaire=' + modele_grille_principale.get(index).id + ' ';
//console.log("Requête = [ " + req + "; ]");
bdd.requete_unique(req);
}
}