#!/usr/bin/env python

# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################

"""This package provides a dialog for graphically choosing a Taurus class
"""

from taurus.external.qt import Qt


__docformat__ = "restructuredtext"


class GraphicalChoiceDlg(Qt.QDialog):
    """
    A generic dialog for choosing among a set of choices which are presented as
    an array of, each with a given pixmap.

    The :meth:`getChoice` static method is provided for convenience so that the
    dialog can be invoked wit a single line::

        chosen,ok = GraphicalChoiceDlg.getChoice(
            parent, title, msg, choices, pixmaps, size, defpixmap,
            horizontalScrollBarPolicy, verticalScrollBarPolicy
            )
    """

    def __init__(
        self,
        parent=None,
        designMode=False,
        choices=None,
        pixmaps=None,
        iconSize=128,
        defaultPixmap=None,
        horizontalScrollBarPolicy=Qt.Qt.ScrollBarAsNeeded,
        verticalScrollBarPolicy=Qt.Qt.ScrollBarAsNeeded,
    ):
        Qt.QDialog.__init__(self, parent)
        self._chosen = None
        self.setLayout(Qt.QVBoxLayout())
        self.label = Qt.QLabel("Click on your choice:")
        self.layout().addWidget(self.label)
        self._iconsArea = GraphicalChoiceWidget(
            parent=parent,
            designMode=designMode,
            choices=choices,
            pixmaps=pixmaps,
            iconSize=iconSize,
            defaultPixmap=defaultPixmap,
            horizontalScrollBarPolicy=horizontalScrollBarPolicy,
            verticalScrollBarPolicy=verticalScrollBarPolicy,
        )
        self.layout().addWidget(self._iconsArea)
        self._iconsArea.choiceMade.connect(self.onChoiceMade)

    def setHorizontalScrollBarPolicy(self, policy):
        """sets horizontal scrollbar policy of scrollArea"""
        return self._iconsArea.setHorizontalScrollBarPolicy(policy)

    def setVerticalScrollBarPolicy(self, policy):
        """sets vertical scrollbar policy of scrollArea"""
        return self._iconsArea.setVerticalScrollBarPolicy(policy)

    def setMessage(self, msg):
        """sets the text which is shown to the user in the dialog"""
        self.label.setText(msg)

    def onChoiceMade(self, chosen):
        """slot called when the user chooses an option"""
        self.accept()

    def getChosen(self):
        """
        returns the choice
        :return:
        :rtype: str
        """
        return self._iconsArea.getChosen()

    @staticmethod
    def getChoice(
        parent=None,
        title="",
        msg="",
        choices=None,
        pixmaps=None,
        iconSize=128,
        defaultPixmap=None,
        horizontalScrollBarPolicy=Qt.Qt.ScrollBarAsNeeded,
        verticalScrollBarPolicy=Qt.Qt.ScrollBarAsNeeded,
    ):
        """
        Static method which launches a GraphicalChoiceDlg with the given
        options and returns the result

        :param parent: The parent of the dialog (it will be centered on it)
        :type parent: QWidget
        :param title: the text which is displayed in the title bar of the
            dialog
        :type title: str
        :param msg: the text which is shown to the user in the dialog, above
            the choices.
        :type msg: str
        :param choices: a list of lists of strings to be used as choices names.
            The (possibly sparse) 2D array defined by the nested lists will be
            used to present the choices in a grid. The choice names will be
            used as keys for pixmaps
        :type choices: list<list>
        :param pixmaps: dictionary mapping the choices text to corresponding
            pixmap. If no valid pixmap is provided for a given choice, the
            defaultPixmap will be used
        :type pixmaps: dict<str,QPixmap>
        :param iconSize: size of the icons to be displayed (128px by default)
        :type iconSize: int
        :param defaultPixmap: Default Pixmap to use if none passed for a given
            choice. No Pixmap will be used if None passed.
        :type defaultPixmap: QPixmap
        :param horizontalScrollBarPolicy: defines the mode of the horizontal
            scroll bar. The default mode is ScrollBarAsNeeded.
        :type horizontalScrollBarPolicy: enum Qt.ScrollBarPolicy
        :param verticalScrollBarPolicy: defines the mode of the vertical scroll
            bar. The default mode is ScrollBarAsNeeded
        :type verticalScrollBarPolicy: enum Qt.ScrollBarPolicy
        :return: A tuple containing choice,ok. choice is the name of the chosen
            option. ok is true if the user pressed OK and false if the user
            pressed Cancel.
        :rtype: tuple<str,bool>
        """
        dlg = GraphicalChoiceDlg(
            parent=parent,
            choices=choices,
            pixmaps=pixmaps,
            iconSize=iconSize,
            defaultPixmap=defaultPixmap,
            horizontalScrollBarPolicy=horizontalScrollBarPolicy,
            verticalScrollBarPolicy=verticalScrollBarPolicy,
        )
        dlg.setWindowTitle(title)
        dlg.setMessage(msg)
        dlg.exec_()
        return dlg.getChosen(), (dlg.result() == dlg.Accepted)


class GraphicalChoiceWidget(Qt.QScrollArea):
    """A widget that presents a 2D grid of buttons"""

    choiceMade = Qt.pyqtSignal("QString")

    def __init__(
        self,
        parent=None,
        designMode=False,
        choices=None,
        pixmaps=None,
        iconSize=128,
        defaultPixmap=None,
        horizontalScrollBarPolicy=Qt.Qt.ScrollBarAsNeeded,
        verticalScrollBarPolicy=Qt.Qt.ScrollBarAsNeeded,
    ):
        Qt.QScrollArea.__init__(self, parent)

        self._chosen = None
        self._iconSize = iconSize
        if defaultPixmap is None:
            defaultPixmap = Qt.QPixmap()
        self._defaultPixmap = defaultPixmap

        self.setFrameShape(Qt.QFrame.NoFrame)
        self.setWidgetResizable(True)
        self.setHorizontalScrollBarPolicy(horizontalScrollBarPolicy)
        self.setVerticalScrollBarPolicy(verticalScrollBarPolicy)

        w = Qt.QWidget()
        self.gridLayout = Qt.QGridLayout()
        w.setLayout(self.gridLayout)
        self.setWidget(w)

        if choices is not None:
            self.setChoices(choices, pixmaps)
        elif designMode:
            from taurus.qt.qtgui.icon import getCachedPixmap

            pm = getCachedPixmap("logos:taurus.png")
            self.setChoices(
                [["choice1", "choice2"], ["choice3", "choice4"]],
                dict(choice1=pm, choice2=pm, choice3=pm, choice4=pm),
            )

    def setChoices(self, choices, pixmaps=None):
        """
        sets the available options

        :param choices: a list of lists of strings to be used as choices names.
            The (possibly sparse) 2D array defined by the nested lists will be
            used to present the choices in a grid. The choice names will be
            used as keys for pixmaps
        :type choices: list<list>
        :param pixmaps: dictionary mapping the choices text to corresponding
            pixmap. If no valid pixmap is provided for a given choice, a
            default pixmap will be used
        :type pixmaps: dict<str,QPixmap>
        """
        if pixmaps is None:
            pixmaps = {}
        for i, rowlist in enumerate(choices):
            for j, choice in enumerate(rowlist):
                self.setChoice(
                    i, j, str(choice), pixmap=pixmaps.get(choice, None)
                )

    def setChoice(self, row, col, text, pixmap=None, tooltip=None):
        """
        sets the option for a given row,column coordinate in the grid

        :param row: row in the grid for this option
        :type row: int
        :param col: column in the grid for this option
        :type col: int
        :param text: name for this option
        :type text: str
        :param pixmap: If no valid pixmap is provided for a given choice, the
            default one will be used
        :type pixmap: QPixmap or None
        :param tooltip: tooltip for this option (if None given, the `text` is
            used)
        :type tooltip: str
        """
        if not pixmap or pixmap is None or pixmap.isNull():
            pixmap = self._defaultPixmap
        if tooltip is None:
            tooltip = text
        button = Qt.QToolButton()
        button.setText(text)
        button.setIcon(Qt.QIcon(pixmap))
        button.setIconSize(Qt.QSize(self._iconSize, self._iconSize))
        button.setToolTip(tooltip)
        button.clicked.connect(self.onClick)
        self.gridLayout.addWidget(button, row, col, Qt.Qt.AlignCenter)
        # -------------------------------------------------------
        # Work around for https://bugs.kde.org/show_bug.cgi?id=345023
        # TODO: make better solution for this
        button._id = text  # <-- ugly monkey-patch!
        # -------------------------------------------------------

    def onClick(self):
        """slot called when a button is clicked"""
        # -------------------------------------------------------
        # Work around for https://bugs.kde.org/show_bug.cgi?id=345023
        # TODO: make better solution for this
        # self._chosen = str(self.sender().text())  # <- fails due to added "&"
        self._chosen = self.sender()._id  # <-- this was monkey-patched
        # -------------------------------------------------------
        self.choiceMade.emit(self._chosen)

    def getChosen(self):
        """
        returns the choice
        :return:
        :rtype: str
        """
        return self._chosen

    @classmethod
    def getQtDesignerPluginInfo(cls):
        """Returns pertinent information in order to be able to build a valid
        QtDesigner widget plugin

        The dictionary returned by this method should contain *at least* the
        following keys and values:
        - 'module' : a string representing the full python module name (ex.:
        'taurus.qt.qtgui.base')
        - 'icon' : a string representing valid resource icon (ex.:
        'designer:combobox.png')
        - 'container' : a bool telling if this widget is a container widget or
        not.

        This default implementation returns the following dictionary::

            { 'group'     : 'Taurus Widgets',
              'icon'      : 'logos:taurus.png',
              'container' : False }

        :return: a map with pertinent designer information
        :rtype: dict
        """
        return {
            "module": "taurus.qt.qtgui.input",
            "group": "Taurus Input",
            "icon": "logos:taurus.png",
            "container": False,
        }


def testWidget():
    import sys
    from taurus.qt.qtgui.application import TaurusApplication

    app = TaurusApplication(sys.argv, cmd_line_parser=None)
    w = GraphicalChoiceWidget(None, True)
    w.show()
    sys.exit(app.exec_())


def main():
    import sys
    from taurus.qt.qtgui.icon import getCachedPixmap
    from taurus.qt.qtgui.application import TaurusApplication

    app = TaurusApplication(sys.argv, cmd_line_parser=None)

    pixmaps = {}
    choices = [["TaurusForm", "TaurusTrend"], ["TaurusPlot", "Qub"]]
    for row in choices:
        for k in row:
            pixmaps[k] = getCachedPixmap("snapshot:%s.png" % k)

    print(
        GraphicalChoiceDlg.getChoice(
            parent=None,
            title="Panel chooser",
            msg="Choose the type of Panel:",
            choices=choices,
            pixmaps=pixmaps,
        )
    )

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
    # testWidget()
