# --------------------------------------------------------------------------------- #
# PYPROGRESS wxPython IMPLEMENTATION
#
# Andrea Gavana, @ 03 Nov 2006
# Latest Revision: 19 Dec 2012, 21.00 GMT
#
#
# TODO List
#
# 1. Do we support all the styles of wx.ProgressDialog in indeterminated mode?
#
# 2. Other ideas?
#
#
# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
# Write To Me At:
#
# andrea.gavana@maerskoil.com
# andrea.gavana@gmail.com
#
# Or, Obviously, To The wxPython Mailing List!!!
#
# Tags:        phoenix-port, unittest, documented
#
# End Of Comments
# --------------------------------------------------------------------------------- #

"""
:class:`~wx.lib.agw.pyprogress.PyProgress` is similar to :class:`ProgressDialog` in indeterminated mode, but with a
different gauge appearance and a different spinning behavior.


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

:class:`PyProgress` is similar to :class:`ProgressDialog` in indeterminated mode, but with a
different gauge appearance and a different spinning behavior. The moving gauge
can be drawn with a single solid colour or with a shading gradient foreground.
The gauge background colour is user customizable.

The bar does not move always from the beginning to the end as in :class:`ProgressDialog`
in indeterminated mode, but spins cyclically forward and backward.

Other options include:

- Possibility to change the proportion between the spinning bar and the
  entire gauge, so that the bar can be longer or shorter (the default is 20%);
- Modifying the number of steps the spinning bar performs before a forward
  (or backward) loop reverses.

:class:`PyProgress` can optionally display a ``Cancel`` button, and a :class:`StaticText` which
outputs the elapsed time from the starting of the process.


Usage
=====

Usage example::

    import wx
    import wx.lib.agw.pyprogress as PP

    # Our normal wxApp-derived class, as usual
    app = wx.App(0)

    dlg = PP.PyProgress(None, -1, "PyProgress Example",
                        "An Informative Message",
                        agwStyle=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME)

    dlg.SetGaugeProportion(0.2)
    dlg.SetGaugeSteps(50)
    dlg.SetGaugeBackground(wx.WHITE)
    dlg.SetFirstGradientColour(wx.WHITE)
    dlg.SetSecondGradientColour(wx.BLUE)

    max = 400
    keepGoing = 1
    count = 0

    while keepGoing and count < max:
        count += 1
        wx.MilliSleep(30)

        if count >= max // 2:
            keepGoing = dlg.UpdatePulse("Half-time!")
        else:
            keepGoing = dlg.UpdatePulse()

    dlg.Destroy()

    app.MainLoop()



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

:class:`PyProgress` has been tested on the following platforms:
  * Windows (Windows XP);
  * Linux Ubuntu (Dapper 6.06)


Window Styles
=============

This class supports the following window styles:

=================== =========== ==================================================
Window Styles       Hex Value   Description
=================== =========== ==================================================
``PD_CAN_ABORT``            0x1 This flag tells the dialog that it should have a ``Cancel`` button which the user may press. If this happens, the next call to `Update` will return ``False``.
``PD_APP_MODAL``            0x2 Make the progress dialog modal. If this flag is not given, it is only 'locally' modal - that is the input to the parent window is disabled, but not to the other ones.
``PD_AUTO_HIDE``            0x4 Causes the progress dialog to disappear from screen as soon as the maximum value of the progress meter has been reached.
``PD_ELAPSED_TIME``         0x8 This flag tells the dialog that it should show elapsed time (since creating the dialog).
=================== =========== ==================================================


Events Processing
=================

`No custom events are available for this class.`


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

:class:`PyProgress` is distributed under the wxPython license.

Latest Revision: Andrea Gavana @ 19 Dec 2012, 21.00 GMT

Version 0.5

"""

import wx
import time

# Some constants, taken straight from wx.ProgressDialog
Uncancelable = -1
""" Classifies the :class:`PyProgress` as "uncancelable". """
Canceled = 0
""" :class:`PyProgress` has been canceled. """
Continue = 1
""" :class:`PyProgress` can continue. """
Finished = 2
""" :class:`PyProgress` has finished. """

# Margins between gauge and text/button
LAYOUT_MARGIN = 8
""" Margins between gauge and text/button (in pixels). """

# PyProgress styles
PD_CAN_ABORT = wx.PD_CAN_ABORT
""" This flag tells the dialog that it should have a "Cancel" button which the user may press. If this happens, the next call to `Update()` will return ``False``. """
PD_APP_MODAL = wx.PD_APP_MODAL
""" Make the progress dialog modal. If this flag is not given, it is only 'locally' modal - that is the input to the parent window is disabled, but not to the other ones. """
PD_AUTO_HIDE = wx.PD_AUTO_HIDE
""" Causes the progress dialog to disappear from screen as soon as the maximum value of the progress meter has been reached. """
PD_ELAPSED_TIME = wx.PD_ELAPSED_TIME
""" This flag tells the dialog that it should show elapsed time (since creating the dialog). """

# ---------------------------------------------------------------------------- #
# Class ProgressGauge
# ---------------------------------------------------------------------------- #

class ProgressGauge(wx.Window):
    """ This class provides a visual alternative for :class:`Gauge`."""

    def __init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition,
                 size=(-1,30)):
        """
        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.
        """

        wx.Window.__init__(self, parent, id, pos, size, style=wx.SUNKEN_BORDER)

        self._value = 0
        self._steps = 50
        self._pos = 0
        self._current = 0
        self._gaugeproportion = 0.2
        self._firstGradient = wx.WHITE
        self._secondGradient = wx.BLUE
        self._background = wx.Brush(wx.WHITE)

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


    def GetFirstGradientColour(self):
        """ Returns the first gradient colour. """

        return self._firstGradient


    def SetFirstGradientColour(self, colour):
        """
        Sets the first gradient colour.

        :param `colour`: a valid :class:`wx.Colour` object.
        """

        self._firstGradient = colour
        self.Refresh()


    def GetSecondGradientColour(self):
        """ Returns the second gradient colour. """

        return self._secondGradient


    def SetSecondGradientColour(self, colour):
        """
        Sets the second gradient colour.

        :param `colour`: a valid :class:`wx.Colour` object.
        """

        self._secondGradient = colour
        self.Refresh()


    def GetGaugeBackground(self):
        """ Returns the gauge background colour. """

        return self._background


    def SetGaugeBackground(self, colour):
        """
        Sets the gauge background colour.

        :param `colour`: a valid :class:`wx.Colour` object.
        """

        self._background = wx.Brush(colour)


    def SetGaugeSteps(self, steps):
        """
        Sets the number of steps the gauge performs before switching from
        forward to backward (or vice-versa) movement.

        :param `steps`: the number of steps the gauge performs before switching from
         forward to backward (or vice-versa) movement.
        """

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

        if steps != self._steps:
            self._steps = steps


    def GetGaugeSteps(self):
        """
        Returns the number of steps the gauge performs before switching from
        forward to backward (or vice-versa) movement.
        """

        return self._steps


    def GetGaugeProportion(self):
        """
        Returns the relative proportion between the sliding bar and the
        whole gauge.
        """

        return self._gaugeproportion


    def SetGaugeProportion(self, proportion):
        """
        Sets the relative proportion between the sliding bar and the
        whole gauge.

        :param `proportion`: a floating point number representing the relative
         proportion between the sliding bar and the whole gauge.
        """

        if proportion <= 0 or proportion >= 1:
            raise Exception("ERROR:\n Gauge proportion must be between 0 and 1. ")

        if proportion != self._gaugeproportion:
            self._gaugeproportion = proportion


    def OnEraseBackground(self, event):
        """
        Handles the ``wx.EVT_ERASE_BACKGROUND`` event for :class:`ProgressGauge`.

        :param `event`: a :class:`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 :class:`ProgressGauge`.

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

        dc = wx.BufferedPaintDC(self)
        dc.SetBackground(self._background)

        dc.Clear()

        xsize, ysize = self.GetClientSize()
        interval = xsize/float(self._steps)

        self._pos = interval*self._value

        status = self._current//(self._steps - int(self._gaugeproportion*xsize)//int(interval))

        if status%2 == 0:
            increment = 1
        else:
            increment = -1

        self._value = self._value + increment
        self._current = self._current + 1
        self.DrawProgress(dc, xsize, ysize, increment)


    def DrawProgress(self, dc, xsize, ysize, increment):
        """
        Actually draws the sliding bar.

        :param `dc`: an instance of :class:`wx.DC`;
        :param `xsize`: the width of the whole progress bar;
        :param `ysize`: the height of the whole progress bar;
        :param `increment`: a positive value if we are spinning from left to right,
         a negative one if we are spinning from right to left.
        """

        if increment > 0:
            col1 = self.GetFirstGradientColour()
            col2 = self.GetSecondGradientColour()
        else:
            col1 = self.GetSecondGradientColour()
            col2 = self.GetFirstGradientColour()

        interval = self._gaugeproportion*xsize

        r1, g1, b1 = int(col1.Red()), int(col1.Green()), int(col1.Blue())
        r2, g2, b2 = int(col2.Red()), int(col2.Green()), int(col2.Blue())

        rstep = float((r2 - r1)) / interval
        gstep = float((g2 - g1)) / interval
        bstep = float((b2 - b1)) / interval

        rf, gf, bf = 0, 0, 0
        dc.SetBrush(wx.TRANSPARENT_BRUSH)

        for ii in range(int(self._pos), int(self._pos+interval)):
            currCol = (r1 + rf, g1 + gf, b1 + bf)
            dc.SetPen(wx.Pen(currCol, 2))
            dc.DrawLine(ii, 1, ii, ysize-2)
            rf = rf + rstep
            gf = gf + gstep
            bf = bf + bstep


    def Update(self):
        """ Updates the gauge with a new value. """

        self.Refresh()


# ---------------------------------------------------------------------------- #
# Class PyProgress
# ---------------------------------------------------------------------------- #

class PyProgress(wx.Dialog):
    """
    :class:`PyProgress` is similar to :class:`ProgressDialog` in indeterminated mode, but with a
    different gauge appearance and a different spinning behavior. The moving gauge
    can be drawn with a single solid colour or with a shading gradient foreground.
    The gauge background colour is user customizable.
    The bar does not move always from the beginning to the end as in :class:`ProgressDialog`
    in indeterminated mode, but spins cyclically forward and backward.
    """

    def __init__(self, parent=None, id=-1, title="", message="",
                 agwStyle=wx.PD_APP_MODAL|wx.PD_AUTO_HIDE):
        """
        Default class constructor.

        :param `parent`: parent window;
        :param `id`: window identifier. A value of -1 indicates a default value;
        :param `title`: dialog title to show in titlebar;
        :param `message`: message displayed above the progress bar;
        :param `style`: the dialog style. This can be a combination of the following bits:

         =================== =========== ==================================================
         Window Styles       Hex Value   Description
         =================== =========== ==================================================
         ``PD_CAN_ABORT``            0x1 This flag tells the dialog that it should have a ``Cancel`` button which the user may press. If this happens, the next call to `UpdatePulse` will return ``False``.
         ``PD_APP_MODAL``            0x2 Make the progress dialog modal. If this flag is not given, it is only 'locally' modal - that is the input to the parent window is disabled, but not to the other ones.
         ``PD_AUTO_HIDE``            0x4 Causes the progress dialog to disappear from screen as soon as the maximum value of the progress meter has been reached.
         ``PD_ELAPSED_TIME``         0x8 This flag tells the dialog that it should show elapsed time (since creating the dialog).
         =================== =========== ==================================================

        """

        wx.Dialog.__init__(self, parent, id, title)

        self._delay = 3
        self._hasAbortButton = False

        # we may disappear at any moment, let the others know about it
        self.SetExtraStyle(self.GetExtraStyle()|wx.WS_EX_TRANSIENT)

        self._hasAbortButton = (agwStyle & wx.PD_CAN_ABORT)

        if wx.Platform == "__WXMSW__":
            # we have to remove the "Close" button from the title bar then as it is
            # confusing to have it - it doesn't work anyhow
            # FIXME: should probably have a (extended?) window style for this
            if not self._hasAbortButton:
                self.EnableClose(False)

        self._state = (self._hasAbortButton and [Continue] or [Uncancelable])[0]
        self._parentTop = wx.GetTopLevelParent(parent)

        dc = wx.ClientDC(self)
        dc.SetFont(wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT))
        widthText, dummy = dc.GetTextExtent(message)

        sizer = wx.BoxSizer(wx.VERTICAL)

        self._msg = wx.StaticText(self, wx.ID_ANY, message)
        sizer.Add(self._msg, 0, wx.LEFT|wx.TOP, 2*LAYOUT_MARGIN)

        sizeDlg = wx.Size()
        sizeLabel = self._msg.GetSize()
        sizeDlg.y = 2*LAYOUT_MARGIN + sizeLabel.y

        self._gauge = ProgressGauge(self, -1)

        sizer.Add(self._gauge, 0, wx.LEFT|wx.RIGHT|wx.TOP|wx.EXPAND, 2*LAYOUT_MARGIN)

        sizeGauge = self._gauge.GetSize()
        sizeDlg.y += 2*LAYOUT_MARGIN + sizeGauge.y

        # create the estimated/remaining/total time zones if requested
        self._elapsed = None
        self._display_estimated = self._last_timeupdate = self._break = 0
        self._ctdelay = 0

        label = None

        nTimeLabels = 0

        if agwStyle & wx.PD_ELAPSED_TIME:

            nTimeLabels += 1
            self._elapsed = self.CreateLabel("Elapsed time : ", sizer)

        if nTimeLabels > 0:

            label = wx.StaticText(self, -1, "")
            # set it to the current time
            self._timeStart = int(time.time())
            sizeDlg.y += nTimeLabels*(label.GetSize().y + LAYOUT_MARGIN)
            label.Destroy()

        sizeDlgModified = False

        if wx.Platform == "__WXMSW__":
            sizerFlags = wx.ALIGN_RIGHT|wx.ALL
        else:
            sizerFlags = wx.ALIGN_CENTER_HORIZONTAL|wx.BOTTOM|wx.TOP

        if self._hasAbortButton:
            buttonSizer = wx.BoxSizer(wx.HORIZONTAL)

            self._btnAbort = wx.Button(self, -1, "Cancel")
            self._btnAbort.Bind(wx.EVT_BUTTON, self.OnCancel)

            # Windows dialogs usually have buttons in the lower right corner
            buttonSizer.Add(self._btnAbort, 0, sizerFlags, LAYOUT_MARGIN)

            if not sizeDlgModified:
                sizeDlg.y += 2*LAYOUT_MARGIN + wx.Button.GetDefaultSize().y

        if self._hasAbortButton:
            sizer.Add(buttonSizer, 0, sizerFlags, LAYOUT_MARGIN )

        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)

        self._agwStyle = agwStyle

        self.SetSizerAndFit(sizer)

        sizeDlg.y += 2*LAYOUT_MARGIN

        # try to make the dialog not square but rectangular of reasonable width
        sizeDlg.x = max(widthText, 4*sizeDlg.y//3)
        sizeDlg.x *= 3
        sizeDlg.x //= 2
        self.SetClientSize(sizeDlg)

        self.Centre(wx.CENTER|wx.BOTH)

        if agwStyle & wx.PD_APP_MODAL:
            self._winDisabler = wx.WindowDisabler(self)
        else:
            if self._parentTop:
                self._parentTop.Disable()
            self._winDisabler = None

        self.ShowDialog()
        self.Enable()

        # this one can be initialized even if the others are unknown for now
        # NB: do it after calling Layout() to keep the labels correctly aligned
        if self._elapsed:
            self.SetTimeLabel(0, self._elapsed)

        self.evtloop = None

        # This is causing the unittests to hang, investigate it later.
        #if not wx.EventLoopBase.GetActive():
        #    self.evtloop = wx.GetApp().GetTraits().CreateEventLoop()
        #    wx.EventLoopBase.SetActive(self.evtloop)

        self.Update()


    def CreateLabel(self, text, sizer):
        """
        Creates the :class:`StaticText` that holds the elapsed time label.

        :param `text`: the dialog message to be displayed above the gauge;
        :param `sizer`: the main :class:`BoxSizer` for :class:`PyProgress`.
        """

        locsizer = wx.BoxSizer(wx.HORIZONTAL)
        dummy = wx.StaticText(self, wx.ID_ANY, text)
        label = wx.StaticText(self, wx.ID_ANY, "unknown")

        if wx.Platform in ["__WXMSW__", "__WXMAC__"]:
            # label and time centered in one row
            locsizer.Add(dummy, 1, wx.ALIGN_LEFT)
            locsizer.Add(label, 1, wx.ALIGN_LEFT|wx.LEFT, LAYOUT_MARGIN)
            sizer.Add(locsizer, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.TOP, LAYOUT_MARGIN)
        else:
            # label and time to the right in one row
            sizer.Add(locsizer, 0, wx.ALIGN_RIGHT|wx.RIGHT|wx.TOP, LAYOUT_MARGIN)
            locsizer.Add(dummy)
            locsizer.Add(label, 0, wx.LEFT, LAYOUT_MARGIN)

        return label


    # ----------------------------------------------------------------------------
    # wxProgressDialog operations
    # ----------------------------------------------------------------------------

    def UpdatePulse(self, newmsg=""):
        """
        Updates the dialog, setting the progress bar to the new value and, if given
        changes the message above it. Returns ``True`` unless the ``Cancel`` button
        has been pressed.

        If ``False`` is returned, the application can either immediately destroy the
        dialog or ask the user for the confirmation.

        :param `newmsg`: The new messages for the progress dialog text, if it is
         empty (which is the default) the message is not changed.
        """

        self._gauge.Update()

        if newmsg and newmsg != self._msg.GetLabel():
            self._msg.SetLabel(newmsg)
            wx.SafeYield()

        if self._elapsed:
            elapsed = int(time.time()) - self._timeStart
            if self._last_timeupdate < elapsed:
                self._last_timeupdate = elapsed

            self.SetTimeLabel(elapsed, self._elapsed)

        if self._state == Finished:

            if not self._agwStyle & wx.PD_AUTO_HIDE:

                self.EnableClose()

                if newmsg == "":
                    # also provide the finishing message if the application didn't
                    self._msg.SetLabel("Done.")

                wx.SafeYield()
                self.ShowModal()
                return False

            else:
                # reenable other windows before hiding this one because otherwise
                # Windows wouldn't give the focus back to the window which had
                # been previously focused because it would still be disabled
                self.ReenableOtherWindows()
                self.Hide()

        # we have to yield because not only we want to update the display but
        # also to process the clicks on the cancel and skip buttons
        wx.SafeYield()

        return self._state != Canceled


    def GetFirstGradientColour(self):
        """ Returns the gauge first gradient colour. """

        return self._gauge.GetFirstGradientColour()


    def SetFirstGradientColour(self, colour):
        """
        Sets the gauge first gradient colour.

        :param `colour`: a valid :class:`wx.Colour` object.
        """

        self._gauge.SetFirstGradientColour(colour)


    def GetSecondGradientColour(self):
        """ Returns the gauge second gradient colour. """

        return self._gauge.GetSecondGradientColour()


    def SetSecondGradientColour(self, colour):
        """
        Sets the gauge second gradient colour.

        :param `colour`: a valid :class:`wx.Colour` object.
        """

        self._gauge.SetSecondGradientColour(colour)


    def GetGaugeBackground(self):
        """ Returns the gauge background colour. """

        return self._gauge.GetGaugeBackground()


    def SetGaugeBackground(self, colour):
        """
        Sets the gauge background colour.

        :param `colour`: a valid :class:`wx.Colour` object.
        """

        self._gauge.SetGaugeBackground(colour)


    def SetGaugeSteps(self, steps):
        """
        Sets the number of steps the gauge performs before switching from
        forward to backward (or vice-versa) movement.

        :param `steps`: the number of steps the gauge performs before switching from
         forward to backward (or vice-versa) movement.
        """

        self._gauge.SetGaugeSteps(steps)


    def GetGaugeSteps(self):
        """
        Returns the number of steps the gauge performs before switching from
        forward to backward (or vice-versa) movement.
        """

        return self._gauge.GetGaugeSteps()


    def GetGaugeProportion(self):
        """
        Returns the relative proportion between the sliding bar and the
        whole gauge.
        """

        return self._gauge.GetGaugeProportion()


    def SetGaugeProportion(self, proportion):
        """
        Sets the relative proportion between the sliding bar and the
        whole gauge.

        :param `proportion`: a floating point number representing the relative
         proportion between the sliding bar and the whole gauge.
        """

        self._gauge.SetGaugeProportion(proportion)


    def ShowDialog(self, show=True):
        """
        Show the dialog.

        :param `show`: ``True`` to show the dialog and re-enable all the other windows,
         ``False`` otherwise.
        """

        # reenable other windows before hiding this one because otherwise
        # Windows wouldn't give the focus back to the window which had
        # been previously focused because it would still be disabled
        if not show:
            self.ReenableOtherWindows()

        return self.Show()


    def GetAGWWindowStyleFlag(self):
        """
        Returns the :class:`PyProgress` style.

        :see: The :meth:`~PyProgress.__init__` method for a list of possible style flags.
        """

        return self._agwStyle


    # ----------------------------------------------------------------------------
    # event handlers
    # ----------------------------------------------------------------------------

    def OnCancel(self, event):
        """
        Handles the ``wx.EVT_BUTTON`` event for the dialog.

        :param `event`: a :class:`CommandEvent` event to be processed.

        :note: This method handles the ``Cancel`` button press.
        """

        if self._state == Finished:

            # this means that the count down is already finished and we're being
            # shown as a modal dialog - so just let the default handler do the job
            event.Skip()

        else:

            # request to cancel was received, the next time Update() is called we
            # will handle it
            self._state = Canceled

            # update the buttons state immediately so that the user knows that the
            # request has been noticed
            self.DisableAbort()

            # save the time when the dialog was stopped
            self._timeStop = int(time.time())

        self.ReenableOtherWindows()


    def OnDestroy(self, event):
        """
        Handles the ``wx.EVT_WINDOW_DESTROY`` event for :class:`PyProgress`.

        :param `event`: a :class:`wx.WindowDestroyEvent` event to be processed.
        """
        if self.evtloop:
            wx.EventLoopBase.SetActive(None)
        self.ReenableOtherWindows()
        event.Skip()


    def OnClose(self, event):
        """
        Handles the ``wx.EVT_CLOSE`` event for :class:`PyProgress`.

        :param `event`: a :class:`CloseEvent` event to be processed.
        """

        if self._state == Uncancelable:

            # can't close this dialog
            event.Veto()

        elif self._state == Finished:

            # let the default handler close the window as we already terminated
            self.Hide()
            event.Skip()

        else:

            # next Update() will notice it
            self._state = Canceled
            self.DisableAbort()

            self._timeStop = int(time.time())


    def ReenableOtherWindows(self):
        """ Re-enables the other windows if using :class:`wx.WindowDisabler`. """

        if self._agwStyle & wx.PD_APP_MODAL:
            if hasattr(self, "_winDisabler"):
                del self._winDisabler

        else:

            if self._parentTop:
                self._parentTop.Enable()


    def SetTimeLabel(self, val, label=None):
        """
        Sets the elapsed time label.

        :param `val`: the elapsed time since the dialog was shown, in seconds;
        :param `label`: the new message to display in the elapsed time text.
        """

        if label:

            hours = val//3600
            minutes = (val%3600)//60
            seconds = val%60
            strs = ("%lu:%02lu:%02lu")%(hours, minutes, seconds)

            if strs != label.GetLabel():
                label.SetLabel(strs)


    def EnableAbort(self, enable=True):
        """
        Enables or disables the ``Cancel`` button.

        :param `enable`: ``True`` to enable the ``Cancel`` button, ``False`` to disable it.
        """

        if self._hasAbortButton:
            if self._btnAbort:
                self._btnAbort.Enable(enable)


    def EnableClose(self, enable=True):
        """
        Enables or disables the ``Close`` button.

        :param `enable`: ``True`` to enable the ``Close`` button, ``False`` to disable it.
        """

        if self._hasAbortButton:
            if self._btnAbort:
                self._btnAbort.Enable(enable)
                self._btnAbort.SetLabel("Close")
                self._btnAbort.Bind(wx.EVT_BUTTON, self.OnClose)


    def DisableAbort(self):
        """ Disables the ``Cancel`` button. """

        self.EnableAbort(False)



if __name__ == '__main__':

    import wx

    # Our normal wxApp-derived class, as usual
    app = wx.App(0)

    dlg = PyProgress(None, -1, "PyProgress Example",
                     "An Informative Message",
                     agwStyle=wx.PD_APP_MODAL|wx.PD_ELAPSED_TIME)

    dlg.SetGaugeProportion(0.2)
    dlg.SetGaugeSteps(50)
    dlg.SetGaugeBackground(wx.WHITE)
    dlg.SetFirstGradientColour(wx.WHITE)
    dlg.SetSecondGradientColour(wx.BLUE)

    max = 400
    keepGoing = True
    count = 0

    while keepGoing and count < max:
        count += 1
        wx.MilliSleep(30)

        if count >= max // 2:
            keepGoing = dlg.UpdatePulse("Half-time!")
        else:
            keepGoing = dlg.UpdatePulse()

    dlg.Destroy()

    app.MainLoop()
