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

# vtoolpanel.py
# This file is part of VWidgets module
# see: http://bazaar.launchpad.net/~vincent-vandevyvre/oqapy/serie-1.0/
#
# Author: Vincent Vande Vyvre <vincent.vandevyvre@swing.be>
# Copyright: 2012-2013 Vincent Vande Vyvre
# Licence: GPL3
#
#
# This file 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 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 file.  If not, see <http://www.gnu.org/licenses/>.
#
# Define a dockable container with header bar for tool boxes.

import os
import sys

from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QGridLayout, QSpacerItem,
                             QSizePolicy, QScrollArea, QDockWidget, QSplitter)
from PyQt5.QtCore import Qt, QMetaType, QObject, pyqtProperty, pyqtSignal, QRect

from VWidgets_qt5.vtoolheader import VToolHeader

class VToolDockPanel(QDockWidget):
    currentToolPageChanged = pyqtSignal(int, int)
    def __init__(self, parent=None):
        """The VToolPanel is a vertical container for tool boxes.

        The widget has a VToolHeader and a QScrollArea.
        The pages of tools can be grouped by categories shown by means of 
        the buttons of the header bar.
        """
        super(VToolDockPanel, self).__init__(parent)
        self.setFeatures(QDockWidget.DockWidgetMovable)
        self.setAllowedAreas(Qt.LeftDockWidgetArea|Qt.RightDockWidgetArea)

        dct = dict(width_ = 300,
                    height_ = 750,
                    title_ = 'Tools Panel',
                    pages = [],
                    buttons = {},
                    current_ = -1)

        for key, value in dct.items():
            setattr(self, key, value)

        # Header
        self.header = VToolHeader(self, None, self.title_, Qt.LeftToRight)
        self.header.headerButtonClicked.connect(self._on_page_changed)
        self.header.setCollapseButton(False)
        self.setTitleBarWidget(self.header)

        self.scrollArea = ScrollArea(self)

        spacerItem4 = QSpacerItem(20, 10, QSizePolicy.Minimum, 
                                  QSizePolicy.MinimumExpanding)
        self.scrollArea.layout.addItem(spacerItem4)
        self.setWidget(self.scrollArea)

    def getTitle(self):
        return self.title_

    def setTitle(self, txt):
        """Sets the title of the panel.

        Args:
        txt -- the title
        """
        self.title_ = txt
        self.header.setTitle(txt)

    title = pyqtProperty('QString', getTitle, setTitle)

    def count(self):
        """Returns the numbers of pages.

        """
        return len(self.pages)

    def getCurrentPageName(self):
        return self.current_.name

    def addToolPage(self, name=None, icon=None):
        """Add a page of tool boxes

        Args:
        name -- the name of the page
        icon -- The icon used for the page button

        Returns:
        ToolPage instance
        """
        new = ToolPage(self.count(), name, self)

        if icon is not None:
            btn = self.header.add_button(icon, new.name)
            new.button = btn
            self.buttons[btn] = new

        self.pages.append(new)

        return new

    def getCurrentPage(self):
        """Returns the current page index.

        """
        return self.current_

    def setCurrentPage(self, idx):
        """Sets the current page.

        Args:
        idx -- index or instance of the page
        """
        if isinstance(idx, ToolPage):
            idx = self.pages.index(idx)

        if idx == self.current_ or idx >= self.count():
            return

        self._on_page_changed(self.pages[idx].button)

    currentPage = pyqtProperty('int', getCurrentPage, setCurrentPage)

    def remove_page(self, page):
        """Remove a page.

        Args:
        page -- index or instance of the page
        """
        if isinstance(page, int):
            page = self.pages[page]

        if page.index == self.current_:
            if self.current_ == 0:
                if self.count > 1:
                    self.setCurrentPage(1)

                else:
                    page.set_current(False)
                    self.current_ = -1

            else:
                self.setCurrentPage(self.current_-1)

        for box in page.boxes:
            self._remove_page(box, page)

        self.header.remove_button(page.button)
        self.pages.remove(page)

    def set_page_icon(self, icon, page=None):
        if page is None:
            page = self.getCurrentPage()
            
        if isinstance(page, int):
            page = self.pages[page]

        page.setIconPage(icon)

    def addToolBox(self, page, idx=None, splitter=None):
        """Add a tool box into a page.

        Args:
        page -- index or instance of the page
        idx -- index where the tool box will be inserted, if None, the tool box
                will be appended to the page
        splitter -- the instance of a splitter if the tool box must be inserted
                    into an existing splitter. In this case the index is 
                    evaluate into the splitter.

        Returns:
        ToolBox instance
        """
        if isinstance(page, int):
            page = self.pages[page]

        if splitter is not None:
            return self.add_toolbox_to_splitter(page, idx, splitter)

        if idx is None:
            idx = len(page.boxes)

        new = ToolBox(page.index, self.scrollArea.container)
        page.boxes.insert(idx, new)
        new.setVisible(False)

        if page.index == self.current_:
            self.scrollArea.layout.insertWidget(idx, new)
            new.setVisible(True)

        return new

    def add_toolbox_to_splitter(self, page, idx, splitter):
        """Add a tool box into a splitter.

        Args:
        page -- index or instance of the page
        idx -- index, into the splitter,where the tool box will be inserted, 
               if None, the tool box will be appended to the page
        splitter -- the instance of a splitter

        Returns:
        ToolBox instance
        """
        if splitter not in page.boxes:
            raise ValueError("VToolDockPanel.addToolBox error: page {0} "\
                         "has no splitter {1}".format(page.index, splitter))

        if idx is None or idx > splitter.count():
            idx = splitter.count()

        new = ToolBox(page.index, splitter)
        splitter.insertWidget(idx, new)
        splitter.boxes.insert(idx, new)

        return new

    def clone_tool_box(self, box, page, idx=None):
        """Sets a tool box that already exists into an other group.

        Args:
        box -- instance of the tool box
        page -- index or instance of the page
        idx -- index where the tool box will be inserted, if None, the tool box
                will be appended to the page
        """
        if isinstance(page, int):
            page = self.pages[page]

        if idx is None:
            idx = len(page.boxes)

        page.boxes.insert(idx, box)
        box.referenced.append(page.index)

        if page.index == self.current_:
            self.scrollArea.layout.insertWidget(idx, box)

    def remove_box(self, box, page=None):
        """Remove a tool box.

        The tool box will be removed from the page provided in argument.
        If the argument page is not provided, the box will be removed from
        all pages where the page is referenced.

        If the box become not referenced in any group, they will be destroyed.

        Args:
        box -- Instance of the tool box
        page -- Index or instance of the page
        """
        if not isinstance(box, ToolBox):
            raise TypeError("VToolDockPanel.remove_box(box, page): arg 1"
                            " has unexpected type: '{0}'\n".format(type(box)))
            return

        if page is None:
            self._remove_box_from_all_pages(box)
            return

        if isinstance(page, int):
            try:
                page = self.pages[page]
            except IndexError:
                raise TypeError("VToolDockPanel.remove_box(box, page): arg 2"
                            " has wrong index: '{0}'\n".format(page))               

        if not isinstance(page, ToolPage):
            raise TypeError("VToolDockPanel.remove_box(box, page): arg 2"
                            " has unexpected type: '{0}'\n".format(type(page)))

        self._remove_box(box, page)

    def add_splitter(self, page, idx=None):
        """Add a splitter layout to a page.

        Args:
        page -- Index or instance of the page
        idx -- index where the splitter will be inserted, if None, the splitter
                will be appended to the page

        Returns:
        The QSplitter instance
        """
        if isinstance(page, int):
            page = self.pages[page]

        if idx is None:
            idx = len(page.boxes)

        split = Splitter(page.index, self.scrollArea.container)
        page.boxes.insert(idx, split)
        split.setVisible(False)

        if page.index == self.current_:
            self.scrollArea.layout.insertWidget(idx, split)
            new.setVisible(True)

        return split

    def get_boxes(self):
        """Returns all instances of tool's boxes.

        Returns:
        Unordered list of boxe's instances
        """
        boxes = set([])
        for page in self.pages:
            boxes = boxes.union(page.boxes)

        return list(boxes)

    def _remove_box_from_all_pages(self, box):
        """Remove a tool box from all pages.

        Args:
        box -- Instance of the tool box
        """
        refs = box.referenced[:]
        for idx in refs:
            self._remove_box(box, self.pages[idx])

    def _remove_box(self, box, page):
        """Remove a page from one group.

        Args:
        box -- Instance of the tool box
        page -- Instance of the page
        """
        if box.isVisible():
            self.scrollArea.layout.removeWidget(box)

        for item in page.boxes:
            tbx = None
            if item == box:
                tbx = item
                break

            elif isinstance(item, QSplitter):
                for i in range(item.count()):
                    if item.widget(i) == box:
                        tbx = item.widget(i)
                        tbx.hide()

        if tbx is None:
            raise ValueError("VToolDockPanel.remove_box(box, page): toolBox "
                             "doesn't exist in the tool page")

        tbx.referenced.remove(page.index)
        if not tbx.referenced:
            tbx.setParent(None)
            del box

    def _on_page_changed(self, btn):
        """Change the current page.

        Args:
        btn -- instance of the button's page
        """
        if btn is None:
            # The panel has only one page
            page = self.pages[0]

        else:
            page = self.buttons[btn]

        if page.index != self.current_:
            self._hide_boxes(self.pages[self.current_])
            self._show_boxes(page)
            old = self.current_
            self.current_ = page.index

            self.currentToolPageChanged.emit(old, page.index)

    def _show_boxes(self, page):
        """Show the tool boxes for a page.

        Args:
        page -- the page instance
        """
        for box in reversed(page.boxes):
            # The last widget is a spacer item
            self.scrollArea.layout.insertWidget(0, box)
            box.setVisible(True)

    def _hide_boxes(self, page):
        """Hide the tool boxes for a page.

        Args:
        page -- the page instance
        """
        for box in page.boxes:
            box.setVisible(False)
            self.scrollArea.layout.removeWidget(box)

    def _make_icon(self, icon):
        """Returns a QIcon made from an image.

        Args:
        icon -- a QIcon or a QPixmap instance or an image file path

        Returns:
        QIcon instance
        """
        if icon is None:
            return

        if isinstance(icon, QIcon):
            return icon

        if isinstance(icon, QPixmap):
            img = icon

        elif os.path.isfile(icon):
            img = QPixmap(icon)
            if img.isNull(): 
                sys.stderr.write('VToolPanel icon is null: {0}\n'.format(icon))
                return

        else:
            sys.stderr.write('VToolPanel._make_icon() expected icon,'
                                    ' got {0}\n'.format(icon))
            return

        return QIcon(img)


class ScrollArea(QScrollArea):
    def __init__(self, parent=None):
        super(ScrollArea, self).__init__(parent)
        self.setWidgetResizable(True)
        self.container = QWidget(self)
        self.container.setGeometry(QRect(0, 0, 300, 720))
        self.layout = QVBoxLayout(self.container)
        self.layout.setContentsMargins(0, 0, 0, 0)
        self.layout.setSpacing(0)
        self.setWidget(self.container)


class ToolPage(QObject):
    def __init__(self, idx, name=None, panel=None):
        super(ToolPage, self).__init__()
        dct = dict(index = idx,
                    name = name,
                    panel = panel,
                    button = None,
                    boxes = [],
                    expanded = [],
                    is_current = False)

        for key, value in dct.items():
            setattr(self, key, value)

        if self.name is None:
            self.name = 'Tool_page_{0}'.format(self.index)

    def getIconPage(self):
        """Returns the Qicon instance used for the button page.

        """
        if self.button is not None:
            return self.button.icon

        return None

    def setIconPage(self, icon):
        """Sets the icon for the button page.

        Args:
        icon -- QIcon or QPixmap instance or image file path
        """
        icon = self.panel._make_icon(icon)
        if icon is None:
            return

        if self.button is None:
            btn = self.panel.header.add_button(icon, self.name)
            self.button = btn
            self.panel.buttons[btn] = self

        else:
            self.button.icon = icon
            # Need update
            self.button.set_icon()

    iconPage = pyqtProperty('QIcon', getIconPage, setIconPage)


class ToolBox(QWidget):
    visibilityChanged = pyqtSignal(bool)
    def __init__(self, pg, parent=None):
        """Instanciate a Tool box.

        Args:
        pg -- the page instance
        parent -- the VToolPanel
        """
        super(ToolBox, self).__init__(parent)
        self.layout = QVBoxLayout(self)
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)
        dct = dict(page = pg,
                    title_ = 'Tool Box {0}'.format(str(pg)),
                    header = VToolHeader(parent=self),
                    collapsible_ = True,
                    is_expanded = True,
                    referenced = [pg])

        for key, value in dct.items():
            setattr(self, key, value)

        self.header.setTitle(self.title_)

        self.layout.addWidget(self.header)
        
        self.header.expandRequest.connect(self._set_expanded)

    def title(self):
        return self.title_

    def setTitle(self, txt):
        """Sets the title of the tool page.

        Args:
        txt -- the title
        """
        self.title_ = txt
        self.header.setTitle(txt)

    title = pyqtProperty('QString', title, setTitle)

    def isCollapsible(self):
        return self.collapsible_

    def setCollapsible(self, b):
        """Sets the tool page collapsible.

        Args:
        b -- boolean
        """
        self.collapsible_ = b
        # set_collapsible(b) changed to setCollapseButton(b) rev.255
        self.header.setCollapseButton(b)
        if not b and not self.is_expanded:
            self._set_expanded(True)

    collapsible = pyqtProperty('bool', isCollapsible, setCollapsible)

    def _set_expanded(self, b):
        for i in range(self.layout.count()):
            try:
                self.layout.itemAt(i).widget().setVisible(b)
            except:
                # Item is not a widget
                pass

        self.is_expanded = b
        self.header.setVisible(True)
        self.visibilityChanged.emit(b)


class Splitter(QSplitter):
    def __init__(self, pg, parent=None):
        super(Splitter, self).__init__(parent)
        self.page = pg
        self.referenced = [pg]
        self.boxes = []
        self.ratios = [50, 50]
        self.setOrientation(Qt.Vertical)
        self.setChildrenCollapsible(False)

    def count(self):
        return len(self.boxes)

    def hideEvent(self, event):
        #print "Splitter hideEvent:", event
        QSplitter.hideEvent(self, event)

    def showEvent(self, event):
        #print "Splitter showEvent:", event
        QSplitter.showEvent(self, event)
