]> git.saurik.com Git - wxWidgets.git/blame - wxPython/wx/lib/throbber.py
pi --> math.pi
[wxWidgets.git] / wxPython / wx / lib / throbber.py
CommitLineData
d14a1e28
RD
1"""
2A throbber displays an animated image that can be
3started, stopped, reversed, etc. Useful for showing
4an ongoing process (like most web browsers use) or
5simply for adding eye-candy to an application.
1fded56b 6
d14a1e28
RD
7Throbbers utilize a wxTimer so that normal processing
8can continue unencumbered.
9"""
10
11#
12# throbber.py - Cliff Wells <clifford.wells@comcast.net>
13#
14# Thanks to Harald Massa <harald.massa@suedvers.de> for
15# suggestions and sample code.
16#
17# $Id$
18#
b881fc78
RD
19# 12/12/2003 - Jeff Grimmett (grimmtooth@softhome.net)
20#
21# o 2.5 compatability update.
22#
23
d14a1e28
RD
24
25import os
26import wx
27
28# ------------------------------------------------------------------------------
fd3f2efe 29
d14a1e28 30THROBBER_EVENT = wx.NewEventType()
b881fc78 31EVT_UPDATE_THROBBER = wx.PyEventBinder(THROBBER_EVENT, 0)
d14a1e28
RD
32
33class UpdateThrobberEvent(wx.PyEvent):
34 def __init__(self):
35 wx.PyEvent.__init__(self)
36 self.SetEventType(THROBBER_EVENT)
37
38# ------------------------------------------------------------------------------
39
102e2b26 40class Throbber(wx.PyPanel):
d14a1e28
RD
41 """
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.
50 """
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
62 name = "throbber"):
102e2b26 63 wx.PyPanel.__init__(self, parent, id, pos, size, style, name)
d14a1e28
RD
64 self.name = name
65 self.label = label
66 self.running = (1 != 1)
67 _seqTypes = (type([]), type(()))
68
69 # set size, guessing if necessary
70 width, height = size
71 if width == -1:
72 if type(bitmap) in _seqTypes:
73 width = bitmap[0].GetWidth()
74 else:
75 if frameWidth:
76 width = frameWidth
77 if height == -1:
78 if type(bitmap) in _seqTypes:
79 height = bitmap[0].GetHeight()
80 else:
81 height = bitmap.GetHeight()
82 self.width, self.height = width, height
83
84 # double check it
85 assert width != -1 and height != -1, "Unable to guess size"
86
87 if label:
88 extentX, extentY = self.GetTextExtent(label)
89 self.labelX = (width - extentX)/2
90 self.labelY = (height - extentY)/2
91 self.frameDelay = frameDelay
92 self.current = 0
93 self.direction = 1
94 self.autoReverse = reverse
95 self.overlay = overlay
96 if overlay is not None:
97 self.overlay = overlay
98 self.overlayX = (width - self.overlay.GetWidth()) / 2
99 self.overlayY = (height - self.overlay.GetHeight()) / 2
100 self.showOverlay = overlay is not None
101 self.showLabel = label is not None
102
103 # do we have a sequence of images?
104 if type(bitmap) in _seqTypes:
105 self.submaps = bitmap
106 self.frames = len(self.submaps)
107 # or a composite image that needs to be split?
108 else:
109 self.frames = frames
110 self.submaps = []
111 for chunk in range(frames):
112 rect = (chunk * frameWidth, 0, width, height)
113 self.submaps.append(bitmap.GetSubBitmap(rect))
114
115 # self.sequence can be changed, but it's not recommended doing it
116 # while the throbber is running. self.sequence[0] should always
117 # refer to whatever frame is to be shown when 'resting' and be sure
118 # that no item in self.sequence >= self.frames or < 0!!!
119 self.sequence = range(self.frames)
120
121 self.SetClientSize((width, height))
122
123 timerID = wx.NewId()
124 self.timer = wx.Timer(self, timerID)
125
b881fc78
RD
126 self.Bind(EVT_UPDATE_THROBBER, self.Rotate)
127 self.Bind(wx.EVT_PAINT, self.OnPaint)
128 self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)
129 self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroyWindow)
102e2b26
RD
130
131
132 def DoGetBestSize(self):
133 return (self.width, self.height)
134
d14a1e28
RD
135
136 def OnTimer(self, event):
137 wx.PostEvent(self, UpdateThrobberEvent())
138
139
140 def OnDestroyWindow(self, event):
141 self.Stop()
142 event.Skip()
143
144
145 def Draw(self, dc):
d7403ad2 146 dc.DrawBitmap(self.submaps[self.sequence[self.current]], 0, 0, True)
d14a1e28 147 if self.overlay and self.showOverlay:
d7403ad2 148 dc.DrawBitmap(self.overlay, self.overlayX, self.overlayY, True)
d14a1e28 149 if self.label and self.showLabel:
d7403ad2 150 dc.DrawText(self.label, self.labelX, self.labelY)
d14a1e28 151 dc.SetTextForeground(wx.WHITE)
d7403ad2 152 dc.DrawText(self.label, self.labelX-1, self.labelY-1)
d14a1e28
RD
153
154
155 def OnPaint(self, event):
156 self.Draw(wx.PaintDC(self))
157 event.Skip()
158
159
160 def Rotate(self, event):
161 self.current += self.direction
162 if self.current >= len(self.sequence):
163 if self.autoReverse:
164 self.Reverse()
165 self.current = len(self.sequence) - 1
166 else:
167 self.current = 1
168 if self.current < 1:
169 if self.autoReverse:
170 self.Reverse()
171 self.current = 1
172 else:
173 self.current = len(self.sequence) - 1
174 self.Draw(wx.ClientDC(self))
175
176
177 # --------- public methods ---------
178 def SetFont(self, font):
179 """Set the font for the label"""
180 wx.Panel.SetFont(self, font)
181 self.SetLabel(self.label)
182 self.Draw(wx.ClientDC(self))
183
184
185 def Rest(self):
186 """Stop the animation and return to frame 0"""
187 self.Stop()
188 self.current = 0
189 self.Draw(wx.ClientDC(self))
190
191
192 def Reverse(self):
193 """Change the direction of the animation"""
194 self.direction = -self.direction
195
196
197 def Running(self):
198 """Returns True if the animation is running"""
199 return self.running
200
201
202 def Start(self):
203 """Start the animation"""
204 if not self.running:
205 self.running = not self.running
fd3f2efe 206 self.timer.Start(int(self.frameDelay * 1000))
d14a1e28
RD
207
208
209 def Stop(self):
210 """Stop the animation"""
211 if self.running:
212 self.timer.Stop()
213 self.running = not self.running
214
215
216 def SetFrameDelay(self, frameDelay = 0.05):
217 """Delay between each frame"""
218 self.frameDelay = frameDelay
219 if self.running:
220 self.Stop()
221 self.Start()
222
223
224 def ToggleOverlay(self, state = None):
225 """Toggle the overlay image"""
226 if state is None:
227 self.showOverlay = not self.showOverlay
228 else:
229 self.showOverlay = state
230 self.Draw(wx.ClientDC(self))
231
232
233 def ToggleLabel(self, state = None):
234 """Toggle the label"""
235 if state is None:
236 self.showLabel = not self.showLabel
237 else:
238 self.showLabel = state
239 self.Draw(wx.ClientDC(self))
240
241
242 def SetLabel(self, label):
243 """Change the text of the label"""
244 self.label = label
245 if label:
246 extentX, extentY = self.GetTextExtent(label)
247 self.labelX = (self.width - extentX)/2
248 self.labelY = (self.height - extentY)/2
249 self.Draw(wx.ClientDC(self))
250
251
252
253# ------------------------------------------------------------------------------
1fded56b 254