# Copyright (c) 2011-2014, B.I.Stepanov Institute of Physics, National Academy
# of Sciences of Belarus.
#
# 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 2 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, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import os.path

from PyQt4.QtCore import *
from PyQt4.QtGui import *

from common.utils import gui
from common.utils import txt

__all__ = ['FileSelectionWidget']

# *****************************************************************************
class FileSelectionWidget(QWidget):
    """Widget that displays a file path and allows the user to select the file
    interactively.

    Signals:
      - 'fileOpened': a path to an existing file has been selected by the user,
        or the widget's initialization process (carried out in 'loadSettings')
        has been completed. Use 'getFilePath' to get the file's path. Note:
        if the widget's initialization process fails due to missing settings
        file or non-existent file path, this signal will still be fired, and
        'getFilePath' will return 'None' in that case.
      - 'fileCreated': A non-existent file path has been selected by the
        user. Use 'getFilePath' to get the path, and 'invalidateFilePath' to
        report failure of creation of the requested file."""

    # ---- Signals ------------------------------------------------------------
    # Paths are not passed as signals' arguments, because it seems to be no way
    # of passing native 'unicode's through PyQt's signals and slots mechanism,
    # and 'QStrings' may cause a lot of confusion in Python code.
    fileOpened = pyqtSignal()
    fileCreated = pyqtSignal()

    # ---- Public methods -----------------------------------------------------
    def __init__(self, labelText, fileNameFilter, openDialogCaption,
        openButtonToolTip = None, parent = None):

        QWidget.__init__(self, parent)

        # ---- Members ----
        self.fileNameFilter = fileNameFilter
        self.openDialogCaption = openDialogCaption
        self.createDialogCaption = None
        # Path to directory of the currently selected file unless it has been
        # invalidated with 'invalidateFilePath', or path to directory of the
        # previously selected one otherwise.
        self.lastFileDir = None

        # ---- Layout ----
        layout = QHBoxLayout()

        if labelText is not None:
            filePathLabel = QLabel(labelText)
            layout.addWidget(filePathLabel)
        else:
            filePathLabel = None

        self.filePathEdit = QLineEdit()
        # Disable direct editing of the file path.
        self.filePathEdit.setReadOnly(True)
        layout.addWidget(self.filePathEdit)

        self.createButton = QPushButton(gui.loadIcon('new'), '')
        self.createButton.clicked.connect(self.onCreateButtonClicked)
        self.createButton.hide()
        layout.addWidget(self.createButton)

        self.reloadButton = QPushButton(gui.loadIcon('reload'), '')
        # Reload button has to be disabled if there's no file selected.
        self.reloadButton.setEnabled(False)
        self.reloadButton.clicked.connect(self.onReloadButtonClicked)
        self.reloadButton.hide()
        layout.addWidget(self.reloadButton)

        self.openButton = QPushButton(gui.loadIcon('open'), '')
        if openButtonToolTip is not None:
            self.openButton.setToolTip(openButtonToolTip)
        self.openButton.clicked.connect(self.onOpenButtonClicked)
        layout.addWidget(self.openButton)

        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        # ---- Initialization ----
        # Make it possible to specify a shortcut for the open button in the
        # widget's label.
        if filePathLabel is not None:
            filePathLabel.setBuddy(self.openButton)

    def showCreateButton(self, createDialogCaption,
        createButtonToolTip = None):
        """Display a button that will make it possible to select a non-existent
        file path.

        Whenever such a file path is selected, 'fileCreated' signal will be
        fired instead of the usual 'fileOpened' signal."""

        self.createDialogCaption = createDialogCaption
        if createButtonToolTip is not None:
            self.createButton.setToolTip(createButtonToolTip)
        self.createButton.show()

    def showReloadButton(self, reloadButtonToolTip = None):
        """Display a button that will make it possible to reload the currently
        selected file in one click.

        Reload button will simply fire 'fileOpened' for the currently selected
        file once again."""

        if reloadButtonToolTip is not None:
            self.reloadButton.setToolTip(reloadButtonToolTip)
        self.reloadButton.show()

    def getOpenButton(self):
        """This may be used with a 'QLabel's 'setBuddy' method, if required."""
        return self.openButton

    def getFilePath(self):
        """Return the currently selected file path or 'None' if no file is
        selected."""

        filePath = self.filePathEdit.text()
        if filePath == '':
            return None

        # Return a canonical representation of the file path.
        return txt.absolutePath(filePath)

    def openFile(self, filePath):
        """Set the currently selected file path to 'filePath' unless it is
        'None' or represents a non-existent file path, and then fire
        'fileOpened' signal regardless of the given value of 'filePath'.

        If 'filePath' turns out to be valid, then 'getFilePath' will return the
        path if called from the signal handler. Otherwise, it will return
        'None'."""

        if filePath is not None:
            # Normalize the file path, if required.
            filePath = txt.absolutePath(filePath)

            if not os.path.isfile(filePath):
                filePath = None

        if filePath is not None:
            self.lastFileDir = os.path.dirname(filePath)
        else:
            self.lastFileDir = None
            filePath = ''

        self.filePathEdit.setText(filePath)
        self.reloadButton.setEnabled(filePath != '')
        self.fileOpened.emit()

    def saveSettings(self, settings, key):
        """Save the currently selected file path to a 'QSettings' instance."""

        filePath = self.getFilePath()

        if filePath is None:
            settings.setValue(key, None)
        else:
            settings.setValue(key, txt.portablePath(filePath))

    def loadSettings(self, settings, key):
        """Load the file path from a 'QSettings' instance and then fire
        'fileOpened' signal regardles of the loading procedure result.

        If a file path is successfully loaded from the settings file and points
        to an existing file, then 'getFilePath' will return the path if called
        from the signal handler. Otherwise, it will return 'None'."""

        # If the file path is missing, an empty string will be returned.
        filePath = settings.value(key).toString()

        if filePath == '':
            filePath = None

        self.openFile(filePath)

    def invalidateFilePath(self):
        """Clear the currently selected file path."""

        # Don't modify 'self.lastFileDir' here, just clear the file path.
        self.filePathEdit.setText('')
        self.reloadButton.setEnabled(False)

    # ---- Private methods ----------------------------------------------------
    def getFileDir(self):
        """Return file directory path suitable for passing to one of the
        'QFileDialog's static file selection functions."""

        if self.lastFileDir is None or not os.path.isdir(self.lastFileDir):
            return '.'
        else:
            return self.lastFileDir

    def setFilePath(self, filePath):
        """Set the file path, update 'self.lastFileDir' and enable the reload
        button."""

        # Normalize the file path, if required.
        filePath = txt.absolutePath(filePath)

        self.lastFileDir = os.path.dirname(filePath)
        self.filePathEdit.setText(filePath)
        self.reloadButton.setEnabled(True)

    # ---- Private slots ------------------------------------------------------
    def onOpenButtonClicked(self):

        filePath = QFileDialog.getOpenFileName(self,
            self.openDialogCaption, self.getFileDir(), self.fileNameFilter)

        if not filePath.isNull():
            self.setFilePath(filePath)
            self.fileOpened.emit()

    def onReloadButtonClicked(self):

        # Reload button has to be disabled if there's no file selected.
        assert self.getFilePath() is not None
        self.fileOpened.emit()

    def onCreateButtonClicked(self):

        # There's actually no "Create File" dialog in Qt, only a "Save As" one.
        # If the user selects an existing file, he will be prompted to either
        # cancel the action or use "Open" command on the selected file instead.
        # Thus, it would be impossible to implicitly delete an existing file
        # with this button.
        filePath = QFileDialog.getSaveFileName(self,
            self.createDialogCaption, self.getFileDir(),
            self.fileNameFilter, None, QFileDialog.DontConfirmOverwrite)

        if filePath.isNull():
            return

        filePath = txt.absolutePath(filePath)

        # Fire 'fileCreated' and validate the path if it does not exist.
        if not os.path.isfile(filePath):
            self.setFilePath(filePath)
            self.fileCreated.emit()
            return

        msgBox = QMessageBox(self)
        msgBox.setWindowTitle(self.createDialogCaption)
        msgBox.setText(
            'The file selected (%s) already exists.' % txt.quotePath(filePath))
        msgBox.setInformativeText(
            'Would you like to open the existing file instead of creating '
            'a new one?')
        msgBox.setIcon(QMessageBox.Warning)
        msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)

        if msgBox.exec_() == QMessageBox.Yes:
            # Fire 'fileOpened' instead of 'fileCreated' on the existing file.
            self.setFilePath(filePath)
            self.fileOpened.emit()
