# --------------------------------------------------------------------------- #
# TOASTERBOX wxPython IMPLEMENTATION
# Ported And Enhanced From wxWidgets Contribution (Aj Bommarito) By:
#
# Andrea Gavana, @ 16 September 2005
# Latest Revision: 14 Mar 2012, 21.00 GMT
#
#
# TODO/Caveats List
#
# 1. Any Idea?
#
#
# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
# Write To Me At:
#
# andrea.gavana@gmail.com
# andrea.gavana@maerskoil.com
#
# Or, Obviously, To The wxPython Mailing List!!!
#
#
# End Of Comments
# --------------------------------------------------------------------------- #


"""
ToasterBox is a cross-platform widget to make the creation of MSN style "toaster"
popups easier.


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

ToasterBox is a cross-platform widget to make the creation of MSN style "toaster"
popups easier. The syntax is really easy especially if you are familiar with the
syntax of wxPython.

It has 2 main styles:

- ``TB_SIMPLE``: using this style, you will be able to specify a background image for
  ToasterBox, text properties as text colour, font and label;

- ``TB_COMPLEX``: this style will allow you to put almost any control inside a
  ToasterBox. You can add a panel in which you can put all the controls you like.

Both styles support the setting of ToasterBox position (on screen coordinates),
size, the time after which the ToasterBox is destroyed (linger), and the scroll
speed of ToasterBox.


Usage
=====

Usage example::

    import wx
    import wx.lib.agw.toasterbox as TB

    class MyFrame(wx.Frame):

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

            toaster = TB.ToasterBox(self, tbstyle=TB.TB_COMPLEX)
            toaster.SetPopupPauseTime(3000)

            tbpanel = toaster.GetToasterBoxWindow()
            panel = wx.Panel(tbpanel, -1)
            sizer = wx.BoxSizer(wx.VERTICAL)

            button = wx.Button(panel, wx.ID_ANY, "Simple button")
            sizer.Add(button, 0, wx.EXPAND)

            panel.SetSizer(sizer)
            toaster.AddPanel(panel)

            wx.CallLater(1000, toaster.Play)


    # our normal wxApp-derived class, as usual

    app = wx.App(0)

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

    app.MainLoop()



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

ToasterBox has been tested on the following platforms:

- Windows (verified on Windows XP, 2000)
- Linux
- Mac


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

This class supports the following window styles:

==================== =========== ==================================================
Window Styles        Hex Value   Description
==================== =========== ==================================================
``TB_SIMPLE``                0x1 A simple `ToasterBox`, with background image and text customization can be created.
``TB_ONTIME``                0x1 `ToasterBox` will close after a specified amount of time.
``TB_COMPLEX``               0x2 ToasterBoxes with different degree of complexity can be created. You can add as  many controls as you want, provided that you call the meth:~ToasterBox.AddPanel` method and pass  to it a dummy frame and a :class:`Panel`. See the demo for details.
``TB_ONCLICK``               0x2 `ToasterBox` can be closed by clicking anywhere on the `ToasterBox` frame.
``TB_DEFAULT_STYLE``   0x2008002 Default window style for `ToasterBox`, with no caption nor close box.
``TB_CAPTION``        0x22009806 `ToasterBox` will have a caption, with the possibility to set a title for the `ToasterBox` frame, and a close box.
==================== =========== ==================================================


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

`No custom events are available for this class.`


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

ToasterBox is distributed under the wxPython license.

Latest revision: Andrea Gavana @ 14 Mar 2012, 21.00 GMT

Version 0.3

"""

import textwrap
import wx


# Define Window List, We Use It Globally
winlist = []
""" Globally defined window list. """

TB_SIMPLE = 1
""" A simple ToasterBox, with background image and text customization can be created. """
TB_COMPLEX = 2
""" ToasterBoxes with different degree of complexity can be created. You can add as  many controls as you want, provided that you call the AddPanel() method and pass to it a dummy frame and a wx.Panel. See the demo for details. """
TB_DEFAULT_STYLE = wx.SIMPLE_BORDER | wx.STAY_ON_TOP | wx.FRAME_NO_TASKBAR
""" Default window style for `ToasterBox`, with no caption nor close box. """
TB_CAPTION = TB_DEFAULT_STYLE | wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.FRAME_NO_TASKBAR
""" `ToasterBox` will have a caption, with the possibility to set a title for the `ToasterBox` frame, and a close box. """
TB_ONTIME = 1
""" `ToasterBox` will close after a specified amount of time. """
TB_ONCLICK = 2
""" `ToasterBox` can be closed by clicking anywhere on the `ToasterBox` frame. """

# scroll from up to down
TB_SCR_TYPE_UD = 1
""" Scroll from up to down. """
# scroll from down to up
TB_SCR_TYPE_DU = 2
""" Scroll from down to up. """
# fade in/out 
TB_SCR_TYPE_FADE = 4
""" Fade in and out. """


# ------------------------------------------------------------------------------ #
# Class ToasterBox
#    Main Class Implementation. It Is Basically A wx.Timer. It Creates And
#    Displays Popups And Handles The "Stacking".
# ------------------------------------------------------------------------------ #

class ToasterBox(wx.Timer):
    """
    ToasterBox is a cross-platform widget to make the creation of MSN style "toaster"
    popups easier.
    """

    def __init__(self, parent, tbstyle=TB_SIMPLE, windowstyle=TB_DEFAULT_STYLE,
                 closingstyle=TB_ONTIME, scrollType=TB_SCR_TYPE_DU):
        """
        Default class constructor.

        :param `parent`: the window parent;
        :param `tbstyle`: the :class:`ToasterBox` main style. Can be one of the following
         bits:

         ====================== ======= ================================
         `ToasterBox` Style      Value  Description
         ====================== ======= ================================
         ``TB_SIMPLE``              0x1 A simple :class:`ToasterBox`, with background image and text customization can be created
         ``TB_COMPLEX``             0x2 `ToasterBoxes` with different degree of complexity can be created. You can add as many controls as you want, provided that you call the :meth:`~ToasterBox.AddPanel` method and pass to it a dummy frame and a :class:`Panel`.
         ====================== ======= ================================

        :param `windowstyle`: this parameter influences the visual appearance of
         :class:`ToasterBox`, and can be one of the following styles:

         ====================== ========== ================================
         Window Style           Hex Value  Description
         ====================== ========== ================================
         ``TB_DEFAULT_STYLE``   0x2008002  Default window style for :class:`ToasterBox`, with no caption nor close box.
         ``TB_CAPTION``         0x22009806 :class:`ToasterBox` will have a caption, with the possibility to set a title for the :class:`ToasterBox` frame, and a close box.
         ====================== ========== ================================
       
        :param `closingstyle`: the closing style for :class:`ToasterBox`. Can be one of the
         following bits:

         ==================== =========== ==================================================
         Closing Styles       Hex Value   Description
         ==================== =========== ==================================================
         ``TB_ONTIME``                0x1 :class:`ToasterBox` will close after a specified amount of time.
         ``TB_ONCLICK``               0x2 :class:`ToasterBox` can be closed by clicking anywhere on the :class:`ToasterBox` frame.
         ==================== =========== ==================================================

        :param `scrollType`: the scrolling direction for :class:`ToasterBox`. Can be one of the
         following bits:

         ==================== =========== ==================================================
         Scroll Styles        Hex Value   Description
         ==================== =========== ==================================================
         ``TB_SCR_TYPE_UD``           0x1 :class:`ToasterBox` will scroll from up to down
         ``TB_SCR_TYPE_DU``           0x2 :class:`ToasterBox` will scroll from down to up
         ``TB_SCR_TYPE_FADE``         0x4 :class:`ToasterBox` will fade in/out (without scrolling). 
         ==================== =========== ==================================================
         
        """

        self._parent = parent
        self._sleeptime = 10
        self._pausetime = 1700
        self._popuptext = "default"
        self._popupposition = wx.Point(100,100)
        self._popuptop = wx.Point(0,0)
        self._popupsize = wx.Size(150, 170)
        self._usefocus = True
        self._originalfocus = wx.Window.FindFocus()

        self._backgroundcolour = wx.WHITE
        self._foregroundcolour = wx.BLACK
        self._textfont = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, False, "Verdana")

        self._bitmap = None

        self._tbstyle = tbstyle
        self._windowstyle = windowstyle
        self._closingstyle = closingstyle
        self._scrollType = scrollType
        
        self._panel = None

        self._bottomright = wx.Point(wx.GetDisplaySize().GetWidth(),
                                     wx.GetDisplaySize().GetHeight())

        if parent is not None:
            parent.Bind(wx.EVT_ICONIZE, lambda evt: [w.Hide() for w in winlist])
            self._moveTimer = wx.Timer(parent, -1)
            parent.Bind(wx.EVT_TIMER, self.OnMoveTimer, self._moveTimer)
 
        self._tb = ToasterBoxWindow(self._parent, self, self._tbstyle, self._windowstyle,
                                    self._closingstyle, scrollType=self._scrollType)


        
    def SetPopupPosition(self, pos):
        """
        Sets the :class:`ToasterBox` position on screen.

        :param `pos`: the widget position, an instance of :class:`Point`.        
        """

        self._popupposition = pos


    def SetPopupPositionByInt(self, pos):
        """
        Sets the :class:`ToasterBox` position on screen, at one of the screen corners.
 
        :param `pos`: an integer specifying the screen corner, namely:

         ============= ========================================
         Corner Number Position
         ============= ========================================
               0       Top left screen corner
               1       Top right screen corner
               2       Bottom left screen corner
               3       Bottom right screen corner
         ============= ========================================
         
        """

        w, h = wx.GetDisplaySize()
        self._bottomright = wx.Point(w, h)

        # top left
        if pos == 0:
            popupposition = wx.Point(0,0)
        # top right
        elif pos == 1:
            popupposition = wx.Point(w - self._popupsize[0], 0)
        # bottom left
        elif pos == 2:
            popupposition = wx.Point(0, h - self._popupsize[1])
        # bottom right
        elif pos == 3:
            popupposition = wx.Point(self._bottomright.x - self._popupsize[0],
                                     self._bottomright.y - self._popupsize[1])

        self._bottomright = wx.Point(popupposition.x + self._popupsize[0],
                                     popupposition.y + self._popupsize[1])

        self._popupposition = popupposition


    def CenterOnParent(self, direction=wx.BOTH):
        """
        Centres the window on its parent (if any). If the :class:`ToasterBox` parent is ``None``,
        it calls :meth:`~ToasterBox.CenterOnScreen`.

        :param `direction`: specifies the direction for the centering. May be ``wx.HORIZONTAL``,
         ``wx.VERTICAL`` or ``wx.BOTH``.

        :note: This methods provides for a way to center :class:`ToasterBox` over their parents instead of the
         entire screen. If there is no parent, then behaviour is the same as :meth:`~ToasterBox.CenterOnScreen`.

        :see: :meth:`~ToasterBox.CenterOnScreen`.
        """

        if not self._parent:
            self.CenterOnScreen(direction)
            return

        parent = self._parent
        screenrect = parent.GetScreenRect()
        toast_width, toast_height = self._popupsize
        x, y = screenrect.GetX(), screenrect.GetY()
        width, height = screenrect.GetWidth(), screenrect.GetHeight()
        
        if direction == wx.VERTICAL:
            pos = wx.Point(x, (y + (height/2) - (toast_height/2)))
        elif direction == wx.HORIZONTAL:
            pos = wx.Point((x + (width/2) - (toast_width/2)), y)
        else:
            pos = wx.Point((x + (width/2) - (toast_width/2)), (y + (height/2) - (toast_height/2)))

        tb.SetPopupPosition(pos)        
                

    CentreOnParent = CenterOnParent


    def CenterOnScreen(self, direction=wx.BOTH):
        """
        Centres the :class:`ToasterBox` on screen.

        :param `direction`: specifies the direction for the centering. May be ``wx.HORIZONTAL``,
         ``wx.VERTICAL`` or ``wx.BOTH``.

        :see: :meth:`~ToasterBox.CenterOnParent`.
        """

        screenSize = wx.GetDisplaySize()
        toast_width, toast_height = self._popupsize
        width, height = screenSize.GetWidth(), screenSize.GetHeight()
        
        if direction == wx.VERTICAL:
            pos = wx.Point(0, (height/2) - (toast_height/2))
        elif direction == wx.HORIZONTAL:
            pos = wx.Point((width/2) - (toast_width/2), 0)
        else:
            pos = wx.Point((width/2) - (toast_width/2), (height/2) - (toast_height/2))

        tb.SetPopupPosition(pos)


    CentreOnScreen = CenterOnScreen        

    
    def SetPopupBackgroundColour(self, colour=None):
        """
        Sets the :class:`ToasterBox` background colour.

        :param `colour`: a valid :class:`Colour` object. If defaulted to ``None``, then
         the background colour will be white.
         
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.
        """

        if colour is None:
            colour = wx.WHITE

        if isinstance(colour, basestring):
            colour = wx.NamedColour(colour)
            
        self._backgroundcolour = colour
        self._tb.SetPopupBackgroundColour(self._backgroundcolour)


    def SetPopupTextColour(self, colour=None):
        """
        Sets the :class:`ToasterBox` foreground colour.

        :param `colour`: a valid :class:`Colour` object. If defaulted to ``None``, then
         the background colour will be black.
         
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.
        """

        if colour is None:
            colour = wx.BLACK

        if isinstance(colour, basestring):
            colour = wx.NamedColour(colour)
            
        self._foregroundcolour = colour


    def SetPopupTextFont(self, font=None):
        """
        Sets the :class:`ToasterBox` text font.

        :param `colour`: a valid :class:`Colour` object. If defaulted to ``None``, then
         a simple generic font will be generated.
         
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.
        """

        if font is None:
            font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, False)

        self._textfont = font


    def SetPopupSize(self, size):
        """
        Sets the :class:`ToasterBox` size.

        :param `size`: the new control size, an instance of :class:`Size`.        
        """

        self._popupsize = size


    def SetPopupPauseTime(self, pausetime):
        """
        Sets the time after which the :class:`ToasterBox` is destroyed (linger).

        :param `pausetime`: the delay after which the control is destroyed, in seconds.
        """

        self._pausetime = pausetime


    def SetPopupBitmap(self, bitmap=None):
        """
        Sets the :class:`ToasterBox` background image.

        :param `bitmap`: a valid :class:`Bitmap` object or filename. If defaulted
         to ``None``, then no background bitmap is used.
         
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.
        """

        if bitmap is not None:
            if isinstance(bitmap, basestring):
                bitmap = wx.Bitmap(bitmap)

        self._bitmap = bitmap


    def SetPopupScrollSpeed(self, speed):
        """
        Sets the :class:`ToasterBox` scroll speed.

        :param `speed`: it is the pause time (in milliseconds) for every step in the
         `ScrollUp` method.
        """

        self._sleeptime = speed


    def SetPopupText(self, text):
        """
        Sets the :class:`ToasterBox` text label.

        :param `text`: the widget label.
         
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.
        """

        self._popuptext = text


    def AddPanel(self, panel):
        """
        Adds a panel to the :class:`ToasterBox`.

        :param `panel`: an instance of :class:`Window`.
        
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_COMPLEX`` style.
        """

        if not self._tbstyle & TB_COMPLEX:
            raise Exception("\nERROR: Panel Can Not Be Added When Using TB_SIMPLE ToasterBox Style")

        self._panel = panel


    def Play(self):
        """ Creates the :class:`ToasterBoxWindow`, that does all the job. """

        # create new window
        self._tb.SetPopupSize((self._popupsize[0], self._popupsize[1]))
        self._tb.SetPopupPosition((self._popupposition[0], self._popupposition[1]))
        self._tb.SetPopupPauseTime(self._pausetime)
        self._tb.SetPopupScrollSpeed(self._sleeptime)
        self._tb.SetUseFocus(self._usefocus, self._originalfocus)

        if self._tbstyle == TB_SIMPLE:
            self._tb.SetPopupTextColour(self._foregroundcolour)
            self._tb.SetPopupBackgroundColour(self._backgroundcolour)
            self._tb.SetPopupTextFont(self._textfont)

            if self._bitmap is not None:
                self._tb.SetPopupBitmap(self._bitmap)

            self._tb.SetPopupText(self._popuptext)

        if self._tbstyle == TB_COMPLEX:
            if self._panel is not None:
                self._tb.AddPanel(self._panel)

        # clean up the list
        self.CleanList()

        # check to see if there is already a window displayed
        # by looking at the linked list
        if len(winlist) > 0:
            # there ARE other windows displayed already
            # reclac where it should display
            self.MoveAbove(self._tb)

        # shift new window on to the list
        winlist.append(self._tb)

        if not self._tb.Play():
            # if we didn't show the window properly, remove it from the list
            winlist.remove(winlist[-1])
            # delete the object too
            self._tb.Destroy()
            return


    def MoveAbove(self, tb):
        """
        If a :class:`ToasterBox` already exists, move the new one above the existing one.

        :param `tb`: another instance of :class:`ToasterBox`.
        """

        # recalc where to place this popup
 
        self._tb.SetPopupPosition((self._popupposition[0], self._popupposition[1] -
                                   self._popupsize[1]*len(winlist)))


    def GetToasterBoxWindow(self):
        """ Returns the :class:`ToasterBox` frame. """

        return self._tb


    def SetTitle(self, title):
        """
        Sets the :class:`ToasterBox` title if it was created with ``TB_CAPTION`` window style.

        :param `title`: the :class:`ToasterBox` caption title.        
        """

        self._tb.SetTitle(title)


    def SetUseFocus(self, focus):
        """
        If `focus` is ``True``, Instructs :class:`ToasterBox` to steal the focus from the
        parent application, otherwise it returns the focus to the original owner.

        :param `focus`: ``True`` to set the focus on :class:`ToasterBox`, ``False`` to
         return it to the original owner.
        """

        self._usefocus = focus


    def GetUseFocus(self):
        """ Returns whether :class:`ToasterBox` will steal the focus from the parent application. """

        return self._usefocus
    

    def Notify(self):
        """ It's time to hide a :class:`ToasterBox`. """

        if len(winlist) == 0:
            return

        # clean the window list
        self.CleanList()

        # figure out how many blanks we have
        try:
            node = winlist[0]
        except:
            return

        if not node:
            return

        self._startPos = node.GetPosition()[1]
        self._moveTimer.Start(self._sleeptime)


    def OnMoveTimer(self, event):
        """
        Handles the ``wx.EVT_TIMER`` event for :class:`ToasterBox`, moving the new window
        on top of the last one created.

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

        current = self._startPos
        if current >= self._popupposition[1]:
            self._moveTimer.Stop()

        # move windows to fill in blank space
        
        if current > self._popupposition[1]:
            current = self._popupposition[1]

        # loop through all the windows
        for j in xrange(0, len(winlist)):
            ourNewHeight = current - (j*self._popupsize[1] - 8)
            tmpTb = winlist[j]
            # reset where the object THINKS its supposed to be
            tmpTb.SetPopupPosition((self._popupposition[0], ourNewHeight))
            # actually move it
            tmpTb.SetDimensions(self._popupposition[0], ourNewHeight, tmpTb.GetSize().GetWidth(),
                                tmpTb.GetSize().GetHeight())

        self._startPos += 4
        

    def CleanList(self):
        """ Cleans the window list, erasing the stack of :class:`ToasterBox` objects. """

        if len(winlist) == 0:
            return

        node = winlist[0]
        while node:
            if not node.IsShown():
                winlist.remove(node)
                node.Close()
                try:
                    node = winlist[0]
                except:
                    node = 0
            else:
                indx = winlist.index(node)
                try:
                    node = winlist[indx+1]
                except:
                    node = 0


# ------------------------------------------------------------------------------ #
# Class ToasterBoxWindow
#    This Class Does All The Job, By Handling Background Images, Text Properties
#    And Panel Adding. Depending On The Style You Choose, ToasterBoxWindow Will
#    Behave Differently In Order To Handle Widgets Inside It.
# ------------------------------------------------------------------------------ #

class ToasterBoxWindow(wx.Frame):
    """
    This class does all the job, by handling background images, text properties
    and panel adding. Depending on the style you choose, :class:`ToasterBoxWindow` will
    behave differently in order to handle widgets inside it.
    """
    
    def __init__(self, parent, parent2, tbstyle, windowstyle, closingstyle,
                 scrollType=TB_SCR_TYPE_DU):
        """
        Default class constructor.
        Used internally. Do not call directly this class in your application!

        :param `parent`: the window parent;
        :param `parent2`: the :class:`ToasterBox` calling this window;
        :param `tbstyle`: the :class:`ToasterBoxWindow` main style. Can be one of the following
         bits:

         ====================== ======= ================================
         `ToasterBox` Style      Value  Description
         ====================== ======= ================================
         ``TB_SIMPLE``              0x1 A simple :class:`ToasterBox`, with background image and text customization can be created
         ``TB_COMPLEX``             0x2 `ToasterBoxes` with different degree of complexity can be created. You can add as many controls as you want, provided that you call the :meth:`~ToasterBoxWindow.AddPanel` method and pass to it a dummy frame and a :class:`Panel`.
         ====================== ======= ================================

        :param `windowstyle`: this parameter influences the visual appearance of
         :class:`ToasterBoxWindow`, and can be one of the following styles:

         ====================== ========== ================================
         Window Style           Hex Value  Description
         ====================== ========== ================================
         ``TB_DEFAULT_STYLE``   0x2008002  Default window style for :class:`ToasterBox`, with no caption nor close box.
         ``TB_CAPTION``         0x22009806 :class:`ToasterBox` will have a caption, with the possibility to set a title for the :class:`ToasterBox` frame, and a close box.
         ====================== ========== ================================
       
        :param `closingstyle`: the closing style for :class:`ToasterBoxWindow`. Can be one of the
         following bits:

         ==================== =========== ==================================================
         Closing Styles       Hex Value   Description
         ==================== =========== ==================================================
         ``TB_ONTIME``                0x1 :class:`ToasterBox` will close after a specified amount of time.
         ``TB_ONCLICK``               0x2 :class:`ToasterBox` can be closed by clicking anywhere on the :class:`ToasterBox` frame.
         ==================== =========== ==================================================

        :param `scrollType`: the scrolling direction for :class:`ToasterBoxWindow`. Can be one of the
         following bits:

         ==================== =========== ==================================================
         Scroll Styles        Hex Value   Description
         ==================== =========== ==================================================
         ``TB_SCR_TYPE_UD``           0x1 :class:`ToasterBox` will scroll from up to down
         ``TB_SCR_TYPE_DU``           0x2 :class:`ToasterBox` will scroll from down to up
         ``TB_SCR_TYPE_FADE``         0x4 :class:`ToasterBox` will fade in/out (without scrolling).
         ==================== =========== ==================================================

        """

        wx.Frame.__init__(self, parent, wx.ID_ANY, "window", wx.DefaultPosition,
                         wx.DefaultSize, style=windowstyle | wx.CLIP_CHILDREN)

        self._starttime = wx.GetLocalTime()
        self._parent2 = parent2
        self._parent = parent
        self._sleeptime = 10
        self._step = 4
        self._pausetime = 1700
        self._textcolour = wx.BLACK
        self._popuptext = "Change Me!"
        # the size we want the dialog to be
        framesize = wx.Size(150, 170)
        self._count = 1
        self._tbstyle = tbstyle
        self._windowstyle = windowstyle
        self._closingstyle = closingstyle
        self._backgroundcolour = wx.WHITE

        if tbstyle == TB_COMPLEX:
            self.sizer = wx.BoxSizer(wx.VERTICAL)
        else:
            self._staticbitmap = None

        if self._windowstyle == TB_CAPTION:
            self.Bind(wx.EVT_CLOSE, self.OnClose)
            self.SetTitle("")

        if scrollType == TB_SCR_TYPE_FADE and not self.CanSetTransparent():
            import warnings
            warnings.warn("The style ``TB_SCR_TYPE_FADE`` is not supported on this platform.")
            scrollType = TB_SCR_TYPE_DU
            
        self._scrollType = scrollType

        if self._closingstyle & TB_ONCLICK and self._windowstyle != TB_CAPTION:
            self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)

        self._bottomright = wx.Point(wx.GetDisplaySize().GetWidth(),
                                     wx.GetDisplaySize().GetHeight())

        self.SetDimensions(self._bottomright.x, self._bottomright.y,
                           framesize.GetWidth(), framesize.GetHeight())

        self._scrollTimer = wx.Timer(self, -1)
        self._alphaTimer = wx.Timer(self, -1)
        
        self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self._scrollTimer)
        self.Bind(wx.EVT_TIMER, self.AlphaCycle, self._alphaTimer)

        if not self._tbstyle & TB_COMPLEX:
            self.Bind(wx.EVT_PAINT, self.OnPaint)
            self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
        

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

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

        self.NotifyTimer(None)
        event.Skip()


    def OnMouseDown(self, event):
        """
        Handles the ``wx.EVT_LEFT_DOWN`` event for :class:`ToasterBoxWindow`.

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

        self.NotifyTimer(None)
        event.Skip()


    def SetPopupBitmap(self, bitmap=None):
        """
        Sets the :class:`ToasterBox` background image.

        :param `bitmap`: a valid :class:`Bitmap` object. If defaulted to ``None``, then
         no background bitmap is used.
         
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.
        """

        if bitmap is None:
            self._staticbitmap = None
        else:
            bitmap = bitmap.ConvertToImage()
            xsize, ysize = self.GetSize()
            bitmap = bitmap.Scale(xsize, ysize)
            self._staticbitmap = bitmap.ConvertToBitmap()


    def SetPopupSize(self, size):
        """
        Sets the :class:`ToasterBox` size.

        :param `size`: the new control size, an instance of :class:`Size`.        
        """

        self.SetDimensions(self._bottomright.x, self._bottomright.y, size[0], size[1])


    def SetPopupPosition(self, pos):
        """
        Sets the :class:`ToasterBox` position on screen.

        :param `pos`: the widget position, an instance of :class:`Point`.        
        """

        self._bottomright = wx.Point(pos[0] + self.GetSize().GetWidth(),
                                     pos[1] + self.GetSize().GetHeight())
        self._dialogtop = pos


    def SetPopupPositionByInt(self, pos):
        """
        Sets the :class:`ToasterBox` position on screen, at one of the screen corners.
 
        :param `pos`: an integer specifying the screen corner, namely:

         ============= ========================================
         Corner Number Position
         ============= ========================================
               0       Top left screen corner
               1       Top right screen corner
               2       Bottom left screen corner
               3       Bottom right screen corner
         ============= ========================================
         
        """

        w, h = wx.GetDisplaySize()
        self._bottomright = wx.Point(w, h)

        # top left
        if pos == 0:
            popupposition = wx.Point(0, 0)
        # top right
        elif pos == 1:
            popupposition = wx.Point(w - self._popupsize[0], 0)
        # bottom left
        elif pos == 2:
           popupposition = wx.Point(0, h - self._popupsize[1])
         # bottom right
        elif pos == 3:
            popupposition = wx.Point(self._bottomright.x - self._popupsize[0],
                                     self._bottomright.y - self._popupsize[1])

        self._bottomright = wx.Point(popupposition.x + self._popupsize[0],
                                     popupposition.y + self._popupsize[1])

        self._dialogtop = popupposition


    def SetPopupPauseTime(self, pausetime):
        """
        Sets the time after which the :class:`ToasterBox` is destroyed (linger).

        :param `pausetime`: the delay after which the control is destroyed, in seconds.
        """

        self._pausetime = pausetime


    def SetPopupScrollSpeed(self, speed):
        """
        Sets the :class:`ToasterBox` scroll speed.

        :param `speed`: it is the pause time (in milliseconds) for every step in the
         :meth:`~ToasterBoxWindow.ScrollUp` method.
        """

        self._sleeptime = speed


    def AddPanel(self, panel):
        """
        Adds a panel to the :class:`ToasterBox`.

        :param `panel`: an instance of :class:`Window`.
        
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_COMPLEX`` style.
        """

        if not self._tbstyle & TB_COMPLEX:
            raise Exception("\nERROR: Panel Can Not Be Added When Using TB_SIMPLE ToasterBox Style")

        self.sizer.Add(panel, 1, wx.EXPAND)
        self.SetSizer(self.sizer)
        self.Layout()
        
        if self._closingstyle & TB_ONCLICK and self._windowstyle != TB_CAPTION:
            panel.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)


    def SetPopupText(self, text):
        """
        Sets the :class:`ToasterBox` text label.

        :param `text`: the widget label.
         
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.
        """

        self._popuptext = text


    def SetPopupTextFont(self, font):
        """
        Sets the :class:`ToasterBox` text font.

        :param `colour`: a valid :class:`Colour` object. If defaulted to ``None``, then
         a simple generic font will be generated.
         
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.
        """

        self._textfont = font


    def GetPopupText(self):
        """
        Returns the :class:`ToasterBox` text.

        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.       
        """

        return self._popuptext


    def Play(self):
        """ Creates the :class:`ToasterBoxWindow`, that does all the job. """

        # do some checks to make sure this window is valid
        if self._bottomright.x < 1 or self._bottomright.y < 1:
            return False

        if self.GetSize().GetWidth() < 50 or self.GetSize().GetWidth() < 50:
            # toasterbox launches into a endless loop for some reason
            # when you try to make the window too small.
            return False

        self._direction = wx.UP
        self.SetupPositions()
        self.ScrollUp()
        timerid = wx.NewId()
        self.showtime = wx.Timer(self, timerid)
        self.showtime.Start(self._pausetime)
        self.Bind(wx.EVT_TIMER, self.NotifyTimer, id=timerid)

        return True


    def NotifyTimer(self, event):
        """ Hides gradually the :class:`ToasterBoxWindow`. """

        if self._scrollType != TB_SCR_TYPE_FADE:
            self.showtime.Stop()
            del self.showtime

        self._direction = wx.DOWN
        self.SetupPositions()

        self.ScrollDown()


    def SetPopupBackgroundColour(self, colour):
        """
        Sets the :class:`ToasterBox` background colour.

        :param `colour`: a valid :class:`Colour` object. If defaulted to ``None``, then
         the background colour will be white.
         
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.
        """

        self.SetBackgroundColour(colour)
        self._backgroundcolour = colour


    def SetPopupTextColour(self, colour):
        """
        Sets the :class:`ToasterBox` foreground colour.

        :param `colour`: a valid :class:`Colour` object. If defaulted to ``None``, then
         the background colour will be black.
         
        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.
        """

        self._textcolour = colour


    def SetUseFocus(self, focus, originalfocus):
        """
        If `focus` is ``True``, Instructs :class:`ToasterBoxWindow` to steal the focus from the
        parent application, otherwise it returns the focus to the original owner.

        :param `focus`: ``True`` to set the focus on :class:`ToasterBoxWindow`, ``False`` to
         return it to the original owner;
        :param `originalfocus`: an instance of :class:`Window`, representing a pointer to
         the window which originally had the focus
        """

        self._usefocus = focus
        self._originalfocus = originalfocus


    def OnScrollTimer(self, event):
        """
        Handles the ``wx.EVT_TIMER`` event for :class:`ToasterBoxWindow` scrolling up/down.

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

        if self._direction == wx.UP:
            self.TearUp()
        else:
            self.TearDown()
            
        
    def TearUp(self):
        """ Scrolls the :class:`ToasterBox` up, which means gradually showing it. """

        self._windowsize = self._windowsize + self._step
        step = self._currentStep

        if step < self._dialogtop[1]:
            step = self._dialogtop[1]

         # checking the type of the scroll (from up to down or from down to up)
        if self._scrollType == TB_SCR_TYPE_UD:
            dimY = self._dialogtop[1]
        elif self._scrollType == TB_SCR_TYPE_DU:
            dimY = step

        self.SetDimensions(self._dialogtop[0], dimY, self.GetSize().GetWidth(), self._windowsize)

        self.Refresh(False)

        self._currentStep += self._scrollStep

        if self._currentStep not in range(self._start, self._stop, self._scrollStep):
            self._scrollTimer.Stop()
            self.Update()

            if self._tbstyle == TB_SIMPLE:
                self.DrawText()

            if self._usefocus:
                self.SetFocus()
            else:
                self._originalfocus.SetFocus()

        
    def TearDown(self):
        """ Scrolls the :class:`ToasterBox` down, which means gradually hiding it. """

        self._windowsize = self._windowsize - self._step
        step = self._currentStep

        if step > self._bottomright.y:
            step = self._bottomright.y
        
        if self._windowsize > 0:            
            # checking the type of the scroll (from up to down or from down to up)
            if self._scrollType == TB_SCR_TYPE_UD:
                dimY = self._dialogtop[1]
            elif self._scrollType == TB_SCR_TYPE_DU:
                dimY = step

            self.SetDimensions(self._dialogtop[0], dimY,
                               self.GetSize().GetWidth(), self._windowsize)

            self.Update()
            self.Refresh()

            self._currentStep += self._scrollStep
            
        else:            
            self._scrollTimer.Stop()
            self.Hide()
            if self._parent2:
                self._parent2.Notify()


    def SetupPositions(self):
        """ Sets up the position, size and scrolling step for :class:`ToasterBoxWindow`. """

        if self._scrollType == TB_SCR_TYPE_FADE:
            self.SetPosition(wx.Point(*self._dialogtop))
            return

        if self._direction == wx.UP:
            # walk the Y value up in a raise motion
            self._xpos = self.GetPosition().x
            self._ypos = self._bottomright[1]
            self._windowsize = 0

            # checking the type of the scroll (from up to down or from down to up)
            if self._scrollType == TB_SCR_TYPE_UD:
                self._start = self._dialogtop[1]
                self._stop = self._ypos
                self._scrollStep = self._step
            elif self._scrollType == TB_SCR_TYPE_DU:
                self._start = self._ypos
                self._stop = self._dialogtop[1]
                self._scrollStep = -self._step

        else:

            # walk down the Y value
            self._windowsize = self.GetSize().GetHeight()

            # checking the type of the scroll (from up to down or from down to up)
            if self._scrollType == TB_SCR_TYPE_UD:
                self._start = self._bottomright.y
                self._stop = self._dialogtop[1]
                self._scrollStep = -self._step
            elif self._scrollType == TB_SCR_TYPE_DU:
                self._start = self._dialogtop[1]
                self._stop = self._bottomright.y
                self._scrollStep = self._step

        self._currentStep = self._start


    def ScrollUp(self):
        """ Scrolls the :class:`ToasterBox` up, which means gradually showing it. """

        if self._scrollType == TB_SCR_TYPE_FADE:
            self._amount = 0
            self._delta = 5
            self.SetSize(self.GetSize())
            self._alphaTimer.Start(self._sleeptime)
        else:
            self.Show(True)
            self._scrollTimer.Start(self._sleeptime)
    

    def ScrollDown(self):
        """ Scrolls the :class:`ToasterBox` down, which means gradually hiding it. """

        if self._scrollType == TB_SCR_TYPE_FADE:
            self._amount = 255
            self._delta = -5
            self._alphaTimer.Start(self._sleeptime)
        else:
            self._scrollTimer.Start(self._sleeptime)


    def OnPaint(self, event):
        """
        Handles the ``wx.EVT_PAINT`` event for :class:`ToasterBoxWindow`.

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

        :note: This event is handled and processed only if the style ``TB_SIMPLE`` is
         given to :class:`ToasterBox`.
        """
        
        dc = wx.AutoBufferedPaintDC(self)
        self.DrawText(dc)
        
            
    def DrawText(self, dc=None):
        """
        Draws the text label for a :class:`ToasterBox` with ``TB_SIMPLE`` style set.

        :param `dc`: an instance of :class:`DC`. If defaulted to ``None``, a :class:`ClientDC`
         will be created on the fly.
        """
      
        if dc is None:
            dc = wx.ClientDC(self)
           
        dc.SetBackground(wx.Brush(self._backgroundcolour))
        dc.Clear()
        
        if self._staticbitmap:
            dc.DrawBitmap(self._staticbitmap, 0, 0)
        dc.SetFont(self._textfont)
        dc.SetTextForeground(self._textcolour)
        
        if not hasattr(self, "text_coords"):
            self._getTextCoords(dc)
        dc.DrawTextList(*self.text_coords)


    def AlphaCycle(self, event):
        """
        Handles the ``wx.EVT_TIMER`` event for :class:`ToasterBoxWindow`.

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

        # Increase (or decrease) the alpha channel
        self._amount += self._delta

        if self._tbstyle == TB_SIMPLE:
            self.Refresh(False)
            
        if self._amount > 255 or self._amount < 0:
            # We're done, stop the timer
            self._alphaTimer.Stop()

            if self._amount < 0:
                self.Hide()
                if self._parent2:
                    self._parent2.Notify()

            elif self._amount > 255:
                if self._usefocus:
                    self.SetFocus()
                else:
                    self._originalfocus.SetFocus()

            return

        # Make the ToasterBoxWindow more or less transparent
        self.MakeWindowTransparent(self._amount)
        if not self.IsShown():
            self.Show()
            

    def MakeWindowTransparent(self, amount):
        """
        Makes the :class:`ToasterBoxWindow` window transparent.

        :param `amount`: the alpha channel value.
        """

        if not self.CanSetTransparent():
            return
        
        self.SetTransparent(amount)
        

    def _getTextCoords(self, dc):
        """
        Draw the user specified text.

        :param `dc`: an instance of :class:`DC`.

        :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style.        
        """

        # border from sides and top to text (in pixels)
        border = 7
        # how much space between text lines
        textPadding = 2

        pText = self.GetPopupText()

        max_len = len(pText)

        tw, th = self._parent2._popupsize

        if self._windowstyle == TB_CAPTION:
            th = th - 20

        while 1:
            lines = textwrap.wrap(pText, max_len)

            for line in lines:
                w, h = dc.GetTextExtent(line)
                if w > tw - border * 2:
                    max_len -= 1
                    break
            else:
                break

        fh = 0
        for line in lines:
            w, h = dc.GetTextExtent(line)
            fh += h + textPadding
        y = (th - fh) / 2; coords = []

        for line in lines:
            w, h = dc.GetTextExtent(line)
            x = (tw - w) / 2
            coords.append((x, y))
            y += h + textPadding

        self.text_coords = (lines, coords)
