# -*- coding: iso-8859-1 -*-

from qgis.core import (QgsCoordinateTransform, QgsExpression, QgsFeature, QgsFeatureRequest, QgsField, QgsFields, QgsGeometry, QgsPageSizeRegistry, QgsProject, QgsVectorLayer)

from qgis.PyQt.QtCore import (QAbstractTableModel, QDateTime, QEventLoop, QMimeData, QObject, QPoint, QPointF, QPropertyAnimation, QRect, QRectF, QSize, Qt,
                              QTime, QTimer, QUrl, pyqtSignal, pyqtSlot, pyqtProperty)
from qgis.PyQt.QtGui import (QBrush, QColor, QFont, QIcon, QImage, QKeySequence, QPageSize, QPainter,  QPalette, QPen, QPixmap, QStandardItem, QStandardItemModel)
from qgis.PyQt.QtWidgets import (QAbstractItemView, QAction, QApplication, QAbstractSpinBox, QApplication, QButtonGroup, QCheckBox, QComboBox, QDateTimeEdit, QDialog, QDockWidget,
                                 QFileDialog, QFrame, QGridLayout, QGroupBox, QHeaderView, QHBoxLayout, QInputDialog, QLabel, QLayout, QLCDNumber, QLineEdit, QMdiSubWindow, QMenu, QMessageBox,
                                 QProgressBar, QPushButton, QRadioButton, QSizePolicy, QSlider, QSpinBox, QStyledItemDelegate, QTableView, QTableWidget, QTableWidgetItem, QTextBrowser,
                                 QTextEdit, QToolBar, QToolButton, QVBoxLayout, QWidget, QGraphicsView, QGraphicsScene, QGraphicsPixmapItem)

from qgis.PyQt.QtPrintSupport import (QPrintPreviewDialog, QPrinter)
from qgis.gui import (QgsExpressionBuilderDialog, QgsMapCanvasItem, QgsMessageBar)
from qgis.core import (QgsExpression, QgsExpressionContext, QgsExpressionContextScope, Qgis, QgsPoint, QgsPointXY)

from qgis.PyQt.QtWebKitWidgets import (QWebInspector, QWebPage, QWebView)
from qgis.PyQt.QtWebKit import QWebSettings

from rdigc.utils import (bufferPlus, changeButtonIcon, convertUnits, dicoHasKey, fixeLabelsFileDialog,
                         getAltiPoint, getExtension, getHarmonicMean, getLitteralDate, getMean, getNivVigilance, getQuartiles,
                         getCloudCover, getRainForce, getStDev, getThemeIcon, getTransformer, getWindForce, getWindOrientation,
                         isInfNumber, jsonDateToStrDate, jsonToMatplotlibDate, lissage, openFile, seriesUpSeuils, setImgPath,
                         setObjSizes, testSeuilVisibility, utc_to_local, windowIcon)

import os, glob, shutil
import os.path
from os.path import (dirname, exists, join)
from os import remove as removeFile


from matplotlib.figure import Figure as pltFigure
import matplotlib.pyplot as plt

from decimal import Decimal
import matplotlib.image as mpimg
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
from mpl_toolkits.mplot3d import axes3d, Axes3D
from matplotlib.offsetbox import (TextArea, DrawingArea, OffsetImage,  AnnotationBbox)
from matplotlib.cbook import get_sample_data

import locale
import csv
import statistics
import json
#NEW 4.0.0
from qgis import processing
from processing.core.Processing import Processing
from qgis.core import (QgsProcessingFeedback, QgsProcessingFeatureSourceDefinition)

from datetime import datetime as thedatetime
from datetime import timedelta

from urllib.parse import (urlencode, unquote_plus, unquote, urlparse)
from qgis.PyQt.QtNetwork import (QNetworkAccessManager, QNetworkDiskCache, QNetworkReply, QNetworkRequest)
from scipy.stats import linregress

import ast
import pytz
import numpy as np
import random

from webbrowser import open as webbrowseropen         
from os import walk
from os.path import (basename, exists, splitext)
from math import (ceil, floor, sqrt)

#NEW 4.0.0
from rdigc.graphmaker import *
from rdigc.params import (APIHUBEAU, APIMETEO, APIMODELMETEOITEMS, COMMENTOBSERVATIONS, COMMENTSSEUILRDI, HTMLGRAPHICSJS, HTMLSTATISTICS, HTMLSTYLESGRAPHICS, HTMLSTYLESMETEO, HTMLSTYLESPROFILS,
                          HTMLSTYLESSTATISTICS, IMAGESFORBULLETIN, INFORMATIONSSTYLES, STYLECOMBOBOX, STYLELABEL, STYLELABELERROR, TIMEZONE)
from pathlib import Path as pathLibPath


def bufferPlus(inputLayer, distance, output, before_processing_reproject_to_epsg_number=None):
    params = {'INPUT': inputLayer, 
              'DISTANCE': distance,
              'END_CAP_STYLE': 2 , # - 0: Round - 1: Flat - 2: Square
              'JOIN_STYLE' : 0, # - 0: Round - 1: Angle droit - 2: Oblique
              "SEGMENTS": 5, 
              'MITER_LIMIT' : 2, 
              'DISSOLVE': False,
              'SEPARATE_DISJOINT': False,
              'OUTPUT': output}
    feedback = QgsProcessingFeedback()
    alg_name = 'native:buffer'
    result = processing.run(alg_name, params, feedback=feedback)
    return result



#NEW BLOC GRAPH
def blocGraph():
    info = """
            <center>
            <details>
            <summary class='graphTheme'><table>
            <tr>
 	       <td><img src="file:///$TYPEGRAPH" width='32px' height='32px' title="Graphique $TITLEGRAPH"></td>
 	       <td class="titleTheme"><u>Graphique $TITLEGRAPH</u> :</td>
 	    </tr>
 	    </table></summary>   
	    <img class="post-content-wrapper" src="file:///$PLOTGRAPH" width='100%' height='100%'>
	    </details>
	    </center>
            <br><br>"""            
    return info



def clamp(x): return max(0, min(x, 255))

#COMMENTS FOR STATUT
def getCommentsForStatut(self, idStation, stationDicoSeuils, coeffReg, dep, ratiosSeuil, lastObs, unitInfos, typeInfos):
    bgColor, color, commentStatut, iAlarm, imgStatut, keySeuil = getNivVigilance(stationDicoSeuils, lastObs, typeInfos, unitInfos)
    return commentStatut, imgStatut, commentStatut.replace("<br>", "\n")

#COMMENTS FOR TREND
def getCommentsForTrend(coeffReg, isTextual):
    dictLegend = COMMENTOBSERVATIONS

    racComment = "3 dernières heures"
    comments = "Tendance des %s : " % racComment if isTextual else ""
    symbol, titleImg2, txtSymbol = "", "", ""

    if not coeffReg : return "Tendance non déterminée pour les %s." % racComment, "<b>?</b>", "%s (%s)" % (dictLegend["e3"], racComment)

    if coeffReg >= -0.005 and coeffReg <= 0.005 : symbol, titleImg2, txtSymbol = "&#8608", "%s (%s)" % (dictLegend["e0"], racComment), dictLegend["e0"]
    elif coeffReg > -0.005  :                     symbol, titleImg2, txtSymbol = "&#8608", "%s (%s)" % (dictLegend["e1"], racComment), dictLegend["e1"]
    else :                                        symbol, titleImg2, txtSymbol = "&#8599", "%s (%s)" % (dictLegend["e2"], racComment), dictLegend["e2"]

    comments = "<b>%s</b> %s<br>\n" % (symbol, txtSymbol) if comments == "" else "%s<b>%s</b> %s<br>\n" % (comments, symbol, txtSymbol)

    return comments, symbol, titleImg2

#END #NEW 4.0.0

def correctPath(path):
    path = "%s" % (path)
    a = len(path)
    letter = "/"
    b = path.rfind(letter, 0, a)    
    return ("%s/" % (path)) if a != b else path

def getListDocForMenu(obj, menu, idStation, pathDoc):
    iconUrlWeb = QIcon(getThemeIcon("urlweb.png"))
    listDocAss = []    
    
    for dirname, dirnames, filenames in walk(pathDoc):
        for fileName in filenames:
            if fileName.startswith(idStation) :

                name = "%s" % (basename(fileName))
                extension = getExtension(fileName)

                extension, icon = getExtension(fileName), None
                if extension.startswith(".") :
                   extension = extension[1:]
                   icon = QIcon(getThemeIcon("%s.png" % extension)) 
                
                if menu :
                    action = QAction(icon, name, obj) if icon else QAction(iconUrlWeb, name, obj)
                    action.setWhatsThis("%s/%s" % (dirname, fileName)) 
                    action.triggered.connect(obj.parent.openUrl)
                    menu.addAction(action)
                else :
                    listDocAss.append("%s/%s" % (dirname, fileName))

    if not menu : return listDocAss

#NEW 4.0.0
class logConsole(QMdiSubWindow):
    def __init__(self, parent, button, width, height, namePlugin):

        super(logConsole, self).__init__()
        self.setObjectName("logConsole")
        self.setWindowTitle("Console journal")
        self.parent = parent
        self.button = button
        self.namePlugin = namePlugin
        self.setWindowOpacity(0.75)
        self.setWindowFlags(Qt.WindowTitleHint | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
        self.w = width
        self.h = height
        self.setFixedSize(self.w, self.h)
        self.resize(self.w, self.h)

        self.setStyleSheet("QMdiSubWindow {color: black; background-color:white; border:1px; border-width: 0px;}")
        windowIcon(self, "logconsole_off.png")
    
    def setupUi(self):
        self.textEdit = QTextEdit(self)
        self.textEdit.setStyleSheet("QTextEdit {font: 14px 'Arial'; color: black; background: #CCC;}")
        self.textEdit.setHtml("<body {font-family: Arial, sans-serif; font-size: 18px; margin: 0; display: grid;}><p>Bienvenue dans la console journal ...</p>")
        self.textEdit.setReadOnly(True) #False

        self.copyTextToClipBoardButton = funnyPushButton(self)
        self.copyTextToClipBoardButton.initPushButton(24, 24, self.width()-180,  1, "copyTextToClipBoardButton", "", "Copier au format brut vers le presse papier", True, getThemeIcon("copytoclipboard.png"), 24, 24, True, False)

        self.copyToClipBoardButton = funnyPushButton(self)
        self.copyToClipBoardButton.initPushButton(24, 24, self.width()-140,  1, "copyToClipBoardButton", "", "Copier au format HTML vers le presse papier pour des applicatifs tiers", True, getThemeIcon("copyhtml.png"), 24, 24, True, False)

        self.addButton = funnyPushButton(self)
        self.addButton.initPushButton(24, 24, self.width()-110,  1, "addButton", "", "Ajouter une information", True, getThemeIcon("add.png"), 24, 24, True, False)
        
        self.printButton = funnyPushButton(self)
        self.printButton.initPushButton(24, 24, self.width()-80, 1, "printButton", "", "Imprimer le journal", True, getThemeIcon("printer.png"), 24, 24, True, False)

        self.cleanButton = funnyPushButton(self)
        self.cleanButton.initPushButton(24, 24, self.width()-40, 1, "cleanButton", "", "Vider le journal", True, getThemeIcon("garbage.png"), 24, 24, True, False)

        self.copyTextToClipBoardButton.clicked.connect(self.copyText)
        self.copyToClipBoardButton.clicked.connect(self.copyHtml)
        self.addButton.clicked.connect(self.addAnnotation)
        self.printButton.clicked.connect(self.printConsole)
        self.cleanButton.clicked.connect(self.cleanConsole)
       
    def addAnnotation(self) :
        text, ok = QInputDialog().getText(self, "Annotation pour le journal", "Texte à ajouter ...", QLineEdit.Normal)
        if not ok: return
        self.setAnnontation(text, False)

    def setAnnontation(self, text, isHtml):     
        html = self.textEdit.toHtml()
        if isHtml == False : text = "<span style='font: 18px Arial;'>%s</span>" % text
        self.textEdit.setHtml(html+text+"<br>")

    def copyText(self):
        text = self.textEdit.toPlainText()
        QApplication.clipboard().setText(text)

   
    def copyHtml(self):
        html = self.textEdit.toHtml()
        reportRichTextMime = QMimeData()
        reportRichTextMime.setHtml(html)
        QApplication.clipboard().setMimeData(reportRichTextMime)


    def printConsole(self):
        printer = QPrinter(); printer.setPageSize(QPrinter.A4); printer.setOrientation(QPrinter.Portrait);
        printer.setPageMargins(5, 5, 5, 5, QPrinter.Millimeter); printer.setOutputFormat(QPrinter.NativeFormat);
        orientation = QPrinter.Portrait
        dlgPrint = printerPreview(self, self.textEdit, "%s : Imprimer le journal" % self.namePlugin, orientation, "printer.png")

    
    def cleanConsole(self): self.textEdit.setHtml("<body {font-family: Arial, sans-serif; font-size: 14px; margin: 0; display: grid;}>")      
        
    def resizeEvent(self, event) :
        self.textEdit.setGeometry(5, 25, self.w-10, self.h-30)

    def closeEvent(self, event):
        self.button.setIcon(QIcon(getThemeIcon("logconsole_off.png")))

	
class CustomStyleDelegate(QStyledItemDelegate):
    def initStyleOption(self, option, index):
        super(CustomStyleDelegate, self).initStyleOption(option, index)
        brush = QColor("black")
        option.backgroundBrush = QColor(235, 235, 235)
        option.palette.setBrush(QPalette.Text, brush)

    def paint(self, painter, option, index):
        super(CustomStyleDelegate, self).paint(painter, option, index)

        margins = 0
        border_color = QColor(235, 235, 235)
        painter.save()
        pen = QPen()
        pen.setColor(border_color)
        pen.setWidth(margins)
        painter.setPen(pen)
        r = QRect(option.rect).adjusted(0, 0, -margins, -margins)
        painter.drawRect(r)
        painter.restore()

      
class toolBarSnifferWidget(QDockWidget):
      def  __init__(self, mainWindow, canvas, plugin):
         super(toolBarSnifferWidget, self).__init__()
         self.setWindowTitle("sniffer")
         self.minWidth, self.minHeight = 1, 1
         setObjSizes(self, self.minWidth, self.minHeight)
         self.setWindowFlags(Qt.CustomizeWindowHint | Qt.FramelessWindowHint)
         self.setWindowOpacity(0.0)
         self.mainWindow = mainWindow
         self.canvas = canvas
         self.plugin = plugin
         self.isClosed = False
         self.setGeometry(0, 0, 1, 1)
         self.setVisible(False)
         self.hide()
         self.mainWindow.installEventFilter(self)


      def eventFilter(self, obj, event):
          if obj is self.mainWindow:
             if event.type() == event.Resize: self.setVisible(True)  ; self.resizeObjects()
             elif event.type() == event.Move : self.setVisible(True)  ; self.resizeObjects()
             elif event.type() == event.WindowStateChange :
                 if event.oldState() and (Qt.WindowMinimized or Qt.WindowMaximized) : self.setVisible(True)  ; self.resizeObjects();
             elif event.type() == event.Close :
                  msgDlgBox = myMsgDlgBox(self, QMessageBox.Question, "Confirmer la sortie ...", "Voulez-vous vraiment quitter\nQGIS et %s maintenant ?" % self.plugin.namePlugin, "rdistop.png", "inondation.png", 0, None, self)
                  ret = msgDlgBox.exec_()
                  if ret == 1 :
                     try : self.plugin.killMemoryLayerFromProject() 
                     except : pass
                     if not (self.mainWindow.menuBar().isVisible() and self.mainWindow.statusBar().isVisible()) :
                           self.plugin.actionToggleMapOnly.triggered.emit()
                           self.isClosed = True
                           event.accept()
                  else : event.ignore() ; return True;
                
          return QObject.eventFilter(self, obj, event)

      def resizeObjects(self): self.plugin.getUpdateRightWidgets()

      def closeEvent(self, event):
          try : 
                if self.isClosed : event.accept() 
                else : event.ignore(); return True ;
          except :  event.ignore(); return True ;
           
      def quit(self): self.plugin.quit()

#NEW 4.0.0       
class tableFieldsMapper(QTableWidget):    
    def __init__(self, *args):
            super(tableFieldsMapper, self).__init__(*args)
            #NEW 4.0.0
            self.params = {
                "administratif" : {"INSEE_COM" : None, "NOM" : None, "FILTERWITH" : None},
                "cours_eau"     : {"ID" : None, "TOPONYME" : None, "FILTERWITH" : None},
                "hydrometrie"   : {"CdStationH" : None, "LbStationH" : None, "CdAncieRef" : None, "SEUIL_RDI" : {}, "FILTERWITH" : None},                
                "infoclimat"    :  {"libelle" : None, "code" : None}                
                }

            self.setRowCount(0)
            self.setColumnCount(2)
            self.styleComboBox = STYLECOMBOBOX
            self.styleLabel = STYLELABEL
            self.styleLabelError = STYLELABELERROR

            self.layer = None
            
            self.setHorizontalHeaderLabels(["Mot clef", "Attribut correspondant"])
            self.setDragEnabled(False)
            self.setAcceptDrops(False)
            self.setDragDropOverwriteMode(False)
            self.setDropIndicatorShown(False)
            self.setSelectionMode(QAbstractItemView.SingleSelection)
            self.setSelectionBehavior(QAbstractItemView.SelectRows)

            #NEW 4.0.0
            self.context = QgsExpressionContext()
            self.scope = QgsExpressionContextScope()

            self.verticalHeader().hide()

            self.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)

            self.initSizeCols()
                       

    def initSizeCols(self):
        self.setColumnWidth(0, 120)
        self.setColumnWidth(1, 250)

    def setFields(self, layer):
        #NEW 4.0.0
        self.layer = layer
        
        provider = layer.dataProvider()
        nameFields = [field.name() for field in provider.fields()]
        
        for i in range(self.rowCount()):
            #NEW 4.0.0
            itemKey = self.item(i, 0).text()
            if itemKey != "FILTERWITH" :
               comboFields = self.cellWidget(i, 1)
               comboFields.clear()
               comboFields.addItems(nameFields)


    def toExpression(self, text): return text.replace("{", "\"").replace("}", "\"")
    def fromExpression(self, text):
        pos, deb = -1, False
        pos = text.find("\"")
        while pos > -1 :
            text = "%s%s%s" % (text[0:pos], "{" if not deb else "}", text[pos+1:])
            deb = not deb
            pos = text.find("\"", pos+1)
            
        return text

    def getItemPosByKey(self, key, nameField):
        for i in range(self.rowCount()):
            #NEW 4.0.0 
            itemKey = self.item(i, 0).text()
            if itemKey == key :
               if itemKey != "FILTERWITH" : 
                  try : index = self.cellWidget(i, 1).findText(nameField, Qt.MatchFixedString)
                  except : index = -1
                  if index >= 0 : self.cellWidget(i, 1).setCurrentIndex(index)
                  self.item(i, 0).setBackground(QColor(200, 245, 80, 250)) if index >= 0 else self.item(i, 0).setBackground(QColor(250, 120, 120, 250))
                  break

               else :
                  self.cellWidget(i, 1).setText(nameField)
                  #VERIFY evaluate expression
                  expressionText = self.toExpression(self.filterWith.text())
                  value = self.evaluateExpression(expressionText)
                  if value != None : self.item(i, 0).setBackground(QColor(200, 245, 80, 250))
                  else :
                     if expressionText == "" : self.item(i, 0).setBackground(QColor(200, 245, 80, 250))
                     else : self.item(i, 0).setBackground(QColor(250, 120, 120, 250)) 
                  

    def evaluateExpression(self, expressionText) :
        expression = QgsExpression(expressionText)
        for feat in self.layer.getFeatures() :
            self.scope.setFeature(feat)
            self.context.appendScope(self.scope)
            self.scope.setFeature(feat); break ;
        value = expression.evaluate(self.context)
        return value

    def getExpression(self):
        if not self.layer : return
        
        changeButtonIcon(self, self.sender(),"expressionedit.png")

        expressionText = self.toExpression(self.filterWith.text())
        value = self.evaluateExpression(expressionText)

        builder = QgsExpressionBuilderDialog(self.layer, expressionText , self ) 
        windowIcon(builder, "geomaker/expressionedit.png")
              
        if (builder.exec_() == QDialog.Accepted):
            if not expressionText != builder.expressionText(): changeButtonIcon(self, self.sender(),"expression.png"); return;
            expressionText = builder.expressionText()
            value = self.evaluateExpression(expressionText)
            self.filterWith.setText(self.fromExpression(expressionText))
            i = self.rowCount()-1
            if value != None : self.item(i, 0).setBackground(QColor(200, 245, 80, 250))
            else :
               if expressionText == "" : self.item(i, 0).setBackground(QColor(200, 245, 80, 250))
               else : self.item(i, 0).setBackground(QColor(250, 120, 120, 250))               

        changeButtonIcon(self, self.sender(),"expression.png")

  
    def setData(self, paramtable, posY) :

        height = 0
        self.data = self.params[paramtable]

        
        for key in self.data.keys():
            self.insertRow(self.rowCount()); height+= 30 #40
            item = QTableWidgetItem(key)
            item.setFlags(item.flags() ^ Qt.ItemIsEditable)
            self.setItem(self.rowCount()-1, 0, item)

            #NEW 4.0.0
            if key != "FILTERWITH" :
                comboFields = QComboBox()
                comboFields.setObjectName("comboFields")
                comboFields.setStyleSheet(self.styleComboBox)
                self.setCellWidget(self.rowCount()-1, 1, comboFields)
            else :
                self.filterWith = QLabel()
                self.expressionButton = QPushButton(self.filterWith)
                self.expressionButton.setObjectName("expression")
                self.expressionButton.setToolTip("Constructeur Expression QGIS ...")
                self.expressionButton.setFixedSize(25, 25)
                self.expressionButton.setGeometry(215, 1, 25, 25)
                self.expressionButton.setIcon(QIcon(getThemeIcon("expression.png")))
                self.expressionButton.clicked.connect(self.getExpression)
                self.setCellWidget(self.rowCount()-1, 1, self.filterWith)

            
            self.setGeometry(100, posY, 380, height)
            self.setMinimumHeight(height)

        
        return posY+height-20
            

class paramToPrintMap(QDialog):
    def  __init__(self, parent):
            super(paramToPrintMap, self).__init__()
            self.setWindowTitle("Paramètres pour l'impression de la carte")
            self.setWindowFlags(Qt.WindowCloseButtonHint)
            self.minWidth, self.minHeight = 280, 290
            self.resize(self.minWidth, self.minHeight)
            setObjSizes(self, self.minWidth, self.minHeight)
            self.parent = parent

            windowIcon(self, "printer.png")

            self.label = QLabel(self)
            self.label.setText("Définissez l'orientation pour l'impression de la carte ...<hr>")
            self.label.setGeometry(10, 10, 260, 30)

            self.comboBoxOrientation = QComboBox(self)
            self.comboBoxOrientation.addItems(["Paysage", "Portrait"])
            self.comboBoxOrientation.setMinimumHeight(80)
            self.comboBoxOrientation.setMaximumHeight(80)

            self.comboBoxOrientation.setIconSize(QSize(64,64))
            self.comboBoxOrientation.setStyleSheet(self.parent.styleComboBox)

            self.comboBoxOrientation.setItemIcon(0, QIcon(getThemeIcon("paysage.png")))
            self.comboBoxOrientation.setItemIcon(1, QIcon(getThemeIcon("portrait.png")))

            self.comboBoxOrientation.setGeometry(10, 40, 265, 85)
            self.comboBoxOrientation.setCurrentIndex(self.parent.orientationIndex)

            self.pagesizes = {}

            pageSizeRegsitry = QgsPageSizeRegistry().entries()
            for elt in pageSizeRegsitry :
                if elt.displayName.find("×") == -1 :
                   self.pagesizes[elt.displayName] = [elt.size.width(), elt.size.height(), elt.size.units()]

            self.labelFormat = QLabel(self)
            self.labelFormat.setText("Définissez le format l'impression de la carte ...<hr>")
            self.labelFormat.setGeometry(10, 130, 265, 25)
            
            self.comboBoxPageSize = QComboBox(self)
            cPageSizes = sorted(self.pagesizes.keys())
            self.comboBoxPageSize.addItems(cPageSizes)
            self.comboBoxPageSize.setCurrentIndex(self.parent.pageFormatIndex) #cPageSizes.index("A4"))
            self.comboBoxPageSize.setGeometry(10, 160, 265, 30)
            
            self.composer = QCheckBox(self)
            self.composer.setText("Me permettre de mofidier la mise en page")
            self.composer.setGeometry(10, 190, 265, 25)
            self.composer.setChecked(self.parent.withComposer)

            self.legende = QCheckBox(self)
            self.legende.setText("Inclure une légende simple")
            self.legende.setGeometry(10, 210, 265, 25)            
            self.legende.setChecked(self.parent.withLegend)

            self.northArrow = QCheckBox(self)
            self.northArrow.setText("Inclure une flèche du Nord")
            self.northArrow.setGeometry(10, 230, 265, 25)            
            self.northArrow.setChecked(self.parent.withNorthArrow)            

            self.acceptButton = QPushButton(self)
            self.acceptButton.setObjectName("acceptButton")
            self.acceptButton.setText("Imprimer") 
            self.acceptButton.setFixedSize(100, 25)
            self.acceptButton.setGeometry(40, 260, 100, 30)
            self.acceptButton.clicked.connect(self.accept)
            
            self.rejectButton = QPushButton(self)
            self.rejectButton.setObjectName("rejectButton")
            self.rejectButton.setText("Annuler")
            self.rejectButton.setFixedSize(100, 25)
            self.rejectButton.setGeometry(140, 260, 100, 30)
            self.rejectButton.clicked.connect(self.reject)

    def getOrientationIndex(self): return self.comboBoxOrientation.currentIndex()
    def getNorthArrow(self): return self.northArrow.isChecked()
    def getLegende(self): return self.legende.isChecked()
    def getComposition(self): return self.composer.isChecked()
    def getPageFormatIndex(self): return self.comboBoxPageSize.currentIndex()
    def getPageFormat(self): return self.comboBoxPageSize.currentText()
    def getPageSize(self): return self.pagesizes[self.comboBoxPageSize.currentText()]
 
    
class Button(QPushButton):
    entered = pyqtSignal()
    leaved = pyqtSignal()

    def initPushButton(self, sizeWidth, sizeHeight, coordX, coordY, name, text, toolTip, isGeom, icon, iconWidth, iconHeight, isStyleSheeted):
        setObjSizes(self, sizeWidth, sizeHeight)
        if isGeom : self.setGeometry(coordX, coordY, sizeWidth, sizeHeight)
        if icon != "" :
           self.setIcon(QIcon(icon))
           self.setIconSize(QSize(iconWidth, iconHeight))
        self.setToolTip(toolTip)
        if isStyleSheeted : self.setStyleSheet("QPushButton {border: none;}")
        self.setObjectName(name)
        if text != "" : self.setText(text)    

    def enterEvent(self, event):
        super().enterEvent(event)
        self.entered.emit()

    def leaveEvent(self, event):
        super().leaveEvent(event)
        self.leaved.emit()                    


class switchButtonRdi(QPushButton):
    def __init__(self, parent = None, img = None):
        super(switchButtonRdi, self).__init__(parent)
        
        self.setCheckable(True)
        self.setIconSize(QSize(24, 24))
        icon = getThemeIcon(img)
        self.setIcon(QIcon(icon))
        self.setWhatsThis(img)
    
    def updateImg(self):
        icon = QIcon(getThemeIcon("%s_on.png" % self.whatsThis())) if self.isChecked() else QIcon(getThemeIcon("%s_off.png" % self.whatsThis()))
        self.setIcon(icon)    


                    
class linkedToolBar(QToolBar):
     def __init__(self, parent, canvas, lbStation, idStation, typeInfos, idHydro, posX, posY, noReturn, pathDoc, *args): 
          super(linkedToolBar, self).__init__(*args)

          self = QToolBar(canvas)
          self.parent = parent

          if noReturn :  icon, text, url = "sandre.png", "Fiche SANDRE pour la station %s ...", "https://www.sandre.eaufrance.fr/geo/StationHydro/%s"
          else :         icon, text, url = "vigicrues.png", "Fiche Vigicrues pour la station %s ...", "https://www.vigicrues.gouv.fr/niv3-station.php?CdEntVigiCru=26&CdStationHydro=%s&GrdSerie="+typeInfos+"&ZoomInitial=3" 
             
          dicoCtrls = {"actionWeb" : (icon, text, lbStation, url, idStation),
                       "actionFiche" : ("hydro.png", "Fiche Synthèse DREAL pour %s ...", lbStation, "http://www.hydro.eaufrance.fr/stationhydro/%s/fiche", idStation),
                       "actionStats" : ("inrae.png", "Satistiques sur INRAE pour %s ...", lbStation, "https://hydrogr.github.io/BDD-HydroClim/images/fr/%s_INRAE_BDD-HydroClim_fact_sheet_FR.png", idStation),
                       "actionData" : ("birdz.png", "Données sur OPENDATASOFT pour %s ...", lbStation, "https://birdz.opendatasoft.com/explore/dataset/vigicrues-hauteurs-et-debits-des-cours-deau/table/?flg=en&q=%s", idHydro[0:4])
                       }

          for key in dicoCtrls :
              icon = getThemeIcon(dicoCtrls[key][0])  
              text = dicoCtrls[key][1]
              button = QToolButton()              
              action = QAction(QIcon(icon), text % dicoCtrls[key][2], self)
              action.setToolTip(action.text())
              action.setObjectName(key)
              action.setWhatsThis(dicoCtrls[key][3] % dicoCtrls[key][4])
              action.triggered.connect(self.parent.openUrl)
              button.setDefaultAction(action)
              button.setStyleSheet("""QToolButton { background: rgb(255, 255, 255); border: 1px  solid  rgb(0, 0, 0); border-style: outset; border-width: 1px; border-radius: 50px; color: white;}
                                      QToolButton:hover { background: qradialgradient(cx: 0.3, cy: -0.4, fx: 0.3, fy: -0.4, radius: 1.35, stop: 0 #96b920, stop: 1 #b5e61d);}
                                      """)
              setattr(self, key, action)
              self.addWidget(button) 

          toolDocLink = QMenu("Documents pour la station")

          getListDocForMenu(self, toolDocLink, idStation, pathDoc)

          button = QPushButton()     
          button.setIcon(QIcon(getThemeIcon("classification.png")))
          button.setToolTip("Documents associés")
          button.setIconSize(QSize(24, 24))
          button.setMinimumWidth(48)
          button.setMaximumWidth(48)
          button.setMinimumHeight(48)
          button.setMaximumHeight(48)
          button.setMenu(toolDocLink)  
          button.setStyleSheet("""QPushButton:hover{background-color:  rgb(0, 255, 255); border: 2px  solid  (0, 255, 255);}""")

          self.addWidget(button)
          
          self.setIconSize(QSize(48, 48))      
          self.setOrientation(Qt.Vertical)
          self.setStyleSheet("QToolBar { border: 1px solid; border-radius: 5px; background-color: rgba(255, 255, 255, 50);}")         
          self.setGeometry(posX, posY, 55, 270)
     

class labeller(QLabel):
     def __init__(self, name, text, styleSheet, alignment, *args):
             super(labeller, self).__init__(*args)
             self.setObjectName(name)
             self.setText(text)
             if styleSheet == "special": self.setStyleSheet("QLabel {display: block;  border: 2px solid gray;  border-radius: 10px; text-align: center;  line-height: 10px; background-color: #C0C0C0; border: 1px solid black; color: black}")
             elif styleSheet == "infos": self.setStyleSheet("QLabel {font-size:10px; background-color:#C0C0C0; border: 1px solid #e1e1e1; padding: 4px ; color: black}")
             elif styleSheet == "coords": self.setStyleSheet("QLabel {  color : white; font-size: 11px; background-color: rgba(93, 93, 93, 125);}")
             self.setAlignment(alignment)


class windowPlotStatsAndGraphs(QDialog):
    def __init__(self, parent, titleWindow, typeStats, data, labels, title, img, minY, maxY, typeInfos, idHydro, lbStation, idStation, pathDoc, tz, service, average, ecartType, coeffReg, constanteReg, stationDicoSeuils):  
        super(windowPlotStatsAndGraphs, self).__init__()
        self.parent = parent
        self.timezone = tz
        self.service = service
        self.average = average
        self.ecartType = ecartType
        self.coeffReg = coeffReg
        self.constanteReg = constanteReg
        self.stationDicoSeuils = stationDicoSeuils
        self.lblOriginalData = 'Observations brutes'
        self.lblSmoothy = 'Courbe lissée'
        self.lblTendancy = 'Droite de tendance'
        self.lblDeltaAverage = '+/- delta écart type'
        self.average1ineup, self.average1inedown, self.averagefill = None, None, None        

        self.setWindowFlags(Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint)
        
        self.typeStats = typeStats
        self.data = data
        self.labels = labels
        infos = title.split("[")
        self.title = infos[0]
        
        self.timelaps = infos[1].replace("]", "")
        
        self.image = img
        self.minY = minY
        self.maxY = maxY
        self.typeInfos = typeInfos
        self.idHydro = idHydro
        self.lbStation = lbStation
        self.idStation = idStation
        self.minWidth, self.minHeight = 1200, 700
        self.resize(self.minWidth, self.minHeight)

        windowIcon(self, "grahique.png") 
        self.setWindowTitle("%s - %s :" % (self.parent.namePlugin, titleWindow)) if titleWindow else self.setWindowTitle("%s - %s :" % (self.parent.namePlugin, QApplication.translate("rdi","Graph statistics")))

        self.figure = pltFigure()
        self.canvas = FigureCanvas(self.figure)
        self.toolbar = NavigationToolbar(self.canvas, self)

        self.addingTb = linkedToolBar(self, self.canvas, self.lbStation, self.idStation, self.typeInfos, self.idHydro, 10, 160, False, pathDoc) 
        
        layout = QVBoxLayout()
        layout.addWidget(self.toolbar)
        layout.addWidget(self.canvas)
        self.setLayout(layout)

        self.chkSmoothy = QCheckBox("Afficher la %s" % self.lblSmoothy.lower(), self)
        self.chkSmoothy.setChecked(True)
        self.chkSmoothy.setObjectName("chkSmoothy")
        self.chkSmoothy.setGeometry(340, 0, 200, 25)
        self.chkSmoothy.stateChanged.connect(self.setVisibilityForSmoothy) 
        
        self.chkDeltaAverage = QCheckBox("Afficher la zone %s" % self.lblDeltaAverage.lower(), self)
        self.chkDeltaAverage.setChecked(False)
        self.chkDeltaAverage.setObjectName("chkDeltaAverage")
        self.chkDeltaAverage.setGeometry(340, 15, 200, 25)
        self.chkDeltaAverage.stateChanged.connect(self.setVisibilityForAverage) 

        self.chkTendency = QCheckBox("Afficher la %s" % self.lblTendancy.lower(), self)
        self.chkTendency.setChecked(False)
        self.chkTendency.setObjectName("chkTendency")
        self.chkTendency.setGeometry(340, 30, 200, 25)
        self.chkTendency.stateChanged.connect(self.setVisibilityForTendancy) 
        
        if self.typeInfos == "Q" :

            self.chkStem = QPushButton(self)
            self.chkStem.setCheckable(True)
            self.chkStem.setChecked(False)
            self.chkStem.setObjectName("chkStem")
            self.chkStem.setGeometry(550, 15, 30, 30)
            self.chkStem.setIcon(QIcon(getThemeIcon("stem.png")))
            self.chkStem.setIconSize(QSize(24 , 24))
            self.chkStem.setToolTip("Basculer en représentation par tiges")
            self.chkStem.clicked.connect(self.updateImgForButton)

        self.plot()

    def resizeEvent(self, event):
        return
        self.chkSmoothy.setGeometry(self.width()-850, 0, 200, 25)
        self.chkDeltaAverage.setGeometry(self.width()-850, 15, 200, 25)
        self.chkTendency.setGeometry(self.width()-850, 30, 200, 25)
        if self.typeInfos == "Q" : self.chkStem.setGeometry(self.width()-600, 15, 30, 30)
        

    def updateLegend(self):
        for origline in self.legend.get_lines():
            visible = origline.get_visible()
            text = "%s" % origline
            if text.find(self.lblSmoothy) > -1 : visible = self.chkSmoothy.isChecked()
            elif text.find(self.lblTendancy) > -1 : visible = self.chkTendency.isChecked()
            elif text.find(self.lblDeltaAverage) > -1 : visible = self.chkDeltaAverage.isChecked()
            origline.set_alpha(1.0 if visible else 0.0)

        i, counter = 1, len(self.legend.get_patches())
        for obj in self.legend.get_patches() :
            if i == counter :
                visible = self.chkDeltaAverage.isChecked()
                obj.set_alpha(1.0 if visible else 0.0)
            i+= 1
            
        self.legend.update
    
    def setVisibilityForSmoothy(self):
        self.linesmoothy.set_visible(self.chkSmoothy.isChecked())
        self.updateLegend()
        self.canvas.draw()
        
        
    def setVisibilityForAverage(self):
        cond = self.chkDeltaAverage.isChecked()
        self.average1ineup.set_visible(cond)
        self.averagefill.set_visible(cond)
        self.average1inedown.set_visible(cond)
        self.updateLegend()
        self.canvas.draw()
        
        
    def setVisibilityForTendancy(self):
        self.tendancy.set_visible(self.chkTendency.isChecked())
        self.updateLegend()
        self.canvas.draw()
        
    def updateImgForButton(self):
        icon = "stackplot.png" if self.chkStem.isChecked() else "stem.png"
        toolTip = "Basculer en représentation pleine" if self.chkStem.isChecked() else "Basculer en représentation par tiges"
        self.chkStem.setIcon(QIcon(getThemeIcon(icon)))
        self.chkStem.setToolTip(toolTip)
        self.plot()

    def format_coord(self, x, y):  return ""   

    def update_annot(self, ind, line, annot, ydata):
        x, y = line.get_data()
        annot.xy = (x[ind["ind"][0]], y[ind["ind"][0]])
       
        x_values = " ".join(list(map(str, ind["ind"])))
        y_values = " ".join(str(ydata[n]) for n in ind["ind"])

        dateObs = "%s" % x[ind["ind"][0]]
        dateObs = "%s%s" % (dateObs.replace(" ", "T"), "Z")
        dateObs = jsonDateToStrDate(dateObs, "", " ", None)
                               
        valueObs = round(float(y[ind["ind"][0]]), 3)
        
        text = "date : {}\nvaleur relevée : {}".format(dateObs, valueObs)
         
        annot.set_text(text)
        annot.get_bbox_patch().set_alpha(0.4)


    def hover(self, event, line_info):
        line, annot, ydata = line_info
        vis = annot.get_visible()
        ax = self.figure.get_axes()[0]

        if event.inaxes == ax:
            cont, ind = line.contains(event)
            if cont:
                self.update_annot(ind, line, annot, ydata)
                annot.set_visible(True)
                self.figure.canvas.draw_idle()
            else:
                if vis:
                    annot.set_visible(False)
                    self.figure.canvas.draw_idle()


    def openUrl(self):
        url = self.sender().whatsThis()
        try : webbrowseropen(url)
        except : pass


    def setDecorations(self) : 
        self.figure.text(0.12, 0.92, self.service, size=10, color='black')
        self.figure.text(0.12, 0.9, 'Mesures de station hydrométrique', size=8, color='black')
        self.figure.text(0.8, 0.05, 'données : www.hydro.eaufrance.fr', size=8, color='black')      
        
        fileLogoLeft = self.parent.fileLogoLeft 
        if exists(fileLogoLeft) :
           newaxlogoLeft = self.figure.add_axes([0.0, 0.8, 0.1, 0.2], anchor='NE', zorder=-1)  
           imgLogoLeft = mpimg.imread(fileLogoLeft)
           newaxlogoLeft.imshow(imgLogoLeft)
           newaxlogoLeft.axis('off')

        
        fileLogoRight = self.parent.fileLogoRight 
        if exists(fileLogoRight) :
           newaxlogoRight = self.figure.add_axes([0.9, 0.8, 0.1, 0.2], anchor='NW', zorder=-1)  
           imgLogoRight = mpimg.imread(fileLogoRight)
           newaxlogoRight.imshow(imgLogoRight)
           newaxlogoRight.axis('off')        

    def plot(self):

        self.figure.clear()
        ax = self.figure.add_subplot(111)

        if self.typeStats == "linechart0" :
           if self.typeInfos == "H" :
              self.basicData, = ax.plot_date(self.labels, self.data, linestyle="dotted",  label=self.lblOriginalData, marker="o", markersize=1.0, color='dodgerblue')
           else :
              self.basicData, = ax.plot_date(self.labels, self.data, linestyle="dotted", marker="o", markersize=1.0, color='dodgerblue')
              
           if self.typeInfos == "Q" :
              if self.chkStem.isChecked() :
                 markerline, stemlines, baseline = ax.stem(self.labels, self.data, label=self.lblOriginalData, linefmt='dodgerblue',  bottom=0.0, use_line_collection=True) 
                 markerline.set_markerfacecolor('none')
              else : ax.stackplot(self.labels, self.data, labels=[self.lblOriginalData], alpha=0.8, color='dodgerblue')
           
           formatter = mdates.DateFormatter("%d/%m/%Y %Hh%Mm")
           formatter.set_tzinfo(pytz.timezone(self.timezone)) 
           ax.xaxis.set_major_formatter(formatter)

           self.annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points",  bbox=dict(boxstyle="round", fc="w"), arrowprops=dict(arrowstyle="->"))
           self.annot.set_visible(False)

           label = "date d'observation - fuseau horaire : %s\npériode d'observation : %s" % (self.timezone, self.timelaps) 

           ax.set(xlabel=label, ylabel='Hauteurs (m)', title=self.title) if self.typeInfos == "H" else ax.set(xlabel=label, ylabel='Débit m3/s', title=self.title)
           self.figure.autofmt_xdate()
           ax.xaxis.set_tick_params(rotation=30, labelsize=10)
           ax.set_ylim([self.minY*0.75, self.maxY*1.25])

           #SET STATUS BAR INFO 
           ax.format_coord = self.format_coord

           for label in (ax.get_xticklabels() + ax.get_yticklabels()):
               label.set_fontname('Arial')
               label.set_fontsize(7)  

           def smooth(y, box_pts):
               box = np.ones(box_pts)/box_pts
               y_smooth = np.convolve(y, box, mode='same') 
               return y_smooth

           data = list(self.data)
           data.insert(0, self.data[0])
           y_smooth = smooth(data, 2) 
           labels = list(self.labels)
           labels.insert(0, self.labels[0])

           if self.stationDicoSeuils != {}:
              firstKey = True
              lineSeuil = []
              for key in self.stationDicoSeuils.keys():
                  valueSeuil = self.stationDicoSeuils[key]['valueSeuil']
                  commentsSeuilRDI = self.stationDicoSeuils[key]['commentsSeuilRDI'].replace("<br>", " ")
                  if firstKey : linestyle, linewidth, firstKey = "dashed", 1.5, False
                  else : linestyle, linewidth = "solid", 2.5 
                  line1 = ax.axhline(valueSeuil, color='red', alpha=.4, linestyle=linestyle, linewidth=linewidth, label="%s (%s)" % (commentsSeuilRDI, valueSeuil))
                  lineSeuil.append(line1)
                  ax.annotate("%s (%s)" % (commentsSeuilRDI, valueSeuil), xy=(0.5, 0.5), xycoords=line1,  xytext=(1, 0.5), textcoords="offset points", size=8, color='red')

           if not self.average :
              self.average = np.nanmean(data)
              obs = np.arange(len(labels))
              self.coeffReg, self.constanteReg = np.polyfit(obs, data, deg=1)
              self.ecartType = np.nanstd(data)

           obs = np.arange(len(labels))
           fit = np.polyfit(obs, data, deg=1)
           fit_function = np.poly1d(fit)
           self.tendancy, = ax.plot(labels, fit_function(obs), alpha=0.8, linestyle="dotted", linewidth=2.0, label=self.lblTendancy, color='navy')           
           
           smoothy = list(y_smooth)
           del labels[0]
           del smoothy[0]
           self.linesmoothy, = ax.plot(labels, smoothy, 'b-', color='navy', label=self.lblSmoothy)

           if self.average :
              self.average1ine = ax.axhline(self.average, color='olive', label='Moyenne (%s)' % self.average, alpha=.8, linestyle="dashed", linewidth=2.0)
              if self.ecartType : 
                  condAvg = self.chkDeltaAverage.isChecked() 
                  self.average1ineup = ax.axhline(self.average + self.ecartType, color='yellowgreen', alpha=.4, linestyle="dotted", linewidth=1.0, label=self.lblDeltaAverage)
                  self.average1inedown =  ax.axhline(self.average - self.ecartType, color='yellowgreen', alpha=.4, linestyle="dotted", linewidth=1.0)
                  self.averagefill = ax.fill_between(self.labels, self.average - self.ecartType, self.average + self.ecartType, label='Zone %s' % self.lblDeltaAverage.lower(), alpha=.2, linewidth=0, color='yellowgreen', hatch='/', zorder=10)

              self.averagenote = ax.annotate('Moyenne sur la période (%s)' % self.average, xy=(0.75, 0.5), xycoords=self.average1ine,  xytext=(1, 0.5), textcoords="offset points", size=8, color='olive')

           self.setDecorations()
           ax.grid(True)

        
        if self.typeStats == "linechart1" :
                
           ax = self.figure.add_subplot(111)
           ax.plot_date(self.labels, self.data)
           ax.set(xlabel="date d'observation", ylabel='Hauteurs (m)', title=self.title)
           self.figure.autofmt_xdate()
           ax.xaxis.set_tick_params(rotation=30, labelsize=10)
           self.setDecorations()
           ax.grid(True)

        self.ax = ax

        self.legend = ax.legend(loc='upper left', title="Légende :", title_fontsize='x-small', fontsize='small', fancybox=True, shadow=True) 
        self.legend.set_zorder(20)

        self.updateLegend()
       
        self.tendancy.set_visible(self.chkTendency.isChecked())
        self.linesmoothy.set_visible(self.chkSmoothy.isChecked())
        if self.average1ineup : self.average1ineup.set_visible(condAvg)
        else : self.chkDeltaAverage.setEnabled(False)        
        if self.average1inedown : self.average1inedown.set_visible(condAvg)
        if self.averagefill : self.averagefill.set_visible(condAvg)        
                
        self.canvas.draw()
        self.canvas.setToolTip("http://www.hydro.eaufrance.fr/stations/%s&procedure=synthese" % self.idHydro)

        self.annot = ax.annotate("", xy=(0, 0), xytext=(-20, 20), textcoords="offset points",  bbox=dict(boxstyle="round", fc="w"),  arrowprops=dict(arrowstyle="->"))
        self.annot.set_visible(False)
        line_info = [self.basicData, self.annot, self.data] 
        self.canvas.mpl_connect("motion_notify_event", lambda event: self.hover(event, line_info))


class printerPreview(object):
      def __init__(self, parent, obj, title, orientation, icon):
                 
              printer = QPrinter() 
              printer.setPageSize(QPrinter.A4)
              printer.setOrientation(orientation)
              printer.setPageMargins(5, 10, 5, 10, QPrinter.Millimeter)
              printer.setOrientation(orientation)
              printer.setOutputFormat(QPrinter.NativeFormat)
              self = QPrintPreviewDialog(printer)
              windowIcon(self, icon)
              self.setWindowTitle(title)
              self.setWindowFlags(Qt.WindowMaximizeButtonHint | Qt.WindowStaysOnTopHint | Qt.WindowCloseButtonHint)
              self.paintRequested.connect(obj.print_)
              self.exec_()        


class myMsgDlgBox(QMessageBox):
        
        def __init__(self, parent, icon, title, msg, image, iconWindow, contexte, ctrl, *args):
            super(myMsgDlgBox, self).__init__()
            self.setWindowTitle(title)
            self.setIcon(icon)
            self.parent = parent
                        
            windowIcon(self, iconWindow)

            groupBoxNodeInfos = QGroupBox(self)
            groupBoxNodeInfos.setFixedSize(420, 320)
            groupBoxNodeInfos.setObjectName("groupBoxNodeInfos")


            if contexte == 0 :
                labelImage = QLabel(groupBoxNodeInfos)
                icon = getThemeIcon(image)
                carIcon = QImage(icon)
                labelImage.setPixmap(QPixmap.fromImage(carIcon).scaled(400, 300))
                labelImage.setFixedSize(400, 300)
                labelImage.setGeometry(5, 5, 400, 300)

                lblText = QLabel(labelImage)
                lblText.setText(msg)
                lblText.setAlignment(Qt.AlignCenter)
                lblText.setStyleSheet("""color: black; background-color: rgba(255, 255, 125, 150);
                    border-left-width: 1px;
                    border-left-color: black;
                    border-left-style: solid;
                    border-right-color: black;
                    border-right-width: 1px;
                    border-right-style: solid;
                    border-right-radius: 3px;""")
                lblText.setFixedSize(390, 65)
                lblText.setGeometry(5, 110, 390, 65)
                
            else :
                labelImage = QTextBrowser(groupBoxNodeInfos)
                labelImage.setStyleSheet("QTextBrowser { border: 1px solid;    border-radius: 5px;    opacity: 0.5;    background-color: white;}")
                labelImage.setFixedSize(400, 300)
                labelImage.setGeometry(5, 5, 400, 300)
                labelImage.append(msg)

            acceptButton = QPushButton(self)
            acceptButton.setObjectName("acceptButton")
            acceptButton.setText("Oui") if contexte == 0 else acceptButton.setText("Ok")
            acceptButton.setFixedSize(100, 25)
            acceptButton.clicked.connect(self.accept)

            if contexte == 0 :
                rejectButton = QPushButton(self)
                rejectButton.setObjectName("rejectButton")
                rejectButton.setText("Non")
                rejectButton.setFixedSize(100, 25)
                rejectButton.clicked.connect(self.reject)

            self.layout().addWidget(groupBoxNodeInfos,0,1,1,2)
            self.layout().addWidget(acceptButton,4,1,1,1) if contexte == 0  else self.layout().addWidget(acceptButton,4,2,1,1)
            if contexte == 0 : self.layout().addWidget(rejectButton,4,2,1,1)

            self.setStandardButtons(self.NoButton)


class funnyPushButton(QPushButton):
     def __init__(self, *args): super(funnyPushButton, self).__init__(*args)
            
     def initPushButton(self, sizeWidth, sizeHeight, coordX, coordY, name, text, toolTip, isGeom, icon, iconWidth, iconHeight, isStyleSheeted, fully):
            setObjSizes(self, sizeWidth, sizeHeight)
            self.iconWidth = iconWidth 
            self.iconHeight = iconHeight
            self.fully = fully
            self.selfFocused = False
            self.subMenuVisble = False
            if isGeom : self.setGeometry(coordX, coordY, sizeWidth, sizeHeight)
            if icon != "" :
               self.setIcon(QIcon(icon))
               self.setIconSize(QSize(self.iconWidth , self.iconHeight))
            self.setToolTip(toolTip)
            if isStyleSheeted :
               if self.fully : self.setStyleSheet("QPushButton { border: 1px solid; border-radius: 5px; opacity: 0.5;  background-color: white;}"
                                                  "QPushButton::checked {icon-size: %spx, %spx; border-color: blue; border-width: 4px; border-radius: 5px; background-color:qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 0.5, stop: 0 #186dc1, stop: 1 #39eafd);}" % (iconWidth, iconHeight))
               else :  self.setStyleSheet("QPushButton {border-width: 1px; border-radius: 10px;  border-color: beige;}")
  
            self.setObjectName(name)
            if text != "" : self.setText(" %s" % text)

     def enterEvent(self, event):
          if not self.selfFocused :
             #NEW 3.6.0 
             self.setIconSize(QSize(int(self.iconWidth * 0.8), int(self.iconHeight * 0.8)))
             self.selfFocused = True
          
     def leaveEvent(self, event):
        if self.subMenuVisble == False : self.restoreButton()
     
     def restoreButton(self):
           self.selfFocused = False    
           self.setIconSize(QSize(self.iconWidth, self.iconHeight))


class myPushButton(QPushButton):
     def __init__(self, *args): super(myPushButton, self).__init__(*args)
            
     def initPushButton(self, sizeWidth, sizeHeight, coordX, coordY, name, text, toolTip, isGeom, icon, iconWidth, iconHeight, isStyleSheeted):
            setObjSizes(self, sizeWidth, sizeHeight)
            if isGeom : self.setGeometry(coordX, coordY, sizeWidth, sizeHeight)
            if icon != "" :
               self.setIcon(QIcon(icon))
               self.setIconSize(QSize(iconWidth, iconHeight))
            self.setToolTip(toolTip)
            if isStyleSheeted : self.setStyleSheet("QPushButton {border: none;}")
            self.setObjectName(name)
            if text != "" : self.setText(text)


class progressBar(QProgressBar):
    def __init__(self, * args):
        QProgressBar.__init__(self, * args)

    def initTextEdit(self, width, height, posX, posY, name, isGeom, initValue, viewText, alignement, isStyleSheet):
        setObjSizes(self, width, height)
        if isGeom: self.setGeometry(posX, posY, width, height)
        self.setProperty("value", initValue)
        self.setAlignment(alignement)
        self.setTextVisible(viewText)
        self.setObjectName(name)
        self.setAccessibleName(name)
        if isStyleSheet == 0:
            self.setStyleSheet(
                               "progressBar {border: 2px solid grey; border-radius: 5px; text-align: center;}"
                               "progressBar::chunk {background-color: #6C96C6; width: 10px; margin: 0.5px;}"
                               )


class textEditFolder(QTextEdit):
    def __init__(self, * args):
        QTextEdit.__init__(self, * args)

    def initTextEdit(self, SizeW, SizeH, posX, posY, name, isGeom, undoRedo, isReadOnly, isRichText, isSizable):
        if not isSizable:  self.setMaximumSize(SizeW, SizeH)
        if isGeom: self.setGeometry(posX, posY, SizeW, SizeH)
        self.setUndoRedoEnabled(undoRedo)
        self.setReadOnly(isReadOnly)
        self.setAcceptRichText(isRichText)
        self.setObjectName(name)
        self.setAccessibleName(name)
        self.setAcceptDrops(True)
        self.slotCtrl = None

    def setSlotCtrl(self, slotCtrl): self.slotCtrl = slotCtrl    
    def mouseDoubleClickEvent(self, event):
        if self.slotCtrl : self.slotCtrl.clicked.emit()
        


class simpleViewerWeb(QDialog):
      def  __init__(self, url, subTitle):
            super(simpleViewerWeb, self).__init__()
            
            self.setWindowTitle("Visualiseur Web - %s" % subTitle)
            self.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint)
            self.minWidth, self.minHeight = 1200, 900
            self.resize(self.minWidth, self.minHeight)

            windowIcon(self, "temporalseries.png")
            
            self.webViewer = QWebView(self) 
            self.webViewer.setObjectName("webViewer")
            self.webViewer.settings().setAttribute(QWebSettings.AutoLoadImages, True)
            self.webViewer.setContextMenuPolicy(Qt.CustomContextMenu)
            self.webViewer.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
                       
            self.closeButton = funnyPushButton(self)
            self.closeButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "closeButton", "", "Fermer", True, getThemeIcon("close.png"), 24, 24, True, False)

            self.closeButton.clicked.connect(self.reject)

            self.setUrl(url)

      def setUrl(self, url): self.webViewer.load(QUrl(url))

      def resizeEvent(self, ev):
          
          self.webViewer.setGeometry(5, 5, self.width()-10, self.height()-40)
          self.closeButton.setGeometry(self.width()-60, self.height()-30, 24, 24)

            
#NEW 4.0.0       
class reportingStations(QDialog):
      def  __init__(self, parent, iface, canvas, buttonFullStations, html, namePlugin):
            super(reportingStations, self).__init__()

            self.namePlugin = namePlugin
            self.setWindowTitle("%s : Bulletin des stations" % self.namePlugin)
            self.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint)
            self.minWidth, self.minHeight = 800, 600
            self.resize(self.minWidth, self.minHeight)

            self.tableFieldsStations = parent.tableFieldsStations
            #NEW 4.0.0
            self.htmlBulletin = ""
            self.refHtmlSynoptic = HTMLSTATISTICS 
            self.refHtmlSynopticJS = HTMLSTATISTICS 
            self.refHtmlSynopticJS+= HTMLGRAPHICSJS
            self.statisticsHtml, self.graphicsHtml, self.profilsHtml, self.meteoHtml = "", "", "", ""
            self.timezone = TIMEZONE

            windowIcon(self, "inondation.png")

            self.baseurl = QUrl('file:///')
            self.parent = parent
            self.iface = iface
            self.canvas = canvas

            #NEW 4.0.0 
            self.getLayerStation()
            self.getLayerTerrain()
            self.getLayerWater()
            
            self.buttonFullStations = buttonFullStations
            self.path = "%s/%s" % (dirname(__file__), "data/stations")
            self.stationIcon = QIcon(getThemeIcon("station.png"))
            self.allstationIcon = QIcon(getThemeIcon("inondation.png"))
            
            self.counter = 0
            self.replyObject = None
            self.dataStations = {}
            self.initDir = dirname(__file__)
            self.stations = {}
            self.typeInfos, self.lblTypeInfos, self.uniteInfos, self.titleTh = None, None, None, None
            self.infosExtraction = ""
     
            self.groupParams = QGroupBox(self)                       
            self.groupParams.setObjectName("groupParams")

            self.labelComboTypeInfos = QLabel(self.groupParams)
            self.labelComboTypeInfos.setObjectName("labelComboTypeInfos")
            self.labelComboTypeInfos.setText("Information : ")
            self.labelComboTypeInfos.setToolTip("Choix de l'information à rapatrier")
            self.labelComboTypeInfos.setAlignment(Qt.AlignRight)

            self.comboTypeInfos = QComboBox(self.groupParams)
            self.comboTypeInfos.setObjectName("comboTypeInfos")
            self.comboTypeInfos.setToolTip("Choix de l'information à rapatrier")
            self.comboTypeInfos.addItems(["Débit","Hauteur"])
            self.comboTypeInfos.setCurrentIndex(1)

            #NEW 4.0.0 - PLACE AFTER comboTypeInfos CREATED
            self.refreshConf()

            self.labelComboDate = QLabel(self.groupParams)
            self.labelComboDate.setObjectName("labelComboDate")
            self.labelComboDate.setText("Plage : ")
            self.labelComboDate.setToolTip("Choix de la plage temporelle")
            self.labelComboDate.setAlignment(Qt.AlignRight)

            self.comboDate = QComboBox(self.groupParams)
            self.comboDate.setObjectName("comboDate")
            self.comboDate.setToolTip("Choix de la plage temporelle")
            self.comboDate.addItems(["Dernières 3 heures", "Dernières 6 heures", "Dernières 12 heures", "Dernières 24 heures", "Dernières 48 heures",
                                     "Dernières 72 heures", "Dernière semaine", "Dernière quinzaine", "Derniers 31 jours"])

            self.timeDataSeries = (180, 360, 720, 1440, 2880, 4320, 10080, 20160, 44640)
            self.comboDate.setCurrentIndex(1)


            self.labelListStations = QLabel(self.groupParams)
            self.labelListStations.setObjectName("labelListStations")
            self.labelListStations.setText("Stations : ")
            self.labelListStations.setAlignment(Qt.AlignRight)

            self.model = QStandardItemModel()
            self.view = QTableView()
            self.view.setModel( self.model )
            self.view.horizontalHeader().setVisible(False)
            self.view.verticalHeader().setVisible(False)
            self.view.setColumnHidden(1, True)
            self.view.verticalHeader().setDefaultSectionSize(20)
            self.view.horizontalHeader().setDefaultSectionSize(500)
            self.view.setStyleSheet("QTableView {min-width: 400px; max-width: 500px; selection-background-color: qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 0.5, stop: 0 #FFFFFF, stop: 1 #000000);}")
            self.view.setShowGrid(False)

            self.listStations = QComboBox(self.groupParams) 
            self.listStations.setEditable(True)
            self.listStations.setEnabled(True)
            self.listStations.setObjectName("listStations")
            self.listStations.setModel(self.model)
            self.listStations.setView(self.view)

            locale.setlocale(locale.LC_TIME,'')

            self.lcdNumber = QLCDNumber(self)            
            self.lcdNumber.setObjectName("lcdNumber")
            self.lcdNumber.setSegmentStyle(QLCDNumber.Filled)
            self.lcdNumber.setStyleSheet("QLCDNumber {color: #c3c3c3; border: 2px solid grey; border-radius: 5px; background-color: #6C96C6; text-align: center;}")
            self.lcdNumber.setDigitCount(8)
            self.lcdNumber.display(thedatetime.now().strftime("%H:%M:%S")) 
            self.idTimer = self.startTimer(1000)

            #NEW 4.0.0
            self.chrono = funnyPushButton(self) 
            self.chrono.initPushButton(25, 25, 235, 5, "chrono", "", "Démarrer le robot", True, getThemeIcon("chrono/chronoplay.png"), 25, 25, True, False)

            fontchrono = QFont()
            fontchrono.setFamily('Courier')
            fontchrono.setFixedPitch(True)
            fontchrono.setPointSize(8)
            fontchrono.setBold(True)

            self.chronoInfo = QLabel(self) 
            carIcon = QImage(getThemeIcon("chrono/chronoinfo.png")) 
            self.chronoInfo.setPixmap(QPixmap.fromImage(carIcon))
            self.chronoInfo.setVisible(False)
            self.chronoInfo.setToolTip("")

            self.chronoInfoText = QLabel(self.chronoInfo)
            self.chronoInfoText.setAlignment(Qt.AlignLeft)
            self.chronoInfoText.setFont(fontchrono)
            self.chronoInfoText.setText("0")
            self.chronoInfoText.setGeometry(38, 12, 80, 20)
            self.chronoInfoText.setStyleSheet("QLabel {border: 2px solid grey; border-radius: 5px; background-color: #009900; color: white;}")

            self.stopChrono = funnyPushButton(self.chronoInfo)
            self.stopChrono.initPushButton(25, 25, 100, 10, "stopChrono", "", "Arrêter le robot", True, getThemeIcon("chrono/stopchrono.png"), 25, 25, True, False)
            self.isStartChrono = False

            self.labellcdNumber = QLabel(self)
            self.labellcdNumber.setObjectName("labellcdNumber")
            self.labellcdNumber.setText("%s %s" % (thedatetime.now().strftime("%a"), thedatetime.now().strftime("%d %B %Y").lstrip("0")))
            self.labellcdNumber.setAlignment(Qt.AlignCenter)
            self.labellcdNumber.setStyleSheet("QLabel {color: #FFF; border: 2px solid grey; border-radius: 5px; background-color: #6C96C6; text-align: center;}")

            self.buttonRefreshConf = funnyPushButton(self) 
            self.buttonRefreshConf.initPushButton(25, 25, 40, 90, "buttonRefreshConf", "", "Recharger la configuration RDI", True, getThemeIcon("refresh.png"), 25, 25, True, False)
            self.buttonListActions = funnyPushButton(self) 
            self.buttonListActions.initPushButton(25, 25, 40, 90, "buttonListActions", "", "Actions ...", True, getThemeIcon("actions.png"), 25, 25, True, False)

            self.contextMnuActions(self.buttonListActions)

            self.dataWebButton = funnyPushButton(self)
            self.dataWebButton.initPushButton(154, 25, self.width()-140,  self.height()-140, "dataWebButton", "", "Interroger les stations", True, getThemeIcon("download.png"), 154, 25, True, False)

            #NEW 4.0.0
            self.optionsHtml = QGroupBox(self)
            self.optionsHtml.setStyleSheet("QGroupBox {border: 2px solid grey; border-radius: 5px; background-color: #FFFFCC; color: black;}")
            self.buttongroup = QButtonGroup(self.optionsHtml)

            texts = ["Bulletin", "Statistiques", "Graphiques", "Profils", "Météo"]
            icons = ["legendstatic_off.png", "bulletin_off.png", "stem.png", "profil.png", "meteo.png"]

            i = 0
            pos = 2
            for text in texts :
                button = QPushButton(self.optionsHtml)
                button.setStatusTip(text)
                button.setCheckable(True)
                button.setChecked(True) if i == 0 else button.setChecked(False)
                self.buttongroup.addButton(button, i+1)
                self.buttongroup.setId(button, i+1)
                button.setObjectName("%sButton" % text.lower())
                button.setIcon(QIcon(getThemeIcon(icons[i])))
                button.setIconSize(QSize(18, 18))
                button.setToolTip("%s des stations" % text)
                button.setGeometry(pos, 2, 30, 25)
                i+= 1
                pos+= 30

            #NEW 4.0.0
            self.isPageLoaded = False
            
            self.busyIndicator = progressBar(self.groupParams)
            self.busyIndicator.setObjectName("busyIndicator")
            self.busyIndicator.setValue(0)
            self.busyIndicator.setVisible(False)               

            self.htmlReporting = QWebView(self) 
            self.htmlReporting.setObjectName("htmlReporting")
            self.htmlReporting.settings().setAttribute(QWebSettings.AutoLoadImages, True)
            self.htmlReporting.settings().setObjectCacheCapacities(0,0,0)
            self.htmlReporting.setContextMenuPolicy(Qt.CustomContextMenu)
            self.htmlReporting.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks) #DelegateAllLinks, DelegateExternalLinks)
            self.htmlReporting.settings().setAttribute(QWebSettings.DeveloperExtrasEnabled, True)
            self.htmlReporting.page().linkClicked.connect(self.getActions)

            self.htmlReporting.setHtml(html) 


            self.copyToClipBoardButton = funnyPushButton(self)
            self.copyToClipBoardButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "copyToClipBoardButton", "", "Copier vers le presse papier pour des applicatifs tiers", True, getThemeIcon("copytoclipboard.png"), 24, 24, True, False)

            self.printButton = funnyPushButton(self)
            self.printButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "printButton", "", "Imprimer", True, getThemeIcon("printer_off.png"), 24, 24, True, False)

            self.saveButton = funnyPushButton(self)
            self.saveButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "saveButton", "", "Sauvegarder l'onglet courant", True, getThemeIcon("save.png"), 24, 24, True, False)

            #NEW 4.0.0           
            self.saveAllButton = funnyPushButton(self)
            self.saveAllButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "saveAllButton", "", "Sauvegarder tous les onglets", True, getThemeIcon("saveall.png"), 24, 24, True, False)


            self.closeButton = funnyPushButton(self)
            self.closeButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "closeButton", "", "Fermer", True, getThemeIcon("close.png"), 24, 24, True, False)

            self.barInfo = QgsMessageBar(self)
            self.barInfo.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)

            self.comboTypeInfos.currentIndexChanged.connect(self.setOptionsForExtraction)

            self.dataWebButton.clicked.connect(self.getDataWeb)
            self.buttonRefreshConf.clicked.connect(self.refreshConf)
            self.copyToClipBoardButton.clicked.connect(self.exportReporting)
            self.saveButton.clicked.connect(self.saveReporting)
            #NEW 4.0.0
            self.saveAllButton.clicked.connect(self.saveAllReporting)
            self.printButton.clicked.connect(self.printReporting)
            self.closeButton.clicked.connect(self.reject)
            self.rejected.connect(self.closeDlg)

            #NEW 4.0.0
            self.buttongroup.idClicked.connect(self.setHtmlReport)
            self.chrono.clicked.connect(self.startStopChrono)
            self.stopChrono.clicked.connect(self.startStopChrono)

            self.updateListStations()
            self.initImgagesAndLabels()
            self.setOptionsForExtraction()

            QgsProject(self.iface).instance().cleared.connect(self.shutDown)

      #NEW 4.0.0
      def timerEvent(self, e):
          self.lcdNumber.display(QTime.currentTime().toString("hh:mm:ss"))
          if self.movieCtrl.isVisible() : self.movieCtrl.repaint()
          if self.isStartChronoSql :
               if ((thedatetime.now()-self.startTimeChrono).total_seconds() * 1000) >= self.deltaChrono :
                    self.startTimeChrono = thedatetime.now()
                    self.endTimeChrono = self.startTimeChrono + timedelta(minutes=self.deltaChronoInMinutes)
                    self.endTimeChronoText = thedatetime.strptime(self.endTimeChrono.replace(microsecond=0).strftime("%d/%m/%Y %Hh:%Mm:%Ss"), "%d/%m/%Y %Hh:%Mm:%Ss")
                    self.sendToConsole("sql", self.getTimeStamp(), "%s..." % QApplication.translate("QSphere","Execute SQL"), self.ipucestartChrono)
                    self.executeSql()

               diff = self.endTimeChronoText - thedatetime.strptime(thedatetime.now().replace(microsecond=0).strftime("%d/%m/%Y %Hh:%Mm:%Ss"), "%d/%m/%Y %Hh:%Mm:%Ss")
               self.chronoInfoText.setText("%s" % diff)

           
      def startStopChrono(self):
          if not self.isStartChrono :
                deltaChrono, ok = QInputDialog.getInt(None, "Paramètre", "<h2>%s :</h2><br>[%s]" % ("Intervalle de scrutation", "minutes"), 1, 0, 60, 1) #QLineEdit.Normal)
                if not ok: return
                if deltaChrono == 0 : return
              
                self.startTimeChrono = thedatetime.now()
                self.deltaChronoInMinutes = deltaChrono 
                self.endTimeChrono = self.startTimeChrono + timedelta(minutes=self.deltaChronoInMinutes)
                self.endTimeChronoText = thedatetime.strptime(self.endTimeChrono.replace(microsecond=0).strftime("%d/%m/%Y %Hh:%Mm:%Ss"), "%d/%m/%Y %Hh:%Mm:%Ss")
                
                diff = self.endTimeChronoText - thedatetime.strptime(thedatetime.now().replace(microsecond=0).strftime("%d/%m/%Y %Hh:%Mm:%Ss"), "%d/%m/%Y %Hh:%Mm:%Ss")
                self.chronoInfoText.setText("%s" % diff)
                
                self.deltaChrono = int(deltaChrono)*1000*60               
               
          self.isStartChrono = not(self.isStartChrono)             
          if self.isStartChrono :  
              self.chronoInfo.setToolTip("Scrutation toutes les %s minutes" % self.deltaChronoInMinutes if self.deltaChronoInMinutes > 1 else "Scrutation minute")
          else : self.chronoInfo.setToolTip("")    
          self.chronoInfo.setVisible(self.isStartChrono)


      def getLayerStation(self):
          self.layerStations = self.parent.layerStations
          layers = QgsProject.instance().mapLayersByName(self.layerStations)
          self.layer = layers[0] if layers != [] else None

          
      #NEW 4.0.0
      def getLayerTerrain(self):
          self.layerTerrain = self.parent.layerTerrain
          layers = QgsProject.instance().mapLayersByName(self.layerTerrain)
          self.layerTerrain = layers[0] if layers != [] else None

      def getLayerWater(self):
          self.layerWater = self.parent.layerWater
          layers = QgsProject.instance().mapLayersByName(self.layerWater)
          self.layerWater = layers[0] if layers != [] else None


      def refreshConf(self):
          self.getLayerStation()
          self.getLayerTerrain()
          self.getLayerWater()

          typeInfos = "Q" if self.comboTypeInfos.currentIndex() == 0 else "H"
          condProfils = self.layer != None and self.layerTerrain != None and self.layerWater != None and typeInfos != "Q"
          try : self.buttongroup.button(4).setEnabled(condProfils)
          except : pass
          
          self.fileLogoLeft = self.parent.fileLogoLeft
          self.fileLogoRight = self.parent.fileLogoRight
          self.titleService = self.parent.titleService
          self.noaction = self.parent.noaction
          self.movieCtrl = self.parent.movieCtrl
          self.pathLocalFolderStations = self.parent.pathLocalFolderStations
          self.namePlugin = self.parent.namePlugin
          self.timezone = self.parent.timezone

          try :  
              if self.sender() == self.buttonRefreshConf : self.getDataWeb()
          except : pass  

      def shutDown(self) :
          self.parent.dialogReportingStations = None
          self.close()

      #NEW 4.0.0  
      def timerEvent(self, e):
          self.lcdNumber.display(QTime.currentTime().toString("hh:mm:ss"))
          if self.isStartChrono :
               if ((thedatetime.now()-self.startTimeChrono).total_seconds() * 1000) >= self.deltaChrono :
                    self.startTimeChrono = thedatetime.now()
                    self.endTimeChrono = self.startTimeChrono + timedelta(minutes=self.deltaChronoInMinutes)
                    self.endTimeChronoText = thedatetime.strptime(self.endTimeChrono.replace(microsecond=0).strftime("%d/%m/%Y %Hh:%Mm:%Ss"), "%d/%m/%Y %Hh:%Mm:%Ss")                    
                    #EXECUTE THE ROBOT ACTION
                    self.getDataWeb() 

               diff = self.endTimeChronoText - thedatetime.strptime(thedatetime.now().replace(microsecond=0).strftime("%d/%m/%Y %Hh:%Mm:%Ss"), "%d/%m/%Y %Hh:%Mm:%Ss")
               self.chronoInfoText.setText("%s" % diff)

      def initImgagesAndLabels(self) :
          self.dictLegend = COMMENTOBSERVATIONS  
          self.commentsSeuilRDI = COMMENTSSEUILRDI 
          for key in IMAGESFORBULLETIN : setattr(self, key, join(dirname(__file__), IMAGESFORBULLETIN[key]).replace("\\","/"))
            

      def closeDlg(self):  changeButtonIcon(self, self.buttonFullStations,"bulletin_off.png")


      def selAllItems(self):
          model = self.listStations.model()
          for index in range(1, model.rowCount()):
              item = model.item(index)
              item.setData(Qt.Checked, Qt.CheckStateRole) 

                      
      def unSelAllItems(self):
          model = self.listStations.model()
          for index in range(1, model.rowCount()):
                  item = model.item(index)
                  item.setData(Qt.Unchecked, Qt.CheckStateRole) 
                  
      def invertSelItems(self):
          model = self.listStations.model()
          for index in range(1, model.rowCount()):
                  item = model.item(index)
                  if  item.checkState() == Qt.Unchecked : item.setData(Qt.Checked, Qt.CheckStateRole)
                  else : item.setData(Qt.Unchecked, Qt.CheckStateRole)

      def contextMnuActions(self, obj):

            actions = {"selectAll" : ("selectall.png", "Sélectionner toutes les stations", "" , self.selAllItems, True, "Ctrl+A"),
                       "unSelectAll" : ("unselectall.png", "Aucune station sélectionnée", "" , self.unSelAllItems, True, "Ctrl+Z"),
                       "invertSelected" : ("invertselection.png", "Inverser la sélection", "" , self.invertSelItems, True, "Ctrl+I"),
                       "~0" : (),
                       "reloadContext" : ("reload.png", "Recharger la liste des stations", "" , self.updateListStations, True, "Ctrl+R")
                       }          
          
            contextMenu = QMenu() 
            for key in actions :
                if key.startswith("~") : contextMenu.addSeparator()
                else :
                      icon = actions[key][0]
                      icon = getThemeIcon(icon)

                      action = QAction(QIcon(icon), actions[key][1], self)
                      
                      if actions[key][2] != "" : action.setShortcut(QKeySequence(actions[key][2]))
                      action.setEnabled(actions[key][4])
                      action.setObjectName(key)
                      action.setShortcut(QKeySequence(actions[key][5]))
                      contextMenu.addAction(action)
                      action.triggered.connect(actions[key][3])

                      setattr(self, key, action)

            obj.setMenu(contextMenu)


      def updateListStations(self):
          self.listStations.clear()
          self.getLayerStation()
          try :  
              if not self.layer : return
          except :
              self.parent.dialogReportingStations = None
              self.close()    
          if self.tableFieldsStations == {} : return

          fieldsNames = self.layer.fields().names()
          mapfieldsNames = map(lambda x:x.upper(), fieldsNames)

          #NEW 4.0.0
          filterWith  = self.parent.tableFieldsStations['FILTERWITH']
          if filterWith == "" : features = self.layer.getFeatures()
          else :
              exp = QgsExpression(filterWith.replace("{", "\"").replace("}", "\""))
              #exp = QgsExpression(" \"{}\" = '{}' ".format("DtFermetur", '0'))
              request = QgsFeatureRequest(exp)
              features = self.layer.getFeatures(request)
          
          self.listStations.addItem(self.allstationIcon, "Stations actives sélectionnées")

          fieldCdStationH = self.parent.tableFieldsStations['CdStationH']
          fieldLbStationH = self.parent.tableFieldsStations['LbStationH']
          fieldCdAncieRef = self.parent.tableFieldsStations['CdAncieRef']
          fieldSeuilRDI   = self.parent.tableFieldsStations['SEUIL_RDI']
 
          try : indexRdi = self.layer.fields().names().index(fieldSeuilRDI) 
          except : indexRdi = -1
          stations = {}

          transformer = getTransformer(self.layer.crs().authid(), 4326) 
          for feat in features:
              idStation = feat[fieldCdStationH]
              seuilRdi = feat[fieldSeuilRDI] if indexRdi >-1 else "Pas d'infos"
              #NEW 4.0.0
              point = feat.geometry().centroid().asPoint()    
              pointLL = transformer.transform(point, QgsCoordinateTransform.ForwardTransform) 
              longitude = pointLL.x()
              latitude =  pointLL.y()
              stations[idStation] = {"lbStation" : feat[fieldLbStationH], "idHydro" : feat[fieldCdAncieRef], "SEUIL_RDI" : seuilRdi if seuilRdi else "Pas d'infos", "latitude" : latitude, "longitude"  : longitude} 

          self.stations = stations
          orderedStations = dict(sorted(stations.items()))

          i = 1

          for idStation in orderedStations :
              lbStation, idHydro = orderedStations[idStation]["lbStation"], orderedStations[idStation]["idHydro"]
              self.listStations.addItem(self.stationIcon, "%s || %s || %s" % (idStation, lbStation, idHydro))
              item = self.listStations.model().item(i, 0)
              item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
              item.setData(Qt.Checked, Qt.CheckStateRole) 
              i+= 1  

          self.updateTextComboListStations()
          self.listStations.model().itemChanged.connect(self.updateTextComboListStations)

      def updateTextComboListStations(self):
          model = self.listStations.model()
          self.counter = 0
          for index in range(1, model.rowCount()):
              item = model.item(index)
              if  item.checkState() == Qt.Checked : self.counter+= 1
          
          item = self.listStations.model().item(0, 0)
          item.setText("Stations actives sélectionnées [%s]" % self.counter)

          self.dataWebButton.setEnabled(self.counter > 0)


      #NEW 4.0.0
      def getCommentaires(self, idStation, typeInfos, average, etendue, minY, maxY, coeffReg, ecartInterQuartile, q1, q3, percQ1, percQ3, ecartType, dateDeb, dateFin, stationDicoSeuils, date_obs, resultat_obs, unitInfos) :

          lblDateDeb = getLitteralDate(dateDeb, "", False, True)
          lblDateFin = getLitteralDate(dateFin, "", False, True)

          commentaires = "<u>Pour la période</u> :<br><br>- du %s<br>- au %s<br><br>\n" % (lblDateDeb, lblDateFin)

          comments, tendancea, titleImg2 = getCommentsForTrend(coeffReg, True)
          commentaires+= comments  
          commentaires+= "<br>"
          
          if ecartType >= 0 and ecartType <= 0.03 : commentaires+= "Les informations recueillies indiquent une très faible dispersion des mesures.\n"
          elif ecartType > 0.03 and ecartType <= 0.08 : commentaires+= "Les informations recueillies indiquent une  faible dispersion des mesures.\n"
          elif ecartType > 0.08 and ecartType <= 0.15 : commentaires+= "Les informations recueillies indiquent une dispersion des mesures.\n"
          elif ecartType > 0.15 and ecartType <= 0.22 : commentaires+= "Les informations recueillies indiquent une forte dispersion des mesures.\n"
          else : commentaires+= "Les informations recueillies indiquent une très forte dispersion des mesures.\n"

          commentaires+= "<br>\n"
          commentaires+= "%s %% des mesures se trouvent en deçà %s %s %s.\n" % (round(percQ1, 2) , "de la hauteur" if typeInfos == "H" else "du débit", round(q1, 3), " m" if typeInfos == "H" else " m3/s")
          commentaires+= "<br>\n"
          commentaires+= "%s %% des mesures se trouvent en deçà %s %s %s.\n" % (round(percQ3, 2), "de la hauteur" if typeInfos == "H" else "du débit", round(q3, 3), " m" if typeInfos == "H" else " m3/s")
          commentaires+= "<br>\n"

          ratiosSeuil = []
          lastObs = resultat_obs[0]

          dep = 0
          if stationDicoSeuils != {} :
             commentsSeuil, titleSeuil, valuesSeuil = [], [], []
             for seuil in stationDicoSeuils.keys():
                 commentSeuil = stationDicoSeuils[seuil]['commentsSeuilRDI'].replace("<br>", " ")
                 commentsSeuil.append(commentSeuil)
                 valueSeuil = stationDicoSeuils[seuil]['valueSeuil']
                 valuesSeuil.append(valueSeuil)
                 titleSeuil.append(commentSeuil)
                 ratioSeuil = round( (lastObs*100) / valueSeuil, 2 )
                 ratiosSeuil.append(ratioSeuil)


             counterDepassement = {}
             for valueSeuil in valuesSeuil :
                 counterSeuilDep = 0
                 for obs in resultat_obs :
                     if obs > valueSeuil : counterSeuilDep+= 1
                 counterDepassement[valueSeuil] = counterSeuilDep
    

             #ESTIMATION DEBIT OU HAUTEUR / SEUIL
             commentaires+= "<br>\n"
             commentaires+= "La station dispose de %s seuil%s RDI : <b>%s</b>.\n"  % (len(valuesSeuil), "s" if len(valuesSeuil) > 1 else "", [commentSeuil for commentSeuil in commentsSeuil])
             commentaires+= "<br>\n"
             commentaires+= "%s : <b>%s</b>.\n" % ( "La valeur du seuil est la suivante" if len(valuesSeuil) ==1 else "Les valeurs des seuils sont les suivantes", ["%s m" % valueSeuil if typeInfos == "H" else "%s m3/s" % valueSeuil for valueSeuil in valuesSeuil])

             i = 0
             for seuil in counterDepassement :
                 if counterDepassement[seuil] > 0 : commentaires+= "<br>Le seuil '<b><i>%s</i></b>' a été dépassé %s fois.\n""" % (titleSeuil[i], counterDepassement[seuil]); dep+= 1; i+= 1;
             if dep == 0 : commentaires+= "<br>Aucun seuil n'a été dépassé sur la période.\n"   
          else :

             commentaires+= "<br>\n"
             commentaires+= "La station ne dispose pas de seuils RDI.\n"     

          commentaires+= "<br>\n"

          commentaireStatut, imgStatut, imgInfoBulle = getCommentsForStatut(self, idStation, stationDicoSeuils, coeffReg, dep, ratiosSeuil, lastObs, unitInfos, typeInfos)
          commentaires+= commentaireStatut
          commentaires+= "<br>"

          return commentaires, dep, ratiosSeuil, imgStatut, commentaireStatut


      def getTitle(self, title) :
          html = "<div class='box-title'>\n"
          html+= "   <div class='ruban'>\n"
          html+= "        <h2>%s</h2>\n" % title
          html+= "     </div>\n"
          html+= "   <div class='ruban_gauche'></div>\n"
          html+= "</div>\n"
          html+= "<br><br>\n"
          return html



      #NEW 4.0.0        
      def getDataWeb(self):

          if self.counter == 0 : return

          self.layer.setSubsetString("")

          imgWaiting = "file:///%s/data/imgs/%s" % (dirname(__file__), "loading.gif")
          imgBuildingGraphics = "file:///%s/data/imgs/%s" % (dirname(__file__), "buildgraphics.gif")
          self.htmlReporting.setHtml("""<center><h4>Récupération des informations (stations & météo) ...</h4><br><img src="%s"></center>""" % imgWaiting)
  
          html = ""

          self.busyIndicator.setValue(0)

          try :  
              if not self.layer : return
          except :
              self.parent.dialogReportingStations = None
              self.close()    

          self.manager = QNetworkAccessManager(self.iface)          
          self.replyObject = None          

          typeInfos = "Q" if self.comboTypeInfos.currentIndex() == 0 else "H"
          unitInfos = "m3/s" if typeInfos == "Q" else "m"
          

          condProfils = self.layer != None and self.layerTerrain != None and self.layerWater != None and typeInfos != "Q"
          self.buttongroup.button(4).setEnabled(condProfils)
          
          size = [36, 72, 144, 288, 576, 864, 2016, 4032, 8928][self.comboDate.currentIndex()] 

          self.infosExtraction = """Type d'information : %s<br>""" % self.comboTypeInfos.currentText()
          self.infosExtraction+= """Période collectée  : %s<br>""" % self.comboDate.currentText()
          self.infosExtraction+= """Extraction du : %s""" % thedatetime.now().strftime("%d/%m/%Y à %Hh%M")

          #NEW 4.0.0 
          self.statisticsHtml = "<HTML><BODY><head><title>Statistiques des stations</title></head>"
          self.statisticsHtml+= self.refHtmlSynopticJS
          self.statisticsHtml+= HTMLSTYLESSTATISTICS

          self.graphicsHtml = "<HTML>\n<head>\n<title>Graphiques des stations</title>\n"  
          self.graphicsHtml+= "</head>\n"
          self.graphicsHtml+= self.refHtmlSynopticJS
          self.graphicsHtml+= HTMLSTYLESGRAPHICS

        

          self.profilsHtml = "<HTML>\n<head>\n<title>Profils des stations</title>\n"  
          self.profilsHtml+= "</head>\n"
          self.profilsHtml+= self.refHtmlSynopticJS
          self.profilsHtml+= HTMLSTYLESPROFILS

          self.meteoHtml = "<HTML>\n<head>\n<title>Météo des stations</title>\n"
          self.meteoHtml+= "</head>\n"
          self.meteoHtml+= "<style>\n"
          self.meteoHtml+= HTMLSTYLESMETEO

          self.meteoHtml+= "</style>\n"

          blocEnteteHtml = "<body>\n"
          blocEnteteHtml+= """<table width='100%%'>\n"""
          blocEnteteHtml+= """<tr>\n"""
          blocEnteteHtml+= """<td width='10%%'><img src='file:///%s' width='90px' height='90px'></td>\n""" % self.fileLogoLeft
          blocEnteteHtml+= """<td width='85%%'><center><h2>%s, [TITLE] des stations</h2>%s</center></td>\n""" % (self.titleService, getLitteralDate(thedatetime.now(), "le", False, True))
          blocEnteteHtml+= """<td width='5%%'><img src='file:///%s' width='170px' height='80px'></td></tr>\n""" %  self.fileLogoRight
          blocEnteteHtml+= "</table>\n" 


          html = "<HTML><BODY><head><title>Bulletin des stations</title></head>%s" % INFORMATIONSSTYLES 
          html+= blocEnteteHtml.replace("[TITLE]", "bulletin")
          
          self.statisticsHtml+= blocEnteteHtml.replace("[TITLE]", "Statistiques")
          self.graphicsHtml+= blocEnteteHtml.replace("[TITLE]", "Graphiques")
          self.profilsHtml+= blocEnteteHtml.replace("[TITLE]", "Profils")
          self.meteoHtml+= blocEnteteHtml.replace("[TITLE]", "météo")

          pathIconMeteo = join(dirname(__file__), "icons/meteo/")
          self.meteoHtml+= "<center>\n"
          self.meteoHtml+= "<table class='blueTable'>\n"
          self.meteoHtml+= "<tr>"
          self.meteoHtml+= "<th><img src='file:///%s/stations.png' class='icon'/>Station hydrométrique</th>\n" % pathIconMeteo
          self.meteoHtml+= "<th><img src='file:///%s/temperature.png' class='icon'/>&nbsp;Températures</th>\n" % pathIconMeteo 
          self.meteoHtml+= "<th><img src='file:///%s/precipitation.png' class='icon'/>&nbsp;Précipitations actuelles</th>\n" % pathIconMeteo
          self.meteoHtml+= "<th><img src='file:///%s/cloud_cover.png' class='icon'/>&nbsp;Couverture nuageuse</th>\n" % pathIconMeteo
          self.meteoHtml+= "<th><img src='file:///%s/vent.png' class='icon'/>&nbsp;Vitesse du vent</th>\n" % pathIconMeteo
          self.meteoHtml+= "<th><img src='file:///%s/direction.png' class='icon'/>&nbsp;Direction du vent</th>" % pathIconMeteo
          self.meteoHtml+= "</tr>\n"

 
          html+= """<style>
                    menu {display: flex; list-style: none; padding: 0;}
                    button {display:inline-block; background-color:#fff; border:#666 1px solid; margin:2px; font-size:24px; cursor:pointer; width:32px; height:32px; border-radius:50%;}
                    button:hover {background-color:#b5e61d; color:#ccc; box-shadow:3px 8px #888;}
                    ul {list-style-type: none;}

                    #stationsinfos { font-family: Montserrat, Arial, Helvetica, sans-serif; font-size: 0.8em;  border-collapse: collapse;  width: 100%;}
                    #stationsinfos td, #customers th {  border: 1px solid #FFFFFF;  padding: 8px;}
                    #stationsinfos td div {width: 200px; position: relative; z-index: 99;}
                    #stationsinfos tr:nth-child(even){background-color: #f2f2f2;}
                    #stationsinfos tr.error {background-color: #ff0000;}
                    #stationsinfos tr.noservice {background-color: #000000; color: #ffffff;}
                    #stationsinfos tr:hover {background-color: #ddd;}
                    #stationsinfos th {  padding-top: 12px;  padding-bottom: 12px;  text-align: left;  background-color: #04AA6D;  color: white;}
                    #stationsinfos th.title {  padding-top: 12px;  padding-bottom: 12px;  text-align: left;  background-color: #326a48;  color: white;}
                    #legende {background:#ffffcc; border: solid 1px #5D1212; border-collapse: separate; border-spacing: 0; border-radius: 16px;}
                    #legende tr.titlelegende {background-color: #04AA6D;  color: white; font-weight: bold;}

                    li a { text-decoration: none; color: black;}

                    .menu-deroulant {width: 60px;}
                    .menu-deroulant > a:after{ content: '>'; font-size: 1.5em; margin-left: 0px; display: inline-block;}
                    .sous-menu { z-index : 10; margin-left: -50px; margin-top: var(--hauteur-menu);font-size: 0.9em; width: 350px; text-align: left; background-color: white; border-radius: 2px; overflow: hidden; max-height: 0;}
                    .sous-menu > li > a {  font-size: 0.9em; height: 32px; padding-left: 0x; width: 200%; align-items: center; display: flex;}
                    nav > ul > li:hover > a{ color: #000; font-weight: bold;}
                    .sous-menu > li:hover { background-color: #CCC;}
                    .sous-menu > li:hover > a { color: white;}
                    @keyframes rotationFleche { 0% { transform: rotate(0deg); } 100%{ transform: rotate(90deg);} }
                    .menu-deroulant:hover > a:after{ animation: rotationFleche 0.2s linear forwards;}
                    @keyframes apparitionSousMenu { 0% { box-shadow: 0px 3px 3px 1px rgba(0,0,0,0); border-top: 3px solid #2169EC; }
                                                    30% { box-shadow: 0px 3px 3px 1px rgba(0,0,0,0.3);}
                                                    100% {max-height: 50em; border-top: 3px solid #2169EC; box-shadow: 0px 3px 3px 1px rgba(0,0,0,0.3);}
                    }
                    .menu-deroulant:hover > .sous-menu { animation: apparitionSousMenu 1s forwards;}

                    .titleDocAss {font-size: 12px; font-family: Montserrat, Arial, Helvetica, sans-serif; letter-spacing: 0.05em; font-weight: bold; background-color: #04AA6D;  color: white; width: 100%;}
               
                    </style>   
                 """

          
          html+= """<table width='100%%'>\n"""
          html+= """<tr><td colspan='6'><hr>\n"""
          html+= """<table id='stationsinfos' cellspacing=0 width='100%'>\n"""
          html+= """<tr>"""
          html+= """<th colspan='4' class='title'><h3><center>[%s] Stations hydrométriques</h3></center>\n""" % self.counter
          html+= """<center><small>%s</small></center></th>\n""" % self.infosExtraction
          html+= """<th colspan='2' scope='colgroup'><center>Dernière Mesure</center></th>\n"""
          html+= """<th colspan='2' scope='colgroup'><center>Mesure Max</center></th>\n"""
          html+= """<th class='title' colspan='4'><h3><center>Indicateurs</h3></th>\n"""
          html+= """</tr>\n"""
          html+= """<tr>\n"""
          html+= """<th></th>\n"""
          html+= """<th>Station</th>\n"""
          html+= """<th title='Délai entre deux interrogations de la station'><center><fonr style='font-size: 2.0em; font-weight: bold;'>&#9201;</font></center></th>\n""" 
          html+= """<th><center>Seuils<br>indicatifs RDI</center></th>\n"""
          html+= """<th><center>%s</center></th>\n""" % self.titleTh
          html+= """<th title="Heure locale"><center>Heure</center></th>\n"""
          html+= """<th><center>%s</center></th>\n""" % self.titleTh
          html+= """<th title="Heure locale"><center>Heure</center></th>\n"""
          html+= """<th title="Moyenne calculée sur la période d'extraction"><center>Moy.</center></th>\n"""
          html+= """<th title="Position par rapport à la moyenne sur la période d'extraction pour la dernière mesure"><center>Pos/Moy</center></th>\n"""
          html+= """<th title="Sens de l'évolution sur les 3 dernières heures d\'observation"><center>Ev.3h</center></th>\n"""
          html+= """<th title="Sens de l'évolution entre les 2 dernières mesures relevées"><center>Der.Ev.</center></th>\n"""
          html+= """</tr>"""
                    
          self.setEnabledCtrl(False)

          self.dataStations = {}

          i = 1
          model = self.listStations.model()
          self.index = 0
          nbTotal = model.rowCount()

          for index in range(1, nbTotal):

                  item = model.item(index)
                  if  item.checkState() == Qt.Checked :

                      infos = item.text().split(" || ")
                      idStation, lbStation, idHydro = infos[0], infos[1], infos[2]

                      url = APIHUBEAU % (idStation, typeInfos, size) 
                      loop = QEventLoop()
                      self.replyObject = self.manager.get(QNetworkRequest(QUrl(url)))
                      self.replyObject.finished.connect(loop.quit)
                      loop.exec_()

                      stationHtml = self.readContent(idStation, lbStation, idHydro, self.comboDate.currentIndex(), i, nbTotal)
                      html+= stationHtml

                      #NEW 4.0.0
                      latitude = self.stations[idStation]["latitude"]
                      longitude = self.stations[idStation]["longitude"]

                      url = APIMETEO % (latitude, longitude, self.parent.apimodelmeteo[1]) 
                      url = url.replace(" ", "%2F")  
                        
                      self.replyObject = self.manager.get(QNetworkRequest(QUrl(url)))
                      self.replyObject.finished.connect(loop.quit)
                      loop.exec_()

                      try  : answerAsText = eval(bytes(self.replyObject.readAll()).decode("utf-8"))
                      except : answerAsText = {}

                      if answerAsText != {} :
                          toolBarStation = self.getToolBar(idStation, lbStation, False, dicoHasKey(self.dataStations, idStation))
                          lblRainForce = getRainForce(answerAsText["current"]["precipitation"])
                          lblWindSpeed10m = getWindForce(answerAsText["current"]["wind_speed_10m"])
                          lblWindOrientation = getWindOrientation(answerAsText["current"]["wind_direction_10m"])
                          lblCloudCover = getCloudCover(answerAsText["current"]["cloud_cover"])  
                          
                          infosMeteo = """<tr>
                                             <td class='entite'>%s %s (%s)</td>
                                             <td><b>%s %s</b><br><br>ressentie : %s %s</td>
                                             <td>%s %s<br><br><i><b>%s</b></i></td>
                                             <td>%s %s<br><br><i><b>%s</b></i></td>
                                             <td>%s %s<br><br><i><b>%s</b></i></td>
                                             <td>%s %s<br><br><i><b>%s</b></i></td>
                                         </tr>\n""" % (toolBarStation, lbStation, idStation,
                                                       answerAsText["current"]["temperature_2m"], answerAsText["current_units"]["temperature_2m"],
                                                       answerAsText["current"]["apparent_temperature"], answerAsText["current_units"]["apparent_temperature"],
                                                       answerAsText["current"]["precipitation"], answerAsText["current_units"]["precipitation"], lblRainForce,
                                                       answerAsText["current"]["cloud_cover"], answerAsText["current_units"]["cloud_cover"], lblCloudCover,
                                                       answerAsText["current"]["wind_speed_10m"], answerAsText["current_units"]["wind_speed_10m"], lblWindSpeed10m,
                                                       answerAsText["current"]["wind_direction_10m"], answerAsText["current_units"]["wind_direction_10m"], lblWindOrientation) 
                          self.meteoHtml+= infosMeteo

                      else : self.meteoHtml+= "<tr><td class='entite' colspan='6'><center><font style='background-color: #FF0000; color: white; font-weight: bold;'><u>%s %s (%s) - Pas de retour du service.</u></center></font></td></tr>" %  (toolBarStation, lbStation, idStation) 
                                          

                      self.busyIndicator.setValue(int(100 * i/ self.counter))
                      self.busyIndicator.repaint()

                      i+= 1

          #NEW 4.0.0
          key = sorted(APIMODELMETEOITEMS.keys())[self.parent.apimodelmeteo[0]]
          organismeMeteo = APIMODELMETEOITEMS[key]["organisme"]
          
          self.meteoHtml+= "</table>\n"
          self.meteoHtml+= "<hr width='65%'>"
          self.meteoHtml+= "<span class='hint'><u>Source</u> : <b>API</b> <i>https://open-meteo.com/</i> - %s.</span>" % organismeMeteo
          self.meteoHtml+= "</center>\n"

          
          html+= "</table>\n"
          html+= "</td></tr>\n"
          html+= "<tr><td colspan='4'><hr></td></tr>\n"
          #BLOC Legend
          legende = "<u>Légende des indicateurs</u> :\n"
          legende+= "<ul><li><b>&#8600</b> : %s</li><li><b>&#8608</b> : %s</li><li><b>&#8599</b> : %s</li><li><b>Pas de contenu</b> : %s</li></ul> " % (self.dictLegend["e1"], self.dictLegend["e0"], self.dictLegend["e2"], self.dictLegend["e3"])

          html+= "<tr>"
          html+= "<td align=left><fonr style='font-size: 0.8em;'>%s</font></td>" % legende
          html+= "<td align=right valign='top' colspan='2'><fonr style='font-size: 0.8em;'><i>%s, édition le %s</i></font></td>\n" % (self.namePlugin, thedatetime.now().strftime("%d/%m/%Y à %Hh%M"))
          html+= "</tr>\n"
          html+= "</table>" 
          html+= "</body>\n</html>\n"

          #NEW 4.0.0                        
          maxAverage = 0
          for idStation in self.dataStations.keys() : maxAverage = max(maxAverage, self.dataStations[idStation]['average'])

          colorR = list(np.random.choice(range(256), size=self.counter))
          colorG = list(np.random.choice(range(256), size=self.counter))
          colorB = list(np.random.choice(range(256), size=self.counter))

          hexaColors, labels = [], []
          for i in range(len(colorR)):
              r, g, b = colorR[i], colorG[i], colorB[i]
              hexaColors.append("#{0:02x}{1:02x}{2:02x}".format(clamp(r), clamp(g), clamp(b)))
          
          averageStations = []
          dictDeltaMinutesOscultation = {}
          for idStation in self.dataStations.keys() :
              lbStation = self.dataStations[idStation]['lbStation']
              deltaMinutesOscultation = self.dataStations[idStation]['deltaMinutesOscultation']
              averageStations.append(self.dataStations[idStation]['average'])
              
              if not deltaMinutesOscultation in dictDeltaMinutesOscultation.keys(): dictDeltaMinutesOscultation[deltaMinutesOscultation] = {'counter' : 1, 'listStations' : [lbStation]}
              else :
                  dictDeltaMinutesOscultation[deltaMinutesOscultation]['counter']+= 1
                  dictDeltaMinutesOscultation[deltaMinutesOscultation]['listStations'].append(lbStation)

          averageDatas = round(np.nanmean(averageStations), 2) 
          countInfAverage = 0  
          for idStation in self.dataStations.keys() :
              if self.dataStations[idStation]['average'] < averageDatas : countInfAverage+= 1 
          #BUG : ZERO DIVISION - NO SERVICE FOR THE STATION !! 
          if len(self.dataStations.keys()) > 0.0 : ratioInfAverageDatas = round((100 * countInfAverage) / len(self.dataStations.keys()), 2)
          else : ratioInfAverageDatas = round((100 * countInfAverage) / 1, 2)

            
          #START BLOC SOME FIGURES
          self.statisticsHtml+= "<center>\n"
          self.statisticsHtml+= self.getTitle("Statistiques de service")
          self.statisticsHtml+= "<br>\n"
          self.statisticsHtml+= "<div class='tableFixHeaderChiffres'>\n"
          self.statisticsHtml+= "<details class='elite'><summary class='elite'><b>Quelques chiffres ...</b></summary>\n" #<details open>
          self.statisticsHtml+= "<table>\n"
          self.statisticsHtml+= "   <tr><td>\n"
          

          img = "file:///%s/data/imgs/%s" % (dirname(__file__), "hauteur.png" if typeInfos == "H" else "debit.png")
          img = img.replace("\\", "/")
          
          self.statisticsHtml+= """
          <div class="box-infos">
          <center>
          <table border=1 width='80%%'>
          <tr>
          <td width='25%%'>
              <table class="stat-box">
                <tr>
                    <td>
                    <span>
                    <img src="%s" title="Ratio de stations en deçà de la moyenne" width="24px" height="24px" />
                    </span></td>
                    <td><span>%s %s</span></td>
                    <td>%s%%</td>
                </tr>
                <tr>
                   <td colspan="3"><h2 class="number-fs">%s</h2></td>
                </tr>
                </table>
          </td>    
          """ % (img, self.comboTypeInfos.currentText(), "Moyenne" if typeInfos == "H" else "Moyen", ratioInfAverageDatas, averageDatas)

          infosDeltaMinutesOscultation, minDelatMinutes, counterAll, counterMinDeltaMinutes = "", 1000000, 0, 0 
          for deltaMinutesOscultation in dictDeltaMinutesOscultation.keys() :
              counter = dictDeltaMinutesOscultation[deltaMinutesOscultation]['counter']
              listStations = dictDeltaMinutesOscultation[deltaMinutesOscultation]['listStations']
              infosDeltaMinutesOscultation+= "<u><b>%s minutes</b></u> : %s %s<br>" % (deltaMinutesOscultation, counter, listStations)
              minDelatMinutes = min(minDelatMinutes, int(deltaMinutesOscultation))
              counterAll+= counter
              if minDelatMinutes == int(deltaMinutesOscultation) : counterMinDeltaMinutes = counter


          tauxResponse = round((counterAll * 100 )/ self.counter, 2) if self.counter > 0 else 0
          imgFleche = "%s/data/imgs/%s" % (dirname(__file__), "arrow-right.svg")
          imgFleche = imgFleche.replace("\\", "/")

          self.statisticsHtml+= """</td>
          <td width='25%%'>
              <table class="stat-box">
                <tr>
                <td><span>Taux de réponse des stations</span></td>
                <td><span>
                    <img src="file:///%s" alt="" />.
                  </span>
                </td>
                </tr>
                <tr>
                <td colspan="2"><h2 class="number-fs">%s %%</h2></td>
                </tr> 
              </table>
          </td>
          """ % (imgFleche, tauxResponse)

          self.statisticsHtml+= """
          <td width='40%'>
              <table class="stat-box">
               <tr>
                <td>
          """    
          if counterAll == 0 : counterAll = 1  
          ratioDeltaMinutes = round((counterMinDeltaMinutes*100)/counterAll ,2)    
          img = "%s/data/imgs/%s" % (dirname(__file__), "arrow-up-green.svg" if ratioDeltaMinutes > 50.0 else "arrow-down-red.svg")
          img = img.replace("\\", "/")

          self.statisticsHtml+= """
                    &#9201;</td>
                    <td>Délai de scrutation - %s</td>
                    <td>
                    <span class="stat-percentage red">
                    <img src="file:///%s" alt="" />%s%%
                    </span>
                    </td>
                </tr>
                <tr>
                <td colspan="3" style="text-align: justify;">
                <h2 class="infos-fs">""" % (minDelatMinutes, img, ratioDeltaMinutes )

          self.statisticsHtml+= infosDeltaMinutesOscultation  
          self.statisticsHtml+="""  </h2>
            </td>
          </tr>
          </tbody>
          </table>
          </td>
          </tr>
          </table>
          </center>
          </div>
          </td></tr>
          </table>
          </details>
          </div>
          """
          #END BLOC SOME FIGURES

          #TABLEAU STATISTIQUES ET SEUILS - APPRECIATION DISPERSION POPULATION MESURES
          """   
          dico = {'typeStats' : typeStats, 'resultat_obs' : resultat_obs, 'date_obs' : date_obs, 'title' :  title,
                  'img' : img, 'minY' : minY, 'maxY' : maxY, 'theMaxDate' : theMaxDate, 'theLastDateHtml' : theLastDateHtml,
                  'typeInfos' : typeInfos, 'idHydro' : idHydro, 'lbStation' : lbStation,'average' : average,
                  'ecarttype' : ecartType, 'coeffReg' : coeffReg, 'constanteReg' : constanteReg, 'stationDicoSeuils' : stationDicoSeuils, 'IC95' : round(IC95, 2)}
          """
          #START BLOC STATISTICS
          self.statisticsHtml+= self.getTitle("Statistiques des mesures")

          #TEMPO
          img = "file:///%s/data/imgs/%s" % (dirname(__file__), "puce_-1.png")
          self.statisticsHtml+= "<br><img src='%s' class='iconLegend' title='%s'>" % (img, "Pas de seuil RDI")
          img = "file:///%s/data/imgs/%s" % (dirname(__file__), "puce_0.png")
          self.statisticsHtml+= "&nbsp;<img src='%s' class='iconLegend' title='%s'>" % (img, "Pas de dépassement.")
          img = "file:///%s/data/imgs/%s" % (dirname(__file__), "puce_1.png")
          self.statisticsHtml+= "&nbsp;<img src='%s' class='iconLegend' title='%s'>" % (img, "Seuil de vigilance RDI dépassé.")
          img = "file:///%s/data/imgs/%s" % (dirname(__file__), "puce_1.1.png")
          self.statisticsHtml+= "&nbsp;<img src='%s' class='iconLegend' title='%s'>" % (img, "Proche du 1er seuil de débordement.")
          img = "file:///%s/data/imgs/%s" % (dirname(__file__), "puce_2.png")
          self.statisticsHtml+= "&nbsp;<img src='%s' class='iconLegend' title='%s'>" % (img, "Seuil de 1er débordement dépassé.")
          img = "file:///%s/data/imgs/%s" % (dirname(__file__), "puce_2.1.png")
          self.statisticsHtml+= "&nbsp;<img src='%s' class='iconLegend' title='%s'>" % (img, "Proche du seuil critique.")
          img = "file:///%s/data/imgs/%s" % (dirname(__file__), "puce_3.png")
          self.statisticsHtml+= "&nbsp;<img src='%s' class='iconLegend' title='%s'>" % (img, "Seuil critique dépassé.")

          self.statisticsHtml+= "<br><br>\n"
          self.statisticsHtml+= "<div class='tableFixHeader'>\n"
          self.statisticsHtml+= "  <table>\n"
          self.statisticsHtml+= "     <thead>\n"
          self.statisticsHtml+= "        <tr>\n"
          self.statisticsHtml+= "            <th class='titleStation' colspan='6'><center><b>Station</b></center></th>\n"
          self.statisticsHtml+= "        </tr>\n"          
          self.statisticsHtml+= "        <tr>\n"
          self.statisticsHtml+= "            <th class='indicateur1'>Etendue<br>min - max</th>\n"
          self.statisticsHtml+= "            <th class='indicateur2'>Plage<br>temporelle</th>\n"
          self.statisticsHtml+= "            <th class='indicateur3'>Moyenne</th>\n"
          self.statisticsHtml+= "            <th class='indicateur4'>Ecart-Type<br>ET</th>\n"
          self.statisticsHtml+= "            <th class='indicateur5'>Ecart-InterQuartile<br>(EI)</th>\n"
          self.statisticsHtml+= "            <th class='texte7'>Commentaires</th>\n"
          self.statisticsHtml+= "        </tr>\n"
          self.statisticsHtml+= "     </thead>\n"
          self.statisticsHtml+= "  </table>\n"
          self.statisticsHtml+= "</div>\n"
          self.statisticsHtml+= "<div class='tableFixHead'>\n"


          img = "file:///%s/data/imgs/%s" % (dirname(__file__), "hauteur.png" if typeInfos == "H" else "debit.png")
          img = img.replace("\\", "/")
          lblTypeInfo = self.comboTypeInfos.currentText()          
          if typeInfos == "Q" : uniteMesure, labelLastObs = "m3/s", "dernier"
          else :  uniteMesure, labelLastObs = "m", "dernière" 
          
          icol = 1  
          for idStation in self.dataStations.keys() :
              infosStation = self.dataStations[idStation]
              lbStation = infosStation['lbStation']
              resultat_obs = infosStation['resultat_obs']
              date_obs = infosStation['date_obs']
              theMaxDate = infosStation['theMaxDate'].replace("<br>", "\n")
              minY = infosStation['minY']
              maxY = infosStation['maxY']
              average = infosStation['average']
              coeffReg = infosStation['coeffReg']
              ecartType = infosStation['ecarttype']
              stationDicoSeuils = infosStation['stationDicoSeuils']
              lastObs = resultat_obs[0]
              deltaMinutesOscultation = infosStation['deltaMinutesOscultation']
              IC95 = infosStation['IC95']
              
              lblDateObs = getLitteralDate(date_obs[0], "le")
              etendue = round((maxY-minY),3)

              q1, q2, q3 = getQuartiles(resultat_obs)
              ecartInterQuartile = q3-q1

              data0 = [val for val in resultat_obs if val >= minY and val < q1] 
              data1 = [val for val in resultat_obs if val >= q1 and val < q2]
              data2 = [val for val in resultat_obs if val >= q2 and val < q3]
              data3 = [val for val in resultat_obs if val >= q3]

              nbData0, nbData1, nbData2, nbData3, nbDataTotal = len(data0), len(data1), len(data2), len(data3), len(resultat_obs)
              ratio0 = round(100 * nbData0 / nbDataTotal, 2)
              theoRatio = [25, 50, 75]
              ratio1 = round(100 * nbData1 / nbDataTotal, 2)
              ratio2 = round(100 * nbData2 / nbDataTotal, 2)
              ratio3 = round(100 * nbData3 / nbDataTotal, 2)
              ratioTotal = 100 #" - "self.isPageLoaded

              average0 = getMean(data0, 3) if data0 != [] else 0
              average1 = getMean(data1, 3) if data1 != [] else 0
              average2 = getMean(data2, 3) if data2 != [] else 0
              average3 = getMean(data3, 3) if data3 != [] else 0

              ecartType0 = getStDev(data0, 3) if data0 != [] else 0
              IC950 = round(average0-(1.96 * (ecartType0 / sqrt(len(data0)))), 2) if data0 != [] else 0
              intervalleConf0 = [round(average0-IC950, 2), round(average0+IC950, 2)] if data0 != [] else "[ - , - ]"

              ecartType1 = getStDev(data1, 3) if data1 != [] else 0
              IC951 = round(average1-(1.96 * (ecartType1 / sqrt(len(data1)))), 2) if data1 != [] else 0
              intervalleConf1 = [round(average1-IC951, 2), round(average1+IC951, 2)] if data1 != [] else "[ - , - ]"
              
              ecartType2 = getStDev(data2, 3) if data2 != [] else 0
              IC952 = round(average2-(1.96 * (ecartType2 / sqrt(len(data2)))), 2) if data2 != [] else 0
              intervalleConf2 = [round(average2-IC952, 2), round(average2+IC952, 2)] if data2 != [] else "[ - , - ]"

              ecartType3 = getStDev(data3, 3) if data3 != [] else 0
              IC953 = round(average3-(1.96 * (ecartType3 / sqrt(len(data3)))), 2) if data3 != [] else 0
              intervalleConf3 = [round(average3-IC953, 2), round(average3+IC953, 2)] if data3 != [] else "[ - , - ]"

              intervalleConf = [round(average-IC95, 2), round(average+IC95, 2)]

              commentaires, dep, ratiosSeuil, imgStatut, commentaireStatut = self.getCommentaires(idStation, typeInfos, average, etendue, minY, maxY, coeffReg, ecartInterQuartile, q1, q3, ratio0, (ratio0+ratio1+ratio2), ecartType, date_obs[-1], date_obs[0], stationDicoSeuils, date_obs, resultat_obs, unitInfos)

             
              #SEUILS BLOCK 
              if stationDicoSeuils != {} :

                  htmlSeuils = "<br>\n<center>\n<table border=2 width='90%'>\n"
                  htmlSeuils+= "<tr>\n"

                  ratiosSeuil = []  
                  
                  for seuil in stationDicoSeuils.keys() :
                      valueSeuil = stationDicoSeuils[seuil]['valueSeuil']
                      commentsSeuilRDI = stationDicoSeuils[seuil]['commentsSeuilRDI'].replace("<br>", " ")
                      ratioSeuil = round( (lastObs *100) / valueSeuil, 2 )
                      ratiosSeuil.append(ratioSeuil)

                      htmlSeuils+= "<tr style='height: 15px;'>\n"
                      htmlSeuils+= "   <td style='width : 25%%; text-align: right;'><small>%s %s : %s (%s %%)</small></td>\n"  % (valueSeuil, uniteMesure, commentsSeuilRDI, ratioSeuil)
                      htmlSeuils+= "   <td width='75%'>\n"
                      htmlSeuils+= "       <div class='month-bar-light-back' style='width: 100%;height: 15px;'></div>\n"""
                      htmlSeuils+= "       <div class='month-bar-light' style='width: %s%%;height: 15px;'></div>\n" % int(ratioSeuil)
                      htmlSeuils+= "   </td>\n"
                      htmlSeuils+= "</tr>\n"

                  commentaireStatut, imgStatut, lblImgStatut = getCommentsForStatut(self, idStation, stationDicoSeuils, coeffReg, dep, ratiosSeuil, lastObs, uniteMesure, typeInfos)
                  
                  htmlSeuils+= "</table>\n</center>\n"
                  htmlSeuils+= "<br>\n"

              else : commentaireStatut, imgStatut, lblImgStatut = getCommentsForStatut(self, idStation, stationDicoSeuils, coeffReg, dep, ratiosSeuil, lastObs, uniteMesure, typeInfos)


              toolBarStation = self.getToolBar(idStation, lbStation, False, dicoHasKey(self.dataStations, idStation))    
              
              self.statisticsHtml+= "<details class='elite'><summary class='elite'><b>%s <img src='%s' class='icon' title='%s'></b></summary>\n" % (lbStation, imgStatut, lblImgStatut) 
              self.statisticsHtml+= "<table>\n"
              self.statisticsHtml+= "<tbody>\n"
            
              self.statisticsHtml+= "<tr class='titleStation'>\n"
              self.statisticsHtml+=  "   <td>%s</td><td class='texte1' colspan='5'><img src='%s' class='icon' title='%s'> <small>%s %s %s %s, %s</small></td>\n" % (toolBarStation, imgStatut, lblImgStatut, labelLastObs, lblTypeInfo, lastObs, uniteMesure, lblDateObs) #NOM DE LA STATION
              self.statisticsHtml+= "</tr>\n"
              self.statisticsHtml+= "<tr>\n"
              self.statisticsHtml+=  "   <td class='indicateur1'>%s<br><small>%s - %s</small></td>\n" % (etendue, minY, maxY) #ETENDUE - MIN - MAX
              self.statisticsHtml+=  "   <td class='indicateur2'><small>%s<br>%s</small></td>\n" % (date_obs[-1], date_obs[0])#PLAGE TEMPORELLE
              self.statisticsHtml+=  "   <td class='indicateur3'>%s</td>\n" %  average #MOYENNE
              self.statisticsHtml+=  "   <td class='indicateur4'>%s</td>\n" %  ecartType #ECART-TYPE
              self.statisticsHtml+=  "   <td class='indicateur5'>%s</td>\n"  % round(ecartInterQuartile, 3) #ECART-INTERQUARTILE
              self.statisticsHtml+=  "   <td class='texte7'>%s</td>\n" %  commentaires #COMMENTAIRES
              self.statisticsHtml+= "</tr>\n"              
              self.statisticsHtml+= "<tr>\n"
              self.statisticsHtml+=  "   <td colspan='6' style='background-color: #ddd; text-align: center;'><center>Statistiques détaillées :</center></td>\n"
              self.statisticsHtml+= "</tr>\n"
              self.statisticsHtml+= "<tr>\n"
              self.statisticsHtml+=  "   <td class='texte1' colspan='6'>\n"
              self.statisticsHtml+=  "      <table style='font-size: smaller;'>\n"
              self.statisticsHtml+=  "        <tr style='background-color: #1C6EA4; box-shadow: 0px 5px 14px 0px #0e1635; color: white;'><td style='text-align: right; margin-right: 2px;'>Intervalle :</td><td>Valeur minimale</td><td>Qrt.1</td><td>Qrt.2</td><td>Qrt.3</td><td>Total</td></tr>\n"
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Valeur :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td> - </td></tr>\n" % (minY, q1, q2, q3)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Intervalle :</td><td>[ %s - %s [</td><td>[ %s - %s [</td><td>[ %s - %s [</td><td>[ %s - %s ]</td><td>[ %s - %s ]</td></tr>\n" % (minY, q1, q1, q2, q2, q3, q3, maxY, minY, maxY)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Sous-population :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (nbData0, nbData1, nbData2, nbData3, nbDataTotal)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Part de la sous-population :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (ratio0, ratio1, ratio2, ratio3, ratioTotal)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Moyenne pour la sous-population :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (average0, average1, average2, average3, average)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Ecart-type pour la sous-population :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (ecartType0, ecartType1, ecartType2, ecartType3, ecartType)
              self.statisticsHtml+=  "        <tr><td style='background-color: #1C6EA4; color: white; text-align: right; margin-right: 2px;'>Indice de confiance (IC95)<br>sous-population :</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n" % (IC950, IC951, IC952, IC953, IC95)
              self.statisticsHtml+=  "      </table>\n"  
              self.statisticsHtml+=  "   </td>\n"
              self.statisticsHtml+= "</tr>\n"

              if stationDicoSeuils != {} :
                 for seuil in stationDicoSeuils.keys() :
                     valueSeuil = stationDicoSeuils[seuil]['valueSeuil']
                     commentsSeuilRDI = stationDicoSeuils[seuil]['commentsSeuilRDI'].replace("<br>", " ")

                     subHtml, nbDebord = seriesUpSeuils(date_obs, resultat_obs, deltaMinutesOscultation, valueSeuil, commentsSeuilRDI, uniteMesure)   

                     self.statisticsHtml+= "<tr>\n"
                     self.statisticsHtml+=  "   <td colspan='6' style='background-color: #ddd; text-align: center;'><small>%s</small></td>\n" % subHtml
                     self.statisticsHtml+= "</tr>\n"

                     if nbDebord == 0 : break   


              if stationDicoSeuils != {} :  
                  self.statisticsHtml+= "<tr>\n"
                  self.statisticsHtml+= "<td colspan='6'>\n"
                  self.statisticsHtml+= htmlSeuils
                  self.statisticsHtml+= "</td>\n"
                  self.statisticsHtml+= "</tr>\n"

              self.statisticsHtml+= "</table>\n"  
              self.statisticsHtml+= "</details>\n"           

              self.statisticsHtml+= "<tr><td colspan=6 class='cellStation' height='5px'></td></tr>\n"

          self.statisticsHtml+= "</tbody>\n"  
          self.statisticsHtml+= "</table>\n"
          self.statisticsHtml+= "</div>\n"
          self.statisticsHtml+= "</center>\n"
          #END OLD STATISTICS 

          self.statisticsHtml+= """<br>\n"""
          #END BLOC STATISTICS
          #FIN TABLEAU STATISTIQUES - APPRECIATION DISPERSION POPULATION MESURES
          

          #GRAPHIQUE MOYENNE DES STATIONS RETIRE ICI 
          #START BLOC GRAPHIC BY STATION
          dimObs = -1
          if self.comboDate.currentIndex() < 3 : labelObs = "Série courte : %s" % self.comboDate.currentText()
          elif self.comboDate.currentIndex() >= 3 and self.comboDate.currentIndex() < 6 : dimObs, labelObs = 150, "Série moyenne : %s" % self.comboDate.currentText()
          elif self.comboDate.currentIndex() >= 6 : dimObs, labelObs = 300, "Série longue : %s" % self.comboDate.currentText()

          self.graphicsHtml+= "<center>\n"
          self.graphicsHtml+= self.getTitle("<h2>Graphiques des stations (%s)</h2>" % labelObs)
          self.graphicsHtml+= "<table width='90%'><tr><td>\n"
          ic = 0
          
          for idStation in self.dataStations.keys() :

              resultat_obs = self.dataStations[idStation]['resultat_obs']
              totalObs = len(resultat_obs)
              sDimObs = len(resultat_obs) if dimObs == -1 else min(len(resultat_obs), dimObs)

              self.graphicsHtml+= """<details open>\n"""
              self.graphicsHtml+= """<summary class="summaryTheme">\n"""
              
              date_obs = self.dataStations[idStation]['date_obs'][:sDimObs]
              lbStation = self.dataStations[idStation]['lbStation']
              resultat_obs = self.dataStations[idStation]['resultat_obs'][:sDimObs]
              stationDicoSeuils = self.dataStations[idStation]['stationDicoSeuils'] 

              labels, mesures = [], []
              for i in range(len(date_obs)):
                  labels.insert(0, date_obs[i].strftime("%d/%m/%Y à %Hh%M"))
                  mesures.insert(0, resultat_obs[i])

              #GET TOOLBAR ACTIONS SIMPLIFY
              toolBarStation = self.getToolBar(idStation, lbStation, False, dicoHasKey(self.dataStations, idStation))
              dateDeb = getLitteralDate(date_obs[-1], "")
              dateFin = getLitteralDate(date_obs[0], "")
              
              self.graphicsHtml+= """%s [%s] - %s dernières mesures sur %s totales. Du %s au %s.\n</summary>""" % (lbStation, idStation, sDimObs, totalObs, dateDeb, dateFin)
              self.graphicsHtml+= """<br>\n<br>\n<br>\n<br>\n<table class="graphes">\n"""
              self.graphicsHtml+= """<tr style='vertical-align: top;'><td>%s</td><td>\n"""  % toolBarStation
              self.graphicsHtml+= """   <p class="post-content-wrapper">\n"""
              self.graphicsHtml+= """   <canvas id="line-chart-%s" name="echo" style="width:800px; height:400px;"></canvas>\n""" % ic
              self.graphicsHtml+= "     </p>\n"
              self.graphicsHtml+= "<script language='javascript'>\n"
              self.graphicsHtml+= """new Chart(document.getElementById("line-chart-%s"), {\n""" % ic
              self.graphicsHtml+= "    type: 'line',\n"
              self.graphicsHtml+= "    data: {\n"
              self.graphicsHtml+= "      labels: %s,\n" % labels
              self.graphicsHtml+= "      datasets: [\n"
              self.graphicsHtml+= "{\n"
              self.graphicsHtml+= "data: %s,\n" % mesures
              self.graphicsHtml+= """label: "%s [%s]",\n""" % (lbStation, idStation)
              self.graphicsHtml+= """borderColor: "#4bf8ed",\n""" #% hexaColors[ic]
              self.graphicsHtml+= "fill: false}\n" 


              #NEW 4.0.0
              #ADD SEUILS INFORMATIONS
              maxMesures = max(mesures)
              
              colorsSeuil = ["#FEFF05", "#FF7B05", "#FF0000"]
              icolors = 0
              if stationDicoSeuils != {} :
                 for key in stationDicoSeuils.keys():                     
                     mesures = []
                     valueSeuil = stationDicoSeuils[key]['valueSeuil']

                     commentsSeuilRDI = stationDicoSeuils[key]['commentsSeuilRDI'].replace("<br>", " ")
                                          
                     if valueSeuil != -1 :
                        for i in range(len(date_obs)): mesures.insert(0, valueSeuil)                        
                        condVisibility = testSeuilVisibility(idStation, valueSeuil, maxMesures)
                        
                        self.graphicsHtml+= ",\n"
                        self.graphicsHtml+= "{\n"
                        self.graphicsHtml+= "data: %s,\n" % mesures
                        self.graphicsHtml+= """label: "%s [%s]",\n""" % (commentsSeuilRDI, valueSeuil)
                        self.graphicsHtml+= """borderColor: "%s",\n""" % (colorsSeuil[icolors])
                        self.graphicsHtml+= "showLine: true,\n"
                        self.graphicsHtml+= "hidden: %s,\n" % condVisibility
                        self.graphicsHtml+= "fill: false}\n"
                        icolors+= 1 if icolors < 3 else 0


              self.graphicsHtml+= "     ]\n"
              self.graphicsHtml+= "   },\n"              
              self.graphicsHtml+= "   options: {title: {display: true, text: 'Courbe des stations (%s)' }\n" % ("hauteur en m" if typeInfos == "H" else "débit m3/s")
              self.graphicsHtml+= "}\n"
              self.graphicsHtml+= "});\n"
              self.graphicsHtml+= "</script>\n"
              self.graphicsHtml+= """</td></tr></table>\n"""
              self.graphicsHtml+= """</details>\n"""
              self.graphicsHtml+= """<br>\n"""

              ic+= 1
              #END BLOC GRAPHIC
              
          self.graphicsHtml+= "</td>\n</tr>\n</table>\n<br>\n"
          self.graphicsHtml+= "</center>\n"

          self.statisticsHtml+= "</body>\n"

          #START BLOC PROFILS STATIONS
          fieldCdStationH = self.tableFieldsStations['CdStationH']
          if condProfils :
              
              newlayer = QgsVectorLayer("Point?crs=EPSG:2154", "projPoint", "memory")
              provider = newlayer.dataProvider()
              newlayer.startEditing()
              feature = QgsFeature()
              
              mem_layer_data = newlayer.dataProvider()
              attr = self.layer.dataProvider().fields().toList()
              mem_layer_data.addAttributes(attr)
              newlayer.updateFields()

              condCrs = (self.layer.crs() == self.layerTerrain.crs())
              transformCrs = None
              if not condCrs : transformCrs = QgsCoordinateTransform(self.layer.crs(), self.layerTerrain.crs(), QgsProject().instance())

              self.profilsHtml+= "<center>\n"
              self.profilsHtml+= self.getTitle("<h2>Profils en travers des stations</h2>")

              ic, listStations = 0, []

              #METTRE UN RECHARGEMENT A CHAUD PARAMETRES !!   
              model = self.listStations.model()
              self.index = 0

              for index in range(1, nbTotal):
                  item = model.item(index)
                  if  item.checkState() == Qt.Checked :
                      infos = item.text().split(" || ")
                      idStation, lbStation, idHydro = infos[0], infos[1], infos[2]
                      listStations.append(idStation)
              
              vlayerLines = QgsVectorLayer('LineString?crs=epsg:2154', 'lines', 'memory')
              proLayerLines = vlayerLines.dataProvider()
              proLayerLines.addAttributes(attr)
              vlayerLines.updateFields()
              featLayerLines = QgsFeature()
              
              for station in listStations :
                  self.layer.setSubsetString("")
                  sexp = "\"{}\" = '{}'".format(fieldCdStationH, station)
                  self.layer.setSubsetString(sexp)

                  for feat in self.layer.getFeatures():
                    idStation = station
                    infosStation = self.dataStations[idStation] if dicoHasKey(self.dataStations, idStation) else []
                    lastObs = infosStation['resultat_obs'][0] if infosStation != [] else 0 
                    lbStation = feat["LbStationH"].replace("'", "&rsquo;")
                    stationDicoSeuils = self.dataStations[idStation]['stationDicoSeuils'] if dicoHasKey(self.dataStations, idStation) else {}
                    
                    geom  =  feat.geometry()
                    point = feat.geometry().asPoint()
                    attributes = feat.attributes()
                      
                         
                    line = None
                    #SELECT BY LOCATION
                    result = bufferPlus(self.layer, distance=25, output=r'TEMPORARY_OUTPUT')
                    bufferLayer = result['OUTPUT']
                    
                    result = processing.run("native:selectbylocation", { 'INPUT' : self.layerWater, 
                                                                         'INTERSECT' : bufferLayer, 
                                                                         'METHOD' : 0, 'PREDICATE' : [0] })
                    
                    for feat in self.layerWater.selectedFeatures() : line = feat.geometry(); break

                    if line :

                        self.profilsHtml+= "<details open>\n"
                        self.profilsHtml+= "<summary class='summaryTheme'>\n"
                        self.profilsHtml+= "Profil de la station %s [%s]" % (lbStation, idStation)
                        self.profilsHtml+= "</summary><br>\n"
                        self.profilsHtml+= "<br>\n"
                        self.profilsHtml+= "<br>\n"
                        self.profilsHtml+= "<br>\n"
                                                  
                        newPoint = line.nearestPoint(geom) 
                        
                        hauteurRef = 0
                        if newPoint :
                           altiRef = getAltiPoint(self.layerTerrain, geom.asPoint(), condCrs, transformCrs)
                           hauteurRef = altiRef + lastObs
                        
                        feature.setGeometry(newPoint)
                        feature.setAttributes(attributes)
                        newlayer.dataProvider().addFeatures([ feature ] )
                        newlayer.updateExtents()
                        
                        newLine = QgsGeometry().fromPolyline([QgsPoint(point.x(), point.y()), QgsPoint(newPoint.asPoint().x(), newPoint.asPoint().y())]) 
                        v_layer = QgsVectorLayer('LineString?crs=epsg:2154', 'line %s' % station, 'memory')
                        pr = v_layer.dataProvider()
                        seg = QgsFeature()
                        seg.setGeometry(newLine)
                        pr.addFeatures([ seg ])
                        v_layer.commitChanges()
                       
                        distance = ceil(geom.distance(newPoint))
                        #TEMPO
                        alg_params = {'START_DISTANCE' : (200 - distance),
                                      'END_DISTANCE' : 200,
                                      'INPUT' : v_layer,
                                      'OUTPUT' : 'TEMPORARY_OUTPUT' }
                        result = processing.run('native:extendlines', alg_params)
                        layerLine = result['OUTPUT']
                        
                        for featLine in layerLine.getFeatures() : geomLine = featLine.geometry()
                        featLayerLines.setGeometry(geomLine)
                        featLayerLines.setAttributes(attributes)
                        proLayerLines.addFeatures([featLayerLines])

                        result = processing.run("native:pointsalonglines",{'DISTANCE' : 1, 
                                                                           'END_OFFSET' : 0, 
                                                                           'INPUT' : layerLine,  
                                                                           'OUTPUT' : 'TEMPORARY_OUTPUT', 
                                                                           'START_OFFSET' : 0 })

                        layerResult = result['OUTPUT']
                        altitudesProfil = []
                        for featPoints in layerResult.getFeatures() :
                            geomPoint = featPoints.geometry().asPoint()
                            altiPoint = getAltiPoint(self.layerTerrain, geomPoint, condCrs, transformCrs)
                            altitudesProfil.append(round(altiPoint, 2))

                        labels, posX, hauteurs = [], [], []
                        for i in range(len(altitudesProfil)): 
                            labels.append('')
                            posX.append(i)
                            hauteurs.append(0) if hauteurRef < altitudesProfil[i] else  hauteurs.append(round(hauteurRef,2))


                        toolBarStation = self.getToolBar(idStation, lbStation, False, dicoHasKey(self.dataStations, idStation))

                        #TEST
                        posX, altitudesProfil = lissage(posX, altitudesProfil, 4)
                        
                        self.profilsHtml+= """<table class="graphes">\n"""
                        self.profilsHtml+= """<tr style='vertical-align: top;'><td>%s</td><td>\n"""  % toolBarStation
                        self.profilsHtml+= """   <p class="post-content-wrapper">\n"""
                        self.profilsHtml+= """   <canvas id="line-chart-%s" name="echo" style="width:800px; height:400px;"></canvas>\n""" % ic
                        self.profilsHtml+= "     </p>\n"                
                        self.profilsHtml+= "<script language='javascript'>\n"
                        self.profilsHtml+= """new Chart(document.getElementById("line-chart-%s"), {\n""" % ic
                        self.profilsHtml+= "    type: 'line',\n"
                        self.profilsHtml+= "    title : { text : 'Profil du terrain station %s [%s]'},\n" % (lbStation, idStation)

                        
                        self.profilsHtml+= "    data: {\n"
                        self.profilsHtml+= "       datasets: [{\n"
                        self.profilsHtml+= "           label: 'Niveau eau (PB + H)',\n"
                        self.profilsHtml+= "           data: %s,\n" % hauteurs
                        self.profilsHtml+= "           type: 'line',\n"
                        self.profilsHtml+= "           pointRadius: 0,\n"
                        self.profilsHtml+= """           backgroundColor: "#5262F3",\n"""
                        self.profilsHtml+= "           fill: true\n"
                        self.profilsHtml+= "           }, {\n"                
                        self.profilsHtml+= "           label: 'Terrain (MNT)',\n"
                        self.profilsHtml+= "           data: %s,\n" % altitudesProfil
                        self.profilsHtml+= "           type:'line',\n"
                        self.profilsHtml+= """           borderColor: "rgba(0,0,0,100)",\n"""
                        self.profilsHtml+= "           borderWidth: 2,\n"
                        self.profilsHtml+= "           pointRadius: 0,\n"
                        self.profilsHtml+= "           fill: true}\n"


                        maxMesures = max(mesures)
                      
                        colorsSeuil = ["#FEFF05", "#FF7B05", "#FF0000"]
                        icolors = 0
                        if stationDicoSeuils != {} :
                          for key in stationDicoSeuils.keys():                     
                             mesures = []
                             valueSeuil = stationDicoSeuils[key]['valueSeuil']
                             valueSeuilOnMnt = round(valueSeuil + altiRef, 2)

                             commentsSeuilRDI = stationDicoSeuils[key]['commentsSeuilRDI'].replace("<br>", " ")
                                                  
                             if valueSeuil != -1 :
                                for i in range(len(hauteurs)): mesures.insert(0, valueSeuilOnMnt)                        
                                condVisibility = testSeuilVisibility(idStation, valueSeuilOnMnt, maxMesures)
                                
                                self.profilsHtml+= ",\n"
                                self.profilsHtml+= "{\n"
                                self.profilsHtml+= "data: %s,\n" % mesures
                                self.profilsHtml+= """label: "%s [%s, PB + S : %s]",\n""" % (commentsSeuilRDI, valueSeuil, valueSeuilOnMnt)
                                self.profilsHtml+= """borderColor: "%s",\n""" % (colorsSeuil[icolors])
                                self.profilsHtml+= "pointRadius: 0,\n"
                                self.profilsHtml+= "showLine: true,\n"
                                self.profilsHtml+= "hidden: false,\n" #% condVisibility
                                self.profilsHtml+= "fill: false}\n"
                                icolors+= 1 if icolors < 3 else 0



                        self.profilsHtml+= "           ],//end data sets\n"
                        self.profilsHtml+= "           labels: %s\n" % labels
                        self.profilsHtml+= "    }, // END DATA\n"
                        

                        minY = 0 if floor(min(altitudesProfil)) <= 10 else floor(min(altitudesProfil))-10
                        
                        self.profilsHtml+= "options: {\n"
                        self.profilsHtml+= "    scales: {\n"
                        self.profilsHtml+= "    xAxes: [{ gridLines: {display: false }}],\n"
                        self.profilsHtml+= "    yAxes: [{ gridLines: { display: true }}],\n"
                        self.profilsHtml+= "    yAxes: [{ ticks: {min: %s, max: %s, stepSize: 100}}]\n" % (minY, floor(max(altitudesProfil))+25)
                        self.profilsHtml+= "    }\n"
                        self.profilsHtml+= "}\n"
                        
                                                
                        self.profilsHtml+= "           });\n"
                        self.profilsHtml+= "           </script>\n"


                        profilStationImg = "%s/L_%s.webp" % (self.path, idStation)
                        if not exists(profilStationImg) : profilStationImg = "%s/noimg.jpg" % (self.path)
                        
                        self.profilsHtml+= "</td>\n"
                        self.profilsHtml+= "<td><u>H : Dernière hauteur relevée</u> : %sm<br>\n" % lastObs
                        self.profilsHtml+= "<u>PB : Point bas (projeté station sur cours d'eau)</u> : %sm<br>\n" % round(altiRef, 2)
                        self.profilsHtml+= "<u>PB + H : Point bas + dernière hauteur relevée</u> : %sm<br><br>\n" % round(altiRef + lastObs, 2)
                        if exists(profilStationImg) :
                           self.profilsHtml+= "<img src='file:///%s' width='640px' height='366px'>" % profilStationImg 
                        self.profilsHtml+= "</td>\n</tr>\n</table>\n<br>\n" 
                        self.profilsHtml+= "</td></tr></table>\n"
                        self.profilsHtml+= "</details>\n"
                        self.profilsHtml+= "<br>\n"

                        ic+= 1

              if self.layerWater : self.layerWater.removeSelection()
              self.layer.setSubsetString("")


          self.setEnabledCtrl(True)
          #NEW 4.0.0
          self.htmlBulletin = html

          if self.parent.logConsole : self.exportReporting()

          self.setHtmlReport()
          

      #NEW 4.0.0
      def getToolBar(self, idStation, lbStation, withName, hasKey):
          html = "<div><menu>\n<table class='toolbar'>\n"
          html+= "<tr>\n"
          html+= "<td class='toolbartd'><a href='VIEWSTATION?%s'><button><img src='file:///%s' width='16px' height='16px' title='Localiser la station sur la carte'></button></a></td>\n" % (idStation, self.viewstation)
          if hasKey :
              html+= "<td class='toolbartd'><a href='VIEWGRAPH?%s'><button><img src='file:///%s' width='16px' height='16px' title='Visualiser le graphique sur la période d&#39;extraction'></button></a></td>\n" % (idStation, self.graphique)
              html+= "<td class='toolbartd'><a href='VIEWDATA?%s'><button><img src='file:///%s' width='16px' height='16px' title='Tableau des données sur la période d&#39;extraction'></button></a></td>\n" % (idStation, self.tabledata)
          if withName : html+= "<td class='toolbartdStation'>%s</td>\n" % lbStation
          html+= "</tr>\n"
          html+= "</table>\n</menu></div>\n"
          return html
          

          
      def setHtmlReport(self): 
          if self.buttongroup.checkedId() == 2 : self.htmlReporting.setHtml(self.statisticsHtml)
          elif self.buttongroup.checkedId() == 3 : self.htmlReporting.setHtml(self.graphicsHtml)
          elif self.buttongroup.checkedId() == 4 : self.htmlReporting.setHtml(self.profilsHtml)
          elif self.buttongroup.checkedId() == 5 : self.htmlReporting.setHtml(self.meteoHtml)
          else : self.htmlReporting.setHtml(self.htmlBulletin)
          self.setWindowTitle("%s : %s des stations" % (self.namePlugin, self.buttongroup.checkedButton().statusTip()))
        

      def setEnabledCtrl(self, cond): 
          self.dataWebButton.setEnabled(cond)
          #NEW 4.0.0
          self.chrono.setVisible(cond)
          self.chronoInfo.setEnabled(cond)
          self.optionsHtml.setEnabled(cond)
          self.htmlReporting.setEnabled(cond)
          self.buttonRefreshConf.setEnabled(cond)
          self.copyToClipBoardButton.setEnabled(cond)
          self.saveButton.setEnabled(cond)
          self.saveAllButton.setEnabled(cond)
          self.printButton.setEnabled(cond)
          self.busyIndicator.setVisible(not cond)
          
          
      def getHtmlForDicoSeuils(self, idStation, valueInfo, withColor, lastData, fontColor):
         
          SEUIL_RDI = self.stations[idStation]['SEUIL_RDI']
          dicoSeuils = None
          stationDicoSeuils = {}
          color = " style='background-color : #CCC ;'" if withColor else ""
          title = ""
          try : dicoSeuils = ast.literal_eval(SEUIL_RDI)
          except : pass

          htmlDicoSeuils = "<table id='stationsinfos' border='0' width='100%'>\n"

          if withColor : depSeuil, valueDep, indexDep = None, None, -1

          if dicoSeuils :
             i = 1
             htmlDicoSeuilsTH, htmlDicoSeuilsTD = "", ""

             #FIRST SCRUTATION TO FIX RATIO COLUMNS
             nbCols = 1
             for key in dicoSeuils.keys() :
                 if key == "S%s%s" % (self.lblTypeInfos, i) :
                    valueSeuil = dicoSeuils[key]
                    if valueSeuil > -1 :
                       nKey = key.replace(self.lblTypeInfos, "")
                       nKeyDico = 'S3' if i > 3 else nKey
                       stationDicoSeuils[nKey] = { "valueSeuil" : valueSeuil, "commentsSeuilRDI" : COMMENTSSEUILRDI[nKeyDico]}  

                       nbCols+= 1

                    i+= 1   
             espaceCol = int(100 / nbCols)

             color50, color65, color75, color85, color100 = "#a5d1ff", "#81bcff", "#3679f5", "#FCA487", "#F4665F" 
             bgColor = "#E7F1F5" 
             
             i = 1
             for key in dicoSeuils.keys() :
                 if key == "S%s%s" % (self.lblTypeInfos, i) :
                    valueSeuil = dicoSeuils[key]
                    if valueSeuil > -1 :
                       if withColor and lastData :

                           if float(lastData) > valueSeuil : color = " style='background-color : %s ;'" % color100 ; title = " title='Le seuil S%s a été dépassé.'" % i ; depSeuil = True ; valueDep = valueSeuil; indexDep = i;
                           else :
                             seuilColor = int((float(lastData) / valueSeuil) * 100)
                             
                             if seuilColor < 50 : color = color50
                             elif 50<= seuilColor < 65 :  color = color65
                             elif 65<= seuilColor < 75 :  color = color75
                             elif 75<= seuilColor < 85 :  color = color85
                             else : color = color100
                             
                             color = " style='background : linear-gradient(to top, "+color+" 0%, "+color+" "+str(seuilColor)+"%, "+bgColor+" "+str(seuilColor+1)+"%, "+bgColor+" 100%);'"
                             title = " title='Dernière mesure en deçà du seuil S%s (%s%%).'" % (i, seuilColor)
                             depSeuil = False

                       subTitle = ""
                       nKey = key.replace(self.lblTypeInfos, "")
                       valueIndex = int(nKey.replace("S", ""))
                       if valueIndex > 3 : nKey = 'S3'
                       subTitle = COMMENTSSEUILRDI[nKey] 
     
                       htmlDicoSeuilsTH+= "<th%s width='%s%%'><b><center>%s</center></b></th>\n" % (title, espaceCol, subTitle)
                       htmlDicoSeuilsTD+= "<td%s%s><center>%s %s</center</td>\n" % (color, title, valueSeuil, self.uniteInfos)

                    i+= 1
             if htmlDicoSeuilsTH == "" : htmlDicoSeuils+= "<tr><td><font style='%s : black;'><center>Pas d'infos</center></font></td></tr>\n" % fontColor   
             else : htmlDicoSeuils+= "<tr>%s</tr>\n<tr>%s</tr>\n" % (htmlDicoSeuilsTH, htmlDicoSeuilsTD)
           
          else : htmlDicoSeuils+= "<tr><td><font style='color : %s;'><center>Pas d'infos</center></font></td></tr>\n" % fontColor  
          
          htmlDicoSeuils+= "</table>"          

          return htmlDicoSeuils, stationDicoSeuils  


      def setOptionsForExtraction(self):
          if self.comboTypeInfos.currentIndex()== 0 :
             self.typeInfos, self.lblTypeInfos, self.uniteInfos, self.titleTh = "Q", "D", "m<sup>3</sup>/s", "Débit<br>(m<sup>3</sup>/s)"
          else :    
             self.typeInfos, self.lblTypeInfos, self.uniteInfos, self.titleTh = "H", "H", "m", "Hauteur<br>(m)"
          #NEW 4.0.0
          index = self.buttongroup.checkedId()
          self.buttongroup.button(4).setEnabled(self.typeInfos == "H")
          if index == 4 : self.buttongroup.button(1).setChecked(True) 

      def getHtmlError(self, typeClass, idStation, idHydro, lbStation, img, lblError):
          htmlDicoSeuils, stationDicoSeuils = self.getHtmlForDicoSeuils(idStation, None, False, None, 'white')   

          link = "'https://www.vigicrues.gouv.fr/niv3-station.php?CdEntVigiCru=26&CdStationHydro=%s&GrdSerie=%s&ZoomInitial=3'" % (idStation, self.typeInfos)

          html = """<tr class='%s'>\n""" % typeClass
          html+= """<td><div><menu>\n"""
          html+= """<li><a href='VIEWSTATION?%s'><button><img src='file:///%s' width='18' height='18' title='Localiser la station sur la carte'></button></a></li>\n""" % (idStation, img)
          html+= self.subHTmlDocAss(idStation, lbStation, idHydro, self.typeInfos, -1)
          html+= """</div></menu></td>"""
          html+= """<td><a href='%s' target='_blank' title='Consulter Vigicrues'>%s</a> - %s<br><img src='file:///%s' width='24' height='24'> %s</td>\n""" % (link, idStation, lbStation, self.warning, lblError)
          html+= """<td><center> - </center></td>\n"""
          html+= """<td style='background-color : #000 ;'><center><b><u>%s</u></b></center></td>\n""" % htmlDicoSeuils
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          html+= """<td><center> - </center></td>\n"""
          
          return  html


      #NEW 4.0.0
      def imgTest(self, imgStation, counterStation, idStation, lbStation, uCase):
          html = ""
          imgStation = imgStation.replace("\\", "/")
          if uCase == 0 : #ERROR
              if exists(imgStation) :
                    html+= """<td class='stationNoView'><img src="file:///%s" title="%s" class="imgWithout"> %s</td>\n""" % (imgStation, "Pas de données trouvées dans la réponse du service web pour la station %s." % lbStation, lbStation)
              else :
                    html+= """<td class='stationNoView'><img src="file:///%s/noimg.jpg" title="%s" class="imgWithout"> %s</td>\n""" % (self.path, "Pas de données trouvées dans la réponse du service web pour la station %s." % lbStation, lbStation) 
          elif uCase == 1 : #VIEWGRAPH
              html+= "<td class='stationView'>\n"
              html+= self.getToolBar(idStation, lbStation, True)
              html+= "</td>\n" 
          if counterStation in (4, 8, 12, 16, 20, 24) : html+= "</tr><tr>\n"
          return html  
                  

      def readContent(self, idStation, lbStation, idHydro, iMaxTime, counterStation, nbTotal):
          html = ""
          answerAsText = None  
          answerAsText = bytes(self.replyObject.readAll()).decode("utf-8")
          datenow = thedatetime.now().strftime("%d/%m/%Y à %Hh%M")
          typeInfos = "Q" if self.comboTypeInfos.currentIndex()== 0 else "H"
         
          img = "%s/%s.jpg" % (self.path, idStation)
          img = img.replace("\\", "/")
          title = "Station %s : %s" % (idStation, lbStation)

          if not answerAsText :
             #NEW 4.0.0
             html = self.getHtmlError('error', idStation, idHydro, lbStation, self.viewstation, "<b><i>Service non joignable.</i></b>");
             return html
  
          else : 
                denum = 1000
                try : json_data = json.loads(answerAsText)
                except :
                        html = self.getHtmlError('error', idStation, idHydro, lbStation, self.viewstation, "<b><i>Format JSON de la réponse incorrect.</i></b>");
                        return html
                  

                if  dicoHasKey(json_data, "count") :
                    if int(json_data["count"]) == 0 :
                     html = self.getHtmlError('noservice', idStation, idHydro, lbStation, self.viewstation, "<b><i>Station non couverte pour cette information.</i></b>") #self.noservice
                      
                
                if dicoHasKey(json_data, "data") :
                   mesures = json_data["data"]
                  
                   if mesures :

                        valuesMaxTime = [180, 360, 720, 1440, 2880, 4320, 10080, 20160, 44640]
                        maxTime = valuesMaxTime[iMaxTime]
                       
                        case = 0 if "resultat_obs" in mesures[0].keys() else  1
                        date_obs, resultat_obs, firstDate = [], [], False

                        typeStats = "linechart0"
                        borne = 10

                        if case == 0 :
                              theLastDate, theMaxDate, minY, maxY = "", "", 1000000000.0, 0.0
                              date0 = thedatetime.fromisoformat(mesures[0]["date_obs"][:-1])

                              try :
                                  deltaTimeOscultation = thedatetime.fromisoformat(mesures[0]["date_obs"][:-1]) - thedatetime.fromisoformat(mesures[1]["date_obs"][:-1])
                                  deltaMinutesOscultation = int(deltaTimeOscultation.total_seconds() / 60)    
                              except :
                                  if len(mesures) == 1 :
                                     deltaTimeOscultation = 0
                                     deltaMinutesOscultation = 1
                                  
                              if deltaMinutesOscultation == 0 : deltaMinutesOscultation = 1       
                              facteurDeltaTime = (60 / deltaMinutesOscultation)
                              borne = 0  
                              
                              for mesure in mesures :
                                  data = mesure["resultat_obs"]/denum  
                                  resultat_obs.append(data)
                                  if data > maxY : maxY = data; theMaxDate = jsonDateToStrDate(mesure["date_obs"], "", "<br>", self.timezone) ;
                                  if data < minY : minY = data
                                  if not firstDate : theLastDateHtml = jsonDateToStrDate(mesure["date_obs"], "", "<br>", self.timezone) ;  theLastDate = jsonDateToStrDate(mesure["date_obs"], "", " ", self.timezone) ; firstDate = True 
                                  date_obs.append(jsonToMatplotlibDate(mesure["date_obs"], self.timezone))
                                                                         
                                  currentDate = thedatetime.fromisoformat(mesure["date_obs"][:-1])

                                  if (date0 - currentDate).total_seconds()/60 <  valuesMaxTime[0] : borne+= 1  
                                  if (date0 - currentDate).total_seconds()/60 >  maxTime : break 
                                  
                              title = "%s\n[du %s au %s]" % (title, jsonDateToStrDate(mesure["date_obs"],"", " ", self.timezone), theLastDate)
                                  
                        else :
                              theMaxDate, maxY = "", 0.0
                              for mesure in mesures :
                                  data = mesure["resultat"]
                                  resultat_obs.append(data)
                                  if data > maxY : maxY = data ; theMaxDate = jsonDateToStrDate(mesure["date_prelevement"],"", "<br>", self.timezone) ;
                                  if not firstDate : self.title = "%s\n[du %s" % (self.title, jsonDateToStrDate(mesure["date_prelevement"], "", "<br>", self.timezone)); firstDate = True
                                  date_obs.append(jsonToMatplotlibDate(mesure["date_prelevement"], self.timezone))

                                  
                              title = "%s au %s]" % (title, jsonDateToStrDate(mesure["date_prelevement"], "", " ", self.timezone))

                        lastData = round(resultat_obs[0], 3)
                        
                        htmlDicoSeuils, stationDicoSeuils = self.getHtmlForDicoSeuils(idStation, None, True, lastData, 'black')
                        beforeLastData = round(resultat_obs[1], 3) if len(resultat_obs)> 1 else round(resultat_obs[0], 3)
                        maxY = round(maxY, 3)
                        average = getMean(resultat_obs, 3) 
                        ecartType = getStDev(resultat_obs, 3)
                        #INTERVALLE DE CONFIANCE (95%)
                        IC95 = average-(1.96 * (ecartType / sqrt(len(mesures))))
                        intervalleConf = [average-IC95, average+IC95]                        
                        ecart = (lastData - average)
                        
                        #INDICATEUR POSITION / AVERAGE
                        ratio = (lastData / average)

                        if lastData > average :
                           if ratio > 2.0 : 
                               if lastData > (average + (4*ecartType)) : commentRatio = "&#x3E;&#x3E;&#x3E;" ; titleCommentRatio = self.dictLegend["v4"]
                               elif lastData > (average + (2*ecartType)) : commentRatio = "&#x3E;&#x3E;" ; titleCommentRatio = self.dictLegend["v6"]
                               else : commentRatio = "&#x3E;" ; titleCommentRatio = self.dictLegend["v6"]
                           else :
                               if ratio < 1.03 : commentRatio = "&#x2248;" ; titleCommentRatio = self.dictLegend["v5"]
                               elif ratio < 1.06 : commentRatio = "&#x2265;" ; titleCommentRatio = self.dictLegend["v0"] #"&#x2A7E;" - CODE ALTERNATIF SUP ou EGAL  
                               else : commentRatio = "&#x3E;" ; titleCommentRatio = self.dictLegend["v6"]
                        elif lastData == average or ratio >= 0.95 :
                             if lastData == average : commentRatio = "=" ; titleCommentRatio = self.dictLegend["v0"]
                             else : commentRatio = "&#x2248;" ; titleCommentRatio = self.dictLegend["v0"] #"&plusmn;" - CODE ALTERNATIF PLUS OU MOINS
                        else :
                           if ratio < 0.5 : 
                               if lastData > (average - (4*ecartType)) : commentRatio = "&#x3C;&#x3C;&#x3C;"; titleCommentRatio = self.dictLegend["v1"]
                               elif lastData > (average - (2*ecartType)) : commentRatio = "&#x3C;&#x3C;"; titleCommentRatio = self.dictLegend["v3"]
                               else : commentRatio = "&#x3C;"; titleCommentRatio = self.dictLegend["v3"]
                           else :
                               commentRatio = "&#x3C;" ; titleCommentRatio = self.dictLegend["v2"] #CODE INF ou EGAL &#x2264; 
                            
                       
                        #INDICATEUR EV.3H
                        observations = [i for i in range(borne)]
                        coeffReg, constanteReg = None, None

                        if borne == 1 :  tendancea = "<b> </b>"; titleImg2 = "%s (%s)" % (self.dictLegend["e3"], "3 dernières heures")
                        else :  
                        
                            try :
                                slope, intercept, r_value, p_value, std_err = linregress(observations, resultat_obs[:borne])                                
                                coeffReg = round(slope, 5)
                                constanteReg = round(intercept, 5)
                                #NEW 4.0.0 
                                comments, tendancea, titleImg2 = getCommentsForTrend(slope, False)    
                            except : 
                                tendancea = "<b>?</b>"; titleImg2 = "%s (%s)" % (self.dictLegend["e3"], "3 dernières heures")    

                        
                        #INDICATEUR LAST EVOLUTION
                        if beforeLastData == lastData : tendance = "<b>&#8608</b>"; titleImg3 = "%s (%s)" % (self.dictLegend["e0"], "Deux dernières mesures")
                        elif beforeLastData > lastData : tendance = "<b>&#8600</b>"; titleImg3 = "%s (%s)" % (self.dictLegend["e1"], "Deux dernières mesures")
                        else : tendance = "<b>&#8599</b>"; titleImg3 = "%s (%s)" % (self.dictLegend["e2"], "Deux dernières mesures")                      

                        self.dataStations[idStation] = {'typeStats' : typeStats, 'resultat_obs' : resultat_obs, 'date_obs' : date_obs, 'title' :  title, 'img' : img, 'minY' : minY, 'maxY' : maxY,
                                                        'theMaxDate' : theMaxDate, 'theLastDateHtml' : theLastDateHtml, 'lastData' : lastData,
                                                        'typeInfos' : typeInfos, 'idHydro' : idHydro, 'lbStation' : lbStation,
                                                        'average' : average, 'ecarttype' : ecartType, 'coeffReg' : coeffReg, 'constanteReg' : constanteReg,
                                                        'stationDicoSeuils' : stationDicoSeuils, 'deltaMinutesOscultation' : deltaMinutesOscultation, 'IC95' : round(IC95, 2)}

                        link = "https://www.vigicrues.gouv.fr/niv3-station.php?CdEntVigiCru=26&CdStationHydro=%s&GrdSerie=%s&ZoomInitial=3" % (idStation, typeInfos)
                                                
                        html = "<tr>\n"
                        html+= "<td><div><menu>\n"
                        html+= "<li><a href='VIEWSTATION?%s'><button><img src='file:///%s' width='18' height='18' title='Localiser la station sur la carte'></button></a></li>\n" % (idStation, self.viewstation)
                        html+= "<li><a href='VIEWGRAPH?%s'><button><img src='file:///%s' width='18' height='18' title='Visualiser le graphique sur la période d&#39;extraction'></button></a></li>\n" % (idStation, self.graphique)
                        html+= "<li><a href='VIEWDATA?%s'><button><img src='file:///%s' width='18' height='18' title='Tableau des données sur la période d&#39;extraction'></button></a></li>\n" % (idStation, self.tabledata)
                        html+= self.subHTmlDocAss(idStation, lbStation, idHydro, typeInfos, self.index)
                        html+= "</div></menu></td>\n"
                        html+= "<td><a href='%s' target='_blank' title='Consulter Vigicrues'>%s</a> - %s</td>\n" % (link, idStation, lbStation) 
                        html+= "<td><center>%s</center></td>\n" % ("%s<br>min." % int(deltaMinutesOscultation))
                        html+= "<td>%s</td>\n" % htmlDicoSeuils
                        html+= "<td><center><b>%s</b></center></td>\n" % lastData
                        html+= "<td><center>%s</center></td>\n" % theLastDateHtml
                        html+= "<td><center><b>%s</b></center></td>\n" % maxY
                        html+= "<td><center>%s</center></td>\n" % theMaxDate
                        html+= "<td><center><b>%s</b></center></td>\n" % average
                        html+= "<td title='%s'><center><fonr style='font-size: 2.0em; font-weight: bold;'>%s</font></center></td>\n" % (titleCommentRatio, commentRatio) 
                        html+= "<td title='%s'><center><fonr style='font-size: 2.0em; font-weight: bold;'>%s</font></center></td>\n" % (titleImg2, tendancea) #, titleImg2)
                        html+= "<td title='%s'><center><fonr style='font-size: 2.0em; font-weight: bold;'>%s</font></center></td>\n" % (titleImg3, tendance) #, titleImg3)

                        self.index+= 1
                else :
                        html = self.getHtmlError('error', idStation, idHydro, lbStation, self.viewstation, "<b><i>Pas de données trouvées dans la réponse du service web.</i></b>")

                        

          return html  
  
      def subHTmlDocAss(self, idStation, lbStation, idHydro, typeInfos, index):  
          listDocAss = getListDocForMenu(self, None, idStation, self.pathLocalFolderStations)
    
          if listDocAss != [] : icon, title = self.docAss, "Documents associés et sites web d&#39;information pour la station"
          else : icon, title = self.webAss, "Sites web d&#39;information pour la station"
          
          html = """<li class="menu-deroulant"><button onclick="return false;"><img src='file:///%s' width='18px' height='18px' title="%s"></button>\n""" % (icon, title)
          html+= """<ul class="sous-menu">\n"""
          
          if listDocAss != [] :

             html+= """<table class='titleDocAss'>\n<tr><td>Documents locaux associés</td></tr>\n</table><hr>\n"""
             
             for docAss in listDocAss :
                 extension, icon = getExtension(docAss), None
                 if extension.startswith(".") :
                    extension = extension[1:]
                    icon = getThemeIcon("%s.png" % extension)
                 html+= """<li><space><a href="VIEWDOC?%s"><img src='file:///%s' width='24px' ><span style="top:-5px; position:relative;"> %s</span></a></space></li>\n""" % (docAss, icon if icon else self.urlweb, basename(docAss))

             html+= """<br>"""

          if lbStation.find("'") > -1 : lbStation = lbStation.replace( "'", "&#39;")
          #NEW 4.0.0
          listWebAss = {"statsStation" : (self.statsStation, "Données statistiques HubEau (moyenne du débit, journalier ou mensuel) pour la station %s" % lbStation, "Données statistiques", "https://www.hydro.eaufrance.fr/stationhydro/%s/monthly_vcn3_01/statistique/visualisation-echantillon" % idStation),
                        "temporalSeries" : (self.temporalseries, "Données élaborées (moyenne du débit, journalier ou mensuel) pour la station %s" % lbStation, "Données élaborées", "https://hubeau.eaufrance.fr/sites/default/files/api/demo/hydro_elab.htm?code_station=%s&grandeur=QmJ&fdp=3" % idStation),
                        "vigicrues" : (self.vigicrues, "Consulter Vigicrues pour la station %s" % lbStation, "Vigicrues", "https://www.vigicrues.gouv.fr/niv3-station.php?CdEntVigiCru=26&CdStationHydro=%s&GrdSerie=%s&ZoomInitial=3" % (idStation, typeInfos)),
                        "hydro" : (self.hydro, "Fiche Synthèse DREAL pour la station %s" % lbStation, "Fiche DREAL", "http://www.hydro.eaufrance.fr/stationhydro/%s/fiche" % idStation),
                        "inrae" : (self.inrae, "Satistiques sur INRAE pour la station %s" % lbStation, "Satistiques INRAE", "https://hydrogr.github.io/BDD-HydroClim/images/fr/%s_INRAE_BDD-HydroClim_fact_sheet_FR.png" % idStation),
                        "birdz" : (self.birdz, "Données sur OPENDATASOFT pour la station %s" % lbStation, "Données OPENDATASOFT", "https://birdz.opendatasoft.com/explore/dataset/vigicrues-hauteurs-et-debits-des-cours-deau/table/?flg=en&q=%s" % idHydro[0:4])
                       }          


          html+= """<table class='titleDocAss'><tr><td>Sites Web</td></tr></table><hr>\n"""

          html+= """<li><space>\n"""  
          for key in listWebAss :
              icon = listWebAss[key][0]
              title = listWebAss[key][1]
              text = listWebAss[key][2]
              url = listWebAss[key][3]
              html+= """&nbsp;<a href="%s"><button><img src='file:///%s' width='24px' title='%s'></button></a>\n""" % (url, icon if icon else self.urlweb, title)

          html+= """</space></li>\n""" 
          html+= """</ul>\n"""   
          return html

  
      def getActions(self, url):

          if type(url) == QUrl :
              
             if url.toString().startswith("VIEWSTATION?") :

                 idStation = url.toString().split("?")[1]
                 fieldCdStationH = self.tableFieldsStations['CdStationH']
                 exp = QgsExpression(" \"{}\" = '{}' ".format(fieldCdStationH, idStation.replace("'", "''")))
                 request = QgsFeatureRequest(exp)
                 
                 geomType = self.layer.geometryType()

                 for feat in self.layer.getFeatures(request):
                      geom = feat.geometry()
                      if geomType != 2 : geom = geom.buffer(500, 12)
                      rect = geom.boundingBox()
                      self.canvas.setExtent(rect)
                      self.canvas.refresh()
                      break

             elif url.toString().startswith("VIEWGRAPH?") :

                 idStation = url.toString().split("?")[1]

                 if dicoHasKey(self.dataStations, idStation) :
                     infos = self.dataStations[idStation]

                     self.movieCtrl.setVisible(True)
                     self.dlgStats = windowPlotStatsAndGraphs(self, "https://hubeau.eaufrance.fr", infos['typeStats'],
                                                              infos['resultat_obs'], infos['date_obs'], infos['title'],
                                                              infos['img'], infos['minY'], infos['maxY'], infos['typeInfos'],
                                                              infos['idHydro'], infos['lbStation'], idStation, self.pathLocalFolderStations, self.timezone, self.titleService,
                                                              infos['average'], infos['ecarttype'], infos['coeffReg'], infos['constanteReg'], infos['stationDicoSeuils'])
                     self.movieCtrl.setVisible(False)

                     self.dlgStats.show()

                 else :
                       img = "%s/%s.jpg" % (self.path, idStation) 
                       msgDlgBox = myMsgDlgBox(self, QMessageBox.Information, "Information %s" % self.parent.namePlugin,
                                          "<center><img src='%s'><br>Pas de service pour la station <b>%s</b>.</center>" % (img, idStation), "rdistop.png", "inondation.png", 1, None, self)
                       msgDlgBox.exec_()


             elif url.toString().startswith("VIEWDATA?") :
                  idStation = url.toString().split("?")[1]

                  if dicoHasKey(self.dataStations, idStation) :
                      infos = self.dataStations[idStation]

                      records = [ [infos['date_obs'][i], infos['resultat_obs'][i]] for i in range(len(infos['date_obs']))]
                      
                      typeInfos = "Débit en m3/s" if infos['typeInfos'] == "Q" else "Hauteur en m"  
                      
                      self.dlgData = simpleViewerData(idStation, infos['lbStation'], ['date_obs', 'resultat_obs'], records, typeInfos)
                      self.dlgData.exec_()             
                  else :
                       img = "%s/%s.jpg" % (self.path, idStation) 
                       msgDlgBox = myMsgDlgBox(self, QMessageBox.Information, "Information %s" % self.parent.namePlugin,
                                          "<center><img src='%s'><br>Pas de service pour la station <b>%s</b>.</center>" % (img, idStation), "rdistop.png", "inondation.png", 1, None, self)
                       msgDlgBox.exec_()                 

             elif url.toString().startswith("VIEWDOC?") :

                  docAss = url.toString().split("?")[1] 
                  openFile("%s" % docAss)

             elif url.toString().startswith("VIEWSERIES?") :

                 idStation = url.toString().split("?")[1]

                 url = "https://hubeau.eaufrance.fr/sites/default/files/api/demo/hydro_elab.htm?code_station=%s&grandeur=QmJ&fdp=3" % idStation

                 self.dlgStats = simpleViewerWeb(url, "Séries temporelles anciennes")
                 self.dlgStats.exec_()

             else :
                 
                 try : webbrowseropen(url.toString())
                 except : pass 


      #NEW 4.0.0
      def cleanAnchors(self, html) :
           Elts = ["VIEWSTATION?", "VIEWGRAPH?", "VIEWDATA?"]

           newHtml = html
           for elt in Elts :
               posd = -1
               posd = html.find(elt)
               while posd > 0 :
                     posf = html.find("\"", posd)
                     toKill = html[posd : posf]
                     newHtml = newHtml.replace(toKill, "#")
                     posd = html.find(elt, posf)
                    
           return newHtml


      #NEW 4.0.0 
      def saveAllReporting(self):
          #CHOOSE FOLDER
          dlgFile = QFileDialog(self, "Sélectionner le dossier pour la sauvegarde")
          dlgFile.setDirectory(self.initDir)
          dlgFile.setFileMode(QFileDialog.DirectoryOnly)

          fixeLabelsFileDialog(self, dlgFile, 0, False)      

          if dlgFile.exec_():
             dirFiles = dlgFile.selectedFiles()[0]
             self.initDir = dirFiles
             datation = thedatetime.now().strftime("%Y%m%d-%Hh%M")   
             #LOOP ON ALL REPORTS
             indexReport = self.buttongroup.checkedId()-1
             index = 0
             tempoFile = "%s/tempo.html" % (dirFiles)
             
             for button in self.buttongroup.buttons() :
                 i = 0
                 button.setChecked(True)
                 self.buttongroup.idClicked.emit(index)
                 self.htmlReporting.update()
                 self.htmlReporting.repaint()
                 self.repaint()
                 QApplication.processEvents()
                 QMessageBox.information(self, "Information", "Sauvegarde du rapport : %s." % button.statusTip())
                 
                 if exists(tempoFile) : removeFile(tempoFile)
                 html = self.htmlReporting.page().currentFrame().toHtml()                  
                 fileName = "%s/RDI_%s_%s.html" % (dirFiles, button.statusTip(), datation)
                 self.getSaveReporting(fileName, tempoFile, html)
                 index+= 1

             self.initDir = dirFiles

             if exists(tempoFile) : removeFile(tempoFile)
             self.buttongroup.buttons()[indexReport].setChecked(True)
             self.buttongroup.idClicked.emit(indexReport)
             self.htmlReporting.update()
             self.htmlReporting.repaint()
             self.repaint()

        
      def saveReporting(self):
          
            fileDialog = QFileDialog(self, "Sauvegarder la page %s au format HTML" % self.buttongroup.checkedButton().statusTip()) 
            fileDialog.setNameFilters(("*.html", ))
            fileDialog.selectNameFilter("*.html")
            fileDialog.setViewMode(QFileDialog.Detail)
            fileDialog.setDirectory(self.initDir)
            fileDialog.setAcceptMode(QFileDialog.AcceptSave)
            fixeLabelsFileDialog(self, fileDialog, 1, False)
            windowIcon(self, "save.png")
            fileDialog.selectFile("RDI_%s_%s.html" % (self.buttongroup.checkedButton().statusTip(), thedatetime.now().strftime("%Y%m%d-%Hh%M")))
            
            if fileDialog.exec_():
               fileName = fileDialog.selectedFiles()[0]
               self.initDir = dirname(fileName)
               tempoFile = "%s/tempo.html" % (self.initDir)
               html = self.htmlReporting.page().currentFrame().toHtml()
               self.getSaveReporting(fileName, tempoFile, html)
               


      def getSaveReporting(self, fileName, tempoFile, html):
         
           relativeFolder = pathLibPath(fileName).stem 
           imgFolder = "%s/%s" % (dirname(fileName), relativeFolder)
           subFolder = ""
           pathLibPath(imgFolder).mkdir(parents=True, exist_ok=True)

           imgToCopy = [self.viewstation, self.docAss, self.statsStation, self.temporalseries, self.webAss,
                     self.vigicrues, self.hydro, self.inrae, self.birdz, self.noaction,
                     self.graphique, self.tabledata, self.warning, self.urlweb,
                     self.fileLogoLeft, self.fileLogoRight]
 
           #COPY ICONS  
           for img in imgToCopy :
               html = html.replace("file:///%s" % dirname(img).replace("\\", "/"), "%s" % relativeFolder)
               shutil.copy2(img, imgFolder)
           
           texts = ["%s" % join(dirname(__file__), "data/logos/"),
                    "%s" % join(dirname(__file__), "data/imgs/"),
                    "%s" % join(dirname(__file__), "data/graph/"),
                    "%s" % join(dirname(__file__), "data/stations/")
                            ]
           
           for text in texts :
               html = html.replace("file:///%s" % text.replace("\\", "/"), "%s/" % relativeFolder)

               images = glob.glob(text + '/*')
               for image in images:                       
                   if os.path.isdir(image):
                      subFolder = "/" + image.replace("\\", "/").split("/")[-1] + "/"
                      pathLibPath(imgFolder+"/"+subFolder+"/").mkdir(parents=True, exist_ok=True)
                   else : shutil.copy2(image, imgFolder + subFolder)
               

           #CLEAN ANCHORS AND SYSTEMIC ACTIONS
           html = self.cleanAnchors(html)

           with open(tempoFile, "w", encoding="utf-8", errors="surrogateescape") as inFile :
                try :                        
                    inFile.write(unquote_plus(html))
                except :
                    try :
                        html = "%s" % (html)
                        inFile.write(html)
                    except :
                        inFile.write("<html><style>h1 {font-size: 1.5em;}</style><h1>error</h1></html>")
                        self.barInfo.pushMessage("Avertissement :", "Impossible de sauvegarder le bulletin !", Qgis.Warning, 3)
                        return

           #CLEAN BAD DIM GRAPH
           while not exists(tempoFile) : pass
           
           f = open(tempoFile, "r")
           lines = f.readlines()
           html = ""
           for line in lines :
               if line.find("<div class=\"chartjs-size-monitor\">") > -1 :
                   toKill = "<div class=\"chartjs-size-monitor\"><div class=\"chartjs-size-monitor-expand\"><div class=\"\"></div></div><div class=\"chartjs-size-monitor-shrink\"><div class=\"\"></div></div></div>"
                   html+= line.replace(toKill, "")
               
               elif line.find("""name="echo""") > -1 :
                  posd = line.find("line-chart-")+len("line-chart-")
                  posf = line.find("\"", posd)
                  id =  line[posd:posf]
                  html+= "<canvas id=\"line-chart-%s\" name=\"echo\" style=\"width: 779px; height: 389px; display: block;\" width=\"1168\" height=\"583\" class=\"chartjs-render-monitor\"></canvas>" % id
               else : html+= line
                
           with open(fileName, "w", encoding="utf-8", errors="surrogateescape") as inFile : inFile.write(html)
           self.barInfo.pushMessage("Information :", "Fichier <b>%s</b> sauvegardé !" % basename(fileName), Qgis.Info, 3)

           



      def exportReporting(self): 
          typeInfos =  None
          for key in self.dataStations :
              typeInfos = self.dataStations[key]['typeInfos']
              break
          if not typeInfos : return
          if typeInfos == "Q" : lblTypeInfos, uniteInfos, titleTh= "D", "m<sup>3</sup>/s", "Débit<br>(m<sup>3</sup>/s)" 
          else : lblTypeInfos, uniteInfos, titleTh = "H", "m", "Hauteur<br>(m)"

          html = """<font style='font-size: xx-small;'>%s</font><br>""" % self.infosExtraction.replace("<br>", ", ")
          html+= """<table border='1'>
                    <thead>
                    <tr>
                        <th colspan='2'></th>
                        <th colspan='2' bgcolor='#f5f5f5' style='font-family: Marianne; font-size: 11px;'>Dernière observation</th>
                        <th colspan='2' style='font-family: Marianne; font-size: 11px;'><center>ENJEUX</center></th>
                        <th colspan='2' bgcolor='#f5f5f5' style='font-family: Marianne; font-size: 11px;'>Bulletin précédent</th>
                        <th colspan='2' bgcolor='#f5f5f5' style='font-family: Marianne; font-size: 11px;'>Max sur la période d'observation</th>
                    </tr>    
                    <tr bgcolor='#f5f5f5'>
                        <th style='font-family: Marianne; font-size: 11px;'>Station hydrométrique</th>
                        <th style='font-family: Marianne; font-size: 11px;'>Seuils<br>indicatifs RDI</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>%s</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>heure</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>Constats, débordements et enjeux</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>Prévisions de débordements et enjeux</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>%s</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>Heure</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>%s</th>
                        <th style='font-family: Marianne; font-size: xx-small;'>Heure</th>
                    </tr></thead>""" % (titleTh, titleTh, titleTh)

          noData = " - " 
          
          for key in self.dataStations :
              infos = self.dataStations[key]

              heure = infos['theLastDateHtml'].split("<br>")[-1]
              infosMax = infos['theMaxDate'].split("<br>")
              dateMax, heureMax = infosMax[0], infosMax[-1]
              SEUIL_RDI = self.stations[key]['SEUIL_RDI'] 
              dicoSeuils = None
              try : dicoSeuils = ast.literal_eval(SEUIL_RDI)
              except : pass

              htmlDicoSeuils = ""
                
              if dicoSeuils :
                 i = 1
                 for key in dicoSeuils.keys() :
                     if key == "S%s%s" % (lblTypeInfos, i) :
                        valueSeuil = dicoSeuils[key]
                        if valueSeuil > -1 :
                           htmlDicoSeuils+= "<b>%s</b> : %s %s<br>" % (key.replace(lblTypeInfos, ""), valueSeuil, uniteInfos)
                        i+= 1
                 if htmlDicoSeuils == "" : htmlDicoSeuils = "Pas d'infos"    
              else : htmlDicoSeuils+= "Pas d'infos" 


              html+= """<tr>
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td>
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td>
                          <td style='font-family: Marianne; font-size: 10px;'><b><center>%s</center></b></td>
                          <td style='font-family: Marianne; font-size: 10px;'><center>%s</center></td>
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td> 
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td>
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td>
                          <td style='font-family: Marianne; font-size: 10px;'>%s</td>
                          <td style='font-family: Marianne; font-size: 10px;'><b><center>%s</center></b></td>
                          <td style='font-family: Marianne; font-size: 10px;'><center>%s<br>%s</center></td>
                        </tr>                           
                      """ % (infos['lbStation'], htmlDicoSeuils, infos['resultat_obs'][0], heure, noData, noData, noData, noData, infos['maxY'], dateMax, heureMax)

          html+= "</table>"
          comments = ""
          for key in COMMENTSSEUILRDI.keys() : 
              comment = COMMENTSSEUILRDI[key].replace("<br>", " ") 
              comments+= "%s : %s" % (key, comment) if comments == "" else  ", %s : %s" % (key, comment)
          html+= "<font style='font-size: xx-small;'>%s</font>" % comments  
            
          reportRichTextMime = QMimeData()
          reportRichTextMime.setHtml(html)
          QApplication.clipboard().setMimeData(reportRichTextMime)

          if self.parent.logConsole : self.parent.logConsole.setAnnontation(html.replace("<font style='font-size: xx-small;'>", "<font style='font-size: 18px;'>").replace("font-family: Marianne; font-size: 10px;", "font-family: Marianne; font-size: 14px;")+"<br><br>", True)


      def printReporting(self):
          printer = QPrinter(); printer.setPageSize(QPrinter.A4); printer.setOrientation(QPrinter.Portrait);
          printer.setPageMargins(5, 5, 5, 5, QPrinter.Millimeter); printer.setOutputFormat(QPrinter.NativeFormat);
          orientation = QPrinter.Portrait
          dlgPrint = printerPreview(self, self.htmlReporting, "%s : Imprimer le bulletin" % self.namePlugin, orientation, "printer.png")

      def resizeEvent(self, ev):
          self.barInfo.setGeometry(0, 0, self.width(), 60)

          self.groupParams.setGeometry(5, 0, self.width()-10, 100)
          self.labelComboTypeInfos.setGeometry(5, 30, 80, 25)
          self.comboTypeInfos.setGeometry(85, 25, 90, 25)
          self.labelComboDate.setGeometry(195, 30, 50, 25) 
          self.comboDate.setGeometry(255, 25, 150, 25)
          
          self.labelListStations.setGeometry(5, 60, 80, 25) 
          self.listStations.setGeometry(85, 55, 300, 25)
          self.buttonListActions.setGeometry(390, 55, 25, 25)

          self.labellcdNumber.setGeometry(self.width()-210, 5, 160, 25)
          self.lcdNumber.setGeometry(self.width()-210, 30, 160, 25)
          #NEW 4.0.0
          self.chrono.setGeometry(self.width()-140, 60, 25, 25)
          self.chronoInfo.setGeometry(self.width()-195, 50, 125, 43)

          self.dataWebButton.setGeometry(420, 25, 154, 25)
          #NEW 4.0.0
          self.optionsHtml.setGeometry(420, 55, 154, 30)
                    
          self.busyIndicator.setGeometry(575, 60, self.width()-self.labelListStations.width()-self.listStations.width()-250, 15)

          self.buttonRefreshConf.setGeometry(10, self.height()-40, 24, 24)
          
          self.htmlReporting.setGeometry(5, 85, self.width()-10, self.height()-135)
          self.copyToClipBoardButton.setGeometry(self.width()-220, self.height()-40, 24, 24)
          self.printButton.setGeometry(self.width()-180, self.height()-40, 24, 24)
          self.saveButton.setGeometry(self.width()-140, self.height()-40, 24, 24)
          self.saveAllButton.setGeometry(self.width()-100, self.height()-40, 24, 24)
          self.closeButton.setGeometry(self.width()-60, self.height()-40, 24, 24) 



class simpleViewerData(QDialog):
      def  __init__(self, idStation, lbStation, fieldNames, records, typeInfos):
            super(simpleViewerData, self).__init__()
            
            self.setWindowTitle("Visualiseur Données (%s) : Station %s - %s [%s]" % (typeInfos, idStation, lbStation, len(records)))
            self.setWindowFlags(Qt.WindowMinimizeButtonHint | Qt.WindowMaximizeButtonHint | Qt.WindowCloseButtonHint)
            self.minWidth, self.minHeight = 650, 650
            self.resize(self.minWidth, self.minHeight)

            self.idStation = idStation
            self.initDir = dirname(__file__)

            windowIcon(self, "tabledatam.png")

            self.dataViewer = QTableView(self) 
            self.dataViewer.setAccessibleDescription("")

            sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
            sizePolicy.setHorizontalStretch(0)
            sizePolicy.setVerticalStretch(3)
            sizePolicy.setHeightForWidth(self.dataViewer.sizePolicy().hasHeightForWidth())
            self.dataViewer.setSizePolicy(sizePolicy)
            self.dataViewer.setSelectionBehavior(QAbstractItemView.SelectRows)
            self.dataViewer.setSelectionMode(QAbstractItemView.SingleSelection)
            self.dataViewer.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
            self.dataViewer.setVisible(True)
            self.dataViewer.setAlternatingRowColors(True)
            self.dataViewer.setStyleSheet("QTableView {alternate-background-color: cyan;background-color: white;}")


            model = baseTableModel(fieldNames, records, self)
            self.dataViewer.setModel(model)

            self.dataViewer.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

            self.saveButton = funnyPushButton(self)
            self.saveButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "saveButton", "", "Sauvegarder l'onglet courant", True, getThemeIcon("save.png"), 24, 24, True, False)

            self.closeButton = funnyPushButton(self)
            self.closeButton.initPushButton(24, 24, self.width()-140,  self.height()-140, "closeButton", "", "Fermer", True, getThemeIcon("close.png"), 24, 24, True, False)

            self.saveButton.clicked.connect(self.saveDataInFile)
            self.closeButton.clicked.connect(self.reject)


      def saveDataInFile(self):
            
            fileDialog = QFileDialog(self, "Sauvegarder les données")
            fileDialog.setNameFilters(("*.csv", ))
            fileDialog.selectNameFilter("*.csv")
            fileDialog.setViewMode(QFileDialog.Detail)
            fileDialog.setDirectory(self.initDir)
            fileDialog.setAcceptMode(QFileDialog.AcceptSave)

            fixeLabelsFileDialog(self, fileDialog, 1, False)
            windowIcon(self, "save.png")
            fileDialog.selectFile("%s_%s.csv" % (self.idStation, thedatetime.now().strftime("%d%m%Y-%Hh%Mm%Ss")))
            
            if fileDialog.exec_():
               fileName = fileDialog.selectedFiles()[0]
               self.initDir = dirname(fileName)
               
               model = self.dataViewer.model()
               with open(fileName, "w") as fileOutput:
                   writer = csv.writer(fileOutput, lineterminator='\n')
                   fields = [col for col in model.columnNames()]
                   writer.writerow(fields)
               
                   for row in range(model.rowCount()) :
                        data = [ model.getData(row, column) for column in range(model.columnCount()) ]
                        writer.writerow(data)


      def resizeEvent(self, ev):
          
          self.dataViewer.setGeometry(5, 5, self.width()-10, self.height()-40)
          self.saveButton.setGeometry(self.width()-100, self.height()-30, 24, 24) 
          self.closeButton.setGeometry(self.width()-60, self.height()-30, 24, 24)        
          

class baseTableModel(QAbstractTableModel):
    def __init__(self, header=None, data=None, parent=None):
        super(baseTableModel, self).__init__(parent)
        self.header = header if header else []
        self.resdata = data if data else []

    def headerToString(self, sep=u"\t"):
        header = self.header
        return sep.join(header)

    def rowToString(self, row, sep=u"\t"):
        text = u""
        for col in range(self.columnCount()):
            text += u"%s" % self.getData(row, col) + sep
        return text[:-1]

    def getData(self, row, col):
        try : return self.resdata[row][col]
        except : pass

    def columnNames(self): return list(self.header)
    def rowCount(self, parent=None): return len(self.resdata)
    def columnCount(self, parent=None): return len(self.header)
    def data(self, index, role):
        if role != Qt.DisplayRole and role != Qt.FontRole: return None

        val = self.getData(index.row(), index.column())

        if role == Qt.FontRole:  
            if val is not None: return None
            f = QFont()
            f.setItalic(True)
            return f

        if val is None: return "NULL"
        elif isinstance(val, memoryview): return None
        elif isinstance(val, (str, unicode)) and len(val) > 300: return u"%s..." % val[:300]
        return unicode(val)  

    def headerData(self, section, orientation, role):
        if role != Qt.DisplayRole: return None
        if orientation == Qt.Vertical: return section + 1
        else : return self.header[section]
