# --------------------------------------------------------------------------------- #
# PYGAUGE wxPython IMPLEMENTATION
#
# Mark Reed, @ 28 Jul 2010
# Latest Revision: 02 Aug 2010, 09.00 GMT
#
# TODO List
#
# 1. Indeterminate mode (see wx.Gauge)
# 2. Vertical bar
# 3. Bitmap support (bar, background)
# 4. UpdateFunction - Pass a function to PyGauge which will be called every X
#    milliseconds and the value will be updated to the returned value.
# 5. Currently the full gradient is drawn from 0 to value. Perhaps the gradient
#    should be drawn from 0 to range and clipped at 0 to value.
# 6. Add a label?
#
# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
# Write To The:
#
# wxPython Mailing List!!!
#
# End Of Comments
# --------------------------------------------------------------------------------- #

"""
PyGauge is a generic `wx.Gauge` implementation.


Description
===========

PyGauge supports the determinate mode functions as `wx.Gauge` and adds an L{Update} function
which takes a value and a time parameter. The `value` is added to the current value over
a period of `time` milliseconds.


Supported Platforms
===================

PyGauge has been tested on the following platforms:
  * Windows (Windows XP);


License And Version
===================

PyGauge is distributed under the wxPython license.
PyGauge has been kindly contributed to the AGW library by Mark Reed.

Latest Revision: Andrea Gavana @ 02 Aug 2010, 09.00 GMT

Version 0.1
"""

import wx
import copy


class PyGauge(wx.PyWindow):
    """
    This class provides a visual alternative for `wx.Gauge`. It currently
    only support determinate mode (see L{PyGauge.SetValue} and L{PyGauge.SetRange})
    """

    def __init__(
        self,
        parent,
        id=wx.ID_ANY,
        range=100,
        pos=wx.DefaultPosition,
        size=(-1, 30),
        style=0,
    ):
        """Default class constructor.

        :param parent: parent window. Must not be ``None``;
        :param id: window identifier. A value of -1 indicates a default value;
        :param pos: the control position. A value of (-1, -1) indicates a default position,
         chosen by either the windowing system or wxPython, depending on platform;
        :param size: the control size. A value of (-1, -1) indicates a default size,
         chosen by either the windowing system or wxPython, depending on platform;
        :param style: the underlying `wx.PyWindow` window style.
        """

        wx.PyWindow.__init__(self, parent, id, pos, size, style)

        self._size = size

        self._border_colour = None
        self._barColour = self._barColourSorted = [wx.Colour(212, 228, 255)]
        self._barGradient = self._barGradientSorted = None

        self._border_padding = 0
        self._range = range
        self._value = [0]
        self._valueSorted = [0]

        self._timer = None

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
        self.Bind(wx.EVT_TIMER, self.OnTimer)

    def DoGetBestSize(self):
        """Overridden base class virtual. Determines the best size of the
        button based on the label and bezel size.
        """

        return wx.Size(self._size[0], self._size[1])

    def GetBorderColour(self):
        """Returns the L{PyGauge} border colour."""

        return self._border_colour

    def SetBorderColour(self, colour):
        """Sets the L{PyGauge} border colour.

        :param colour: an instance of `wx.Colour`.
        """

        self._border_colour = colour

    SetBorderColor = SetBorderColour
    GetBorderColor = GetBorderColour

    def GetBarColour(self):
        """Returns the L{PyGauge} main bar colour."""

        return self._barColour[0]

    def SetBarColour(self, colour):
        """Sets the L{PyGauge} main bar colour.

        :param colour: an instance of `wx.Colour`.
        """

        if not isinstance(colour, list):
            self._barColour = [colour]
        else:
            self._barColour = list(colour)

        self.SortForDisplay()

    SetBarColor = SetBarColour
    GetBarColor = GetBarColour

    def GetBarGradient(self):
        """Returns a tuple containing the gradient start and end colours."""

        if self._barGradient is None:
            return None

        return self._barGradient[0]

    def SetBarGradient(self, gradient):
        """
        Sets the bar gradient.

        :param gradient: a tuple containing the gradient start and end colours.

        :note: This overrides the bar colour previously set with L{SetBarColour}.
        """

        if not isinstance(gradient, list):
            self._barGradient = [gradient]
        else:
            self._barGradient = list(gradient)

        self.SortForDisplay()

    def GetBorderPadding(self):
        """Gets the border padding."""

        return self._border_padding

    def SetBorderPadding(self, padding):
        """
        Sets the border padding.

        :param padding: pixels between the border and the progress bar.
        """

        self._border_padding = padding

    def GetRange(self):
        """Returns the maximum value of the gauge."""

        return self._range

    def SetRange(self, range):
        """
        Sets the range of the gauge. The gauge length is its
        value as a proportion of the range.

        :param range: The maximum value of the gauge.
        """

        if range <= 0:
            raise Exception("ERROR:\n Gauge range must be greater than 0.")

        self._range = range

    def GetValue(self):
        """Returns the current position of the gauge."""

        return self._value[0]

    def SetValue(self, value):
        """Sets the current position of the gauge.

        :param value: an integer specifying the current position of the gauge.
        """

        if not isinstance(value, list):
            self._value = [value]
        else:
            self._value = list(value)

        self.SortForDisplay()

        for v in self._value:
            if v < 0 or v > self._range:
                raise Exception("ERROR:\n Gauge value must be between 0 and its range.")

    def OnEraseBackground(self, event):
        """Handles the ``wx.EVT_ERASE_BACKGROUND`` event for L{PyGauge}.

        :param event: a `wx.EraseEvent` event to be processed.

        :note: This method is intentionally empty to reduce flicker.
        """

        pass

    def OnPaint(self, event):
        """Handles the ``wx.EVT_PAINT`` event for L{PyGauge}.

        :param event: a `wx.PaintEvent` event to be processed.
        """

        dc = wx.BufferedPaintDC(self)
        rect = self.GetClientRect()

        dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
        dc.Clear()
        colour = self.GetBackgroundColour()
        dc.SetBrush(wx.Brush(colour))
        dc.SetPen(wx.Pen(colour))
        dc.DrawRectangle(rect)

        if self._border_colour:
            dc.SetPen(wx.Pen(self.GetBorderColour()))
            dc.DrawRectangle(rect)
            pad = 1 + self.GetBorderPadding()
            rect.Deflate(pad, pad)

        if self.GetBarGradient():
            for i, gradient in enumerate(self._barGradientSorted):
                c1, c2 = gradient
                w = rect.width * (float(self._valueSorted[i]) / self._range)
                r = copy.copy(rect)
                r.width = int(w)
                dc.GradientFillLinear(r, c1, c2, wx.EAST)
        else:
            for i, colour in enumerate(self._barColourSorted):
                dc.SetBrush(wx.Brush(colour))
                dc.SetPen(wx.Pen(colour))
                w = rect.width * (float(self._valueSorted[i]) / self._range)
                r = copy.copy(rect)
                r.width = int(w)
                dc.DrawRectangle(r)

    def OnTimer(self, event):
        """Handles the ``wx.EVT_TIMER`` event for L{PyGauge}.

        :param event: a `wx.TimerEvent` event to be processed.
        """

        if self._timer and self._timer.Id == event.GetId():
            stop_timer = True
            for i, _v in enumerate(self._value):
                self._value[i] += self._update_step[i]

                if self._update_step[i] > 0:
                    if self._value[i] > self._update_value[i]:
                        self._value[i] = self._update_value[i]
                    else:
                        stop_timer = False
                else:
                    if self._value[i] < self._update_value[i]:
                        self._value[i] = self._update_value[i]
                    else:
                        stop_timer = False

            if stop_timer:
                self._timer.Stop()

            self.SortForDisplay()

            self.Refresh()

    def Update(self, value, time=0):
        """Update the gauge by adding `value` to it over `time` milliseconds. The `time` parameter
        **must** be a multiple of 50 milliseconds.

        :param value: The value to be added to the gauge;
        :param time: The length of time in milliseconds that it will take to move the gauge.
        """

        if not isinstance(value, list):
            value = [value]

        if len(value) != len(self._value):
            raise Exception("ERROR:\n len(value) != len(self.GetValue())")

        self._update_value = []
        self._update_step = []
        for i, v in enumerate(self._value):
            if value[i] + v <= 0 or value[i] + v > self._range:
                raise Exception(
                    "ERROR:\n Gauge value must be between 0 and its range. "
                )

            self._update_value.append(value[i] + v)
            self._update_step.append(float(value[i]) / (time / 50))

        # print self._update_

        if not self._timer:
            self._timer = wx.Timer(self)

        self._timer.Start(100)

    def SortForDisplay(self):
        """Internal method which sorts things so we draw the longest bar first."""

        if self.GetBarGradient():
            tmp = sorted(zip(self._value, self._barGradient))
            tmp.reverse()
            a, b = list(zip(*tmp))
            self._valueSorted = list(a)
            self._barGradientSorted = list(b)
        else:
            tmp = sorted(zip(self._value, self._barColour))
            tmp.reverse()
            a, b = list(zip(*tmp))
            self._valueSorted = list(a)
            self._barColourSorted = list(b)
