# --------------------------------------------------------------------------- # # TOASTERBOX wxPython IMPLEMENTATION # Ported And Enhanced From wxWidgets Contribution (Aj Bommarito) By: # # Andrea Gavana, @ 16 September 2005 # Latest Revision: 27 Dec 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!!! # # Tags: phoenix-port, documented, unittest, py3-port # # 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 @ 27 Dec 2012, 21.00 GMT Version 0.3 """ import textwrap import time 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 colour = wx.Colour(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 colour = wx.Colour(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: 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 range(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.SetSize(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 = int(time.time()) 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.SetSize(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.SetSize(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.SetSize(self._dialogtop[0], dimY, self.GetSize().GetWidth(), self._windowsize) self.Refresh(False) self._currentStep += self._scrollStep if self._currentStep not in list(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.SetSize(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)