# -*- coding: utf-8 -*-

# -----------------------------------------------------------------
# This file is a part of PAMPI project.
# Name:         PAMPI
# Copyright:    (C) 2017-2021 Pascal PETER
#               contributions in 2021 by G. Khaznadar
# Licence:      GNU General Public Licence version 3
# Website:      http://pascal.peter.free.fr/
# Email:        pascal.peter at free.fr
# -----------------------------------------------------------------
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
# -----------------------------------------------------------------

"""
DESCRIPTION :
    contient l'interface graphique.
    La plupart des actions renvoient à des fonctions qui sont dans les
    modules utils_aaa.
"""

# importation des modules utiles :
from __future__ import division, print_function

# importation des modules perso :
import utils
import utils_functions
import utils_webengine
import utils_filesdirs
import utils_editor
from ui_main import Ui_MainWindow
import rc_main

from utils_editor import markdownEditor

import os, re, importlib

# PyQt5 ou PyQt4 :
if utils.PYQT == 'PYQT5':
    from PyQt5 import QtCore, QtWidgets, QtGui
else:
    from PyQt4 import QtCore, QtGui as QtWidgets, QtGui


class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    # la liste des actions de la barre d'outils ; une entrée vide
    # insère un séparateur
    toolBarContents = (
        'actionQuit', 
        'SEPARATOR', 'actionOpen', 'actionSave', 
        'SEPARATOR', 'actionSaveDoShow', 
        'SEPARATOR', 'actionFullScreen', 'actionLaunchInBrowser', 
        'SEPARATOR', 'actionOpenPresentationDir')

    """
    Paramètres du constructeur :
    :param locale: une chaîne signifiant la locale, par exemple "fr_FR"
    :type  locale: str
    :param translator: le système de traduction
    :type  translator: QtCore.QTranslator
    :param parent: une fenêtre parente
    :type  parent: QtWidgets.QWidget
    """
    def __init__(self, locale, translator, parent=None):
        """
        mise en place de l'interface
        plus des variables utiles
        """
        super(MainWindow, self).__init__(parent)
        # i18n :
        self.translator = translator
        self.locale = locale
        s = utils.STYLE['PM_LargeIconSize']
        self.iconsize = QtCore.QSize(s, s)
        # on définit le dossier du logiciel et des fichiers, la configuration :
        utils.definePathsAndConfig(self)
        # on localise Pandoc
        self.pandocBin = self.searchPandoc()
        # on vérifie l'existence des derniers fichiers :
        utils.updateLastFiles(self)

        # le nom du fichier :
        self.actualFile = {
            'baseName': '', 
            'mustSave': False}

        # les outils supplémentaires :
        if not('KaTeX' in self.configDict['TOOLS']):
            self.configDict['TOOLS']['ORDER'] = utils.DEFAULTCONFIG['TOOLS']['ORDER']
            self.configDict['TOOLS']['KaTeX'] = utils.DEFAULTCONFIG['TOOLS']['KaTeX']
        self.otherTools = {}
        for tool in self.configDict['TOOLS']['ORDER']:
            self.otherTools[tool] = [None, None]

        # on lit la couleur de fond par défaut dans le css :
        try:
            cssFileName = utils_functions.u(
                '{0}/assets/css/default.css').format(self.presentationsDir)
            lines = utils_filesdirs.readTextFile(cssFileName)
            tempResult = []
            for e in lines.split('/*'):
                if len(e.split('*/')) > 1:
                    tempResult.append(e.split('*/')[1])
            tempResult = [e for e in tempResult if len(e) > 0]
            tempResult = utils_functions.u('').join(tempResult)
            tempResult = tempResult.split('body')[1].split('}')[0].split('{')[1]
            tempResult = tempResult.split('background:')[1].split(';')[0].replace(' ', '')
            color = tempResult
            if QtGui.QColor(color).isValid():
                utils.DEFAULTCOLOR = color
        except:
            pass

        # mise en place de l'interface :
        utils.loadSupportedImageFormats()
        self.createInterface()
        self.updateWindowTitle()
        self.updateLastFilesMenu()
        rect = QtWidgets.QApplication.desktop().availableGeometry()
        self.resize(3 * rect.width() / 4, 3 * rect.height() / 4)
        self.showHelp(here=True)
        self.markdownEditor.setFocus()
        self.loadPlugins()
        QtCore.QTimer.singleShot(500, self.interfaceLoaded)

    def createInterface(self):
        """
        Création de l'interface utilisateur
        """
        # mise en place de l'UI définie par Qt Designer
        self.setupUi(self)
        # ZONE DE GAUCHE
        # gestion des boutons des « autres outils »
        # le reste est géré par Qt Designer
        iconsize = utils.STYLE['PM_ToolBarIconSize']
        iconsize = QtCore.QSize(iconsize, iconsize)
        hLayout = self.toolFrame.layout()
        for tool in self.configDict['TOOLS']['ORDER']:
            icon = QtGui.QIcon(":/img/icons/tool-{}.png".format(tool))
            a = QtWidgets.QAction(tool, self, icon=icon)
            self.otherTools[tool][1] = a
            # créations des boutons pour les « autres outils »
            b = QtWidgets.QToolButton()
            self.otherTools[tool][0] = b
            a.setCheckable(True)
            b.setDefaultAction(self.otherTools[tool][1])
            b.setIconSize(iconsize)
            hLayout.addWidget(b)
        hLayout.addStretch()
        # ajout de la barre d'outils de gauche (Designer ne fait pas ça bien)
        self.toolBar = QtWidgets.QToolBar(self)
        self.toolBar.setIconSize(self.iconsize)
        self.toolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
        self.addToolBar(QtCore.Qt.LeftToolBarArea, self.toolBar)
        for actionName in self.toolBarContents:
            if actionName == 'SEPARATOR':
                self.toolBar.addSeparator()
            else:
                self.toolBar.addAction(getattr(self, actionName))
        # ZONE DE DROITE
        self.helpWebView.linksInBrowser = True
        self.examplesFile = utils_functions.doLocale(
            self.locale, 
            utils_functions.u(
                '{0}/assets/EXAMPLES').format(self.presentationsDir), 
            '.md')
        # lecture du fichier :
        lines = utils_filesdirs.readTextFile(self.examplesFile)        
        self.samplesEditor.setPlainText(lines)
        rect = QtWidgets.QApplication.desktop().availableGeometry()
        self.hsplitter.setSizes([rect.width() / 3, rect.width() / 2])
        self.closing = False
        self.createActions()
        self.createMenusAndButtons()

        # Barre d'outils en haut, pour les plugins
        self.pluginsWidget.setMinimumHeight(1.5 * utils.STYLE['PM_ToolBarIconSize'])
        #self.pluginToolBar = QtWidgets.QToolBar(self)
        self.pluginToolBar = QtWidgets.QToolBar(self.pluginsWidget)
        self.pluginToolBar.setIconSize(iconsize)
        self.pluginToolBar.setToolButtonStyle(QtCore.Qt.ToolButtonIconOnly)
        #self.addToolBar(QtCore.Qt.BottomToolBarArea, self.pluginToolBar)

    def createActions(self):
        self.actionQuit.triggered.connect(self.close)
        self.actionNew.triggered.connect(self.newFile)
        self.actionOpen.triggered.connect(self.openFile)
        self.actionSave.triggered.connect(self.saveFile)
        self.actionSaveAs.triggered.connect(self.saveFileAs)
        self.actionExport.triggered.connect(self.export)
        self.actionSaveDoShow.triggered.connect(self.saveDoShow)

        self.actionFullScreen.triggered.connect(self.fullScreen) # button
        self.actionShowExamples.triggered.connect(self.showExamples)
        self.showExamplesButton.setDefaultAction(self.actionShowExamples)

        self.actionLaunchInBrowser.triggered.connect(self.launchInBrowser)

        self.actionColor.triggered.connect(self.changeColor)
        self.colorButton.setDefaultAction(self.actionColor)
        color = QtGui.QColor(utils.DEFAULTCOLOR)
        self.updateColor(color)

        self.actionCreatePDF.triggered.connect(self.createPDF)
        self.actionOpenPresentationDir.triggered.connect(self.openPresentationDir)
        self.actionMovePresentationDir.triggered.connect(self.movePresentationDir)
        self.actionUpdatePresentationDir.triggered.connect(self.updatePresentationDir)
        self.actionCreateDesktopFileLinux.triggered.connect(self.doCreateDesktopFileLinux)
        self.actionHelp.triggered.connect(self.showHelp)
        self.actionAbout.triggered.connect(self.about)
        self.actionWizard.triggered.connect(self.doWizard)

        self.editExamplesCheckBox.stateChanged.connect(self.editExamples)
        self.markdownEditor.textChanged.connect(self.textChanged)
        self.hsplitter.splitterMoved.connect(self.interfaceLoaded)

    def createMenusAndButtons(self):
        """
        la plupart des menus et des boutons sont gérés par
        Qt Designer, dans le fichier menu.ui
        """
        self.editExamplesCheckBox.setVisible(False)
        # self.lastFilesMenu est créé par Qt Designer
        if utils.OS_NAME[0] != 'linux':
            self.actionCreate_a_launcher.setVisible(False)
        # self.otherToolsMenu est créé par Qt Designer
        # mais il convient d'y attacher les actions.
        for tool in self.configDict['TOOLS']['ORDER']:
            self.otherToolsMenu.addAction(self.otherTools[tool][1])
        return

    PLUGIN_ORDER = (
        'bold', 'italic', 'tt', 'underlined', 
        'title', 
        'SEPARATOR', 
        'anim_rotate_x', 'anim_rotate_y', 'anim_rotate_z', 
        'anim_rotate_y_infinite', 'anim_scale', 
        'SEPARATOR', 
        'image', 'audio', 'video', 
        )

    def loadPlugins(self):
        """
        Met en place les plugins dans une nouvelle barre d'outils.

        Les plugins sont les fichiers Python du sous-dossier plugins
        sauf certains fichier spéciaux. La constante self.PLUGIN_ORDER
        permet une mise en ordre.
        """
        # expression pour trouver les fichiers Python
        pattern = re.compile('.py$', re.IGNORECASE)
        # expression pour rejeter ceux qui ne peuvent pas être des plugins
        ignore = re.compile(r"^_.*$|^ui_.*$|^rc_.*$|^.*_rc.py$")
        pluginfiles = filter(
            pattern.search,
            os.listdir(os.path.join(os.path.dirname(__file__), 'plugins')))
        plugins = [ os.path.splitext(fp)[0] for fp in  pluginfiles \
                    if not ignore.match(fp)]

        for p in self.PLUGIN_ORDER:
            if p == 'SEPARATOR':
                self.pluginToolBar.addSeparator()
            elif p in plugins:
                module = importlib.import_module('.{0}'.format(p), package='plugins')
                plugin = module.Plugin(self)
                self.pluginToolBar.addAction(plugin.action)
        plugins = ['.{0}'.format(p) for p in  plugins if not p in self.PLUGIN_ORDER]
        #print(plugins)
        if len(plugins) > 0:
            self.pluginToolBar.addSeparator()
            # on précise un chemin absolu
            modules = [importlib.import_module(p, package="plugins") \
                    for p in plugins]
            for m in modules:
                plugin = m.Plugin(self)
                self.pluginToolBar.addAction(plugin.action)

    def interfaceLoaded(self):
        """
        fonction appelée une fois que l'interface est affichée.
        Permet de régler les positionnements des éléments
        """
        #print('.................... interfaceLoaded ....................')
        #print(self.markdownEditor.width())
        self.pluginToolBar.setFixedWidth(self.markdownEditor.width())

    def resizeEvent(self, event):
        #print('.................... resizeEvent ....................')
        #print(self.markdownEditor.width())
        self.pluginToolBar.setFixedWidth(self.markdownEditor.width())

    def textChanged(self):
        """
        pour prendre en compte dans le titre 
        le fait que le texte a été modifié
        """
        if self.markdownEditor.noChange:
            self.markdownEditor.noChange = False
            return
        self.updateWindowTitle(mustSave=True)

    def updateLastFilesMenu(self, baseName=''):
        """
        mise à jour du menu des fichiers récents
        """
        if len(baseName) > 0:
            if (baseName in self.configDict['LASTFILES']):
                self.configDict['LASTFILES'].remove(baseName)
            self.configDict['LASTFILES'].insert(0, baseName)
        self.lastFilesMenu.clear()
        for fileName in self.configDict['LASTFILES']:
            newAction = QtWidgets.QAction(fileName, self)
            newAction.triggered.connect(self.openFile)
            newAction.setData(fileName)
            self.lastFilesMenu.addAction(newAction)

    def updateWindowTitle(self, mustSave=False):
        """
        mise à jour du titre de la fenêtre.
        Suivant que le fichier est enregistré, a été modifié, etc
        """
        self.actualFile['mustSave'] = mustSave
        if mustSave:
            mustSaveText = '*'
        else:
            mustSaveText = ''
        if len(self.actualFile['baseName']) < 1:
            windowTitle = utils_functions.u('{0}  []').format(
                utils.PROGTITLE)
        else:
            windowTitle = utils_functions.u('{0} {1}[{2}]').format(
                utils.PROGTITLE, mustSaveText, self.actualFile['baseName'])
        self.setWindowTitle(windowTitle)
        fileNamed = ((self.actualFile['baseName'] != '') and (self.pandocBin != ''))
        actions = (
            self.actionExport, 
            self.actionSaveDoShow, 
            self.actionLaunchInBrowser, 
            self.actionCreatePDF, 
            )
        for action in actions:
            action.setEnabled(fileNamed)

    def testMustSave(self):
        """
        pour savoir s'il faut proposer d'enregistrer le fichier.
        """
        result = True
        if self.actualFile['mustSave']:
            message = QtWidgets.QApplication.translate(
                'main',
                'The file has been modified.\n'
                'Do you want to save your changes?')
            reponseMustSave = utils_functions.messageBox(
                self, level='warning', message=message,
                buttons=['Save', 'Discard', 'Cancel'])
            if reponseMustSave == QtWidgets.QMessageBox.Cancel:
                result = False
            elif reponseMustSave == QtWidgets.QMessageBox.Save:
                self.saveFile()
        return result

    def closeEvent(self, event):
        """
        on quitte.
        suppression du dossier temporaire
        """
        self.closing = True
        if not(self.testMustSave()):
            event.ignore()
            return
        # on enregistre le fichier d'exemple s'il est éditable :
        if self.editExamplesCheckBox.isChecked():
            lines = self.samplesEditor.toPlainText()
            utils_filesdirs.writeTextFile(self.examplesFile, lines)
        # on écrit le fichier de config :
        utils_filesdirs.writeConfigFile(
            self.configDir, 
            self.configDict, 
            self.presentationsDir)
        # suppression du dossier temporaire :
        utils_filesdirs.emptyDir(self.tempPath)
        QtCore.QDir.temp().rmdir(utils.PROGNAME)
        event.accept()

    def disableInterface(self, dialog):
        """
        cache les barres d'outils lors de l'appel d'un dialog
        affiché dans le stackedWidget.
        On retourne aussi :
            lastIndex : le widget affiché précédemment dans le stackedWidget
            lastTitle : le titre de la fenêtre
        """
        lastIndex = self.stackedWidget.currentIndex()
        lastTitle = self.windowTitle()
        self.menuBar().setVisible(False)
        self.toolBar.setVisible(False)
        self.pluginToolBar.setVisible(False)
        self.setWindowTitle(dialog.windowTitle())
        newIndex = self.stackedWidget.addWidget(dialog)
        self.stackedWidget.setCurrentIndex(newIndex)
        return (lastIndex, lastTitle)

    def enableInterface(self, dialog, lastState=(0, '')):
        """
        replace les barres d'outils après l'appel d'un dialog
        affiché dans le stackedWidget.
        On remet aussi l'état précédent via lastState :
            widget affiché dans le stackedWidget
            titre de la fenêtre
        """
        if self.closing:
            return
        self.stackedWidget.removeWidget(dialog)
        self.stackedWidget.setCurrentIndex(lastState[0])
        self.setWindowTitle(lastState[1])
        self.menuBar().setVisible(True)
        self.toolBar.setVisible(True)
        self.pluginToolBar.setVisible(True)

    def newFile(self):
        """
        création d'un nouveau fichier.
        """
        if not(self.testMustSave()):
            return
        self.titleEdit.setText('')
        self.cssEdit.setText('')
        color = QtGui.QColor(utils.DEFAULTCOLOR)
        self.updateColor(color)
        for tool in self.configDict['TOOLS']['ORDER']:
            self.otherTools[tool][1].setChecked(False)
        self.markdownEditor.setPlainText('')
        self.actualFile = {
            'baseName': '', 
            'mustSave': False}
        self.updateWindowTitle()
        self.preview()

    def openFile(self):
        """
        ouverture d'un fichier.
        """

        def readHeader(lines):
            """
            lecture de l'entête perso du fichier.
            Permet de sauvegarder divers réglages 
            dans le fichier md (title, etc)
            """
            title = ''
            css = ''
            color = ''
            otherTools = ''
            try:
                header = lines.split('-->')[0].split('<!--')[1].split('\n')
                for line in header:
                    uline = utils_functions.u(line)
                    if ('TITLE:' in uline):
                        title = uline[6:]
                    elif ('CSS:' in uline):
                        css = uline[4:]
                    elif ('COLOR:' in uline):
                        color = uline[6:]
                    elif ('OTHER TOOLS:' in uline):
                        otherTools = uline[12:].replace('MathJax', 'KaTeX')
                    # anciens fichiers :
                    elif ('MATHJAX' in uline):
                        otherTools += '|KaTeX'
                    elif ('VIS_JS' in uline):
                        otherTools += '|Vis'
                lines = '-->'.join(lines.split('-->')[1:])
                lines = '\n'.join(lines.split('\n')[3:])
            except:
                pass
            finally:
                return (title, css, color, otherTools, lines)

        # début du code pour openFile
        if not(self.testMustSave()):
            return
        if self.sender() != self.actionOpen:
            # on a demandé un fichier récent :
            who = 'recent'
            fileName = utils_functions.u('{0}/md/{1}').format(
                self.presentationsDir, self.sender().data())
        else:
            # on commence par sélectionner le fichier :
            who = 'action'
            fileName = QtWidgets.QFileDialog.getOpenFileName(
                self,
                QtWidgets.QApplication.translate('main', 'Open'),
                utils_functions.u('{0}/md').format(self.presentationsDir),
                QtWidgets.QApplication.translate(
                    'main', 'Markdown Files (*.md)'))
            if len(fileName) < 1:
                return
            if isinstance(fileName, tuple):
                fileName = fileName[0]
            # on recopie le fichier dans presentationsDir/md si besoin :
            fileInfo = QtCore.QFileInfo(fileName)
            mdDir = utils_functions.u('{0}/md').format(self.presentationsDir)
            mdDir = QtCore.QDir(mdDir).absolutePath()
            fileDir = QtCore.QDir(fileInfo.canonicalPath()).absolutePath()
            if fileDir != mdDir:
                goodFile = utils_functions.u('{0}/md/{1}.md').format(
                    self.presentationsDir, fileInfo.baseName())
                utils_filesdirs.removeAndCopy(fileName, goodFile)
                fileName = goodFile
        fileInfo = QtCore.QFileInfo(fileName)
        # lecture du fichier :
        lines = utils_filesdirs.readTextFile(fileName)        
        (title, css, color, otherTools, lines) = readHeader(lines)
        otherTools = [tool for tool in otherTools.split('|') if len(tool) > 0]

        self.titleEdit.setText(title)
        self.cssEdit.setText(css)
        if color == '':
            color = QtGui.QColor(utils.DEFAULTCOLOR)
        else:
            color = QtGui.QColor(color)
        self.updateColor(color)
        for tool in self.configDict['TOOLS']['ORDER']:
            self.otherTools[tool][1].setChecked(tool in otherTools)
        self.markdownEditor.setPlainText(lines)
        self.actualFile = {
            'baseName': fileInfo.baseName(), 
            'mustSave': False}
        self.updateWindowTitle()
        # on met à jour les fichiers récents :
        baseName = utils_functions.u(
            '{0}.md').format(fileInfo.baseName())
        self.updateLastFilesMenu(baseName=baseName)
        self.preview()

    def doSave(self, fileName):
        """
        enregistrement réel d'un fichier.
        Procédure appelée depuis saveFile ou saveFileAs.
        On insère l'entête perso au début du fichier.
        Elle permet de sauvegarder divers réglages (title, etc)
        """
        utils_functions.doWaitCursor()
        try:
            if len(self.titleEdit.text()) > 0:
                titleText = utils_functions.u(
                    'TITLE:{0}\n').format(self.titleEdit.text())
            else:
                titleText = ''
            if len(self.cssEdit.text()) > 0:
                cssText = utils_functions.u(
                    'CSS:{0}\n').format(self.cssEdit.text())
            else:
                cssText = ''
            color = self.actionColor.data().name()
            if color == utils.DEFAULTCOLOR:
                colorText = ''
            else:
                colorText = 'COLOR:{0}\n'.format(color)
            otherTools = ''
            for tool in self.configDict['TOOLS']['ORDER']:
                if self.otherTools[tool][1].isChecked():
                    otherTools = '{0}|{1}'.format(otherTools, tool)
            if len(otherTools) > 0:
                otherTools = 'OTHER TOOLS:{0}\n'.format(otherTools)
            lines = utils_functions.u(
                '<!--\n'
                'HEADER FOR PAMPI CONFIG\n'
                '{1}{2}{3}{4}'
                '-->\n'
                '\n\n'
                '{0}')
            lines = utils_functions.u(lines).format(
                self.markdownEditor.toPlainText(), 
                titleText, 
                cssText, 
                colorText, 
                otherTools)
            utils_filesdirs.writeTextFile(fileName, lines)
            self.updateWindowTitle()
        finally:
            utils_functions.restoreCursor()

    def saveFile(self):
        """
        enregistrement d'un fichier.
        """
        if len(self.actualFile['baseName']) < 1:
            self.saveFileAs()
            return
        fileName = utils_functions.u('{0}/md/{1}.md').format(
            self.presentationsDir, self.actualFile['baseName'])
        self.doSave(fileName)

    def saveFileAs(self):
        """
        enregistrement d'un fichier.
        """
        proposedName = self.actualFile['baseName']
        if len(proposedName) < 1:
            proposedName = QtWidgets.QApplication.translate(
                'main', 'No name')
        proposedName = utils_functions.u('{0}/md/{1}.md').format(
            self.presentationsDir, proposedName)
        fileName = QtWidgets.QFileDialog.getSaveFileName(
            self,
            QtWidgets.QApplication.translate(
                'main', 'Save as...'),
            proposedName,
            QtWidgets.QApplication.translate(
                'main', 'Markdown Files (*.md)'))
        if len(fileName) < 1:
            return
        if isinstance(fileName, tuple):
            fileName = fileName[0]
        # on met à jour actualFile :
        fileInfo = QtCore.QFileInfo(fileName)
        self.actualFile = {
            'baseName': fileInfo.baseName(), 
            'mustSave': False}
        # on met à jour les fichiers récents :
        baseName = utils_functions.u(
            '{0}.md').format(fileInfo.baseName())
        self.updateLastFilesMenu(baseName=baseName)
        # on enregistre le fichier :
        self.doSave(fileName)

    def export(self):
        """
        export d'un fichier dans un dossier.
        On sélectionne le dossier parent.
        On crée un sous-dossier portant le nom du fichier de présentation
        et on y recopie tout le nécessaire.
        Enfin on ouvre le dossier.
        """
        if not(self.testMustSave()):
            return

        # sélection du dossier de destination :
        directory = QtWidgets.QFileDialog.getExistingDirectory(
            self, 
            QtWidgets.QApplication.translate(
                'main', 'Select a directory to export the presentation'), 
            QtCore.QDir.homePath(), 
            QtWidgets.QFileDialog.DontResolveSymlinks | QtWidgets.QFileDialog.ShowDirsOnly)
        if len(directory) < 1:
            return
        if isinstance(directory, tuple):
            directory = directory[0]
        #print('directory:', directory)

        # création des sous-dossiers :
        for dirName in ('assets', 'data', 'md'):
            utils_filesdirs.createDirs(
                directory, 
                utils_functions.u('{0}/{1}').format(
                    self.actualFile['baseName'], dirName))
        # copie des fichiers md et html :
        fileName = utils_functions.u('{0}/md/{1}.md').format(
            self.presentationsDir, self.actualFile['baseName'])
        utils_filesdirs.removeAndCopy(
            utils_functions.u('{0}/md/{1}.md').format(
                self.presentationsDir, self.actualFile['baseName']), 
            utils_functions.u('{0}/{1}/md/{1}.md').format(
                directory, self.actualFile['baseName']))
        utils_filesdirs.removeAndCopy(
            utils_functions.u('{0}/{1}.html').format(
                self.presentationsDir, self.actualFile['baseName']), 
            utils_functions.u('{0}/{1}/{1}.html').format(
                directory, self.actualFile['baseName']))
        # copie du dossier assets :
        utils_filesdirs.copyDir(
            utils_functions.u('{0}/assets').format(
                self.presentationsDir), 
            utils_functions.u('{0}/{1}/assets').format(
                directory, self.actualFile['baseName']))

        # récupération des fichiers utilisés et copie dans data :
        data = []
        for (before, after) in (('](data/', ')'), ('"data/', '"')):
            lines = self.markdownEditor.toPlainText().split(before)
            for line in lines[1:]:
                line2 = line.split(after)[0]
                if len(line2) > 0:
                    if not(line2 in  data):
                        data.append(line2)
        #print('data:', data)
        for fileName in data:
            newDirs = utils_functions.u('/').join(fileName.split('/')[:-1])
            if len(newDirs) > 0:
                utils_filesdirs.createDirs(
                    utils_functions.u('{0}/{1}/data').format(
                        directory, self.actualFile['baseName']), 
                    newDirs)
            utils_filesdirs.removeAndCopy(
                utils_functions.u('{0}/data/{1}').format(
                    self.presentationsDir, fileName), 
                utils_functions.u('{0}/{1}/data/{2}').format(
                    directory, self.actualFile['baseName'], fileName))

        # pour terminer on ouvre le dossier :
        utils_filesdirs.openDir(utils_functions.u('{0}/{1}').format(
            directory, self.actualFile['baseName']))

    def doCreateDesktopFileLinux(self):
        """
        Sous GNU/Linux, propose de créer un fichier .desktop
        pour lancer le logiciel.
        """
        title = QtWidgets.QApplication.translate(
            'main', 
            'Choose the Directory where the desktop file will be created')
        flag = (
            QtWidgets.QFileDialog.DontResolveSymlinks | 
            QtWidgets.QFileDialog.ShowDirsOnly)
        directory = QtWidgets.QFileDialog.getExistingDirectory(
            self,
            title,
            QtCore.QDir.homePath(),
            flag)
        if directory != '':
            utils_functions.doWaitCursor()
            try:
                result = utils_filesdirs.createLinuxDesktopFile(
                    self, 
                    self.beginDir, 
                    directory, 
                    'PAMPI', 'icon', 'pampi')
            finally:
                utils_functions.restoreCursor()

    def saveDoShow(self):
        """
        enchaînement des 3 actions
        """
        self.saveFile()
        self.doPandoc()
        self.preview()

    def searchPandoc(self):
        """
        cherche si Pandoc est bien installé
        """
        pandocBin = ''
        if utils.OS_NAME[0] == 'win':
            pandocBin = utils_functions.u(
                '{0}\libs\win\pandoc.exe').format(
                    QtCore.QDir.toNativeSeparators(self.beginDir))
        else:
            possiblePaths = ('/usr/local/bin', '/opt', '/usr/bin', )
            for possiblePath in possiblePaths:
                fileName = utils_functions.u('{0}/pandoc').format(possiblePath)
                if QtCore.QFileInfo(fileName).isFile():
                    pandocBin = fileName
                    break
        if len(pandocBin) < 1:
            link = utils.HELPPAGE
            m1 = QtWidgets.QApplication.translate(
                'main', 'PANDOC IS NOT INSTALLED.')
            m2 = QtWidgets.QApplication.translate(
                'main', 'This is the tool to convert Markdown files to html.')
            m3 = QtWidgets.QApplication.translate(
                'main', 'See the PAMPI help page:')
            message = utils_functions.u(
                '<p align="center"><b>{0}</b></p>'
                '<p align="center">__________________________</p>'
                '<p align="center">{1}</p>'
                '<p align="center">{2} <a href="{3}">PAMPI</a>.</p>'
                '<p></p>').format(m1, m2, m3, link)
            utils_functions.messageBox(self, message=message)
        return pandocBin

    def doPandoc(self, pdfCSS=''):
        """
        mise en place et exécution du process de Pandoc
        """
        if len(self.actualFile['baseName']) < 1:
            return
        utils_functions.doWaitCursor()
        try:
            QtCore.QDir.setCurrent(self.presentationsDir)
            if len(self.titleEdit.text()) > 0:
                title = self.titleEdit.text()
            else:
                title = self.actualFile['baseName']
            if len(self.cssEdit.text()) > 0:
                css = self.cssEdit.text()
            else:
                css = 'default'
            if len(pdfCSS) > 0:
                pdfText = '-pdf'
                outFileName = '_temp'
            else:
                pdfText = ''
                outFileName = self.actualFile['baseName']
            utils_filesdirs.removeAndCopy(
                utils_functions.u('{0}/assets/_template{1}.html').format(
                    self.presentationsDir, pdfText), 
                utils_functions.u('{0}/_template.html').format(
                    self.tempPath))

            inFileName = utils_functions.u(
                '{0}/assets/_template{1}.html').format(self.presentationsDir, pdfText)
            htmlModele = utils_filesdirs.readTextFile(inFileName)
            if len(htmlModele) < 1:
                message = QtWidgets.QApplication.translate(
                    'main', 'Cannot read file {0}').format(inFileName)
                utils_functions.messageBox(self, level='warning', message=message)
                return
            htmlCurrent = utils_functions.u(htmlModele)

            # on inscrit les appels supplémentaires
            # dans head ou à la fin :
            otherHead = ''
            if len(pdfCSS) < 1:
                color = self.actionColor.data().name()
                if color != utils.DEFAULTCOLOR:
                    otherHead += '\n        <style>body {0}background-color: {2};{1}</style>'.format('{', '}', color)
            for tool in self.configDict['TOOLS']['ORDER']:
                if self.otherTools[tool][1].isChecked():
                    for line in self.configDict['TOOLS'][tool]['HEAD']:
                        otherHead = '{0}\n        {1}'.format(otherHead, line) 
            htmlCurrent = htmlCurrent.replace('<!--OTHER HEAD-->', otherHead)
            otherEnd = ''
            for tool in self.configDict['TOOLS']['ORDER']:
                if self.otherTools[tool][1].isChecked():
                    for line in self.configDict['TOOLS'][tool]['END']:
                        otherEnd = '{0}\n        {1}'.format(otherEnd, line)
            htmlCurrent = htmlCurrent.replace('<!--OTHER END-->', otherEnd)

            # enregistrement du fichier :
            templateFileName = utils_functions.u(
                '{0}/_template.html').format(self.tempPath)
            utils_filesdirs.writeTextFile(templateFileName, htmlCurrent)

            if utils.OS_NAME[0] == 'win':
                args = [
                    '--template', 
                    utils_functions.u(
                        '{0}\_template.html').format(
                            QtCore.QDir.toNativeSeparators(self.tempPath))]
            else:
                args = ['--template', templateFileName]
            args.extend([
                '-V', utils_functions.u('css={0}').format(css), 
                '-V', utils_functions.u('title={0}').format(title), 
                ])
            if len(pdfCSS) > 0:
                args.extend([
                    '-V', utils_functions.u('pdf={0}').format(pdfCSS)])
            """
            if 'MathJax' in self.otherTools:
                if self.otherTools['MathJax'][1].isChecked():
                    args.extend(['-s', '--mathjax'])
            """
            args.extend([
                '-s', '-t', 'html5', '--section-divs', '-o'])
            if utils.OS_NAME[0] == 'win':
                args.append(
                    utils_functions.u('{0}\{1}.html').format(
                        QtCore.QDir.toNativeSeparators(self.presentationsDir), 
                        outFileName))
                args.append(
                    utils_functions.u('{0}\md\{1}.md').format(
                        QtCore.QDir.toNativeSeparators(self.presentationsDir), 
                        self.actualFile['baseName']))
            else:
                args.append(utils_functions.u('{0}.html').format(outFileName))
                args.append(utils_functions.u('md/{0}.md').format(self.actualFile['baseName']))

            #print(self.pandocBin, ' '.join(args))
            process = QtCore.QProcess(self)
            process.start(self.pandocBin, args)
            if not process.waitForStarted(3000):
                QtCore.qDebug('BUG IN PROCESS')
                return False
            if not process.waitForFinished():
                return False
        finally:
            QtCore.QDir.setCurrent(self.beginDir)
            utils_functions.restoreCursor()

    def preview(self):
        """
        (re)charge le fichier html de la présentation
        """
        if len(self.actualFile['baseName']) < 1:
            return
        utils_functions.doWaitCursor()
        try:
            outFileName = utils_functions.u(
                '{0}/{1}.html').format(
                    self.presentationsDir, self.actualFile['baseName'])
            if outFileName == self.helpWebView.url().path():
                self.helpWebView.load(self.helpWebView.url())
            else:
                url = QtCore.QUrl().fromLocalFile(outFileName)
                self.helpWebView.load(url)
        finally:
            utils_functions.restoreCursor()

    def fullScreen(self):
        unchecked = not(self.actionFullScreen.isChecked())
        if self.actionShowExamples.isChecked():
            self.actionShowExamples.setChecked(False)
            self.editExamplesCheckBox.setVisible(False)
            self.whatStackedWidget.setCurrentIndex(0)
        self.leftGroup.setVisible(unchecked)
        self.showExamplesButton.setVisible(unchecked)

    def launchInBrowser(self):
        if len(self.actualFile['baseName']) < 1:
            return
        utils_filesdirs.openFile(
            utils_functions.u('{0}/{1}.html').format(
                self.presentationsDir, self.actualFile['baseName']))

    def openPresentationDir(self):
        utils_filesdirs.openDir(self.presentationsDir)

    def movePresentationDir(self):
        """
        déplacement du dossier des présentations.
        On sélectionne le nouveau dossier.
        Tout est ensuite recopié
        """
        # sélection du nouveau dossier :
        directory = QtWidgets.QFileDialog.getExistingDirectory(
            self, 
            QtWidgets.QApplication.translate(
                'main', 'Select a directory'), 
            QtCore.QDir.homePath(), 
            QtWidgets.QFileDialog.DontResolveSymlinks | QtWidgets.QFileDialog.ShowDirsOnly)
        if len(directory) < 1:
            return
        if isinstance(directory, tuple):
            directory = directory[0]
        utils_functions.doWaitCursor()
        try:
            # copie du dossier :
            utils_filesdirs.copyDir(
                self.presentationsDir, 
                directory)
            self.presentationsDir = directory
            self.configDict['OTHER']['presentationsDir'] = directory
        finally:
            QtCore.QDir.setCurrent(self.beginDir)
            utils_functions.restoreCursor()

    def updatePresentationDir(self):
        """
        mise à jour du dossier des présentations.
        Utile après mise à jour du logiciel
        """
        message = QtWidgets.QApplication.translate(
            'main',
            'This action will update the tools and examples provided with PAMPI.\n'
            'Your personal files will not be deleted.\n\n'
            'Do you want to continue ?')
        reponse = utils_functions.messageBox(
            self, level='warning', message=message,
            buttons=['Yes', 'No'])
        if reponse != QtWidgets.QMessageBox.Yes:
            return

        utils_functions.doWaitCursor()
        try:
            utils_filesdirs.copyDir(
                utils_functions.u('{0}/presentations').format(self.beginDir), 
                self.presentationsDir)
        finally:
            utils_functions.restoreCursor()

    def showHelp(self, here=False):
        """
        permet à la fois de charger la présentation d'aide au lancement
        et d'ouvrir l'aide dans le navigateur
        """
        if here:
            url = QtCore.QUrl().fromLocalFile(
                utils_functions.u(
                    '{0}/pampi-help.html').format(self.presentationsDir))
            self.helpWebView.load(url)
        else:
            url = QtCore.QUrl(utils.HELPPAGE)
            QtGui.QDesktopServices.openUrl(url)

    def about(self):
        """
        affichage de la fenêtre À propos
        """
        import utils_about
        aboutdialog = utils_about.AboutDlg(
            self, self.locale, icon='./images/icon.svgz')
        lastState = self.disableInterface(aboutdialog)
        aboutdialog.exec_()
        self.enableInterface(aboutdialog, lastState)

    def showExamples(self):
        """
        affichage du pense-bête
        """
        checked = self.actionShowExamples.isChecked()
        self.editExamplesCheckBox.setVisible(checked)
        if checked:
            self.whatStackedWidget.setCurrentIndex(1)
        else:
            self.whatStackedWidget.setCurrentIndex(0)

    def editExamples(self):
        """
        pour rendre le fichier pense-bête modifiable
        """
        checked = self.editExamplesCheckBox.isChecked()
        if checked:
            self.samplesEditor.setReadOnly(False)
        else:
            # on enregistre le fichier d'exemple :
            lines = self.samplesEditor.toPlainText()
            utils_filesdirs.writeTextFile(self.examplesFile, lines)
            self.samplesEditor.setReadOnly(True)

    def changeColor(self):
        color = self.actionColor.data()
        newColor = QtWidgets.QColorDialog.getColor(
            color, 
            self, 
            QtWidgets.QApplication.translate('main', 'Select color'), 
            QtWidgets.QColorDialog.ShowAlphaChannel)
        self.updateColor(newColor)
        self.updateWindowTitle(mustSave=True)

    def updateColor(self, color):
        if color.isValid():
            pixmap = QtGui.QPixmap(self.iconsize)
            pixmap.fill(color)
            icon = QtGui.QIcon(pixmap)
            self.actionColor.setIcon(icon)
            self.actionColor.setData(color)

    def doWizard(self):
        """
        lance l'assistant de modèle de présentation
        """
        import utils_wizard
        dialog = utils_wizard.WizardDlg(
            self, self.locale, icon='./images/icon.svgz')
        lastState = self.disableInterface(dialog)
        if dialog.exec_() == QtWidgets.QDialog.Accepted:
            if dialog.copyOnlyCheckBox.isChecked():
                # envoi au presse-papier :
                clipboard = QtWidgets.QApplication.clipboard()
                clipboard.setText(dialog.lines)
            else:
                self.markdownEditor.setPlainText(dialog.lines)
        self.enableInterface(dialog, lastState)

    def createPDF(self):
        if len(self.actualFile['baseName']) < 1:
            return
        import utils_pdf
        # on récupère les choix faits par l'utilisateur :
        outFileNamePdf = utils_functions.u(
            '{0}/{1}.pdf').format(
                self.pdfDir, self.actualFile['baseName'])
        dialog = utils_pdf.PDFConfigDlg(
            parent=self, fileNamePdf=outFileNamePdf)
        lastState = self.disableInterface(dialog)
        if dialog.exec_() != QtWidgets.QDialog.Accepted:
            self.enableInterface(dialog, lastState)
            return
        self.enableInterface(dialog, lastState)
        if dialog.withNotes.isChecked():
            pdfCSS = 'pdf-notes'
        else:
            pdfCSS = 'pdf'

        # on crée le fichier :
        utils_functions.doWaitCursor()
        try:
            self.doPandoc(pdfCSS=pdfCSS)
            outFileName = utils_functions.u(
                '{0}/_temp.html').format(
                    self.presentationsDir)
            if dialog.createPDF.isChecked():
                outFileNamePdf = dialog.pdfFileNameEdit.text()
                self.pdfDir = QtCore.QFileInfo(outFileNamePdf).absolutePath()
                utils_pdf.htmlToPdf(
                    self, outFileName, outFileNamePdf, orientation='Landscape')
                utils_filesdirs.openFile(outFileNamePdf)
            if dialog.openHtml.isChecked():
                utils_filesdirs.openFile(outFileName)
        finally:
            utils_functions.restoreCursor()
        return


