1
# --------------------------------------------------------------------------- #
2
# TOASTERBOX wxPython IMPLEMENTATION
3
# Ported And Enhanced From wxWidgets Contribution (Aj Bommarito) By:
5
# Andrea Gavana, @ 16 September 2005
6
# Latest Revision: 12 May 2009, 15.00 GMT
14
# For All Kind Of Problems, Requests Of Enhancements And Bug Reports, Please
17
# andrea.gavana@gmail.com
20
# Or, Obviously, To The wxPython Mailing List!!!
24
# --------------------------------------------------------------------------- #
30
ToasterBox is a cross-platform widget to make the creation of MSN style "toaster"
31
popups easier. The syntax is really easy especially if you are familiar with the
36
- TB_SIMPLE: using this style, you will be able to specify a background image for
37
ToasterBox, text properties as text colour, font and label.
39
- TB_COMPLEX: this style will allow you to put almost any control inside a
40
ToasterBox. You can add a panel in which you can put all the controls
43
Both styles support the setting of ToasterBox position (on screen coordinates),
44
size, the time after which the ToasterBox is destroyed (linger), and the scroll
47
ToasterBox has been tested on the following platforms:
49
Windows (verified on Windows XP, 2000)
52
Latest revision: Andrea Gavana @ 12 May 2009, 15.00 GMT
60
from wx.lib.statbmp import GenStaticBitmap as StaticBitmap
62
# Define Window List, We Use It Globally
68
DEFAULT_TB_STYLE = wx.SIMPLE_BORDER | wx.STAY_ON_TOP | wx.FRAME_NO_TASKBAR
69
TB_CAPTION = DEFAULT_TB_STYLE | wx.CAPTION | wx.SYSTEM_MENU | wx.CLOSE_BOX | wx.FRAME_TOOL_WINDOW
74
# scroll from up to down
76
# scroll from down to up
79
# ------------------------------------------------------------------------------ #
81
# Main Class Implementation. It Is Basically A wx.Timer. It Creates And
82
# Displays Popups And Handles The "Stacking".
83
# ------------------------------------------------------------------------------ #
85
class ToasterBox(wx.Timer):
87
def __init__(self, parent, tbstyle=TB_SIMPLE, windowstyle=DEFAULT_TB_STYLE,
88
closingstyle=TB_ONTIME, scrollType=TB_SCR_TYPE_DU):
90
Default class constructor.
92
ToasterBox.__init__(self, tbstyle=TB_SIMPLE, windowstyle=DEFAULT_TB_STYLE)
96
- tbstyle: this parameter may have 2 values:
97
(a) TB_SIMPLE: a simple ToasterBox, with background image and text
98
customization can be created;
99
(b) TB_COMPLEX: ToasterBoxes with different degree of complexity can
100
be created. You can add as many controls as you want, provided
101
that you call the AddPanel() method and pass to it a dummy frame
102
and a wx.Panel. see the demo for details.
104
- windowstyle: this parameter influences the visual appearance of ToasterBox:
105
(a) DEFAULT_TB_STYLE: default style, no caption nor close box;
106
(b) TB_CAPTION: ToasterBox will have a caption, with the possibility to
107
set a title for ToasterBox frame, and a close box;
109
- closingstyle: set this value to TB_ONCLICK if you want to be able to close
110
ToasterBox by a mouse click anywhere in the ToasterBox frame.
114
self._parent = parent
116
self._pausetime = 1700
117
self._popuptext = "default"
118
self._popupposition = wx.Point(100,100)
119
self._popuptop = wx.Point(0,0)
120
self._popupsize = wx.Size(150, 170)
122
self._backgroundcolour = wx.WHITE
123
self._foregroundcolour = wx.BLACK
124
self._textfont = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, False, "Verdana")
128
self._tbstyle = tbstyle
129
self._windowstyle = windowstyle
130
self._closingstyle = closingstyle
134
self._bottomright = wx.Point(wx.GetDisplaySize().GetWidth(),
135
wx.GetDisplaySize().GetHeight())
137
parent.Bind(wx.EVT_ICONIZE, lambda evt: [w.Hide() for w in winlist])
139
self._tb = ToasterBoxWindow(self._parent, self, self._tbstyle, self._windowstyle,
140
self._closingstyle, scrollType=scrollType)
143
def SetPopupPosition(self, pos):
144
""" Sets the ToasterBox position on screen. """
146
self._popupposition = pos
149
def SetPopupPositionByInt(self, pos):
150
""" Sets the ToasterBox position on screen, at one of the screen corners. """
152
self._bottomright = wx.Point(wx.GetDisplaySize().GetWidth(),
153
wx.GetDisplaySize().GetHeight())
157
popupposition = wx.Point(0,0)
160
popupposition = wx.Point(wx.GetDisplaySize().GetWidth() -
161
self._popupsize[0], 0)
164
popupposition = wx.Point(0, wx.GetDisplaySize().GetHeight() -
168
popupposition = wx.Point(self._bottomright.x - self._popupsize[0],
169
self._bottomright.y - self._popupsize[1])
171
self._bottomright = wx.Point(popupposition.x + self._popupsize[0],
172
popupposition.y + self._popupsize[1])
175
def SetPopupBackgroundColor(self, colour=None):
177
Sets the ToasterBox background colour. Use it only for ToasterBoxes created
178
with TB_SIMPLE style.
184
self._backgroundcolour = colour
187
def SetPopupTextColor(self, colour=None):
189
Sets the ToasterBox foreground colour. Use it only for ToasterBoxes created
190
with TB_SIMPLE style.
196
self._foregroundcolour = colour
199
def SetPopupTextFont(self, font=None):
201
Sets the ToasterBox text font. Use it only for ToasterBoxes created
202
with TB_SIMPLE style.
206
font = wx.Font(8, wx.SWISS, wx.NORMAL, wx.NORMAL, False)
208
self._textfont = font
211
def SetPopupSize(self, size):
212
""" Sets the ToasterBox size. """
214
self._popupsize = size
217
def SetPopupPauseTime(self, pausetime):
218
""" Sets the time after which the ToasterBox is destroyed (linger). """
220
self._pausetime = pausetime
223
def SetPopupBitmap(self, bitmap=None):
225
Sets the ToasterBox background image. Use it only for ToasterBoxes created
226
with TB_SIMPLE style.
229
if bitmap is not None:
230
bitmap = wx.Bitmap(bitmap, wx.BITMAP_TYPE_BMP)
232
self._bitmap = bitmap
235
def SetPopupScrollSpeed(self, speed):
237
Sets the ToasterBox scroll speed. The speed parameter is the pause
238
time (in ms) for every step in the ScrollUp() method.
241
self._sleeptime = speed
244
def SetPopupText(self, text):
246
Sets the ToasterBox text. Use it only for ToasterBoxes created
247
with TB_SIMPLE style.
250
self._popuptext = text
253
def AddPanel(self, panel):
255
Adds a panel to the ToasterBox. Use it only for ToasterBoxes created
256
with TB_COMPLEX style.
259
if not self._tbstyle & TB_COMPLEX:
260
raise Exception("\nERROR: Panel Can Not Be Added When Using TB_SIMPLE ToasterBox Style")
266
""" Creates the ToasterBoxWindow, that does all the job. """
269
self._tb.SetPopupSize((self._popupsize[0], self._popupsize[1]))
270
self._tb.SetPopupPosition((self._popupposition[0], self._popupposition[1]))
271
self._tb.SetPopupPauseTime(self._pausetime)
272
self._tb.SetPopupScrollSpeed(self._sleeptime)
274
if self._tbstyle == TB_SIMPLE:
275
self._tb.SetPopupTextColor(self._foregroundcolour)
276
self._tb.SetPopupBackgroundColor(self._backgroundcolour)
277
self._tb.SetPopupTextFont(self._textfont)
279
if self._bitmap is not None:
280
self._tb.SetPopupBitmap(self._bitmap)
282
self._tb.SetPopupText(self._popuptext)
284
if self._tbstyle == TB_COMPLEX:
285
if self._panel is not None:
286
self._tb.AddPanel(self._panel)
291
# check to see if there is already a window displayed
292
# by looking at the linked list
294
# there ARE other windows displayed already
295
# reclac where it should display
296
self.MoveAbove(self._tb)
298
# shift new window on to the list
299
winlist.append(self._tb)
301
if not self._tb.Play():
302
# if we didn't show the window properly, remove it from the list
303
winlist.remove(winlist[-1])
304
# delete the object too
309
def MoveAbove(self, tb):
310
""" If a ToasterBox already exists, move the new one above. """
312
# recalc where to place this popup
314
self._tb.SetPopupPosition((self._popupposition[0], self._popupposition[1] -
315
self._popupsize[1]*len(winlist)))
318
def GetToasterBoxWindow(self):
319
""" Returns the ToasterBox frame. """
324
def SetTitle(self, title):
325
""" Sets the ToasterBox title if it was created with TB_CAPTION window style. """
327
self._tb.SetTitle(title)
331
""" It's time to hide a ToasterBox! """
333
if len(winlist) == 0:
336
# clean the window list
339
# figure out how many blanks we have
348
# move windows to fill in blank space
349
for i in xrange(node.GetPosition()[1], self._popupposition[1], 4):
350
if i > self._popupposition[1]:
351
i = self._popupposition[1]
353
# loop through all the windows
354
for j in xrange(0, len(winlist)):
355
ourNewHeight = i - (j*self._popupsize[1] - 8)
357
# reset where the object THINKS its supposed to be
358
tmpTb.SetPopupPosition((self._popupposition[0], ourNewHeight))
360
tmpTb.SetDimensions(self._popupposition[0], ourNewHeight, tmpTb.GetSize().GetWidth(),
361
tmpTb.GetSize().GetHeight())
363
wx.Usleep(self._sleeptime)
367
""" Clean the window list. """
369
if len(winlist) == 0:
374
if not node.IsShown():
381
indx = winlist.index(node)
383
node = winlist[indx+1]
388
# ------------------------------------------------------------------------------ #
389
# Class ToasterBoxWindow
390
# This Class Does All The Job, By Handling Background Images, Text Properties
391
# And Panel Adding. Depending On The Style You Choose, ToasterBoxWindow Will
392
# Behave Differently In Order To Handle Widgets Inside It.
393
# ------------------------------------------------------------------------------ #
395
class ToasterBoxWindow(wx.Frame):
397
def __init__(self, parent, parent2, tbstyle, windowstyle,
398
closingstyle, scrollType=TB_SCR_TYPE_DU):
400
Default class constructor.
402
Used internally. Do not call directly this class in your application!
405
wx.Frame.__init__(self, parent, wx.ID_ANY, "window", wx.DefaultPosition,
406
wx.DefaultSize, style=windowstyle | wx.CLIP_CHILDREN)
408
self._starttime = wx.GetLocalTime()
409
self._parent2 = parent2
410
self._parent = parent
413
self._pausetime = 1700
414
self._textcolour = wx.BLACK
415
self._popuptext = "Change Me!"
416
# the size we want the dialog to be
417
framesize = wx.Size(150, 170)
419
self._tbstyle = tbstyle
420
self._windowstyle = windowstyle
421
self._closingstyle = closingstyle
422
self._scrollType = scrollType
425
if tbstyle == TB_COMPLEX:
426
self.sizer = wx.BoxSizer(wx.VERTICAL)
428
self._staticbitmap = None
430
if self._windowstyle == TB_CAPTION:
431
self.Bind(wx.EVT_CLOSE, self.OnClose)
434
if self._closingstyle & TB_ONCLICK and self._windowstyle != TB_CAPTION:
435
self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
437
self._bottomright = wx.Point(wx.GetDisplaySize().GetWidth(),
438
wx.GetDisplaySize().GetHeight())
440
self.SetDimensions(self._bottomright.x, self._bottomright.y,
441
framesize.GetWidth(), framesize.GetHeight())
444
def OnClose(self, event):
446
self.NotifyTimer(None)
450
def OnMouseDown(self, event):
452
self.NotifyTimer(None)
456
def SetPopupBitmap(self, bitmap):
458
Sets the ToasterBox background image. Use it only for ToasterBoxes created
459
with TB_SIMPLE style.
462
bitmap = bitmap.ConvertToImage()
463
xsize, ysize = self.GetSize()
464
bitmap = bitmap.Scale(xsize, ysize)
465
bitmap = bitmap.ConvertToBitmap()
466
self._staticbitmap = StaticBitmap(self, -1, bitmap, pos=(0,0))
468
if self._closingstyle & TB_ONCLICK and self._windowstyle != TB_CAPTION:
469
self._staticbitmap.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
472
def SetPopupSize(self, size):
473
""" Sets the ToasterBox size. """
475
self.SetDimensions(self._bottomright.x, self._bottomright.y, size[0], size[1])
478
def SetPopupPosition(self, pos):
479
""" Sets the ToasterBox position on screen. """
481
self._bottomright = wx.Point(pos[0] + self.GetSize().GetWidth(),
482
pos[1] + self.GetSize().GetHeight())
483
self._dialogtop = pos
486
def SetPopupPositionByInt(self, pos):
487
""" Sets the ToasterBox position on screen, at one of the screen corners. """
489
self._bottomright = wx.Point(wx.GetDisplaySize().GetWidth(),
490
wx.GetDisplaySize().GetHeight())
494
popupposition = wx.Point(0,0)
497
popupposition = wx.Point(wx.GetDisplaySize().GetWidth() -
498
self._popupsize[0], 0)
501
popupposition = wx.Point(0, wx.GetDisplaySize().GetHeight() -
505
popupposition = wx.Point(self._bottomright.x - self._popupsize[0],
506
self._bottomright.y - self._popupsize[1])
508
self._bottomright = wx.Point(popupposition.x + self._popupsize[0],
509
popupposition.y + self._popupsize[1])
511
self._dialogtop = popupposition
514
def SetPopupPauseTime(self, pausetime):
515
""" Sets the time after which the ToasterBox is destroyed (linger). """
517
self._pausetime = pausetime
520
def SetPopupScrollSpeed(self, speed):
522
Sets the ToasterBox scroll speed. The speed parameter is the pause
523
time (in ms) for every step in the ScrollUp() method.
526
self._sleeptime = speed
529
def AddPanel(self, panel):
531
Adds a panel to the ToasterBox. Use it only for ToasterBoxes created
532
with TB_COMPLEX style.
535
if not self._tbstyle & TB_COMPLEX:
536
raise Exception("\nERROR: Panel Can Not Be Added When Using TB_SIMPLE ToasterBox Style")
538
self.sizer.Add(panel, 1, wx.EXPAND)
540
self.SetSizer(self.sizer)
542
if self._closingstyle & TB_ONCLICK and self._windowstyle != TB_CAPTION:
543
panel.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
546
def SetPopupText(self, text):
548
Sets the ToasterBox text. Use it only for ToasterBoxes created
549
with TB_SIMPLE style.
552
self._popuptext = text
555
def SetPopupTextFont(self, font):
557
Sets the ToasterBox text font. Use it only for ToasterBoxes created
558
with TB_SIMPLE style.
561
self._textfont = font
564
def GetPopupText(self):
566
Returns the ToasterBox text. Use it only for ToasterBoxes created
567
with TB_SIMPLE style.
570
return self._popuptext
574
""" Creates the ToasterBoxWindow, that does all the job. """
576
# do some checks to make sure this window is valid
577
if self._bottomright.x < 1 or self._bottomright.y < 1:
580
if self.GetSize().GetWidth() < 50 or self.GetSize().GetWidth() < 50:
581
# toasterbox launches into a endless loop for some reason
582
# when you try to make the window too small.
587
self.showtime = wx.Timer(self, timerid)
588
self.showtime.Start(self._pausetime)
589
self.Bind(wx.EVT_TIMER, self.NotifyTimer, id=timerid)
594
def NotifyTimer(self, event):
595
""" Hides gradually the ToasterBoxWindow. """
602
def SetPopupBackgroundColor(self, colour):
604
Sets the ToasterBox background colour. Use it only for ToasterBoxes created
605
with TB_SIMPLE style.
608
self.SetBackgroundColour(colour)
611
def SetPopupTextColor(self, colour):
613
Sets the ToasterBox foreground colour. Use it only for ToasterBoxes created
614
with TB_SIMPLE style.
617
self._textcolour = colour
621
""" Scrolls the ToasterBox up, which means gradually showing the ToasterBox. """
625
# walk the Y value up in a raise motion
626
xpos = self.GetPosition().x
627
ypos = self._bottomright[1]
630
# checking the type of the scroll (from up to down or from down to up)
631
if self._scrollType == TB_SCR_TYPE_UD:
632
start = self._dialogtop[1]
635
elif self._scrollType == TB_SCR_TYPE_DU:
637
stop = self._dialogtop[1]
640
errMsg = ("scrollType not supported (in ToasterBoxWindow.ScrollUp): %s" %
642
raise ValueError(errMsg)
644
for i in xrange(start, stop, step):
645
if i < self._dialogtop[1]:
646
i = self._dialogtop[1]
648
windowsize = windowsize + self._step
650
# checking the type of the scroll (from up to down or from down to up)
651
if self._scrollType == TB_SCR_TYPE_UD:
652
dimY = self._dialogtop[1]
653
elif self._scrollType == TB_SCR_TYPE_DU:
656
errMsg = ("scrollType not supported (in ToasterBoxWindow.ScrollUp): %s" %
658
raise ValueError(errMsg)
660
self.SetDimensions(self._dialogtop[0], dimY, self.GetSize().GetWidth(),
663
if self._tbstyle == TB_SIMPLE:
666
wx.Usleep(self._sleeptime)
672
if self._tbstyle == TB_SIMPLE:
678
def ScrollDown(self):
679
""" Scrolls the ToasterBox down, which means gradually hiding the ToasterBox. """
681
# walk down the Y value
682
windowsize = self.GetSize().GetHeight()
684
# checking the type of the scroll (from up to down or from down to up)
685
if self._scrollType == TB_SCR_TYPE_UD:
686
start = self._bottomright.y
687
stop = self._dialogtop[1]
689
elif self._scrollType == TB_SCR_TYPE_DU:
690
start = self._dialogtop[1]
691
stop = self._bottomright.y
694
errMsg = ("scrollType not supported (in ToasterBoxWindow.ScrollUp): %s" %
696
raise ValueError(errMsg)
698
for i in xrange(start, stop, step):
699
if i > self._bottomright.y:
700
i = self._bottomright.y
702
windowsize = windowsize - self._step
704
# checking the type of the scroll (from up to down or from down to up)
705
if self._scrollType == TB_SCR_TYPE_UD:
706
dimY = self._dialogtop[1]
707
elif self._scrollType == TB_SCR_TYPE_DU:
710
errMsg = ("scrollType not supported (in ToasterBoxWindow.ScrollUp): %s" %
712
raise ValueError(errMsg)
714
self.SetDimensions(self._dialogtop[0], dimY,
715
self.GetSize().GetWidth(), windowsize)
717
wx.Usleep(self._sleeptime)
722
self._parent2.Notify()
727
if self._staticbitmap is not None:
728
dc = wx.ClientDC(self._staticbitmap)
730
dc = wx.ClientDC(self)
732
dc.SetFont(self._textfont)
734
if not hasattr(self, "text_coords"):
735
self._getTextCoords(dc)
737
fg = dc.GetTextForeground()
738
dc.SetTextForeground(self._textcolour)
739
dc.DrawTextList(*self.text_coords)
740
dc.SetTextForeground(fg)
743
def _getTextCoords(self, dc):
745
Draw the user specified text using the wx.DC. Use it only for ToasterBoxes created
746
with TB_SIMPLE style.
749
# border from sides and top to text (in pixels)
751
# how much space between text lines
754
pText = self.GetPopupText()
758
tw, th = self._parent2._popupsize
760
if self._windowstyle == TB_CAPTION:
764
lines = textwrap.wrap(pText, max_len)
767
w, h = dc.GetTextExtent(line)
768
if w > tw - border * 2:
776
w, h = dc.GetTextExtent(line)
777
fh += h + textPadding
778
y = (th - fh) / 2; coords = []
781
w, h = dc.GetTextExtent(line)
783
coords.append((x, y))
786
self.text_coords = (lines, coords)