Source

wxPython / wx / lib / agw / pybusyinfo.py

Full commit
"""
L{PyBusyInfo} constructs a busy info window and displays a message in it.


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

L{PyBusyInfo} constructs a busy info window and displays a message in it.

This class makes it easy to tell your user that the program is temporarily busy.
Just create a L{PyBusyInfo} object, and within the current scope, a message window
will be shown.

For example::

    busy = PyBusyInfo("Please wait, working...")

    for i in xrange(10000):
        DoACalculation()

    del busy


It works by creating a window in the constructor, and deleting it in the destructor.
You may also want to call `wx.Yield()` to refresh the window periodically (in case
it had been obscured by other windows, for example).


Usage
=====

Usage example::

    import wx    
    import wx.lib.agw.pybusyinfo as PBI

    class MyFrame(wx.Frame):

        def __init__(self, parent):
        
            wx.Frame.__init__(self, parent, -1, "PyBusyInfo Demo")

            panel = wx.Panel(self)
    
            b = wx.Button(panel, -1, "Test PyBusyInfo ", (50,50))
            self.Bind(wx.EVT_BUTTON, self.OnButton, b)


        def OnButton(self, event):
            
            message = "Please wait 5 seconds, working..."
            busy = PBI.PyBusyInfo(message, parent=self, title="Really Busy")

            wx.Yield()
            
            for indx in xrange(5):
                wx.MilliSleep(1000)

            del busy


    # our normal wxApp-derived class, as usual

    app = wx.PySimpleApp()

    frame = MyFrame(None)
    app.SetTopWindow(frame)
    frame.Show()

    app.MainLoop()



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

L{PyBusyInfo} has been tested on the following platforms:
  * Windows (Windows XP).


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

`No particular window styles are available for this class.`


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

`No custom events are available for this class.`


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

L{PyBusyInfo} is distributed under the wxPython license.

Latest Revision: Andrea Gavana @ 17 Aug 2011, 15.00 GMT

Version 0.1

"""


import wx

_ = wx.GetTranslation


class PyInfoFrame(wx.Frame):
    """ Base class for L{PyBusyInfo}. """

    def __init__(self, parent, message, title, icon):
        """
        Default class constructor.
        
        :param `parent`: the frame parent;
        :param `message`: the message to display in the L{PyBusyInfo};
        :param `title`: the main L{PyBusyInfo} title;
        :param `icon`: an icon to draw as the frame icon, an instance of `wx.Bitmap`.
        """
        
        wx.Frame.__init__(self, parent, wx.ID_ANY, title, wx.DefaultPosition,
                          wx.DefaultSize, wx.NO_BORDER|wx.FRAME_TOOL_WINDOW|wx.FRAME_SHAPED|wx.STAY_ON_TOP)

        panel = wx.Panel(self)
        panel.SetCursor(wx.HOURGLASS_CURSOR)

        self._message = message
        self._title = title
        self._icon = icon

        dc = wx.ClientDC(self)
        textWidth, textHeight, dummy = dc.GetMultiLineTextExtent(self._message)
        sizeText = wx.Size(textWidth, textHeight)

        self.SetClientSize((max(sizeText.x, 340) + 60, max(sizeText.y, 40) + 60))
        # need to size the panel correctly first so that text.Centre() works
        panel.SetSize(self.GetClientSize())

        # Bind the events to draw ourselves
        panel.Bind(wx.EVT_PAINT, self.OnPaint)
        panel.Bind(wx.EVT_ERASE_BACKGROUND, self.OnErase)
            
        self.Centre(wx.BOTH)

        # Create a non-rectangular region to set the frame shape
        size = self.GetSize()
        bmp = wx.EmptyBitmap(size.x, size.y)
        dc = wx.BufferedDC(None, bmp)
        dc.SetBackground(wx.Brush(wx.Colour(0, 0, 0), wx.SOLID))
        dc.Clear()
        dc.SetPen(wx.Pen(wx.Colour(0, 0, 0), 1))
        dc.DrawRoundedRectangle(0, 0, size.x, size.y, 12)                
        r = wx.RegionFromBitmapColour(bmp, wx.Colour(0, 0, 0))
        # Store the non-rectangular region
        self.reg = r

        if wx.Platform == "__WXGTK__":
            self.Bind(wx.EVT_WINDOW_CREATE, self.SetBusyShape)
        else:
            self.SetBusyShape()

        # Add a custom bitmap at the top (if any)


    def SetBusyShape(self, event=None):
        """
        Sets L{PyInfoFrame} shape using the region created from the bitmap.

        :param `event`: a `wx.WindowCreateEvent` event (GTK only, as GTK supports setting
         the window shape only during window creation).
        """

        self.SetShape(self.reg)
        if event:
            # GTK only
            event.Skip()
            

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

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

        panel = event.GetEventObject()
        
        dc = wx.BufferedPaintDC(panel)
        dc.Clear()

        # Fill the background with a gradient shading
        startColour = wx.SystemSettings_GetColour(wx.SYS_COLOUR_ACTIVECAPTION)
        endColour = wx.WHITE

        rect = panel.GetRect()
        dc.GradientFillLinear(rect, startColour, endColour, wx.SOUTH)

        # Draw the label
        font = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
        dc.SetFont(font)

        # Draw the message
        rect2 = wx.Rect(*rect)
        rect2.height += 20
        dc.DrawLabel(self._message, rect2, alignment=wx.ALIGN_CENTER|wx.ALIGN_CENTER)

        # Draw the top title
        font.SetWeight(wx.BOLD)
        dc.SetFont(font)
        dc.SetPen(wx.Pen(wx.SystemSettings_GetColour(wx.SYS_COLOUR_CAPTIONTEXT)))
        dc.SetTextForeground(wx.SystemSettings_GetColour(wx.SYS_COLOUR_CAPTIONTEXT))

        if self._icon.IsOk():
            iconWidth, iconHeight = self._icon.GetWidth(), self._icon.GetHeight()
            dummy, textHeight = dc.GetTextExtent(self._title)
            textXPos, textYPos = iconWidth + 10, (iconHeight-textHeight)/2
            dc.DrawBitmap(self._icon, 5, 5, True)
        else:
            textXPos, textYPos = 5, 0
        
        dc.DrawText(self._title, textXPos, textYPos+5)
        dc.DrawLine(5, 25, rect.width-5, 25)

        size = self.GetSize()
        dc.SetPen(wx.Pen(startColour, 1))
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.DrawRoundedRectangle(0, 0, size.x, size.y-1, 12)
        

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

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

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

        # This is empty on purpose, to avoid flickering
        pass

                
# -------------------------------------------------------------------- #
# The actual PyBusyInfo implementation
# -------------------------------------------------------------------- #

class PyBusyInfo(object):
    """
    Constructs a busy info window as child of parent and displays a message in it.
    """

    def __init__(self, message, parent=None, title=_("Busy"), icon=wx.NullBitmap):
        """
        Default class constructor.
        
        :param `parent`: the L{PyBusyInfo} parent;
        :param `message`: the message to display in the L{PyBusyInfo};
        :param `title`: the main L{PyBusyInfo} title;
        :param `icon`: an icon to draw as the frame icon, an instance of `wx.Bitmap`.

        :note: If `parent` is not ``None`` you must ensure that it is not closed
         while the busy info is shown.
        """

        self._infoFrame = PyInfoFrame(parent, message, title, icon)

        if parent and parent.HasFlag(wx.STAY_ON_TOP):
            # we must have this flag to be in front of our parent if it has it
            self._infoFrame.SetWindowStyleFlag(wx.STAY_ON_TOP)
            
        self._infoFrame.Show(True)
        self._infoFrame.Refresh()
        self._infoFrame.Update()
        

    def __del__(self):
        """ Overloaded method, for compatibility with wxWidgets. """

        self._infoFrame.Show(False)
        self._infoFrame.Destroy()