2
A throbber displays an animated image that can be
3
started, stopped, reversed, etc. Useful for showing
4
an ongoing process (like most web browsers use) or
5
simply for adding eye-candy to an application.
7
Throbbers utilize a wxTimer so that normal processing
8
can continue unencumbered.
12
# throbber.py - Cliff Wells <clifford.wells@comcast.net>
14
# Thanks to Harald Massa <harald.massa@suedvers.de> for
15
# suggestions and sample code.
17
# $Id: throbber.py,v 1.8.2.1 2005/12/19 23:13:52 RD Exp $
19
# 12/12/2003 - Jeff Grimmett (grimmtooth@softhome.net)
21
# o 2.5 compatability update.
28
# ------------------------------------------------------------------------------
30
THROBBER_EVENT = wx.NewEventType()
31
EVT_UPDATE_THROBBER = wx.PyEventBinder(THROBBER_EVENT, 0)
33
class UpdateThrobberEvent(wx.PyEvent):
35
wx.PyEvent.__init__(self)
36
self.SetEventType(THROBBER_EVENT)
38
# ------------------------------------------------------------------------------
40
class Throbber(wx.PyPanel):
42
The first argument is either the name of a file that will be split into frames
43
(a composite image) or a list of strings of image names that will be treated
44
as individual frames. If a single (composite) image is given, then additional
45
information must be provided: the number of frames in the image and the width
46
of each frame. The first frame is treated as the "at rest" frame (it is not
47
shown during animation, but only when Throbber.Rest() is called.
48
A second, single image may be optionally specified to overlay on top of the
49
animation. A label may also be specified to show on top of the animation.
51
def __init__(self, parent, id,
52
bitmap, # single (composite) bitmap or list of bitmaps
53
pos = wx.DefaultPosition,
54
size = wx.DefaultSize,
55
frameDelay = 0.1,# time between frames
56
frames = 0, # number of frames (only necessary for composite image)
57
frameWidth = 0, # width of each frame (only necessary for composite image)
58
label = None, # optional text to be displayed
59
overlay = None, # optional image to overlay on animation
60
reverse = 0, # reverse direction at end of animation
61
style = 0, # window style
68
wx.PyPanel.__init__(self, parent, id, pos, size, style, name)
71
self.running = (1 != 1)
72
_seqTypes = (type([]), type(()))
74
# set size, guessing if necessary
77
if type(bitmap) in _seqTypes:
78
width = bitmap[0].GetWidth()
83
if type(bitmap) in _seqTypes:
84
height = bitmap[0].GetHeight()
86
height = bitmap.GetHeight()
87
self.width, self.height = width, height
90
assert width != -1 and height != -1, "Unable to guess size"
93
extentX, extentY = self.GetTextExtent(label)
94
self.labelX = (width - extentX)/2
95
self.labelY = (height - extentY)/2
96
self.frameDelay = frameDelay
98
self.current = current
99
self.direction = direction
100
self.autoReverse = reverse
101
self.overlay = overlay
102
if overlay is not None:
103
self.overlay = overlay
104
self.overlayX = (width - self.overlay.GetWidth()) / 2
105
self.overlayY = (height - self.overlay.GetHeight()) / 2
106
self.showOverlay = overlay is not None
107
self.showLabel = label is not None
109
# do we have a sequence of images?
110
if type(bitmap) in _seqTypes:
111
self.submaps = bitmap
112
self.frames = len(self.submaps)
113
# or a composite image that needs to be split?
117
for chunk in range(frames):
118
rect = (chunk * frameWidth, 0, width, height)
119
self.submaps.append(bitmap.GetSubBitmap(rect))
121
# self.sequence can be changed, but it's not recommended doing it
122
# while the throbber is running. self.sequence[0] should always
123
# refer to whatever frame is to be shown when 'resting' and be sure
124
# that no item in self.sequence >= self.frames or < 0!!!
125
self.SetSequence(sequence)
127
self.SetClientSize((width, height))
130
self.timer = wx.Timer(self, timerID)
132
self.Bind(EVT_UPDATE_THROBBER, self.Update)
133
self.Bind(wx.EVT_PAINT, self.OnPaint)
134
self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
135
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroyWindow)
138
def DoGetBestSize(self):
139
return (self.width, self.height)
142
def OnTimer(self, event):
143
wx.PostEvent(self, UpdateThrobberEvent())
146
def OnDestroyWindow(self, event):
152
dc.DrawBitmap(self.submaps[self.sequence[self.current]], 0, 0, True)
153
if self.overlay and self.showOverlay:
154
dc.DrawBitmap(self.overlay, self.overlayX, self.overlayY, True)
155
if self.label and self.showLabel:
156
dc.DrawText(self.label, self.labelX, self.labelY)
157
dc.SetTextForeground(wx.WHITE)
158
dc.DrawText(self.label, self.labelX-1, self.labelY-1)
161
def OnPaint(self, event):
162
self.Draw(wx.PaintDC(self))
166
def Update(self, event):
171
if self.current >= len(self.sequence):
174
self.current = len(self.sequence) - 1
182
self.current = len(self.sequence) - 1
183
self.Draw(wx.ClientDC(self))
186
# --------- public methods ---------
187
def SetFont(self, font):
188
"""Set the font for the label"""
189
wx.Panel.SetFont(self, font)
190
self.SetLabel(self.label)
191
self.Draw(wx.ClientDC(self))
195
"""Stop the animation and return to frame 0"""
197
self.current = self.rest
198
self.Draw(wx.ClientDC(self))
202
"""Change the direction of the animation"""
203
self.direction = -self.direction
207
"""Returns True if the animation is running"""
212
"""Start the animation"""
214
self.running = not self.running
215
self.timer.Start(int(self.frameDelay * 1000))
219
"""Stop the animation"""
222
self.running = not self.running
225
def SetCurrent(self, current):
226
"""Set current image"""
227
running = self.Running()
229
#FIXME: need to make sure value is within range!!!
230
self.current = current
231
self.Draw(wx.ClientDC(self))
234
def SetRest(self, rest):
239
def SetSequence(self, sequence = None):
240
"""Order to display images"""
242
# self.sequence can be changed, but it's not recommended doing it
243
# while the throbber is running. self.sequence[0] should always
244
# refer to whatever frame is to be shown when 'resting' and be sure
245
# that no item in self.sequence >= self.frames or < 0!!!
247
running = self.Running()
250
if sequence is not None:
251
#FIXME: need to make sure values are within range!!!
252
self.sequence = sequence
254
self.sequence = range(self.frames)
261
"""Display next image in sequence"""
267
"""Display previous image in sequence"""
273
"""Display next image in sequence according to direction"""
274
self.current += self.direction
279
"""Display previous image in sequence according to direction"""
280
self.current -= self.direction
284
def SetFrameDelay(self, frameDelay = 0.05):
285
"""Delay between each frame"""
286
self.frameDelay = frameDelay
292
def ToggleOverlay(self, state = None):
293
"""Toggle the overlay image"""
295
self.showOverlay = not self.showOverlay
297
self.showOverlay = state
298
self.Draw(wx.ClientDC(self))
301
def ToggleLabel(self, state = None):
302
"""Toggle the label"""
304
self.showLabel = not self.showLabel
306
self.showLabel = state
307
self.Draw(wx.ClientDC(self))
310
def SetLabel(self, label):
311
"""Change the text of the label"""
314
extentX, extentY = self.GetTextExtent(label)
315
self.labelX = (self.width - extentX)/2
316
self.labelY = (self.height - extentY)/2
317
self.Draw(wx.ClientDC(self))
321
# ------------------------------------------------------------------------------