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

# vcolorcombobox.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 comboBox designed to show a color palette.

import sys

from PyQt5.QtCore import Qt, pyqtProperty, QObject, QSize, QRect, QRectF,\
                        pyqtSignal
from PyQt5.QtGui import (QColor, QPixmap, QPainter, QPainterPath, QPen, QBrush,
                         QIcon, QLinearGradient)
from PyQt5.QtWidgets import  QComboBox, QColorDialog

# Compatibility Python 2 and 3
try:
    from PyQt5.QtCore import QString as str_
except ImportError:
    str_ = str

try:
    x = unicode('x')
except NameError:
    unicode = str
    basestring = str

from VWidgets.colorsdata import SVG_NAMES, BASIC_PLT

class VColorComboBox(QComboBox):
    """The VColorComboBox widget is a QComboBox used to show color's palette.

    The VColorComboBox has a default palette, called 'BASIC_PLT' wich present
    twenty colors. A custom palette can be set with set_palette().

    After setting the VColorComboBox, new colors may be appended or inserted
    into the palette.

    The palette may be sorted by name or by color.

    The VColorComboBox may have a 'More...' item wich make the user be able to
    add a color of his choice.
    """
    currentColorChanged = pyqtSignal(str, QColor)
    moreColorsRequested = pyqtSignal()
    SortingModes = ['provided', 'alpha', 'red', 'green', 'blue']
    ColorSpaces = ['RGB', 'HSV', 'CMYK', 'HSL']
    def __init__(self, parent=None):
        """Instanciate the VColorComboBox.

        """
        super(VColorComboBox, self).__init__(parent)
        dct = dict(size_ = QSize(80, 25),
                    border_icon_color = QColor(0, 0, 0),
                    palette_name = BASIC_PLT,
                    palette_ = None,
                    colors = [],
                    names = [],
                    identifiers = [],
                    is_named_color = True,
                    sorting = "alpha",
                    is_created = False,
                    color_space = 'RGB',
                    add_more_choice = True,
                    call_color_dialog = True,
                    use_svg_naming = True,
                    more_text = 'More...')

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

        self.currentIndexChanged.connect(self.on_current_color_changed)
        self.setIconSize(QSize(50, 24))

        self.set_palette()

    def palette(self):
        return self.palette_

    def set_palette(self, palette=None):
        """Sets the color palette.

        The palette must be a dictionnary with the color name as key and the
        value may be QColor() or a tuple (r, g, b) or (r, g, b, a) or again
        a str('#RRGGBB').

        If the dictionnary contents a key 'SPACE', a colors space other than RGB
        may be defined. Colors spaces supported: 'RGB', 'CMYK', 'HSV' and 'HSL'.

        With the key 'ORDER' the sorting order may be defined. The value must
        be the list of the names of the colors. Default sorting mode is
        alphabetical.

        The keys 'ORDER' and 'SPACE', if presents, must be in upper case.

        Args:
        palette -- python dict()
        """
        if palette is not None:
            palette = palette
            self.palette_name = palette

        else:
            palette = self.palette_name

        parser = PaletteParser(self, palette)
        self.palette_ = parser.get_palette()
        self.blockSignals(True)
        self._sort_palette()
        self._build_palette()
        self.blockSignals(False)

    def borderIconColor(self):
        return self.border_icon_color

    def setBorderIconColor(self, color):
        """Sets the color for the icon border.

        This allow to enhance the contrast of the icon when the background of
        the comboBox is colored with a style sheet.

        Args:
        color -- QColor or tuple(r, g, b)
        """
        if isinstance(color, QColor):
            self.border_icon_color = color

        else:
            try:
                self.border_icon_color = QColor(color[0], color[1], color[2])
            except TypeError as why:
                raise("VColorComboBox.setBorderIconColor(color): '{0}'"
                        .format(why))

        self.update_()

    borderIconColor_ = pyqtProperty("QColor", borderIconColor,
                                        setBorderIconColor)

    def isNamedColor(self):
        return self.is_named_color

    def setNamedColor(self, b):
        """Sets or not the color's names into the VColorComboBox.

        Default: True

        Args:
        b -- boolean
        """
        if not isinstance(b, bool):
            raise TypeError("VColorComboBox.setNamedColor(bool): arg 1"
                            " has unexpected type: '{0}'".format(type(b)))

        self.is_named_color = b
        self.update_()

    namedColor = pyqtProperty("bool", isNamedColor, setNamedColor)

    def set_sorting_mode(self, order):
        """Sets the sorting mode.

        Sorting mode may be 'alpha' for alphabetical order and 'red', 'green'
        or 'blue' for color order. Case is ignored.

        Note: If the order is given with the palette, this property is ignored.

        Default: 'alpha'

        Args:
        order -- mode
        """
        if not isinstance(order, str):
            raise TypeError("VColorComboBox.set_sorting_mode(str): arg 1"
                            " has unexpected type: '{0}'".format(type(order)))

        if order.lower() not in self.SortingModes:
            raise ValueError("VColorComboBox.set_sorting_mode(str): arg 1"
                            " has unexpected value: '{0}'".format(order))

        self.sorting = order
        self.update_()

    def set_tool_tip(self, tip):
        """Sets toolTip on items.

        NOT YET IMPLEMENTED
        """
        if not isinstance(tip, bool):
            raise TypeError("VColorComboBox.set_tool_tip(bool): arg 1"
                            " has unexpected type: '{0}'".format(type(tip)))

        self.tip = tip
        self.update_()

    def set_current_color(self, color):
        """Sets the current item color.

        Args:
        color -- int(index), QColor instance or color name
        """
        idx = None
        if isinstance(color, int):
            if self.count() > color and color >= 0:
                self.setCurrentIndex(color)
                return

            else:
                sys.stderr.write('set_current_color(color): list index out '
                                    'of range')
                return

        if isinstance(color, QColor):
            for i, c in enumerate(self.colors):
                if c[1] == color:
                    idx = i
                    break

        else:
            idx = self._find_item_with_string(color)

        if idx is not None:
            self.setCurrentIndex(idx)

        else:
            sys.stderr.write('set_current_color(color): Color {0} not defined '
                                'in palette.\n'.format(color))

    def hasMoreChoice(self):
        return self.add_more_choice

    def addMoreChoice(self, add, call=True):
        """Add or not an item text 'More...' at the end of the color list.

        The 'More...' item allows to the user the ability to choose a new
        color into a QColorDialog. The new color is automatically inserted
        into the comboBox in respect with the order defined.

        The call to the QColorDialog may be overrided with 'call=False', in
        this case a signal 'moreColorsRequested' will be emitted.

        The text 'More...' may be translated with set_more_text().

        Default: True

        Args:
        add -- if True, add a 'More...' item
        call -- if True, call the QColorDialog, otherwise emit the signal
                'moreColorsRequested'
        """
        if not isinstance(add, bool):
            raise TypeError("VColorComboBox.addMoreChoice"
                            "(bool, bool): arg 1"
                            " has unexpected type: '{0}'".format(type(add)))

        if not isinstance(call, bool):
            raise TypeError("VColorComboBox.addMoreChoice"
                            "(bool, bool): arg 2"
                            " has unexpected type: '{0}'".format(type(call)))

        self.add_more_choice = add
        self.call_color_dialog = call

        if add:
            self._add_more_item()

        else:
            self._remove_more_item()

    addMoreChoice_ = pyqtProperty("bool", hasMoreChoice, addMoreChoice)

    def moreText(self):
        return self.more_text

    def setMoreText(self, text='More...'):
        """Sets the text for the item 'More...'.

        Default: 'More...'

        Args:
        text -- translated text
        """
        if isinstance(text, str_):
            text = text

        elif not isinstance(text, basestring):
            raise TypeError("VColorComboBox.setMoreText(str):"
                                " arg 1 has unexpected type: '{0}'"
                                .format(type(text)))

        self._remove_more_item()
        self.more_text = text
        self._add_more_item()

    moreText_ = pyqtProperty("QString",  moreText, setMoreText)

    def callColorDialog(self):
        return self.call_color_dialog

    def setCallColorDialog(self, call):
        """Allow to the comboBox to call the QColorDialog himself.

        Args:
        call -- bool, if True, the QColorDialog will be called when the user
                click on the 'More...' item, otherwise, the signal
                'moreColorsRequested' will be emitted
        """
        if not isinstance(call, bool):
            raise TypeError("VColorComboBox.setCallColorDialog(bool):"
                            " arg 1 has unexpected type: '{0}'"
                            .format(type(call)))

        self.call_color_dialog = call

    callColorDialog_ = pyqtProperty("bool", callColorDialog, setCallColorDialog)

    def useSvgNaming(self):
        """Returns True if is using the svg color naming.

        """
        return self.use_svg_naming

    def setUseSvgNaming(self, b):
        """Sets the SVG color naming for the color wich don't have a human
        readable name.

        Args:
        b -- boolean
        """
        if not isinstance(b, bool):
            raise TypeError("VColorComboBox.setUseSvgNaming(bool): arg 1 "
                            "has unexpected type: '{0}'".format(type(b)))

        self.use_svg_naming = b
        self.update_()

    useSvgNaming_ = pyqtProperty("bool", useSvgNaming, setUseSvgNaming)

    def get_colors(self):
        return self.colors

    def append_color(self, color, name=None):
        """Add a new color at the end of the list.

        Args:
        color -- color
        name -- color name, if not provided and if 'is_named_color' == True,
                the name returned by QColor.name() will be used
        """
        self._remove_more_item()
        self.insert_color(self.count(), color, name)

    def insert_color(self, idx, color, name=None):
        """Insert a new color at the given place.

        Args:
        idx -- index of insertion place
        color -- color, can be a QColor() instance or a tuple(r, g, b[, a])
        name -- color name, if not provided and if attribute 
                'is_named_color' == True, the name returned by QColor.name() 
                will be used
        """
        color = self._get_QColor(color)

        if name is None:
            name = color.name()

        if self._is_accepted(color):
            name = self.normalise_name(name)
            self.palette_[name] = color
            self._remove_more_item()
            self._add_color(color, name, idx)

    def insert_color_in_order(self, color, name=None):
        """Insert a new color.

        The new color will be inserted in respect with the predifined order.

        Args:
        color -- color
        name -- color name, if not provided and if 'is_named_color' == True,
                the name returned by QColor.name() will be used
        """

        color = self._get_QColor(color)
        if name is None:
            name = color.name()

        if self._is_accepted(color):
            self._remove_more_item()
            val = color.getRgb()

            name = self.normalise_name(name)
            self.palette_[name] = color

            if self.sorting == 'provided':
                idx = self.count()

            elif self.sorting == 'alpha':
                idx = self._find_index_by_alpha_order(name)

            else:
                self._sort_palette()
                idx = self.colors.index((name, color))
                # Remove from list, function _add_color will reinsert it
                self.colors.pop(idx)
            self._add_color(color, name, idx)

        else:
            # Maybe the color already exists
            idx = self._find_item_with_string(unicode(name))
            if idx is None:
                idx = 0
            self.setCurrentIndex(idx)

    def remove_color(self, item):
        """Remove an item color from the comboBox.

        Args:
        item -- int(index) or QColor() or tuple(r, g, b) or again str('#RRGGBB')
        """
        idx = None
        if isinstance(item, int):
            self.remove_item_at(item)

        elif isinstance(item, QColor):
            for i, c in enumerate(self.colors):
                if c[1] == item:
                    idx = i
                    break

        elif isinstance(item, tuple):
            idx = self._find_item_with_values(item)

        elif isinstance(item, str):
            idx = self._find_item_with_string(item)

        if idx is not None:
            self._remove_item_at(idx)

    def update_(self):
        if self.is_created:
            self.set_palette()

    def get_color_space(self):
        return self.color_space

    def get_size(self):
        """Returns the size of the comboBox.

        Returns:
        tuple(width, height)
        """
        return self.get_width(), self.get_height()

    def get_width(self):
        """Returns the width of the comboBox.

        Returns:
        int(width)
        """
        return self.size_.width()

    def get_height(self):
        """Returns the height of the comboBox.

        Returns:
        int(height)
        """
        return self.size_.height()

    def get_icon_size(self):
        """Returns the size of the icon.

        Returns:
        tuple(width, height)
        """
        return self.get_icon_width(), self.get_icon_height()

    def get_icon_width(self):
        """Returns the width of the icon.

        Returns:
        int(width)
        """
        return self.iconSize().width()

    def get_icon_height(self):
        """Returns the height of the icon.

        Returns:
        int(height)
        """
        return self.iconSize().height()

    def get_sorting_mode(self):
        """Returns the sorting mode.

        Args:
        str()
        """
        return self.sorting

    def get_current_color(self):
        """Returns the current color.

        Returns:
        tuple(name, QColor(r, g, b))
        """
        return self.colors[self.currentIndex()]

    def get_current_name(self):
        """Return the name of the current color.

        Returns:
        str(name)
        """
        return self.colors[self.currentIndex()][0]

    def rgb_to_QColor(self, rgb):
        """Returns a QColor(r, g, b).

        Raise a TypeError if the argument is invalid.

        Args:
        tuple(r, g, b)

        Returns:
        QColor(r, g, b)
        """
        try:
            color = QColor(rgb[0], rgb[1], rgb[2])
        except:
            raise TypeError("VColorComboBox.rgb_to_QColor((r, g, b)): arg 1"
                            " has unexpected type: '{0}'".format(type(rgb)))

        return color

    def find_color(self, color):
        """Returns the index of a color.

        Args:
        color -- QColor() instance or tuple(r, g, b[, a])

        Returns:
        int(index)
        """
        if not isinstance(color, QColor):
            color = self.rgb_to_QColor(color)

        ident = color.name()
        try:
            idx = self.identifiers.index(ident)
        except ValueError:
            return -1

        return idx

    def _sort_palette(self, dct=None):
        """Sort the color palette.

        If the dict has a key 'ORDER', this order is choosed in priority,
        otherwise the sorting mode defined with set_sorting_mode() or, at
        least, the default mode 'alpha' will be used.

        Args:
        dct -- dict(palette), if None, 'BASIC_PLT' will be used
        """
        if dct is None:
            dct = self.palette_

        keys = dct.keys()
        if 'ORDER' in keys and dct['ORDER'] is not None:
            self.sorting = 'provided'
            keys = self._check_colors_list(dct['ORDER'], dct)
            del dct['ORDER']

        elif self.sorting == 'alpha':
            keys.sort(key=str.lower)

        else:
            if self.sorting == 'red':
                seq = (0, 1, 2)

            elif self.sorting == 'green':
                seq = (1, 2, 0)

            else:
                seq = (2, 0, 1)

            keys = self._sort_by_color(dct, seq)
        self.colors = self.get_ordered_colors(dct, keys)

    def _sort_by_color(self, dct, seq):
        """Sort the colors list.

        Args:
        dct -- dict(palette)
        seq -- sequence (r-g-b), (g-b-r), or (b-r-g)
        """
        keys = dct.keys()
        sw = []
        for key in keys:
            val = dct[key]
            if isinstance(val, QColor):
                val = val.getRgb()

            s = self._get_colors_summ(val, seq)
            sw.append([key, s])

        od = reversed(sorted(sw, key=lambda t: t[1]))
        return [k[0] for k in od]

    def get_ordered_colors(self, colors, order):
        """Returns the sorted list of colors.

        """
        return [(o, colors[o]) for o in order]

    def _get_colors_summ(self, col, s):
        return col[s[0]] * 1000 + 1 / (col[s[1]] + col[s[2]] + 1)

    def _check_colors_list(self, lst, colors):
        """Returns colors list from palette.

        If a color from lst is not in colors, this item will be rejected.

        Args:
        lst -- ordered name's list provided with the palette.
        colors -- palette

        Returns:
        list(color's names)
        """
        clrs = []
        for name in lst:
            if name in colors:
                clrs.append(name)

            else:
                sys.stderr.write("VColorComboBox() invalid color name: '{0}'\n"
                                    .format(name))

        return clrs

    def _build_palette(self):
        """Items maker.

        """
        drawer = PixmapDrawer()
        drawer.set_border_color(self.border_icon_color)
        self._reset_all()

        for color in self.colors:
            if drawer.set_color(color[1]):
                # Ignoring invalid color
                if self._is_accepted(drawer.color):
                    img = drawer.draw_pixmap()
                    self.icons.append(img)
                    self.names.append(color[0])

        self._set_comboBox()

    def _set_comboBox(self):
        """Sets items into the comboBox.

        """
        for idx, img, in enumerate(self.icons):
            icon = self._get_icon(img)
            if self.is_named_color:
                self.addItem(icon, self.names[idx])

            else:
                self.addItem(icon, "")

        del self.icons
        self._add_more_item()
        self.is_created = True

    def _add_more_item(self):
        """Add the item 'More...' at the end of item's list.

        """
        if self.add_more_choice:
            self.addItem(self.more_text)

    def _remove_more_item(self):
        """Remove the item 'More...' from the list.

        """
        idx = self.findText(self.more_text)
        if idx >= 0:
            self.removeItem(idx)

    def _add_color(self, color, name=None, index=None):
        """Add a new color into comboBox.

        Args:
        color -- QColor() instance
        name -- poetic name, if not provided, QColor.name() will be used
        index -- index
        """
        drawer = PixmapDrawer()
        if not drawer.set_color(color):
            sys.stderr.write("VColorComboBox._add_color: invalid color\n")
            return
        drawer.set_border_color(self.border_icon_color)
        icon = self._get_icon(drawer.draw_pixmap())
        if name is None:
            name = color.name()

        name = self.normalise_name(name)
        self.names.append(name)

        if index is None:
            index = self.count()
        self.colors.insert(index, (name, color.getRgb()[:3]))

        if self.is_named_color:
            self.insertItem(index, icon, name)

        else:
            self.insertItem(index, icon, "")

        self.is_created = True
        self.setCurrentIndex(index)
        self._add_more_item()

    def _remove_item_at(self, idx):
        """Remove a color from comboBox.

        Args:
        idx -- item's index
        """
        self.palette_.pop(self.colors[idx][0], None)
        self.identifiers.remove(self.colors[idx][1].name())
        self.removeItem(idx)
        self.colors.pop(idx)

    def _get_icon(self, pix):
        """Returns a QIcon instance.

        Args:
        pix -- QPixmap instance
        """
        icon = QIcon()
        icon.addPixmap(pix, QIcon.Normal, QIcon.Off)
        return icon

    def _reset_all(self):
        """Clear all properties.

        """
        self.clear()
        self.icons = []
        self.names = []
        self.identifiers = []

    def _get_QColor(self, color):
        """Returns QColor() instance from rgb values.

        Args:
        color -- (r, g, b)

        Returns:
        QColor()
        """
        if isinstance(color, tuple):
            color = self.rgb_to_QColor(color)

        elif not isinstance(color, QColor):
            raise TypeError("VColorComboBox._get_QColor(color): arg 1"
                            " has unexpected type: '{0}'".format(type(color)))

        return color

    def _find_index_by_alpha_order(self, name):
        """Return index of a new color in alphabetical order.

        The new color isn't added into the current palette.

        Args:
        name -- color's name

        Returns:
        Color's index in names list
        """
        names = self.names[:]
        names.append(name)
        names.sort(key=str.lower)
        return names.index(name)

    def _find_item_with_values(self, val):
        """Returns index of item with values val.

        Args:
        val -- color values, must be in same color space as declared.

        Returns:
        int(index)
        """
        if self.get_color_space() == 'RGB':
            try:
                color = QColor(val[0], val[1], val[2])
            except:
                return

        elif self.get_color_space() == 'CMYK':
            try:
                c = QColor()
                color = c.setCmyk(val[0], val[1], val[2])
            except:
                return

        elif self.get_color_space() == 'HSV':
            try:
                c = QColor()
                color = c.setHsv(val[0], val[1], val[2])
            except:
                return

        elif self.get_color_space() == 'HSL':
            try:
                c = QColor()
                color = c.setHsl(val[0], val[1], val[2])
            except:
                return

        idx = None
        for i, c in enumerate(self.colors):
            if c[1] == color:
                idx = i
                break
        return idx

    def _find_item_with_string(self, name):
        """Returns index of item with name name.

        Args:
        name -- str() poetic name or hex value as returned by QColor.name()

        Returns:
        int(index)
        """
        idx = None
        if name.startswith('#'):
            if name in self.identifiers:
                for idx, c in enumerate(self.colors):
                    if c[1].name() == name:
                        break

        else:
            for idx, c in enumerate(self.colors):
                if c[0] == name:
                    break

        return idx

    def _is_accepted(self, color):
        """Filter for duplicate.

        Args:
        color -- QColor() instance

        Returns:
        False if color already exists, otherwise QColor.name()
        """
        ident = color.name()
        if ident in self.identifiers:
            return False

        self.identifiers.append(ident)
        return ident

    def normalise_name(self, name):
        """Apply the SVG naming convention for unamed color.

        All 'gray' are replaced by 'grey', 'Aqua' is replaced by 'Cyan'
        and Fuchsia value (same as Magenta) is replaced by '#f400a1'.
        Ref: http://www.w3.org/TR/SVG/types.html#ColorKeywords

        Args:
        name -- hexadecimal name as returned by QColor.name()

        Returns:
        Poetic color name
        """
        if isinstance(name, str_):
            name = unicode(name)

        elif not isinstance(name, unicode):
            name = unicode(name, 'utf8')

        if self.use_svg_naming and name.startswith('#'):
            try:
                name = SVG_NAMES[name]
            except KeyError:
                pass

        return name

    def on_current_color_changed(self, arg):
        """Called when current color has changed.

        A signal 'currentColorChanged(str(name), QColor())' will be emitted

        Args:
        item's index
        """
        if self.itemText(arg) == str_(self.more_text):
            self.call_more_colors()
            return

        name, val = self.colors[arg]
        if isinstance(val, QColor):
            self.currentColorChanged.emit(name, val)

        else:
            self.currentColorChanged.emit(name, QColor(val[0], val[1], val[2]))

    def call_more_colors(self):
        """Called when the item 'More..' has been clicked.

        If call_color_dialog is True, the dialog QColorDialog will be called,
        if not, the signal 'moreColorsRequested()' will be emitted.
        """
        if not self.call_color_dialog:
            self.moreColorsRequested.emit()

        else:
            color =  QColorDialog.getColor()
            if color.isValid():
                self.insert_color_in_order(color)
       

class PixmapDrawer(QObject):
    def __init__(self):
        """Initialize the icon maker.

        """
        super(PixmapDrawer, self).__init__()
        dct = dict(width = 100.0,
                    height = 45.0,
                    color = None,
                    border = 4,
                    border_color = QColor(0, 0, 0),
                    reflection = True)

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

    def set_color(self, color):
        """Set the icon color.

        Args:
        color -- QColor() or tuple(r, g, b)

        Returns:
        False if arg color is not valid, True otherwise
        """
        if isinstance(color, QColor):
            self.color = color
            return True

        try:
            self.color = QColor(color[0], color[1], color[2], 255)
        except:
            return False

        return True

    def set_border_color(self, color):
        """Set the color of the border of the icon.

        By default the color is black, but if a style sheet is applied on the
        comboBox, this fonction allows the possibility to change the contrast
        of the icon.

        Args:
        color -- QColor() or tuple(r, g, b)
        """
        if isinstance(color, QColor):
            self.border_color = color

        else:
            try:
                self.border_color = QColor(color[0], color[1], color[2], 255)
            except:
                sys.stderr.write("PixmapDrawer.set_border_color() invalid "
                                    "color: '{0}'\n".format(color))

    def draw_pixmap(self):
        """Drawer.

        Returns:
        QPixmap
        """
        back = self._get_background()
        pix = self._paint_border(back)
        pix = self._paint_reflection(pix)
        return pix

    def _get_background(self):
        """Create the icon's background.

        Returns:
        QPixmap()
        """
        pix = self._get_pix()
        rect = QRectF(1.0, 1.0, self.width - 3, self.height - 3)
        painter = QPainter()
        painter.begin(pix)
        painter.setRenderHints(QPainter.Antialiasing, True)
        path = QPainterPath()
        path.addRoundedRect(rect, 5.0, 5.0)
        painter.drawPath(path)
        gradient = QLinearGradient(0, 0, 0, 35)
        gradient.setColorAt(0.0, QColor(255, 255, 255, 255))
        gradient.setColorAt(0.8, self.color)
        brush = QBrush(gradient)
        painter.fillPath(path, brush)
        painter.end()
        return pix

    def _paint_border(self, pix):
        """Paint the border over the background.

        Args:
        pix -- QPixmap()

        Returns:
        QPixmap
        """
        rect = QRect(2, 2, self.width - 4, self.height - 4)
        painter = QPainter()
        painter.begin(pix)
        painter.setRenderHints(QPainter.Antialiasing, True)
        painter.setPen(QPen(self.border_color, self.border,
                                  Qt.SolidLine, Qt.RoundCap,
                                  Qt.RoundJoin))
        painter.drawRoundedRect(rect, 5.0, 5.0)
        painter.end()
        return pix

    def _paint_reflection(self, pix):
        """Paint the icon's reflection.

        Args:
        pix -- QPixmap()

        Returns:
        QPixmap
        """
        rect = QRectF(6.0, 6.0, 6.0, 6.0)
        path = QPainterPath()
        path.moveTo(6.0, 37.0)
        path.lineTo(6.0, 12.0)
        path.arcTo(rect, 180, -90)
        path.lineTo(11.0, 6.0)
        path.lineTo(92, 6)
        painter = QPainter()
        painter.begin(pix)
        painter.setRenderHints(QPainter.Antialiasing, True)
        painter.setPen(QPen(QColor(255, 255, 255, 180), 4,
                                  Qt.SolidLine, Qt.RoundCap,
                                  Qt.RoundJoin))
        painter.drawPath(path)
        painter.end()
        return pix

    def _get_pix(self):
        """Create the pixmap support for the texture.

        Returns:
        QPixmap square filled transparent
        """
        color = QColor()
        color.setRgb(0, 0, 0, 0)
        pix = QPixmap(QSize(self.width, self.height))
        pix.fill(color)
        return pix


class PaletteParser(object):
    ColorsSpaces = ['RGB', 'RGBA', 'CMYK', 'HSV', 'HSL']
    def __init__(self, main, palette=None):
        """Initialise the palette parser.

        The job of the parser is 1. Normalise the color name to unicode
                                 2. Convert color space given to 'RGB'
                                 3. Exclude invalid color
        """
        dct = dict(palette = palette,
                    main = main,
                    pal_ = {},
                    color_space = 'RGB',
                    convertRGB = self._qcolor_from_RGB,
                    convertRGBA = self._qcolor_from_RGB,
                    convertCMYK = self._qcolor_from_CMYK,
                    convertHSV = self._qcolor_from_HSV,
                    convertHSL = self._qcolor_from_HSL)

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

        if palette is not None:
            self.parse()

    def set_palette(self, palette):
        self.palette = palette

    def get_palette(self):
        """Return the palette ready-to-use in comboBox.

        """
        return self.pal_

    def parse(self):
        if self.palette is None:
            return None

        pal = self.palette
        self._set_color_space()
        convert = getattr(self, 'convert' + self.space_color)

        for key in pal:
            if key in ['ORDER', 'SPACE']:
                self.pal_[key] = pal[key]

            if isinstance(pal[key], QColor):
                self.pal_[key] = pal[key]

            else:
                value = convert(pal[key])
                if value is not None:
                    key = self.main.normalise_name(key)
                    self.pal_[key] = value

    def get_color_space(self):
        return self.color_space

    def _set_color_space(self):
        space = 'RGB'
        try:
            space = self.palette['SPACE'].upper()
            if space not in self.ColorsSpaces:
                space = 'RGB'
        except KeyError:
            pass

        self.space_color = space

    def _qcolor_from_RGB(self, rgb):
        if isinstance(rgb, tuple):
            try:
                return QColor(rgb[0], rgb[1], rgb[2])
            except:
                return None

        elif isinstance(rgb, str):
            try:
                return QColor(rgb)
            except Exception as why:
                sys.stderr.write("_qcolor_from_RGB invalid {0}\n\t{1}\n"
                                .format(rgb, why))
                return None

    def _qcolor_from_CMYK(self, cmyk):
        try:
            return QColor.fromCmyk(cmyk[0], cmyk[1], cmyk[2], cmyk[3])
        except Exception as why:
            sys.stderr.write("_qcolor_from_CMYK invalid {0}\n\t{1}\n"
                                .format(cmyk, why))
            return None

    def _qcolor_from_HSV(self, hsv):
        try:
            return QColor.fromHsv(hsv[0], hsv[1], hsv[2])
        except Exception as why:
            sys.stderr.write("_qcolor_from_HSV invalid {0}\n\t{1}\n"
                                .format(hsv, why))
            return None

    def _qcolor_from_HSL(self, hsl):
        try:
            return QColor.fromHsl(hsl[0], hsl[1], hsl[2])
        except Exception as why:
            sys.stderr.write("_qcolor_from_HSL invalid {0}\n\t{1}\n"
                                .format(hsl, why))
            return None

