1471 lines
77 KiB
QML
1471 lines
77 KiB
QML
//========================================================================================================================================================================
|
|
// CONFIG_VALEURS_NIVEAUX.QML
|
|
// Ce fichier traite la configuration de courbes de conversion valeurs <=> niveaux (batteries et réservoirs). La page permet de visualiser ou modifier
|
|
// des points et d'afficher la courbe de conversion résultante. Il est aussi possible de recharger la config usine.
|
|
// 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, en cliquant sur le bouton "courbe de conversion"
|
|
//========================================================================================================================================================================
|
|
|
|
import QtQuick 2.12
|
|
import QtQuick.Controls 2.12
|
|
import QtQml.Models 2.1
|
|
import QtCharts 2.3
|
|
|
|
|
|
import qt.koriolan.shipheart 1.0
|
|
import"fonctions.js" as Fonctions
|
|
import"fonctions_batteries.js" as Fonctions_batteries
|
|
import"fonctions_reservoirs.js" as Fonctions_reservoirs
|
|
|
|
|
|
Shipheart_page {
|
|
id: page_config_valeurs_niveaux
|
|
|
|
|
|
// Mis en forme :
|
|
readonly property double pourcentage_largeur_colonne_gauche: 0.25
|
|
readonly property double pourcentage_hauteur_ligne_haut: 0.70
|
|
readonly property double espace_colonnes_principales: 20
|
|
readonly property double espace_lignes_principales: 20
|
|
readonly property double affichage_hauteur_utile: shipheart_hauteur_page - shipheart_hauteur_bandeau_haut - shipheart_hauteur_bandeau_bas
|
|
readonly property int nombre_lignes_points_visibles: 6
|
|
readonly property int diametre_points_deplacables: 200
|
|
readonly property double coeff_fleche_reglage_fin: 0.8
|
|
|
|
readonly property int periode_init_raffraichissement_parametres: 1 // En ms
|
|
readonly property int periode_raffraichissement_parametres: 1000 // En ms
|
|
readonly property int step_timer_appui_long: 30 // en ms
|
|
|
|
|
|
// Affichage :
|
|
property string texte_titre: "Courbe de conversion"
|
|
property string texte_aucun_element: "Aucun élément n'est actuellement configuré"
|
|
property bool courbe_flag_vide: true
|
|
|
|
property bool flag_init: true // Permet de bloquer certains affichage tant qu el'init n'est pas terminée
|
|
property bool flag_alignement_demande: false
|
|
property bool flag_nouvelles_valeurs: false
|
|
property bool flag_nombre_max_points_atteint: false
|
|
property bool flag_affichage_reglage_fin: false
|
|
property int point_en_cours: -1
|
|
property int point_a_supprimer: -1
|
|
|
|
|
|
// Données :
|
|
property string table: ""
|
|
property int id: 0
|
|
property string champ_liste: "" // Permet de conserver le nom du champ utilisé pour stocker la liste dans la table TB_Batteries ou TB_Reservoirs
|
|
property int nb_points: 0
|
|
property string unite_x: ""
|
|
property int precision_x: 3
|
|
property int precision_x_graphique: 2
|
|
property int tension_nominale: 12
|
|
property int type_entrees_reservoir: 0
|
|
property int mode_reservoir: 0
|
|
property int sous_mode_reservoir: 0
|
|
property int capacite: 0
|
|
property int valeur_timer_appui_long: 0
|
|
|
|
property real xMin
|
|
property real xMax
|
|
property real yMin
|
|
property real yMax
|
|
property bool flag_yMax_hors_limite: false
|
|
property bool flag_valeurs_discretes: false // Indique que les capteurs en cours d'utilisation ne peuvent prendre que des valeurs discretes (a priori, uniquement le cas d'Entrées Aux)
|
|
property int nb_intervalles_graphique: 10 // Nb : cela se traduit par N+1 valeurs d'absice
|
|
property int etat_entrees: 0 // 0=Ok, 1=Electrodes plongées dans le désordre
|
|
|
|
property double step_reglage_fin_vertical: 0.1
|
|
property double step_reglage_fin_horizontal: 0.1
|
|
|
|
// 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
|
|
{
|
|
mise_a_jour_valeur_actuelle();
|
|
}
|
|
}
|
|
}
|
|
|
|
//===========================================================
|
|
// Timer pour gérer les appuis longs :
|
|
//===========================================================
|
|
Timer {
|
|
id: timer_appui_long
|
|
interval: step_timer_appui_long
|
|
triggeredOnStart: false
|
|
repeat: true
|
|
onTriggered: {
|
|
if (valeur_timer_appui_long < shipheart_mouse_press_and_hold_interval_tres_long) valeur_timer_appui_long+= step_timer_appui_long;
|
|
else // Le fait de ne pas utilise le OnPressandHold permet d'être sût que la sortie se fait au même moment que la fin du comptage (sinon, on peut avoir de petits décalages)
|
|
{
|
|
timer_appui_long.running = false;
|
|
valeur_timer_appui_long = 0;
|
|
|
|
//---------------------------------------------
|
|
// Suppression de point :
|
|
if (point_a_supprimer !== -1) supprimer_point(); // On ne peut pas utiliser 'mousearea_bouton_suppression_point.pressed' car il ya 1 bouton pour chaque délégué et cela génère une erreur (à juste raison car on ne saurait pas lequel)
|
|
|
|
//---------------------------------------------
|
|
// Enregistrement :
|
|
if (mousearea_bouton_enregistrer.pressed)
|
|
{
|
|
if (flag_nouvelles_valeurs) enregistrement();
|
|
}
|
|
|
|
//---------------------------------------------
|
|
// Config usine :
|
|
if (mousearea_bouton_courbe_usine.pressed)
|
|
{
|
|
flag_init = true;
|
|
courbe_usine();
|
|
mise_a_jour_valeur_actuelle();
|
|
enregistrement();
|
|
flag_init = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Titre de la page :
|
|
Shipheart_titre_1 {titre:texte_titre; hauteur: shipheart_hauteur_bandeau_haut}
|
|
|
|
//-------------------------------------------------------
|
|
// Ecran Haut - Gauche :
|
|
//-------------------------------------------------------
|
|
Rectangle {
|
|
id: zone_affichage_haut_gauche
|
|
x: 0
|
|
y: shipheart_hauteur_bandeau_haut;
|
|
width: (shipheart_largeur_page * pourcentage_largeur_colonne_gauche) - (espace_colonnes_principales / 2)
|
|
height: (affichage_hauteur_utile * pourcentage_hauteur_ligne_haut) - (espace_lignes_principales / 2)
|
|
clip:true // Evite que le ListView suivant ne puisse dépasser du rectangle
|
|
color: "transparent"
|
|
|
|
ListModel {
|
|
id: modele_points
|
|
}
|
|
|
|
ListView {
|
|
id: liste_points
|
|
anchors.fill: parent
|
|
model: modele_points
|
|
spacing: 5
|
|
snapMode: GridView.SnapToRow // L'arret de l'affichage se fera sur une rangée entière
|
|
highlightMoveDuration: 600 // défini la vitesse à laquelle on va se déplacer sur l'item sélectionné (utilisé avec l'affectation du 'currentIndex' quand on touche un rectangle/cercle)
|
|
highlightMoveVelocity: -1 // -1 si le précedent est actif et réciproquement
|
|
footerPositioning: ListView.InlineFooter // Positionne le footer à la fin des enregistrements (pratique pour le bouton MORE...)
|
|
visible: flag_init ? false : true;
|
|
opacity: Math.max(0.1, 1.0 - (Math.abs(verticalOvershoot) *3 / height)) // Permet de rendre transparent le contenu lorsqu'on dépasse la zone de défilement
|
|
delegate: Rectangle {
|
|
height: ((liste_points.height - ((nombre_lignes_points_visibles - 1) * liste_points.spacing)) / nombre_lignes_points_visibles)
|
|
width:zone_affichage_haut_gauche.width
|
|
color: model.actif ? shipheart_couleur_fond_bouton_actif : shipheart_couleur_fond_bouton
|
|
radius: shipheart_rayon_bouton
|
|
|
|
Rectangle { // Barre grandissante pour montrer durée d'appui vs tempo nécessaire :
|
|
width: (valeur_timer_appui_long / shipheart_mouse_press_and_hold_interval_tres_long) * parent.width
|
|
height: parent.height
|
|
color: couleur_rouge_transparent
|
|
radius: shipheart_rayon_bouton
|
|
visible: mousearea_bouton_suppression_point.pressed
|
|
}
|
|
Text {
|
|
id: no_ligne // Premier élément du bouton : n° de ligne
|
|
width: parent.width * 0.2;
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
horizontalAlignment: Text.AlignHCenter
|
|
text: index + 1
|
|
font.pixelSize: 14
|
|
font.bold: true
|
|
color: couleur_gris_moyen
|
|
}
|
|
Text {
|
|
id: xs // Deuxième élément du bouton : la valeur de X
|
|
x: no_ligne.width;
|
|
width: parent.width * 0.4;
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
horizontalAlignment: Text.AlignHCenter
|
|
text: Number(model.x).toLocaleString(Qt.locale(),'f',precision_x) + " " + unite_x
|
|
font.pixelSize: 14
|
|
color: couleur_blanc_casse
|
|
}
|
|
Text {
|
|
id: ys_pc // Troisième élément du bouton : valeur de Y en %
|
|
x: no_ligne.width + xs.width;
|
|
width: parent.width - no_ligne.width - xs.width;
|
|
anchors {
|
|
verticalCenter: parent.verticalCenter
|
|
verticalCenterOffset: (table === "TB_Reservoirs") ? -10 : 0
|
|
}
|
|
horizontalAlignment: Text.AlignHCenter
|
|
text: Number(model.y).toLocaleString(Qt.locale(),'f',1) + " %"
|
|
font.pixelSize: 14
|
|
rightPadding: 20
|
|
color: couleur_blanc_casse
|
|
}
|
|
Text {
|
|
id: ys_l // Troisième élément du bouton : valeur de Y en litres (ou unité de volume en cours)
|
|
visible: (table === "TB_Reservoirs")
|
|
x: no_ligne.width + xs.width;
|
|
width: parent.width - no_ligne.width - xs.width;
|
|
anchors {
|
|
verticalCenter: parent.verticalCenter
|
|
verticalCenterOffset: 10
|
|
}
|
|
horizontalAlignment: Text.AlignHCenter
|
|
text: Fonctions.conversion_volume((Number(model.y) * capacite / 100), shipheart_unite_volume, shipheart_unite_volume_symbole, 1);
|
|
font.pixelSize: 14
|
|
rightPadding: 20
|
|
color: couleur_blanc_casse
|
|
}
|
|
Image {
|
|
id: image_changed // Affichage du coin inférieur-droit en orange si le point a été modifié
|
|
source: "/images/coin_bas_droite_orange.png"
|
|
sourceSize.height: 20
|
|
anchors {
|
|
bottom: parent.bottom
|
|
right: parent.right
|
|
}
|
|
visible: model.changed ? true : false
|
|
}
|
|
MouseArea {
|
|
id: mousearea_bouton_suppression_point
|
|
anchors.fill: parent
|
|
onPressed: { // Déclenché lors de l'appui inital
|
|
if ((index === point_en_cours) && // On n'autorise la suppression d'un point que s'il est sélectionnés depuis le graphique (évite que les tentative de suppression se traduisent par un toggle de la sélection)
|
|
(!flag_valeurs_discretes) && // On ne peut pas supprimer de points sur des électrodes plongées
|
|
(nb_points > 2)) // On n'accepte pas de supprimer des points s'il n'y en a plus que 2 (ou moins)...
|
|
{
|
|
timer_appui_long.running = true;
|
|
valeur_timer_appui_long = 0;
|
|
point_a_supprimer = point_en_cours;
|
|
}
|
|
shipheart_clic();
|
|
}
|
|
onClicked: { // Déclenché si l'appui n'a pas été suffisement long pour avoir un Press&Hold :
|
|
timer_appui_long.running = false;
|
|
valeur_timer_appui_long = 0;
|
|
point_a_supprimer = -1;
|
|
|
|
// Sélection du point :
|
|
if (point_en_cours !== -1) modele_points.setProperty(point_en_cours, "actif", false); // Déselection du point en cours précédent
|
|
modele_points.setProperty(index, "actif", true); // Sélection du nouveau point en cours
|
|
point_en_cours = index; // Mémorisation du point en cours
|
|
}
|
|
onDoubleClicked: {
|
|
flag_affichage_reglage_fin = !flag_affichage_reglage_fin;
|
|
}
|
|
|
|
onExited: { // Déclenché si le doigt sort de la zone du bouton (sinon, on continue à compter alors qu'on est sorti de la zone et même relaché le doigt !!)
|
|
timer_appui_long.running = false;
|
|
valeur_timer_appui_long = 0;
|
|
point_a_supprimer = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
Label {
|
|
anchors.fill: parent
|
|
horizontalAlignment: Qt.AlignHCenter
|
|
verticalAlignment: Qt.AlignVCenter
|
|
text: qsTr("Aucun point de conversion n'est configuré pour le moment...")
|
|
color: couleur_rouge_transparent
|
|
font.pixelSize: 15
|
|
font.bold: true
|
|
wrapMode: Label.Wrap
|
|
visible: courbe_flag_vide
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------
|
|
// Ecran Bas - Gauche :
|
|
//-------------------------------------------------------
|
|
Rectangle {
|
|
id: zone_affichage_bas_gauche
|
|
x: 0
|
|
y: shipheart_hauteur_bandeau_haut + zone_affichage_haut_gauche.height + espace_lignes_principales;
|
|
width: (shipheart_largeur_page * pourcentage_largeur_colonne_gauche) - (espace_colonnes_principales / 2)
|
|
height: (affichage_hauteur_utile * (1 - pourcentage_hauteur_ligne_haut)) - (espace_lignes_principales * 3 / 2)
|
|
color: "transparent"
|
|
|
|
Column {
|
|
anchors.fill: parent
|
|
spacing: 5
|
|
|
|
//-----------------------
|
|
// Ajout d'un point :
|
|
Rectangle {
|
|
width: parent.width
|
|
height: (parent.height/3) - (parent.spacing/3)
|
|
color: shipheart_couleur_fond_bouton
|
|
radius: shipheart_rayon_bouton
|
|
visible: !flag_valeurs_discretes // On ne peut pas ajouter de points sur des électrodes plongées
|
|
|
|
Text {
|
|
anchors {
|
|
verticalCenter: parent.verticalCenter
|
|
horizontalCenter: parent.horizontalCenter
|
|
}
|
|
text: "+"
|
|
font.pixelSize: 40
|
|
font.bold: true
|
|
color: !flag_nombre_max_points_atteint ? couleur_blanc_casse : couleur_gris_fonce
|
|
}
|
|
MouseArea{
|
|
id: mousearea_ajout_point
|
|
anchors.fill : parent
|
|
onClicked: {
|
|
if (!flag_nombre_max_points_atteint) ajouter_point();
|
|
else
|
|
{
|
|
boite_nombre_max_points_atteint.imageTitreSource = "/images/ampoule.png";
|
|
boite_nombre_max_points_atteint.message = qsTr("Le nombre maximal de points d'étalonnage a été atteint (%1 points)<br />Vous ne pouvez pas en ajouter sans en avoir prélablement supprimé.").arg(courbe_etalonnage_max_points);
|
|
boite_nombre_max_points_atteint.open();
|
|
}
|
|
}
|
|
onPressed: shipheart_clic();
|
|
enabled: point_en_cours !== -1 // Si aucun point n'est sélectionné, on n'autorise pas l'ajout (car on ne sait pas où insérer le nouveau point). NB : ce cas ne doit pas arriver car on fait en sorte d'avoir toujours un point actif...
|
|
}
|
|
}
|
|
|
|
//-----------------------
|
|
// Paramètres usine :
|
|
Rectangle {
|
|
width: parent.width
|
|
height: (parent.height/3) - (parent.spacing/3)
|
|
color: shipheart_couleur_fond_bouton
|
|
radius: shipheart_rayon_bouton
|
|
|
|
Rectangle { // Barre grandissante pour montrer durée d'appui vs tempo nécessaire :
|
|
width: (valeur_timer_appui_long / shipheart_mouse_press_and_hold_interval_tres_long) * parent.width
|
|
height: parent.height
|
|
color: couleur_rouge_transparent
|
|
radius: shipheart_rayon_bouton
|
|
visible: mousearea_bouton_courbe_usine.pressed
|
|
}
|
|
Text {
|
|
anchors {
|
|
verticalCenter: parent.verticalCenter
|
|
horizontalCenter: parent.horizontalCenter
|
|
}
|
|
font.pixelSize: 20
|
|
text: qsTr("Courbe usine")
|
|
color: couleur_blanc_casse
|
|
}
|
|
MouseArea {
|
|
id: mousearea_bouton_courbe_usine
|
|
anchors.fill: parent
|
|
onPressed: { // Déclenché lors de l'appui inital
|
|
timer_appui_long.running = true;
|
|
valeur_timer_appui_long = 0;
|
|
shipheart_clic();
|
|
}
|
|
onClicked: { // Déclenché si l'appui n'a pas été suffisement long pour avoir un Press&Hold :
|
|
timer_appui_long.running = false;
|
|
valeur_timer_appui_long = 0;
|
|
}
|
|
onExited: { // Déclenché si le doigt sort de la zone du bouton (sinon, on continue à compter alors qu'on est sorti de la zone et même relaché le doigt !!)
|
|
timer_appui_long.running = false;
|
|
valeur_timer_appui_long = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------
|
|
// Bouton Enregistrer :
|
|
Rectangle {
|
|
width: parent.width
|
|
height: (parent.height/3) - (parent.spacing/3)
|
|
color: shipheart_couleur_fond_bouton
|
|
radius: shipheart_rayon_bouton
|
|
|
|
Rectangle { // Barre grandissante pour montrer durée d'appui vs tempo nécessaire :
|
|
width: (valeur_timer_appui_long / shipheart_mouse_press_and_hold_interval_tres_long) * parent.width
|
|
height: parent.height
|
|
color: couleur_vert_transparent
|
|
radius: shipheart_rayon_bouton
|
|
visible: mousearea_bouton_enregistrer.pressed
|
|
}
|
|
Text {
|
|
anchors {
|
|
verticalCenter: parent.verticalCenter
|
|
horizontalCenter: parent.horizontalCenter
|
|
}
|
|
font.pixelSize: 20
|
|
text: qsTr("Enregistrer")
|
|
color: flag_nouvelles_valeurs ? couleur_blanc_casse : couleur_gris_fonce
|
|
}
|
|
MouseArea {
|
|
id: mousearea_bouton_enregistrer
|
|
anchors.fill: parent
|
|
onPressed: { // Déclenché lors de l'appui inital
|
|
timer_appui_long.running = true;
|
|
valeur_timer_appui_long = 0;
|
|
shipheart_clic();
|
|
}
|
|
onClicked: { // Déclenché si l'appui n'a pas été suffisement long pour avoir un Press&Hold :
|
|
timer_appui_long.running = false;
|
|
valeur_timer_appui_long = 0;
|
|
}
|
|
onExited: { // Déclenché si le doigt sort de la zone du bouton (sinon, on continue à compter alors qu'on est sorti de la zone et même relaché le doigt !!)
|
|
timer_appui_long.running = false;
|
|
valeur_timer_appui_long = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------
|
|
// Ecran Droite :
|
|
//-------------------------------------------------------
|
|
Rectangle {
|
|
id: zone_affichage_droite
|
|
x: zone_affichage_haut_gauche.width + espace_colonnes_principales
|
|
y: shipheart_hauteur_bandeau_haut;
|
|
width: (shipheart_largeur_page * (1 - pourcentage_largeur_colonne_gauche)) - (espace_colonnes_principales / 2)
|
|
height: affichage_hauteur_utile;
|
|
color: "transparent"
|
|
|
|
//========================
|
|
// Graphique :
|
|
ChartView {
|
|
id: graphique
|
|
antialiasing: true
|
|
anchors.fill: parent
|
|
margins { // Attention : ces marges représentent l'espace entre le ChartView et sa plotArea (c'est donc plutôt un padding...)
|
|
right: 50
|
|
bottom: 10
|
|
left: 0
|
|
top: 20
|
|
}
|
|
width: parent.width
|
|
backgroundColor: "transparent"
|
|
legend {
|
|
visible: false
|
|
}
|
|
|
|
ValueAxis {
|
|
id: axisX
|
|
min: xMin // Défini pendant l'init
|
|
max: xMax // Idem
|
|
tickCount: nb_intervalles_graphique + 1 // Il faut N+1 pour avoir N intervalles
|
|
labelFormat: "%." + precision_x_graphique + "f"
|
|
labelsFont:Qt.font({pointSize: 10})
|
|
color: couleur_gris_moyen
|
|
shadesVisible: false
|
|
gridVisible: true
|
|
gridLineColor: couleur_gris_tres_fonce
|
|
labelsColor: couleur_gris_tres_tres_pale
|
|
titleText: "<font color='" + couleur_gris_tres_tres_pale + "' pointSize='50'>" + unite_x + "</font>"
|
|
}
|
|
ValueAxis {
|
|
id: axisY
|
|
min: yMin // Défini pendant l'init
|
|
max: yMax // idem
|
|
tickCount: 11
|
|
labelFormat: "%.0f"
|
|
labelsFont:Qt.font({pointSize: 10})
|
|
color: couleur_gris_moyen
|
|
shadesColor: courbes_couleur_shades_fond
|
|
shadesVisible: true
|
|
gridVisible: true
|
|
gridLineColor: couleur_gris_tres_fonce
|
|
labelsColor: couleur_gris_tres_tres_pale
|
|
titleText: "<font color='" + couleur_gris_tres_tres_pale + "' pointSize=50>%</font>"
|
|
}
|
|
|
|
LineSeries {
|
|
id: courbe // Courbe fin bleue représentant la courbe finale (elle suit donc les modifications au fur et à mesure)
|
|
axisX: axisX
|
|
axisY: axisY
|
|
capStyle: Qt.RoundCap
|
|
style: Qt.SolidLine
|
|
width: courbes_epaisseur_bordure // On reprend les valeurs standard des courbes de ShipHeart
|
|
color: shipheart_couleur_bordure_courbes // Idem pour la couleur
|
|
}
|
|
ScatterSeries {
|
|
id: points_originaux // Uniquement les points. De plus, ils ne bougent pas au fur et à mesure et représentent donc la version initiale.
|
|
axisX: axisX
|
|
axisY: axisY
|
|
}
|
|
ScatterSeries {
|
|
id: points_nouveaux // Uniquement les points. Mais ceux-ci changent avec les modifications. Ils suivent donc la 'courbe' et sont utilisés juste pour la renforcer.
|
|
axisX: axisX
|
|
axisY: axisY
|
|
borderColor: couleur_rouge
|
|
borderWidth: 3
|
|
markerSize: 4
|
|
markerShape: ScatterSeries.MarkerShapeRectangle
|
|
}
|
|
LineSeries {
|
|
id: croix_h // Trace horizontale de la croix de valeur actuelle
|
|
axisX: axisX
|
|
axisY: axisY
|
|
style: Qt.DashLine
|
|
width: 1
|
|
color: couleur_jaune_post_it
|
|
visible: !etat_entrees && !courbe_flag_vide
|
|
}
|
|
LineSeries {
|
|
id: croix_v // Trace verticale de la croix de valeur actuelle
|
|
axisX: axisX
|
|
axisY: axisY
|
|
style: Qt.DashDotLine
|
|
width: 1
|
|
color: couleur_jaune_post_it
|
|
visible: !etat_entrees && !courbe_flag_vide
|
|
}
|
|
|
|
Text { // Contient la valeur actuelle sur l'axe X
|
|
id: texte_valeur_x
|
|
anchors.top: parent.top
|
|
font.pixelSize: 18
|
|
font.weight: Font.Bold
|
|
color: !flag_yMax_hors_limite ? couleur_jaune_post_it : couleur_rouge
|
|
visible: !etat_entrees && !courbe_flag_vide
|
|
}
|
|
Text { // Contient la valeur actuelle sur l'axe Y
|
|
id: texte_valeur_y
|
|
anchors.right: parent.right
|
|
font.pixelSize: 18
|
|
font.weight: Font.Bold
|
|
color: couleur_jaune_post_it
|
|
visible: !etat_entrees && !courbe_flag_vide
|
|
}
|
|
|
|
Rectangle {
|
|
id: rectangle_remplissage
|
|
x: graphique.plotArea.x
|
|
|
|
width: graphique.plotArea.width
|
|
color: couleur_bleu_koriolan_transparent
|
|
visible: !etat_entrees && !courbe_flag_vide
|
|
}
|
|
|
|
// Ajout des points dragables :
|
|
// On utilise un répéteur qui se base sur le même modèle que l'écran de gauche.
|
|
// Chaque délégué est un rectangle retaillé en cercle (!) sur lequel on superpose un mouseArea.
|
|
// C'est ce MouseArea qui est dragable avec comme target le rectangle/cercle sur lequel il est.
|
|
// Quand on bouge la Mouserarea, elle bouge le rectangle/cercle. Et quand on détecte un changement
|
|
// de coordonnées du rectangle/cercle, on lance une fonction de mise à jour du modèle de 'courbe' et 'points_nouveaux'
|
|
// qui sont donc mis à jour en temps réel.
|
|
// En plus, dès qu'on touche l'une des MouseArea, on met en actif le bouton correspondant de l'écran de gauche via OnPressed de MouseArea.
|
|
Repeater {
|
|
model: modele_points
|
|
|
|
Rectangle {
|
|
id: point_deplacable
|
|
|
|
property bool flag_init_point_deplacable: flag_init // Recopie au niveau du delegate du flag général pour pouvoir utiliser onFlag_xxxChanged et déclencher l'appel à une fonction JS une seule fois en fin d'init
|
|
property bool flag_align_point_deplacable: flag_alignement_demande // Idem lorsqu'une mise à jour directe des coordonnées a été fait et qu'il fait aligner le rectangle/cerlce sur ces nouvelles coordonnées.
|
|
|
|
radius: diametre_points_deplacables // Défini dans l'entete
|
|
width: radius / 2 // Rectangle => devient cercle
|
|
height: width
|
|
border {
|
|
width: 5
|
|
color: (!flag_valeurs_discretes || (etat_entrees === 0)) ?
|
|
"transparent" :
|
|
(modele_points.get(index).etat_entree_discrete ? couleur_bleu_koriolan : couleur_bleu_koriolan_tres_tres_transparent)
|
|
}
|
|
color: modele_points.get(index).actif ? couleur_bleu_koriolan_transparent : couleur_bleu_koriolan_tres_tres_transparent
|
|
|
|
Text {
|
|
id: libelle_point // N° du point dans le modèle (qui est commun à l'écran de gauche et sur les points déplaçables
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
text: (index + 1)
|
|
font.pixelSize: 20
|
|
font.weight: Font.Bold
|
|
color: couleur_gris_tres_pale
|
|
}
|
|
onXChanged: { // Si le rectangle/cercle a bougé (avec le doigt), on met à jour les valeurs de 'courbe', 'points_nouveaux' et 'modèle_points' (ce dernier correspondant à la liste gauche)
|
|
if(mouse_area_point_deplacable.drag.active) {
|
|
point_deplacable.adjustValue(this, index)
|
|
}
|
|
}
|
|
onYChanged: { // Iem pour l'axe Y
|
|
if(mouse_area_point_deplacable.drag.active) {
|
|
point_deplacable.adjustValue(this, index)
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
MouseArea {
|
|
id: mouse_area_point_deplacable // Zone dragable
|
|
enabled: !flag_affichage_reglage_fin || (index === point_en_cours) // On dévalide les zones dragables des autres points lorsqu'on est en mode 'réglage fin' car sinon ils interceptent les clicks sur les flèches de direction (pas trouvé comment gérer cela avec le paramètre Z...)
|
|
anchors.fill: parent
|
|
drag.target: point_deplacable
|
|
drag.axis: flag_valeurs_discretes ? Drag.YAxis : Drag.XAndYAxis
|
|
|
|
// Limitation des mouvements à la zone du graphique. Les valeurs de Min et Max du graphique sont donc importantes car on ne pourra pas aller au-delà...
|
|
// Pour les mouvements X, on limite en plus aux positions X entre le point précédent et le point suivant (cela permet de n'avoir jamais une courbe qui "revient" sur elle même).
|
|
/* drag.minimumX: Math.max( graphique.plotArea.x ,
|
|
Math.min( ((index > 0) ? (graphique.mapToPosition(courbe.at(index-1), courbe).x) : (graphique.plotArea.x + graphique.plotArea.width)),
|
|
((index < modele_points.count) ? (graphique.mapToPosition(courbe.at(index+1), courbe).x) : (graphique.plotArea.x + graphique.plotArea.width))) )
|
|
- (point_deplacable.width / 2)
|
|
*/
|
|
drag.minimumX: graphique.plotArea.x - (point_deplacable.width / 2)
|
|
/* drag.maximumX: Math.min( (graphique.plotArea.x + graphique.plotArea.width) ,
|
|
Math.max( ((index > 0) ? (graphique.mapToPosition(courbe.at(index-1), courbe).x) : (graphique.plotArea.x)),
|
|
((index < modele_points.count) ? (graphique.mapToPosition(courbe.at(index+1), courbe).x) : (graphique.plotArea.x))) )
|
|
- (point_deplacable.width / 2)
|
|
*/
|
|
drag.maximumX: graphique.plotArea.x + graphique.plotArea.width - (point_deplacable.width / 2)
|
|
|
|
drag.minimumY: graphique.plotArea.y - (point_deplacable.height / 2) //
|
|
drag.maximumY: graphique.plotArea.y + graphique.plotArea.height - (point_deplacable.height / 2) // importantes car on ne pourra pas aller au-delà...
|
|
|
|
onClicked: {
|
|
if (point_en_cours !== -1) modele_points.setProperty(point_en_cours, "actif", false);
|
|
modele_points.setProperty(index, "actif", true);
|
|
point_en_cours = index;
|
|
liste_points.currentIndex = index // Permet de ramener le bouton correspondant dans la zone visible du ListView de gauche
|
|
}
|
|
}
|
|
onFlag_init_point_deplacableChanged: {
|
|
adjustPosition(this, index) // Activé en fin d'init : Positionne le rectangle/cercle sur la valeur initiale du modèle.
|
|
}
|
|
|
|
onFlag_align_point_deplacableChanged: {
|
|
if (flag_alignement_demande) // Permet de limiter le traitement au front montant de 'flag_alignement_demande'
|
|
{ // PS : ce traitement est fait pour tous les points de repeater, même si la plupart du temps, un seul point a été modifié...
|
|
adjustPosition(this, index) // Ajuste la position du rectangle/cercle selon les coordonnées présentes dans 'modele_points'
|
|
}
|
|
}
|
|
function adjustPosition(item, index) {
|
|
let point = Qt.point(modele_points.get(index).x, modele_points.get(index).y)
|
|
let position = graphique.mapToPosition(point, courbe)
|
|
item.x = position.x - item.width / 2
|
|
item.y = position.y - item.height / 2
|
|
|
|
}
|
|
function adjustValue(item, index) {
|
|
let position = Qt.point(item.x + item.width / 2, item.y + item.height / 2)
|
|
let point = graphique.mapToValue(position, courbe)
|
|
modele_points.setProperty(index, "x", point.x)
|
|
modele_points.setProperty(index, "y", point.y)
|
|
modele_points.setProperty(index, "changed", true)
|
|
courbe.replace(courbe.at(index).x, courbe.at(index).y, point.x, point.y)
|
|
points_nouveaux.replace(points_nouveaux.at(index).x, points_nouveaux.at(index).y, point.x, point.y)
|
|
flag_nouvelles_valeurs = true;
|
|
}
|
|
|
|
//------------------------------------------------------------
|
|
// Flèches de réglage fin :
|
|
Rectangle { // Flèche vers le haut :
|
|
id: bouton_reglage_fin_haut
|
|
width: (diametre_points_deplacables / 2)
|
|
height: (diametre_points_deplacables / 4)
|
|
y: -(diametre_points_deplacables / 4)
|
|
color: "transparent"
|
|
visible: flag_affichage_reglage_fin && (index === point_en_cours)
|
|
Image {
|
|
anchors {
|
|
horizontalCenter: parent.horizontalCenter
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
source: "/images/fleche_suite_haut.png"
|
|
width: parent.width * coeff_fleche_reglage_fin
|
|
height: parent.height * coeff_fleche_reglage_fin
|
|
opacity: 0.5
|
|
}
|
|
MouseArea {
|
|
id: mouse_area_reglage_fin_haut
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
var new_val = modele_points.get(point_en_cours).y + step_reglage_fin_vertical;
|
|
if (new_val > yMax) new_val = yMax;
|
|
modele_points.setProperty(point_en_cours, "y", new_val);
|
|
modele_points.setProperty(point_en_cours, "changed", true);
|
|
courbe.replace(courbe.at(point_en_cours).x, courbe.at(point_en_cours).y, modele_points.get(point_en_cours).x, modele_points.get(point_en_cours).y);
|
|
points_nouveaux.replace(points_nouveaux.at(point_en_cours).x, points_nouveaux.at(point_en_cours).y, modele_points.get(point_en_cours).x, modele_points.get(point_en_cours).y);
|
|
point_deplacable.y = graphique.plotArea.y + (graphique.plotArea.height * (100 - modele_points.get(point_en_cours).y) / 100) - (diametre_points_deplacables / 4);
|
|
flag_nouvelles_valeurs = true;
|
|
}
|
|
}
|
|
}
|
|
Rectangle { // Flèche vers le bas :
|
|
id: bouton_reglage_fin_bas
|
|
width: (diametre_points_deplacables / 2)
|
|
height: (diametre_points_deplacables / 4)
|
|
y: (diametre_points_deplacables / 2)
|
|
color: "transparent"
|
|
visible: flag_affichage_reglage_fin && (index === point_en_cours)
|
|
Image {
|
|
anchors {
|
|
horizontalCenter: parent.horizontalCenter
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
source: "/images/fleche_suite_bas.png"
|
|
width: parent.width * coeff_fleche_reglage_fin
|
|
height: parent.height * coeff_fleche_reglage_fin
|
|
opacity: 0.5
|
|
}
|
|
MouseArea {
|
|
id: mouse_area_reglage_fin_bas
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
var new_val = modele_points.get(point_en_cours).y - step_reglage_fin_vertical;
|
|
if (new_val < yMin) new_val = yMin;
|
|
modele_points.setProperty(point_en_cours, "y", new_val)
|
|
modele_points.setProperty(point_en_cours, "changed", true)
|
|
courbe.replace(courbe.at(point_en_cours).x, courbe.at(point_en_cours).y, modele_points.get(point_en_cours).x, modele_points.get(point_en_cours).y)
|
|
points_nouveaux.replace(points_nouveaux.at(point_en_cours).x, points_nouveaux.at(point_en_cours).y, modele_points.get(point_en_cours).x, modele_points.get(point_en_cours).y)
|
|
point_deplacable.y = graphique.plotArea.y + (graphique.plotArea.height * (100 - modele_points.get(point_en_cours).y) / 100) - (diametre_points_deplacables / 4)
|
|
flag_nouvelles_valeurs = true;
|
|
}
|
|
}
|
|
}
|
|
Rectangle { // Flèche vers la gauche :
|
|
id: bouton_reglage_fin_gauche
|
|
width: (diametre_points_deplacables / 4)
|
|
height: (diametre_points_deplacables / 2)
|
|
x: -(diametre_points_deplacables / 4)
|
|
color: "transparent"
|
|
visible: flag_affichage_reglage_fin && (index === point_en_cours) && !flag_valeurs_discretes
|
|
Image {
|
|
anchors {
|
|
horizontalCenter: parent.horizontalCenter
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
source: "/images/fleche_suite_gauche.png"
|
|
width: parent.width * coeff_fleche_reglage_fin
|
|
height: parent.height * coeff_fleche_reglage_fin
|
|
opacity: 0.5
|
|
}
|
|
MouseArea {
|
|
id: mouse_area_reglage_fin_gauche
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
var new_val = modele_points.get(point_en_cours).x - step_reglage_fin_horizontal;
|
|
if (new_val < xMin) new_val = xMin;
|
|
modele_points.setProperty(point_en_cours, "x", new_val)
|
|
modele_points.setProperty(point_en_cours, "changed", true)
|
|
courbe.replace(courbe.at(point_en_cours).x, courbe.at(point_en_cours).y, modele_points.get(point_en_cours).x, modele_points.get(point_en_cours).y)
|
|
points_nouveaux.replace(points_nouveaux.at(point_en_cours).x, points_nouveaux.at(point_en_cours).y, modele_points.get(point_en_cours).x, modele_points.get(point_en_cours).y)
|
|
point_deplacable.x = graphique.plotArea.x + (graphique.plotArea.width * (modele_points.get(point_en_cours).x - xMin) / (xMax - xMin)) - (diametre_points_deplacables / 4)
|
|
flag_nouvelles_valeurs = true;
|
|
}
|
|
}
|
|
}
|
|
Rectangle { // Flèche vers la droite :
|
|
id: bouton_reglage_fin_droite
|
|
width: (diametre_points_deplacables / 4)
|
|
height: (diametre_points_deplacables / 2)
|
|
x: (diametre_points_deplacables / 2)
|
|
color: "transparent"
|
|
visible: flag_affichage_reglage_fin && (index === point_en_cours) && !flag_valeurs_discretes
|
|
Image {
|
|
anchors {
|
|
horizontalCenter: parent.horizontalCenter
|
|
verticalCenter: parent.verticalCenter
|
|
}
|
|
source: "/images/fleche_suite_droite.png"
|
|
width: parent.width * coeff_fleche_reglage_fin
|
|
height: parent.height * coeff_fleche_reglage_fin
|
|
opacity: 0.5
|
|
}
|
|
MouseArea {
|
|
id: mouse_area_reglage_fin_droite
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
var new_val = modele_points.get(point_en_cours).x + step_reglage_fin_horizontal;
|
|
if (new_val > xMax) new_val = xMax;
|
|
modele_points.setProperty(point_en_cours, "x", new_val)
|
|
modele_points.setProperty(point_en_cours, "changed", true)
|
|
courbe.replace(courbe.at(point_en_cours).x, courbe.at(point_en_cours).y, modele_points.get(point_en_cours).x, modele_points.get(point_en_cours).y)
|
|
points_nouveaux.replace(points_nouveaux.at(point_en_cours).x, points_nouveaux.at(point_en_cours).y, modele_points.get(point_en_cours).x, modele_points.get(point_en_cours).y)
|
|
point_deplacable.x = graphique.plotArea.x + (graphique.plotArea.width * (modele_points.get(point_en_cours).x - xMin) / (xMax - xMin)) - (diametre_points_deplacables / 4)
|
|
flag_nouvelles_valeurs = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Text {
|
|
id: texte_etat_entrees_invalide // Utilisé si aucune valeur n'est dispo, ce qui ne doit pas arriver trop souvent....
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
width: graphique.plotArea.width
|
|
text: qsTr("La valeur lue sur les electrodes plongées de ce réservoir est incohérente.<br />Vérifier l'ordre de cablage des électrodes sur les entrées auxiliaires à l'arrière de ShipBase. ")
|
|
font.pixelSize: 20
|
|
wrapMode: Text.Wrap
|
|
horizontalAlignment: Text.AlignHCenter
|
|
font.weight: Font.Bold
|
|
color: couleur_rouge
|
|
visible: etat_entrees
|
|
}
|
|
Text {
|
|
id: texte_courbe_vide // Utilisé si aucune valeur n'est dispo, ce qui ne doit pas arriver trop souvent....
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
text: qsTr("Aucune donnée n'est disponible...")
|
|
font.pixelSize: 20
|
|
font.weight: Font.Bold
|
|
color: couleur_rouge
|
|
visible: courbe_flag_vide
|
|
}
|
|
|
|
} // Fin du graphique
|
|
}
|
|
|
|
|
|
//-------------------------------------------------------
|
|
// 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();
|
|
|
|
if (flag_nouvelles_valeurs) // Si on demande le retour alors que des points ont été modifiés et pas enregistrés :
|
|
{
|
|
boite_validation_sortie.imageTitreSource = "/images/attention.png";
|
|
boite_validation_sortie.message = qsTr("Vous avez effectué des modifications qui n'ont pes été enregistrées<br />Cliquer sur Annuler pour revenir en arrière et les enregistrer.<br />Cliquer sur OK pour sortir sans enregistrer.")
|
|
boite_validation_sortie.open();
|
|
}
|
|
else stackView.pop() // Si retour alors qu'il n'y a aucun point non enregistrés, on sort sans rien demander.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------
|
|
// Boite de validation :
|
|
// NB : il est important que ce composant soit directement
|
|
// rattaché à la page pour qu'elle s'affiche en plein écran.
|
|
//-------------------------------------------------------
|
|
Shipheart_boite_dialogue {
|
|
id: boite_validation_sortie
|
|
niveau: 1
|
|
boutonAnnuler: true
|
|
onClicked: {
|
|
switch(bouton)
|
|
{
|
|
case shipheart_boite_dialogue_bouton_1: // Si Ok, on sort sans chercher plus loin
|
|
stackView.pop()
|
|
break;
|
|
case shipheart_boite_dialogue_bouton_2: // Si Annuler, on revient sur la page pour que l'on puisse enregistrer.
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------------
|
|
// Boite d'e validation'info sur nombre max de points atteint :
|
|
// NB : il est important que ce composant soit directement
|
|
// rattaché à la page pour qu'elle s'affiche en plein écran.
|
|
//-------------------------------------------------------
|
|
Shipheart_boite_dialogue {
|
|
id: boite_nombre_max_points_atteint
|
|
niveau: 0
|
|
onClicked: {
|
|
switch(bouton)
|
|
{
|
|
case shipheart_boite_dialogue_bouton_1: // Si Ok
|
|
break;
|
|
case shipheart_boite_dialogue_bouton_2: // Si Annuler
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// 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;
|
|
var champs;
|
|
var nom, liste;
|
|
var chaines_entrees;
|
|
|
|
flag_nouvelles_valeurs = false;
|
|
|
|
// Paramètres spécifiques AVANT init :
|
|
switch(table)
|
|
{
|
|
case "TB_Batteries":
|
|
champ_liste = "Liste_tension_soc";
|
|
champs = "Nom , Tension_nominale, " + champ_liste;
|
|
break;
|
|
case "TB_Reservoirs":
|
|
champ_liste = "Liste_valeurs_niveaux";
|
|
champs = "Nom , Type_entrees, Entrees, Mode_entrees, Type_sonde_entrees, Capacite, " + champ_liste;
|
|
break;
|
|
default:
|
|
console.log("/!\\ Le type de table demandé n'est pas connu. La page est refermée...");
|
|
stackView.pop(); // Il est inutile de rester si le type d'objet n'est pas connu...
|
|
break;
|
|
}
|
|
|
|
|
|
i = 0;
|
|
// Récupération des infos correspondant à l'élément concerné :
|
|
req = 'SELECT ' + champs + ' FROM ' + table + ' WHERE Cle_primaire=' + id;
|
|
//console.log("Requête = [ " + req + " ]");
|
|
res = bdd.requete_unique(req);
|
|
// Récupération des données selon le type d'objet :
|
|
switch(table)
|
|
{
|
|
case "TB_Batteries":
|
|
nom = res[i++];
|
|
tension_nominale = Number(res[i++]);
|
|
liste = res[i++];
|
|
flag_valeurs_discretes = false;
|
|
break;
|
|
|
|
case "TB_Reservoirs":
|
|
nom = res[i++];
|
|
type_entrees_reservoir = Number(res[i++]);
|
|
chaines_entrees = res[i++].split(';');
|
|
mode_reservoir = Number(res[i++]);
|
|
sous_mode_reservoir = Number(res[i++]);
|
|
capacite = Number(res[i++]);
|
|
liste = res[i++];
|
|
if (type_entrees_reservoir === shipheart_reservoir_type_entrees_aux) flag_valeurs_discretes = true;
|
|
else flag_valeurs_discretes = false;
|
|
break;
|
|
|
|
// Pas de défaut car on a purgé ce cas au switch précédent...
|
|
}
|
|
|
|
// Remplissage des courbes et modèles :
|
|
if (liste !== "") creation_courbes(liste.split(';'));
|
|
else creation_courbes(""); // L'appel avec un array vide (cas du résultat de split sur une chaine vide) donne des résultats pas très clairs...
|
|
|
|
// Sélection du premier point à l'ouverture :
|
|
if (!courbe_flag_vide)
|
|
{
|
|
modele_points.setProperty(0, "actif", true);
|
|
point_en_cours = 0;
|
|
}
|
|
|
|
// Paramètres spécifiques APRES init :
|
|
switch(table)
|
|
{
|
|
case "TB_Batteries":
|
|
texte_aucun_element = qsTr("Aucun point n'est actuellement configuré pour cette batterie...")
|
|
texte_titre = qsTr("Courbe de conversion de [%1]").arg(nom)
|
|
|
|
// Valeurs de min et de max du graphique :
|
|
// Attention : ces valeurs sont utilisées pour limiter la zone dragable !
|
|
// Donc les valeurs au-delà de ces min/max ne sont pas atteignables...!!
|
|
switch(tension_nominale)
|
|
{
|
|
default:
|
|
case 12:
|
|
xMin = 9.5;
|
|
xMax = 14.5;
|
|
break;
|
|
case 24:
|
|
xMin = 19;
|
|
xMax = 29;
|
|
break;
|
|
case 48:
|
|
xMin = 40;
|
|
xMax = 56;
|
|
break;
|
|
}
|
|
yMin = 0;
|
|
yMax = 100;
|
|
unite_x = "Volts";
|
|
precision_x = 2;
|
|
precision_x_graphique = 2;
|
|
step_reglage_fin_vertical = 0.1; // En %, donc 0.1%
|
|
step_reglage_fin_horizontal = 0.01; // En V, donc 10mV
|
|
|
|
break;
|
|
|
|
case "TB_Reservoirs":
|
|
texte_aucun_element = qsTr("Aucun réservoir n'est actuellement configuré...")
|
|
texte_titre = qsTr("Courbe de conversion de [%1]").arg(nom)
|
|
|
|
// Valeurs de min et de max du graphique :
|
|
// Attention : ces valeurs sont utilisées pour limiter la zone dragable !
|
|
// Donc les valeurs au-delà de ces min/max ne sont pas atteignables...!!
|
|
switch(type_entrees_reservoir)
|
|
{
|
|
case shipheart_reservoir_type_entrees_aux :
|
|
xMin = 0;
|
|
xMax = 0;
|
|
for (i = 0; i < shipheart_reservoir_max_nombre_entrees_aux; i ++)
|
|
{
|
|
if (chaines_entrees[i]) xMax ++;
|
|
else break;
|
|
}
|
|
mode_reservoir = xMax; // Est utilisé pouer appeler 'Fonctions_reservoirs.chaine_valeur_niveau_reservoir_usine()'
|
|
sous_mode_reservoir = 0;
|
|
nb_intervalles_graphique = xMax; // Permet d'ajuster le nombre d'intervalles au nombre d'entrées utilisé dans ce cas
|
|
|
|
unite_x = "";
|
|
precision_x = 0;
|
|
precision_x_graphique = 0;
|
|
step_reglage_fin_vertical = (0.1) * 100 / capacite;
|
|
step_reglage_fin_horizontal = 0.1; // Non utilisé avec ce type de sondes...
|
|
break;
|
|
|
|
case shipheart_reservoir_type_entrees_level_sensor:
|
|
switch (mode_reservoir) // Mode :
|
|
{
|
|
case shipheart_reservoir_mode_entrees_level_sensor_0_10v: // 0-10V
|
|
sous_mode_reservoir = 0;
|
|
xMin = 0.0;
|
|
xMax = 12.0;
|
|
unite_x = "Volts";
|
|
precision_x = 2;
|
|
precision_x_graphique = 1;
|
|
step_reglage_fin_vertical = (0.1) * 100 / capacite;
|
|
step_reglage_fin_horizontal = 0.01;
|
|
|
|
break;
|
|
case shipheart_reservoir_mode_entrees_level_sensor_500r: // 0-500R
|
|
switch(sous_mode_reservoir) // Sous-mode :
|
|
{
|
|
case shipheart_reservoir_mode_entrees_level_sensor_500r_0_190r: // 0-190R
|
|
xMin = 0;
|
|
xMax = 200;
|
|
break;
|
|
case shipheart_reservoir_mode_entrees_level_sensor_500r_240_30r: // 240-30R
|
|
xMin = 0;
|
|
xMax = 260;
|
|
break;
|
|
case shipheart_reservoir_mode_entrees_level_sensor_500r_0_500r: // 0-500R
|
|
default:
|
|
xMin = 0;
|
|
xMax = 700;
|
|
break;
|
|
}
|
|
unite_x = String.fromCharCode(unicode_omega); //"Ohms";
|
|
precision_x = 1;
|
|
precision_x_graphique = 0;
|
|
step_reglage_fin_vertical = (0.1) * 100 / capacite;
|
|
step_reglage_fin_horizontal = 0.1;
|
|
break;
|
|
case shipheart_reservoir_mode_entrees_level_sensor_4_20ma: // 4-20mA
|
|
sous_mode_reservoir = 0;
|
|
xMin = 0;
|
|
xMax = 22.0;
|
|
unite_x = "mA";
|
|
precision_x = 1;
|
|
precision_x_graphique = 1;
|
|
step_reglage_fin_vertical = (0.1) * 100 / capacite;
|
|
step_reglage_fin_horizontal = 0.1;
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
console.log("/!\\ Le type d'entrée est incorrect. La page est refermée...")
|
|
stackView.pop(); // Il est inutile de rester si le type d'objet n'est pas connu...
|
|
break;
|
|
}
|
|
yMin = 0;
|
|
yMax = 100;
|
|
|
|
break;
|
|
|
|
default:
|
|
texte_aucun_element = qsTr("Aucun point n'est actuellement configuré pour cet élément...")
|
|
texte_titre = qsTr("Courbe de conversion de type INCONNU")
|
|
}
|
|
|
|
// Mise à jour des données actuelles (forcément en fin de traitement car nécessite de connaitre les min et max) :
|
|
mise_a_jour_valeur_actuelle();
|
|
|
|
flag_init = false; // Indique que l'init est terminée
|
|
}
|
|
|
|
//=============================================================================================================
|
|
// Met à jour 'modele_points', 'courbe' et 'points_nouveaux' avec les paramètres contenu dans 'chaines_points'
|
|
//=============================================================================================================
|
|
function creation_courbes(chaines_points)
|
|
{
|
|
var i;
|
|
var chaine_point;
|
|
var point;
|
|
var x,y;
|
|
|
|
// RAZ des courbes :
|
|
nb_points = 0;
|
|
modele_points.clear();
|
|
courbe.clear();
|
|
points_originaux.clear();
|
|
points_nouveaux.clear();
|
|
courbe_flag_vide = true;
|
|
point_en_cours = -1;
|
|
point_a_supprimer = -1;
|
|
|
|
if (chaines_points === "")
|
|
{
|
|
console.log("La liste de points est vide")
|
|
return; // Si le tableau est vide (aucun point), il ne faut pas faire de traitement supplémentaire...
|
|
}
|
|
|
|
// Ajout des points dans les modèles :
|
|
i = 0;
|
|
for (chaine_point of chaines_points)
|
|
{
|
|
point = chaine_point.split('/');
|
|
x = Number(point[0]);
|
|
y = Number(point[1]);
|
|
|
|
if (flag_valeurs_discretes) x = i ++;
|
|
|
|
modele_points.append({ "x": x, // Modèle pour la liste de gauche + les rectangle/cercles utilisés pour les bouger
|
|
"y": y,
|
|
"actif": false,
|
|
"etat_entree_discrete": false,
|
|
"changed": false});
|
|
courbe.append(x, y); // Courbe linéaire bleue
|
|
points_originaux.append(x, y); // Points d'origine (qui ne bougeront plus)
|
|
points_nouveaux.append(x, y); // Points nouveaux (qui bougeront dès qu'ils sont dragués)
|
|
|
|
courbe_flag_vide = false; // Utilisé pour afficher le message "aucune données...'
|
|
|
|
if (++nb_points >= courbe_etalonnage_max_points)
|
|
{
|
|
flag_nombre_max_points_atteint = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
flag_alignement_demande = true // Permet de déclencher un traitement au nivea du repeater pour mettre à jour la position du rectangle/cercle.
|
|
flag_alignement_demande = false // La RAZ ne peut pas se faire dans le repeater. Donc on la fait ici car le front montant a déjà été détecté par le signe de changement.
|
|
}
|
|
|
|
//=============================================================================================================
|
|
// Met à jour de la valeur actuelle (notamment, la croix H et V)
|
|
//=============================================================================================================
|
|
function mise_a_jour_valeur_actuelle()
|
|
{
|
|
var req, res;
|
|
var champs;
|
|
var val_x, val_y;
|
|
var valeurs_brutes, valeur_brute;
|
|
var i;
|
|
|
|
// RAZ des deux modèles :
|
|
croix_h.clear();
|
|
croix_v.clear();
|
|
|
|
switch(table)
|
|
{
|
|
//-------------------------------
|
|
case "TB_Batteries":
|
|
req = 'SELECT Tension, Soc FROM TB_Batteries WHERE Cle_primaire=' + id;
|
|
//console.log("Requête = [ " + req + " ]");
|
|
res = bdd.requete_unique(req);
|
|
|
|
val_x = Number(res[0]);
|
|
val_y = Number(res[1]);
|
|
texte_valeur_x.text = val_x.toFixed(precision_x) + " " + unite_x;
|
|
texte_valeur_y.text = val_y.toFixed(0) + "%";
|
|
|
|
break;
|
|
|
|
//-------------------------------
|
|
case "TB_Reservoirs":
|
|
req = 'SELECT Capacite, Valeur_brute, Niveau, Etat_entrees FROM TB_Reservoirs WHERE Cle_primaire=' + id;
|
|
//console.log("Requête = [ " + req + " ]");
|
|
res = bdd.requete_unique(req);
|
|
|
|
val_x = Number(res[1]);
|
|
val_y = Number(res[2]);
|
|
etat_entrees = Number(res[3]);
|
|
|
|
// Mise à jour de l'état de chaque entrée AUX dans le cas d'électrodes plongées :
|
|
switch(type_entrees_reservoir)
|
|
{
|
|
case shipheart_reservoir_type_entrees_aux :
|
|
|
|
// Mise à jour de l'état réel de chaque entrée AUX :
|
|
var masque = 1;
|
|
modele_points.setProperty(0, "etat_entree_discrete", false); // La valeur '0' est toujours inactive (car c'est un état par absence des autres entrées)
|
|
for (i = 1; i < nb_points; i++)
|
|
{
|
|
if ((val_x & masque) == 0) modele_points.setProperty(i, "etat_entree_discrete", false);
|
|
else modele_points.setProperty(i, "etat_entree_discrete", true);
|
|
masque <<= 1;
|
|
}
|
|
|
|
// Recalcul de val_x pour le ramener à des puissances de 2 :
|
|
val_x = Math.log2(Number(res[1]) + 1); // Les valeurs de 0 à 15 par exemple, deviennent 1 à 16 donc 0-4 pour le log2(). NB : si la valeur est incohérente, le résultat ne sera pas entier mais cela ne pose pas de pb puisqu'il n'est pas affiché dans ce cas.
|
|
|
|
break;
|
|
|
|
case shipheart_reservoir_type_entrees_level_sensor:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Texte de la valeur X :
|
|
if (val_x > xMax) // Si la valeur est hors de l'intervalle affiché (notamment, une sonde résistive non branchée...)
|
|
{
|
|
texte_valeur_x.text = "[" + String.fromCharCode(unicode_fleche_demi_epaisse_discontinue_droite) + " " + val_x.toFixed(precision_x) + " " + unite_x + "]"; // La valeur est affichée entre []
|
|
val_x = xMax; // On cape la position en bout de graphique
|
|
flag_yMax_hors_limite = true;
|
|
}
|
|
else
|
|
{
|
|
texte_valeur_x.text = val_x.toFixed(precision_x) + " " + unite_x; // Affichage normal
|
|
flag_yMax_hors_limite = false;
|
|
}
|
|
|
|
// Texte de la valeur Y mis en forme avec conversion d'unité si nécessaire :
|
|
texte_valeur_y.text = val_y.toFixed(0) + "%<br />" + Fonctions.conversion_volume((val_y * Number(res[0]) / 100), shipheart_unite_volume, shipheart_unite_volume_symbole, 0);
|
|
|
|
break;
|
|
|
|
default: // Ne devrait pas arriver car déjà testé à l'init...
|
|
break;
|
|
}
|
|
|
|
// Points de la croix de valeur actuelle :
|
|
croix_h.append(xMin, val_y);
|
|
croix_h.append(xMax, val_y);
|
|
croix_v.append(val_x, yMin);
|
|
croix_v.append(val_x, yMax);
|
|
|
|
// Positionnement des textes X et Y surle graphique (alignés sur la croix) :
|
|
texte_valeur_x.leftPadding = graphique.mapToPosition(croix_v.at(0), croix_v).x - (texte_valeur_x.contentWidth / 2);
|
|
texte_valeur_y.topPadding = graphique.mapToPosition(croix_h.at(0), croix_h).y - (texte_valeur_y.contentHeight / 2);
|
|
|
|
// Paramètres de hauteur et position du rectangle de remplissage :
|
|
rectangle_remplissage.height = graphique.plotArea.height * val_y / 100;
|
|
rectangle_remplissage.y = graphique.plotArea.y + (graphique.plotArea.height * (100 - val_y) / 100)
|
|
}
|
|
|
|
//=============================================================================================================
|
|
// Met à jour 'modele_points', 'courbe' et 'points_nouveaux' avec les paramètres usines.
|
|
// NB : on ne met PAS à jour la BDD avec ces valeurs. Il faudra ensuite cliquer sur Enregistrer pour que ce soit fait.
|
|
//=============================================================================================================
|
|
function courbe_usine()
|
|
{
|
|
var i;
|
|
var chaines_points;
|
|
|
|
switch(table)
|
|
{
|
|
case "TB_Batteries":
|
|
chaines_points = Fonctions_batteries.chaine_tension_niveau_batterie_usine(tension_nominale).split(';');
|
|
break;
|
|
|
|
case "TB_Reservoirs":
|
|
chaines_points = Fonctions_reservoirs.chaine_valeur_niveau_reservoir_usine(type_entrees_reservoir, mode_reservoir, sous_mode_reservoir).split(';');
|
|
break;
|
|
|
|
default : return;
|
|
}
|
|
|
|
flag_nouvelles_valeurs = true; // En pratique, on n'est pas sûr que les valeurs seront différentes après la config usine c'est le principe de précaution...
|
|
|
|
// Remplissage des courbes et modèles :
|
|
creation_courbes(chaines_points);
|
|
|
|
// Sélection du premier point à l'ouverture :
|
|
if (!courbe_flag_vide)
|
|
{
|
|
modele_points.setProperty(0, "actif", true);
|
|
point_en_cours = 0;
|
|
}
|
|
|
|
flag_affichage_reglage_fin = false;
|
|
}
|
|
|
|
//=============================================================================================================
|
|
// Ajoute un point à la courbe existante
|
|
//=============================================================================================================
|
|
function ajouter_point()
|
|
{
|
|
var x, y;
|
|
var nb;
|
|
|
|
if (point_en_cours === (modele_points.count - 1)) // Cas où on est sur le dernier point
|
|
{
|
|
if (point_en_cours > 0)
|
|
{
|
|
modele_points.setProperty(point_en_cours, "actif", false); // Dévalidation du dernier point (le nouveau point créé sera validé après sa création)
|
|
point_en_cours --; // S'il y a au moins 2 points, on se met sur le point précédent le dernier.
|
|
}
|
|
else return; // Ne doit pas arriver car on interdit la suppression dès qu'il ne rest plus que 2 points...
|
|
}
|
|
|
|
x = modele_points.get(point_en_cours).x + ((modele_points.get(point_en_cours + 1).x - modele_points.get(point_en_cours).x) / 2);
|
|
y = modele_points.get(point_en_cours).y + ((modele_points.get(point_en_cours + 1).y - modele_points.get(point_en_cours).y) / 2);
|
|
|
|
modele_points.insert(point_en_cours + 1,
|
|
{ "x": x, // Modèle pour la liste de gauche + les rectangle/cercles utilisés pour les bouger
|
|
"y": y,
|
|
"actif": true,
|
|
"etat_entree_discrete": false,
|
|
"changed": true});
|
|
|
|
courbe.insert((point_en_cours + 1), x, y); // Courbe linéaire bleue
|
|
points_originaux.insert((point_en_cours + 1), x, y); // Points d'origine (qui ne bougeront plus)
|
|
points_nouveaux.insert((point_en_cours + 1), x, y); // Points nouveaux (qui bougeront dès qu'ils sont dragués)
|
|
|
|
modele_points.setProperty(point_en_cours, "actif", false);
|
|
point_en_cours ++; // On se positionne sur le nouveau point
|
|
|
|
flag_nouvelles_valeurs = true; // Le nouveau point, à ce stade, n'est pas enregistré !
|
|
|
|
if (++nb_points >= courbe_etalonnage_max_points) flag_nombre_max_points_atteint = true;
|
|
|
|
courbe_flag_vide = false; // Utilisé pour afficher le message "aucune données...'
|
|
|
|
flag_alignement_demande = true // Permet de déclencher un traitement au nivea du repeater pour mettre à jour la position du rectangle/cercle.
|
|
flag_alignement_demande = false // La RAZ ne peut pas se faire dans le repeater. Donc on la fait ici car le front montant a déjà été détecté par le signe de changement.
|
|
}
|
|
|
|
|
|
//=============================================================================================================
|
|
// Supprime le point de la courbe correspondant à l'index passé en paramètre
|
|
//=============================================================================================================
|
|
function supprimer_point()
|
|
{
|
|
var x, y;
|
|
var nb;
|
|
|
|
flag_init = true;
|
|
|
|
if (point_a_supprimer === -1) return;
|
|
|
|
modele_points.remove(point_a_supprimer, 1); // Modèle pour la liste de gauche + les rectangle/cercles utilisés pour les bouger
|
|
courbe.remove(point_a_supprimer); // Courbe linéaire bleue
|
|
points_originaux.remove(point_a_supprimer); // Points d'origine (qui ne bougeront plus)
|
|
points_nouveaux.remove(point_a_supprimer); // Points nouveaux (qui bougeront dès qu'ils sont dragués)
|
|
|
|
if (--nb_points < courbe_etalonnage_max_points) flag_nombre_max_points_atteint = false; // Maj du 'nb_points' et réaffichage du bouton '+' si on est passé en-dessous du max.
|
|
if (nb_points < 1)
|
|
{
|
|
nb_points = 0; // Ne devrait pas être utile mais par précaution...
|
|
courbe_flag_vide = true; // Utilisé pour afficher le message "aucune données...'
|
|
}
|
|
|
|
flag_nouvelles_valeurs = true; // Donne accès au bouton d'enregistrement et permet d'alerter si tentative de sortie sans avoir enregistré.
|
|
|
|
flag_alignement_demande = true // Permet de déclencher un traitement au nivea du repeater pour mettre à jour la position du rectangle/cercle.
|
|
flag_alignement_demande = false // La RAZ ne peut pas se faire dans le repeater. Donc on la fait ici car le front montant a déjà été détecté par le signe de changement.
|
|
|
|
flag_affichage_reglage_fin = false;
|
|
|
|
// Mise à jour du point en cours afin qu'il y en ait toujours un d'actif :
|
|
if (point_a_supprimer >= 1) point_en_cours = point_a_supprimer - 1;
|
|
else point_en_cours = 0;
|
|
modele_points.setProperty(point_en_cours, "actif", true); // On sait que le 'point_en_cours' AVANT la suppression est celui qui a été supprimé ; il n'est donc pas utile d'essayer de le rendre inactif.
|
|
|
|
point_a_supprimer = -1;
|
|
|
|
flag_init = false;
|
|
}
|
|
|
|
//=============================================================================================================
|
|
// Enregistre dans la BDD les nouvelles valeurs
|
|
//=============================================================================================================
|
|
function enregistrement()
|
|
{
|
|
var i;
|
|
var req;
|
|
var chaine = "";
|
|
var val, niv;
|
|
|
|
// Génération de la chaine de valeurs/niveaux (exemple : "13800/10000;13400/9800;13150/9600;13000/9400;12900/9000;12800/8000;12443/4887;12100/2000;11800/1000;11500/500;11000/200;10500/0")
|
|
for(i = 0; i < modele_points.count; i++) {
|
|
if (chaine !== "") chaine += ";";
|
|
|
|
if (flag_valeurs_discretes) val = Math.pow(2, modele_points.get(i).x) - 1;
|
|
else val = modele_points.get(i).x.toFixed(3);
|
|
niv= modele_points.get(i).y.toFixed(2);
|
|
|
|
chaine += val + "/" + niv;
|
|
|
|
modele_points.setProperty(i, "changed", false) // On en profite pour RAZ tous les flags Changed
|
|
|
|
points_originaux.replace(points_originaux.at(i).x, points_originaux.at(i).y, modele_points.get(i).x, modele_points.get(i).y)
|
|
points_nouveaux.replace(points_nouveaux.at(i).x, points_nouveaux.at(i).y, modele_points.get(i).x, modele_points.get(i).y)
|
|
}
|
|
flag_nouvelles_valeurs = false; // ainsi que le flag de nouvelles valeurs
|
|
//console.log("Chaine valeurs/niveaux = [" + chaine + " ]")
|
|
|
|
flag_affichage_reglage_fin = false;
|
|
|
|
// Mise à jour de l'entrée dans la table :
|
|
req = 'UPDATE ' + table + ' SET ' + champ_liste + '="' + chaine + '" WHERE Cle_primaire=' + id + ' '
|
|
//console.log("Requête = " + req)
|
|
bdd.requete_unique(req);
|
|
|
|
// Demande de reconfiguration système via la BDD car on a modifié des valeurs de configuration :
|
|
synchro_bdd(shipheart_type_synchro_config);
|
|
|
|
// NB : on ne gère pas la fin de le reconfiguration comme sur la plupart des autres pages de ce type car on ne sort pas directement de la page (après l'enregistrement,
|
|
// il faut cliquer sur BACK). De plus, même si on le fait pendant la reconfiguration, clea n'empèchera pas celle-ci de se poursuivre.
|
|
|
|
}
|
|
}
|