1# --------------------------------------------------------------------------- # 2# TOASTERBOX wxPython IMPLEMENTATION 3# Ported And Enhanced From wxWidgets Contribution (Aj Bommarito) By: 4# 5# Andrea Gavana, @ 16 September 2005 6# Latest Revision: 27 Dec 2012, 21.00 GMT 7# 8# 9# TODO/Caveats List 10# 11# 1. Any Idea? 12# 13# 14# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please 15# Write To Me At: 16# 17# andrea.gavana@gmail.com 18# andrea.gavana@maerskoil.com 19# 20# Or, Obviously, To The wxPython Mailing List!!! 21# 22# Tags: phoenix-port, documented, unittest, py3-port 23# 24# End Of Comments 25# --------------------------------------------------------------------------- # 26 27 28""" 29:class:`~wx.lib.agw.toasterbox.ToasterBox` is a cross-platform widget to make the creation of MSN style "toaster" 30popups easier. 31 32 33Description 34=========== 35 36ToasterBox is a cross-platform widget to make the creation of MSN style "toaster" 37popups easier. The syntax is really easy especially if you are familiar with the 38syntax of wxPython. 39 40It has 2 main styles: 41 42- ``TB_SIMPLE``: using this style, you will be able to specify a background image for 43 ToasterBox, text properties as text colour, font and label; 44 45- ``TB_COMPLEX``: this style will allow you to put almost any control inside a 46 ToasterBox. You can add a panel in which you can put all the controls you like. 47 48Both styles support the setting of ToasterBox position (on screen coordinates), 49size, the time after which the ToasterBox is destroyed (linger), and the scroll 50speed of ToasterBox. 51 52 53Usage 54===== 55 56Usage example:: 57 58 import wx 59 import wx.lib.agw.toasterbox as TB 60 61 class MyFrame(wx.Frame): 62 63 def __init__(self, parent): 64 65 wx.Frame.__init__(self, parent, -1, "ToasterBox Demo") 66 67 toaster = TB.ToasterBox(self, tbstyle=TB.TB_COMPLEX) 68 toaster.SetPopupPauseTime(3000) 69 70 tbpanel = toaster.GetToasterBoxWindow() 71 panel = wx.Panel(tbpanel, -1) 72 sizer = wx.BoxSizer(wx.VERTICAL) 73 74 button = wx.Button(panel, wx.ID_ANY, "Simple button") 75 sizer.Add(button, 0, wx.EXPAND) 76 77 panel.SetSizer(sizer) 78 toaster.AddPanel(panel) 79 80 wx.CallLater(1000, toaster.Play) 81 82 83 # our normal wxApp-derived class, as usual 84 85 app = wx.App(0) 86 87 frame = MyFrame(None) 88 app.SetTopWindow(frame) 89 frame.Show() 90 91 app.MainLoop() 92 93 94 95Supported Platforms 96=================== 97 98ToasterBox has been tested on the following platforms: 99 100- Windows (verified on Windows XP, 2000) 101- Linux 102- Mac 103 104 105Window Styles 106============= 107 108This class supports the following window styles: 109 110==================== =========== ================================================== 111Window Styles Hex Value Description 112==================== =========== ================================================== 113``TB_SIMPLE`` 0x1 A simple `ToasterBox`, with background image and text customization can be created. 114``TB_ONTIME`` 0x1 `ToasterBox` will close after a specified amount of time. 115``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. 116``TB_ONCLICK`` 0x2 `ToasterBox` can be closed by clicking anywhere on the `ToasterBox` frame. 117``TB_DEFAULT_STYLE`` 0x2008002 Default window style for `ToasterBox`, with no caption nor close box. 118``TB_CAPTION`` 0x22009806 `ToasterBox` will have a caption, with the possibility to set a title for the `ToasterBox` frame, and a close box. 119==================== =========== ================================================== 120 121 122Events Processing 123================= 124 125`No custom events are available for this class.` 126 127 128License And Version 129=================== 130 131ToasterBox is distributed under the wxPython license. 132 133Latest revision: Andrea Gavana @ 27 Dec 2012, 21.00 GMT 134 135Version 0.3 136 137""" 138 139import textwrap 140import time 141import wx 142 143 144# Define Window List, We Use It Globally 145winlist = [] 146""" Globally defined window list. """ 147 148TB_SIMPLE = 1 149""" A simple ToasterBox, with background image and text customization can be created. """ 150TB_COMPLEX = 2 151""" 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. """ 152TB_DEFAULT_STYLE = wx.SIMPLE_BORDER | wx.STAY_ON_TOP | wx.FRAME_NO_TASKBAR 153""" Default window style for `ToasterBox`, with no caption nor close box. """ 154TB_CAPTION = TB_DEFAULT_STYLE | wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.FRAME_NO_TASKBAR 155""" `ToasterBox` will have a caption, with the possibility to set a title for the `ToasterBox` frame, and a close box. """ 156TB_ONTIME = 1 157""" `ToasterBox` will close after a specified amount of time. """ 158TB_ONCLICK = 2 159""" `ToasterBox` can be closed by clicking anywhere on the `ToasterBox` frame. """ 160 161# scroll from up to down 162TB_SCR_TYPE_UD = 1 163""" Scroll from up to down. """ 164# scroll from down to up 165TB_SCR_TYPE_DU = 2 166""" Scroll from down to up. """ 167# fade in/out 168TB_SCR_TYPE_FADE = 4 169""" Fade in and out. """ 170 171 172# ------------------------------------------------------------------------------ # 173# Class ToasterBox 174# Main Class Implementation. It Is Basically A wx.Timer. It Creates And 175# Displays Popups And Handles The "Stacking". 176# ------------------------------------------------------------------------------ # 177 178class ToasterBox(wx.Timer): 179 """ 180 ToasterBox is a cross-platform widget to make the creation of MSN style "toaster" 181 popups easier. 182 """ 183 184 def __init__(self, parent, tbstyle=TB_SIMPLE, windowstyle=TB_DEFAULT_STYLE, 185 closingstyle=TB_ONTIME, scrollType=TB_SCR_TYPE_DU): 186 """ 187 Default class constructor. 188 189 :param `parent`: the window parent; 190 :param `tbstyle`: the :class:`ToasterBox` main style. Can be one of the following 191 bits: 192 193 ====================== ======= ================================ 194 `ToasterBox` Style Value Description 195 ====================== ======= ================================ 196 ``TB_SIMPLE`` 0x1 A simple :class:`ToasterBox`, with background image and text customization can be created 197 ``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`. 198 ====================== ======= ================================ 199 200 :param `windowstyle`: this parameter influences the visual appearance of 201 :class:`ToasterBox`, and can be one of the following styles: 202 203 ====================== ========== ================================ 204 Window Style Hex Value Description 205 ====================== ========== ================================ 206 ``TB_DEFAULT_STYLE`` 0x2008002 Default window style for :class:`ToasterBox`, with no caption nor close box. 207 ``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. 208 ====================== ========== ================================ 209 210 :param `closingstyle`: the closing style for :class:`ToasterBox`. Can be one of the 211 following bits: 212 213 ==================== =========== ================================================== 214 Closing Styles Hex Value Description 215 ==================== =========== ================================================== 216 ``TB_ONTIME`` 0x1 :class:`ToasterBox` will close after a specified amount of time. 217 ``TB_ONCLICK`` 0x2 :class:`ToasterBox` can be closed by clicking anywhere on the :class:`ToasterBox` frame. 218 ==================== =========== ================================================== 219 220 :param `scrollType`: the scrolling direction for :class:`ToasterBox`. Can be one of the 221 following bits: 222 223 ==================== =========== ================================================== 224 Scroll Styles Hex Value Description 225 ==================== =========== ================================================== 226 ``TB_SCR_TYPE_UD`` 0x1 :class:`ToasterBox` will scroll from up to down 227 ``TB_SCR_TYPE_DU`` 0x2 :class:`ToasterBox` will scroll from down to up 228 ``TB_SCR_TYPE_FADE`` 0x4 :class:`ToasterBox` will fade in/out (without scrolling). 229 ==================== =========== ================================================== 230 231 """ 232 233 self._parent = parent 234 self._sleeptime = 10 235 self._pausetime = 1700 236 self._popuptext = "default" 237 self._popupposition = wx.Point(100,100) 238 self._popuptop = wx.Point(0,0) 239 self._popupsize = wx.Size(150, 170) 240 self._usefocus = True 241 self._originalfocus = wx.Window.FindFocus() 242 243 self._backgroundcolour = wx.WHITE 244 self._foregroundcolour = wx.BLACK 245 self._textfont = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, "Verdana") 246 247 self._bitmap = None 248 249 self._tbstyle = tbstyle 250 self._windowstyle = windowstyle 251 self._closingstyle = closingstyle 252 self._scrollType = scrollType 253 254 self._panel = None 255 256 self._bottomright = wx.Point(wx.GetDisplaySize().GetWidth(), 257 wx.GetDisplaySize().GetHeight()) 258 259 if parent is not None: 260 parent.Bind(wx.EVT_ICONIZE, lambda evt: [w.Hide() for w in winlist]) 261 self._moveTimer = wx.Timer(parent, -1) 262 parent.Bind(wx.EVT_TIMER, self.OnMoveTimer, self._moveTimer) 263 264 self._tb = ToasterBoxWindow(self._parent, self, self._tbstyle, self._windowstyle, 265 self._closingstyle, scrollType=self._scrollType) 266 267 def SetPopupPosition(self, pos): 268 """ 269 Sets the :class:`ToasterBox` position on screen. 270 271 :param `pos`: the widget position, an instance of :class:`wx.Point`. 272 """ 273 274 self._popupposition = pos 275 276 277 def SetPopupPositionByInt(self, pos): 278 """ 279 Sets the :class:`ToasterBox` position on screen, at one of the screen corners. 280 281 :param `pos`: an integer specifying the screen corner, namely: 282 283 ============= ======================================== 284 Corner Number Position 285 ============= ======================================== 286 0 Top left screen corner 287 1 Top right screen corner 288 2 Bottom left screen corner 289 3 Bottom right screen corner 290 ============= ======================================== 291 292 """ 293 294 w, h = wx.GetDisplaySize() 295 self._bottomright = wx.Point(w, h) 296 297 # top left 298 if pos == 0: 299 popupposition = wx.Point(0,0) 300 # top right 301 elif pos == 1: 302 popupposition = wx.Point(w - self._popupsize[0], 0) 303 # bottom left 304 elif pos == 2: 305 popupposition = wx.Point(0, h - self._popupsize[1]) 306 # bottom right 307 elif pos == 3: 308 popupposition = wx.Point(self._bottomright.x - self._popupsize[0], 309 self._bottomright.y - self._popupsize[1]) 310 311 self._bottomright = wx.Point(popupposition.x + self._popupsize[0], 312 popupposition.y + self._popupsize[1]) 313 314 self._popupposition = popupposition 315 316 317 def CenterOnParent(self, direction=wx.BOTH): 318 """ 319 Centres the window on its parent (if any). If the :class:`ToasterBox` parent is ``None``, 320 it calls :meth:`~ToasterBox.CenterOnScreen`. 321 322 :param `direction`: specifies the direction for the centering. May be ``wx.HORIZONTAL``, 323 ``wx.VERTICAL`` or ``wx.BOTH``. 324 325 :note: This methods provides for a way to center :class:`ToasterBox` over their parents instead of the 326 entire screen. If there is no parent, then behaviour is the same as :meth:`~ToasterBox.CenterOnScreen`. 327 328 :see: :meth:`~ToasterBox.CenterOnScreen`. 329 """ 330 331 if not self._parent: 332 self.CenterOnScreen(direction) 333 return 334 335 parent = self._parent 336 screenrect = parent.GetScreenRect() 337 toast_width, toast_height = self._popupsize 338 x, y = screenrect.GetX(), screenrect.GetY() 339 width, height = screenrect.GetWidth(), screenrect.GetHeight() 340 341 if direction == wx.VERTICAL: 342 pos = wx.Point(x, (y + (height/2) - (toast_height/2))) 343 elif direction == wx.HORIZONTAL: 344 pos = wx.Point((x + (width/2) - (toast_width/2)), y) 345 else: 346 pos = wx.Point((x + (width/2) - (toast_width/2)), (y + (height/2) - (toast_height/2))) 347 348 self.SetPopupPosition(pos) 349 350 351 CentreOnParent = CenterOnParent 352 353 354 def CenterOnScreen(self, direction=wx.BOTH): 355 """ 356 Centres the :class:`ToasterBox` on screen. 357 358 :param `direction`: specifies the direction for the centering. May be ``wx.HORIZONTAL``, 359 ``wx.VERTICAL`` or ``wx.BOTH``. 360 361 :see: :meth:`~ToasterBox.CenterOnParent`. 362 """ 363 364 screenSize = wx.GetDisplaySize() 365 toast_width, toast_height = self._popupsize 366 width, height = screenSize.GetWidth(), screenSize.GetHeight() 367 368 if direction == wx.VERTICAL: 369 pos = wx.Point(0, (height/2) - (toast_height/2)) 370 elif direction == wx.HORIZONTAL: 371 pos = wx.Point((width/2) - (toast_width/2), 0) 372 else: 373 pos = wx.Point((width/2) - (toast_width/2), (height/2) - (toast_height/2)) 374 375 self.SetPopupPosition(pos) 376 377 378 CentreOnScreen = CenterOnScreen 379 380 381 def SetPopupBackgroundColour(self, colour=None): 382 """ 383 Sets the :class:`ToasterBox` background colour. 384 385 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, then 386 the background colour will be white. 387 388 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 389 """ 390 391 if colour is None: 392 colour = wx.WHITE 393 394 colour = wx.Colour(colour) 395 self._backgroundcolour = colour 396 self._tb.SetPopupBackgroundColour(self._backgroundcolour) 397 398 399 def SetPopupTextColour(self, colour=None): 400 """ 401 Sets the :class:`ToasterBox` foreground colour. 402 403 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, then 404 the background colour will be black. 405 406 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 407 """ 408 409 if colour is None: 410 colour = wx.BLACK 411 412 colour = wx.Colour(colour) 413 self._foregroundcolour = colour 414 415 416 def SetPopupTextFont(self, font=None): 417 """ 418 Sets the :class:`ToasterBox` text font. 419 420 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, then 421 a simple generic font will be generated. 422 423 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 424 """ 425 426 if font is None: 427 font = wx.Font(8, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False) 428 429 self._textfont = font 430 431 432 def SetPopupSize(self, size): 433 """ 434 Sets the :class:`ToasterBox` size. 435 436 :param `size`: the new control size, an instance of :class:`wx.Size`. 437 """ 438 439 self._popupsize = size 440 441 442 def SetPopupPauseTime(self, pausetime): 443 """ 444 Sets the time after which the :class:`ToasterBox` is destroyed (linger). 445 446 :param `pausetime`: the delay after which the control is destroyed, in seconds. 447 """ 448 449 self._pausetime = pausetime 450 451 452 def SetPopupBitmap(self, bitmap=None): 453 """ 454 Sets the :class:`ToasterBox` background image. 455 456 :param `bitmap`: a valid :class:`wx.Bitmap` object or filename. If defaulted 457 to ``None``, then no background bitmap is used. 458 459 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 460 """ 461 462 if bitmap is not None: 463 bitmap = wx.Bitmap(bitmap) 464 465 self._bitmap = bitmap 466 467 468 def SetPopupScrollSpeed(self, speed): 469 """ 470 Sets the :class:`ToasterBox` scroll speed. 471 472 :param `speed`: it is the pause time (in milliseconds) for every step in the 473 `ScrollUp` method. 474 """ 475 476 self._sleeptime = speed 477 478 479 def SetPopupText(self, text): 480 """ 481 Sets the :class:`ToasterBox` text label. 482 483 :param `text`: the widget label. 484 485 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 486 """ 487 488 self._popuptext = text 489 490 491 def AddPanel(self, panel): 492 """ 493 Adds a panel to the :class:`ToasterBox`. 494 495 :param `panel`: an instance of :class:`wx.Window`. 496 497 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_COMPLEX`` style. 498 """ 499 500 if not self._tbstyle & TB_COMPLEX: 501 raise Exception("\nERROR: Panel Can Not Be Added When Using TB_SIMPLE ToasterBox Style") 502 503 self._panel = panel 504 505 506 def Play(self): 507 """ Creates the :class:`ToasterBoxWindow`, that does all the job. """ 508 509 # create new window 510 self._tb.SetPopupSize((self._popupsize[0], self._popupsize[1])) 511 self._tb.SetPopupPosition((self._popupposition[0], self._popupposition[1])) 512 self._tb.SetPopupPauseTime(self._pausetime) 513 self._tb.SetPopupScrollSpeed(self._sleeptime) 514 self._tb.SetUseFocus(self._usefocus, self._originalfocus) 515 516 if self._tbstyle == TB_SIMPLE: 517 self._tb.SetPopupTextColour(self._foregroundcolour) 518 self._tb.SetPopupBackgroundColour(self._backgroundcolour) 519 self._tb.SetPopupTextFont(self._textfont) 520 521 if self._bitmap is not None: 522 self._tb.SetPopupBitmap(self._bitmap) 523 524 self._tb.SetPopupText(self._popuptext) 525 526 if self._tbstyle == TB_COMPLEX: 527 if self._panel is not None: 528 self._tb.AddPanel(self._panel) 529 530 # clean up the list 531 self.CleanList() 532 533 # check to see if there is already a window displayed 534 # by looking at the linked list 535 if len(winlist) > 0: 536 # there ARE other windows displayed already 537 # reclac where it should display 538 self.MoveAbove(self._tb) 539 540 # shift new window on to the list 541 winlist.append(self._tb) 542 543 if not self._tb.Play(): 544 # if we didn't show the window properly, remove it from the list 545 winlist.remove(winlist[-1]) 546 # delete the object too 547 self._tb.Destroy() 548 return 549 550 551 def MoveAbove(self, tb): 552 """ 553 If a :class:`ToasterBox` already exists, move the new one above the existing one. 554 555 :param `tb`: another instance of :class:`ToasterBox`. 556 """ 557 558 # recalc where to place this popup 559 560 self._tb.SetPopupPosition((self._popupposition[0], self._popupposition[1] - 561 self._popupsize[1]*len(winlist))) 562 563 564 def GetToasterBoxWindow(self): 565 """ Returns the :class:`ToasterBox` frame. """ 566 567 return self._tb 568 569 570 def SetTitle(self, title): 571 """ 572 Sets the :class:`ToasterBox` title if it was created with ``TB_CAPTION`` window style. 573 574 :param `title`: the :class:`ToasterBox` caption title. 575 """ 576 577 self._tb.SetTitle(title) 578 579 580 def SetUseFocus(self, focus): 581 """ 582 If `focus` is ``True``, Instructs :class:`ToasterBox` to steal the focus from the 583 parent application, otherwise it returns the focus to the original owner. 584 585 :param `focus`: ``True`` to set the focus on :class:`ToasterBox`, ``False`` to 586 return it to the original owner. 587 """ 588 589 self._usefocus = focus 590 591 592 def GetUseFocus(self): 593 """ Returns whether :class:`ToasterBox` will steal the focus from the parent application. """ 594 595 return self._usefocus 596 597 598 def Notify(self): 599 """ It's time to hide a :class:`ToasterBox`. """ 600 601 if len(winlist) == 0: 602 return 603 604 # clean the window list 605 self.CleanList() 606 607 # figure out how many blanks we have 608 try: 609 node = winlist[0] 610 except: 611 return 612 613 if not node: 614 return 615 616 self._startPos = node.GetPosition()[1] 617 self._moveTimer.Start(self._sleeptime) 618 619 620 def OnMoveTimer(self, event): 621 """ 622 Handles the ``wx.EVT_TIMER`` event for :class:`ToasterBox`, moving the new window 623 on top of the last one created. 624 625 :param `event`: a :class:`TimerEvent` event to be processed. 626 """ 627 628 current = self._startPos 629 if current >= self._popupposition[1]: 630 self._moveTimer.Stop() 631 632 # move windows to fill in blank space 633 634 if current > self._popupposition[1]: 635 current = self._popupposition[1] 636 637 # loop through all the windows 638 for j in range(0, len(winlist)): 639 ourNewHeight = current - (j*self._popupsize[1] - 8) 640 tmpTb = winlist[j] 641 # reset where the object THINKS its supposed to be 642 tmpTb.SetPopupPosition((self._popupposition[0], ourNewHeight)) 643 # actually move it 644 tmpTb.SetSize(self._popupposition[0], ourNewHeight, tmpTb.GetSize().GetWidth(), 645 tmpTb.GetSize().GetHeight()) 646 647 self._startPos += 4 648 649 650 def CleanList(self): 651 """ Cleans the window list, erasing the stack of :class:`ToasterBox` objects. """ 652 653 if len(winlist) == 0: 654 return 655 656 node = winlist[0] 657 while node: 658 if not node.IsShown(): 659 winlist.remove(node) 660 node.Close() 661 try: 662 node = winlist[0] 663 except: 664 node = 0 665 else: 666 indx = winlist.index(node) 667 try: 668 node = winlist[indx+1] 669 except: 670 node = 0 671 672 673# ------------------------------------------------------------------------------ # 674# Class ToasterBoxWindow 675# This Class Does All The Job, By Handling Background Images, Text Properties 676# And Panel Adding. Depending On The Style You Choose, ToasterBoxWindow Will 677# Behave Differently In Order To Handle Widgets Inside It. 678# ------------------------------------------------------------------------------ # 679 680class ToasterBoxWindow(wx.Frame): 681 """ 682 This class does all the job, by handling background images, text properties 683 and panel adding. Depending on the style you choose, :class:`ToasterBoxWindow` will 684 behave differently in order to handle widgets inside it. 685 """ 686 687 def __init__(self, parent, parent2, tbstyle, windowstyle, closingstyle, 688 scrollType=TB_SCR_TYPE_DU): 689 """ 690 Default class constructor. 691 Used internally. Do not call directly this class in your application! 692 693 :param `parent`: the window parent; 694 :param `parent2`: the :class:`ToasterBox` calling this window; 695 :param `tbstyle`: the :class:`ToasterBoxWindow` main style. Can be one of the following 696 bits: 697 698 ====================== ======= ================================ 699 `ToasterBox` Style Value Description 700 ====================== ======= ================================ 701 ``TB_SIMPLE`` 0x1 A simple :class:`ToasterBox`, with background image and text customization can be created 702 ``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`. 703 ====================== ======= ================================ 704 705 :param `windowstyle`: this parameter influences the visual appearance of 706 :class:`ToasterBoxWindow`, and can be one of the following styles: 707 708 ====================== ========== ================================ 709 Window Style Hex Value Description 710 ====================== ========== ================================ 711 ``TB_DEFAULT_STYLE`` 0x2008002 Default window style for :class:`ToasterBox`, with no caption nor close box. 712 ``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. 713 ====================== ========== ================================ 714 715 :param `closingstyle`: the closing style for :class:`ToasterBoxWindow`. Can be one of the 716 following bits: 717 718 ==================== =========== ================================================== 719 Closing Styles Hex Value Description 720 ==================== =========== ================================================== 721 ``TB_ONTIME`` 0x1 :class:`ToasterBox` will close after a specified amount of time. 722 ``TB_ONCLICK`` 0x2 :class:`ToasterBox` can be closed by clicking anywhere on the :class:`ToasterBox` frame. 723 ==================== =========== ================================================== 724 725 :param `scrollType`: the scrolling direction for :class:`ToasterBoxWindow`. Can be one of the 726 following bits: 727 728 ==================== =========== ================================================== 729 Scroll Styles Hex Value Description 730 ==================== =========== ================================================== 731 ``TB_SCR_TYPE_UD`` 0x1 :class:`ToasterBox` will scroll from up to down 732 ``TB_SCR_TYPE_DU`` 0x2 :class:`ToasterBox` will scroll from down to up 733 ``TB_SCR_TYPE_FADE`` 0x4 :class:`ToasterBox` will fade in/out (without scrolling). 734 ==================== =========== ================================================== 735 736 """ 737 738 wx.Frame.__init__(self, parent, wx.ID_ANY, "window", wx.DefaultPosition, 739 wx.DefaultSize, style=windowstyle | wx.CLIP_CHILDREN) 740 741 self._starttime = int(time.time()) 742 self._parent2 = parent2 743 self._parent = parent 744 self._sleeptime = 10 745 self._step = 4 746 self._pausetime = 1700 747 self._textcolour = wx.BLACK 748 self._popuptext = "Change Me!" 749 # the size we want the dialog to be 750 framesize = wx.Size(150, 170) 751 self._count = 1 752 self._tbstyle = tbstyle 753 self._windowstyle = windowstyle 754 self._closingstyle = closingstyle 755 self._backgroundcolour = wx.WHITE 756 757 if tbstyle == TB_COMPLEX: 758 self.sizer = wx.BoxSizer(wx.VERTICAL) 759 else: 760 self._staticbitmap = None 761 762 if self._windowstyle == TB_CAPTION: 763 self.Bind(wx.EVT_CLOSE, self.OnClose) 764 self.SetTitle("") 765 766 if scrollType == TB_SCR_TYPE_FADE and not self.CanSetTransparent(): 767 import warnings 768 warnings.warn("The style ``TB_SCR_TYPE_FADE`` is not supported on this platform.") 769 scrollType = TB_SCR_TYPE_DU 770 771 self._scrollType = scrollType 772 773 if self._closingstyle & TB_ONCLICK and self._windowstyle != TB_CAPTION: 774 self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown) 775 776 self._bottomright = wx.Point(wx.GetDisplaySize().GetWidth(), 777 wx.GetDisplaySize().GetHeight()) 778 779 self.SetSize(self._bottomright.x, self._bottomright.y, 780 framesize.GetWidth(), framesize.GetHeight()) 781 782 self._scrollTimer = wx.Timer(self, -1) 783 self._alphaTimer = wx.Timer(self, -1) 784 785 self.Bind(wx.EVT_TIMER, self.OnScrollTimer, self._scrollTimer) 786 self.Bind(wx.EVT_TIMER, self.AlphaCycle, self._alphaTimer) 787 788 if not self._tbstyle & TB_COMPLEX: 789 self.Bind(wx.EVT_PAINT, self.OnPaint) 790 self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM) 791 792 793 def OnClose(self, event): 794 """ 795 Handles the ``wx.EVT_CLOSE`` event for :class:`ToasterBoxWindow`. 796 797 :param `event`: a :class:`CloseEvent` event to be processed. 798 """ 799 800 self.NotifyTimer(None) 801 event.Skip() 802 803 804 def OnMouseDown(self, event): 805 """ 806 Handles the ``wx.EVT_LEFT_DOWN`` event for :class:`ToasterBoxWindow`. 807 808 :param `event`: a :class:`MouseEvent` event to be processed. 809 """ 810 811 self.NotifyTimer(None) 812 event.Skip() 813 814 815 def SetPopupBitmap(self, bitmap=None): 816 """ 817 Sets the :class:`ToasterBox` background image. 818 819 :param `bitmap`: a valid :class:`wx.Bitmap` object. If defaulted to ``None``, then 820 no background bitmap is used. 821 822 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 823 """ 824 825 if bitmap is None: 826 self._staticbitmap = None 827 else: 828 bitmap = bitmap.ConvertToImage() 829 xsize, ysize = self.GetSize() 830 bitmap = bitmap.Scale(xsize, ysize) 831 self._staticbitmap = bitmap.ConvertToBitmap() 832 833 834 def SetPopupSize(self, size): 835 """ 836 Sets the :class:`ToasterBox` size. 837 838 :param `size`: the new control size, an instance of :class:`wx.Size`. 839 """ 840 841 self.SetSize(self._bottomright.x, self._bottomright.y, size[0], size[1]) 842 843 844 def SetPopupPosition(self, pos): 845 """ 846 Sets the :class:`ToasterBox` position on screen. 847 848 :param `pos`: the widget position, an instance of :class:`wx.Point`. 849 """ 850 851 self._bottomright = wx.Point(pos[0] + self.GetSize().GetWidth(), 852 pos[1] + self.GetSize().GetHeight()) 853 self._dialogtop = pos 854 855 856 def SetPopupPositionByInt(self, pos): 857 """ 858 Sets the :class:`ToasterBox` position on screen, at one of the screen corners. 859 860 :param `pos`: an integer specifying the screen corner, namely: 861 862 ============= ======================================== 863 Corner Number Position 864 ============= ======================================== 865 0 Top left screen corner 866 1 Top right screen corner 867 2 Bottom left screen corner 868 3 Bottom right screen corner 869 ============= ======================================== 870 871 """ 872 873 w, h = wx.GetDisplaySize() 874 self._bottomright = wx.Point(w, h) 875 876 # top left 877 if pos == 0: 878 popupposition = wx.Point(0, 0) 879 # top right 880 elif pos == 1: 881 popupposition = wx.Point(w - self._popupsize[0], 0) 882 # bottom left 883 elif pos == 2: 884 popupposition = wx.Point(0, h - self._popupsize[1]) 885 # bottom right 886 elif pos == 3: 887 popupposition = wx.Point(self._bottomright.x - self._popupsize[0], 888 self._bottomright.y - self._popupsize[1]) 889 890 self._bottomright = wx.Point(popupposition.x + self._popupsize[0], 891 popupposition.y + self._popupsize[1]) 892 893 self._dialogtop = popupposition 894 895 896 def SetPopupPauseTime(self, pausetime): 897 """ 898 Sets the time after which the :class:`ToasterBox` is destroyed (linger). 899 900 :param `pausetime`: the delay after which the control is destroyed, in seconds. 901 """ 902 903 self._pausetime = pausetime 904 905 906 def SetPopupScrollSpeed(self, speed): 907 """ 908 Sets the :class:`ToasterBox` scroll speed. 909 910 :param `speed`: it is the pause time (in milliseconds) for every step in the 911 :meth:`~ToasterBoxWindow.ScrollUp` method. 912 """ 913 914 self._sleeptime = speed 915 916 917 def AddPanel(self, panel): 918 """ 919 Adds a panel to the :class:`ToasterBox`. 920 921 :param `panel`: an instance of :class:`wx.Window`. 922 923 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_COMPLEX`` style. 924 """ 925 926 if not self._tbstyle & TB_COMPLEX: 927 raise Exception("\nERROR: Panel Can Not Be Added When Using TB_SIMPLE ToasterBox Style") 928 929 self.sizer.Add(panel, 1, wx.EXPAND) 930 self.SetSizer(self.sizer) 931 self.Layout() 932 933 if self._closingstyle & TB_ONCLICK and self._windowstyle != TB_CAPTION: 934 panel.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown) 935 936 937 def SetPopupText(self, text): 938 """ 939 Sets the :class:`ToasterBox` text label. 940 941 :param `text`: the widget label. 942 943 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 944 """ 945 946 self._popuptext = text 947 948 949 def SetPopupTextFont(self, font): 950 """ 951 Sets the :class:`ToasterBox` text font. 952 953 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, then 954 a simple generic font will be generated. 955 956 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 957 """ 958 959 self._textfont = font 960 961 962 def GetPopupText(self): 963 """ 964 Returns the :class:`ToasterBox` text. 965 966 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 967 """ 968 969 return self._popuptext 970 971 972 def Play(self): 973 """ Creates the :class:`ToasterBoxWindow`, that does all the job. """ 974 975 # do some checks to make sure this window is valid 976 if self._bottomright.x < 1 or self._bottomright.y < 1: 977 return False 978 979 if self.GetSize().GetWidth() < 50 or self.GetSize().GetWidth() < 50: 980 # toasterbox launches into a endless loop for some reason 981 # when you try to make the window too small. 982 return False 983 984 self._direction = wx.UP 985 self.SetupPositions() 986 self.ScrollUp() 987 timerid = wx.NewIdRef() 988 self.showtime = wx.Timer(self, timerid) 989 self.showtime.Start(self._pausetime) 990 self.Bind(wx.EVT_TIMER, self.NotifyTimer, id=timerid) 991 992 return True 993 994 995 def NotifyTimer(self, event): 996 """ Hides gradually the :class:`ToasterBoxWindow`. """ 997 998 if self._scrollType != TB_SCR_TYPE_FADE: 999 self.showtime.Stop() 1000 del self.showtime 1001 1002 self._direction = wx.DOWN 1003 self.SetupPositions() 1004 1005 self.ScrollDown() 1006 1007 1008 def SetPopupBackgroundColour(self, colour): 1009 """ 1010 Sets the :class:`ToasterBox` background colour. 1011 1012 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, then 1013 the background colour will be white. 1014 1015 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 1016 """ 1017 1018 self.SetBackgroundColour(colour) 1019 self._backgroundcolour = colour 1020 1021 1022 def SetPopupTextColour(self, colour): 1023 """ 1024 Sets the :class:`ToasterBox` foreground colour. 1025 1026 :param `colour`: a valid :class:`wx.Colour` object. If defaulted to ``None``, then 1027 the background colour will be black. 1028 1029 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 1030 """ 1031 1032 self._textcolour = colour 1033 1034 1035 def SetUseFocus(self, focus, originalfocus): 1036 """ 1037 If `focus` is ``True``, Instructs :class:`ToasterBoxWindow` to steal the focus from the 1038 parent application, otherwise it returns the focus to the original owner. 1039 1040 :param `focus`: ``True`` to set the focus on :class:`ToasterBoxWindow`, ``False`` to 1041 return it to the original owner; 1042 :param `originalfocus`: an instance of :class:`wx.Window`, representing a pointer to 1043 the window which originally had the focus 1044 """ 1045 1046 self._usefocus = focus 1047 self._originalfocus = originalfocus 1048 1049 1050 def OnScrollTimer(self, event): 1051 """ 1052 Handles the ``wx.EVT_TIMER`` event for :class:`ToasterBoxWindow` scrolling up/down. 1053 1054 :param `event`: a :class:`TimerEvent` event to be processed. 1055 """ 1056 1057 if self._direction == wx.UP: 1058 self.TearUp() 1059 else: 1060 self.TearDown() 1061 1062 1063 def TearUp(self): 1064 """ Scrolls the :class:`ToasterBox` up, which means gradually showing it. """ 1065 1066 self._windowsize = self._windowsize + self._step 1067 step = self._currentStep 1068 1069 if step < self._dialogtop[1]: 1070 step = self._dialogtop[1] 1071 1072 # checking the type of the scroll (from up to down or from down to up) 1073 if self._scrollType == TB_SCR_TYPE_UD: 1074 dimY = self._dialogtop[1] 1075 elif self._scrollType == TB_SCR_TYPE_DU: 1076 dimY = step 1077 1078 self.SetSize(self._dialogtop[0], dimY, self.GetSize().GetWidth(), self._windowsize) 1079 1080 self.Refresh(False) 1081 1082 self._currentStep += self._scrollStep 1083 1084 if self._currentStep not in list(range(self._start, self._stop, self._scrollStep)): 1085 self._scrollTimer.Stop() 1086 self.Update() 1087 1088 if self._tbstyle == TB_SIMPLE: 1089 self.DrawText() 1090 1091 if self._usefocus: 1092 self.SetFocus() 1093 else: 1094 self._originalfocus.SetFocus() 1095 1096 1097 def TearDown(self): 1098 """ Scrolls the :class:`ToasterBox` down, which means gradually hiding it. """ 1099 1100 self._windowsize = self._windowsize - self._step 1101 step = self._currentStep 1102 1103 if step > self._bottomright.y: 1104 step = self._bottomright.y 1105 1106 if self._windowsize > 0: 1107 # checking the type of the scroll (from up to down or from down to up) 1108 if self._scrollType == TB_SCR_TYPE_UD: 1109 dimY = self._dialogtop[1] 1110 elif self._scrollType == TB_SCR_TYPE_DU: 1111 dimY = step 1112 1113 self.SetSize(self._dialogtop[0], dimY, 1114 self.GetSize().GetWidth(), self._windowsize) 1115 1116 self.Update() 1117 self.Refresh() 1118 1119 self._currentStep += self._scrollStep 1120 1121 else: 1122 self._scrollTimer.Stop() 1123 self.Hide() 1124 if self._parent2: 1125 self._parent2.Notify() 1126 1127 1128 def SetupPositions(self): 1129 """ Sets up the position, size and scrolling step for :class:`ToasterBoxWindow`. """ 1130 1131 if self._scrollType == TB_SCR_TYPE_FADE: 1132 self.SetPosition(wx.Point(*self._dialogtop)) 1133 return 1134 1135 if self._direction == wx.UP: 1136 # walk the Y value up in a raise motion 1137 self._xpos = self.GetPosition().x 1138 self._ypos = self._bottomright[1] 1139 self._windowsize = 0 1140 1141 # checking the type of the scroll (from up to down or from down to up) 1142 if self._scrollType == TB_SCR_TYPE_UD: 1143 self._start = self._dialogtop[1] 1144 self._stop = self._ypos 1145 self._scrollStep = self._step 1146 elif self._scrollType == TB_SCR_TYPE_DU: 1147 self._start = self._ypos 1148 self._stop = self._dialogtop[1] 1149 self._scrollStep = -self._step 1150 1151 else: 1152 1153 # walk down the Y value 1154 self._windowsize = self.GetSize().GetHeight() 1155 1156 # checking the type of the scroll (from up to down or from down to up) 1157 if self._scrollType == TB_SCR_TYPE_UD: 1158 self._start = self._bottomright.y 1159 self._stop = self._dialogtop[1] 1160 self._scrollStep = -self._step 1161 elif self._scrollType == TB_SCR_TYPE_DU: 1162 self._start = self._dialogtop[1] 1163 self._stop = self._bottomright.y 1164 self._scrollStep = self._step 1165 1166 self._currentStep = self._start 1167 1168 1169 def ScrollUp(self): 1170 """ Scrolls the :class:`ToasterBox` up, which means gradually showing it. """ 1171 1172 if self._scrollType == TB_SCR_TYPE_FADE: 1173 self._amount = 0 1174 self._delta = 5 1175 self.SetSize(self.GetSize()) 1176 self._alphaTimer.Start(self._sleeptime) 1177 else: 1178 self.Show(True) 1179 self._scrollTimer.Start(self._sleeptime) 1180 1181 1182 def ScrollDown(self): 1183 """ Scrolls the :class:`ToasterBox` down, which means gradually hiding it. """ 1184 1185 if self._scrollType == TB_SCR_TYPE_FADE: 1186 self._amount = 255 1187 self._delta = -5 1188 self._alphaTimer.Start(self._sleeptime) 1189 else: 1190 self._scrollTimer.Start(self._sleeptime) 1191 1192 1193 def OnPaint(self, event): 1194 """ 1195 Handles the ``wx.EVT_PAINT`` event for :class:`ToasterBoxWindow`. 1196 1197 :param `event`: a :class:`PaintEvent` event to be processed. 1198 1199 :note: This event is handled and processed only if the style ``TB_SIMPLE`` is 1200 given to :class:`ToasterBox`. 1201 """ 1202 1203 dc = wx.AutoBufferedPaintDC(self) 1204 self.DrawText(dc) 1205 1206 1207 def DrawText(self, dc=None): 1208 """ 1209 Draws the text label for a :class:`ToasterBox` with ``TB_SIMPLE`` style set. 1210 1211 :param `dc`: an instance of :class:`wx.DC`. If defaulted to ``None``, a :class:`ClientDC` 1212 will be created on the fly. 1213 """ 1214 1215 if dc is None: 1216 dc = wx.ClientDC(self) 1217 1218 dc.SetBackground(wx.Brush(self._backgroundcolour)) 1219 dc.Clear() 1220 1221 if self._staticbitmap: 1222 dc.DrawBitmap(self._staticbitmap, 0, 0) 1223 dc.SetFont(self._textfont) 1224 dc.SetTextForeground(self._textcolour) 1225 1226 if not hasattr(self, "text_coords"): 1227 self._getTextCoords(dc) 1228 dc.DrawTextList(*self.text_coords) 1229 1230 1231 def AlphaCycle(self, event): 1232 """ 1233 Handles the ``wx.EVT_TIMER`` event for :class:`ToasterBoxWindow`. 1234 1235 :param `event`: a :class:`TimerEvent` event to be processed. 1236 """ 1237 1238 # Increase (or decrease) the alpha channel 1239 self._amount += self._delta 1240 1241 if self._tbstyle == TB_SIMPLE: 1242 self.Refresh(False) 1243 1244 if self._amount > 255 or self._amount < 0: 1245 # We're done, stop the timer 1246 self._alphaTimer.Stop() 1247 1248 if self._amount < 0: 1249 self.Hide() 1250 self.showtime.Stop() 1251 if self._parent2: 1252 self._parent2.Notify() 1253 1254 elif self._amount > 255: 1255 if self._usefocus: 1256 self.SetFocus() 1257 else: 1258 self._originalfocus.SetFocus() 1259 1260 return 1261 1262 # Make the ToasterBoxWindow more or less transparent 1263 self.MakeWindowTransparent(self._amount) 1264 if not self.IsShown(): 1265 self.Show() 1266 1267 1268 def MakeWindowTransparent(self, amount): 1269 """ 1270 Makes the :class:`ToasterBoxWindow` window transparent. 1271 1272 :param `amount`: the alpha channel value. 1273 """ 1274 1275 if not self.CanSetTransparent(): 1276 return 1277 1278 self.SetTransparent(amount) 1279 1280 1281 def _getTextCoords(self, dc): 1282 """ 1283 Draw the user specified text. 1284 1285 :param `dc`: an instance of :class:`wx.DC`. 1286 1287 :note: Use this method only for a :class:`ToasterBox` created with the ``TB_SIMPLE`` style. 1288 """ 1289 1290 # border from sides and top to text (in pixels) 1291 border = 7 1292 # how much space between text lines 1293 textPadding = 2 1294 1295 pText = self.GetPopupText() 1296 1297 max_len = len(pText) 1298 1299 tw, th = self._parent2._popupsize 1300 1301 if self._windowstyle == TB_CAPTION: 1302 th = th - 20 1303 1304 while 1: 1305 lines = textwrap.wrap(pText, max_len) 1306 1307 for line in lines: 1308 w, h = dc.GetTextExtent(line) 1309 if w > tw - border * 2: 1310 max_len -= 1 1311 break 1312 else: 1313 break 1314 1315 fh = 0 1316 for line in lines: 1317 w, h = dc.GetTextExtent(line) 1318 fh += h + textPadding 1319 y = (th - fh) / 2; coords = [] 1320 1321 for line in lines: 1322 w, h = dc.GetTextExtent(line) 1323 x = (tw - w) / 2 1324 coords.append((x, y)) 1325 y += h + textPadding 1326 1327 self.text_coords = (lines, coords) 1328