]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/throbber.py
Smartphone menus.
[wxWidgets.git] / wxPython / wx / lib / throbber.py
1 """
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.
6
7 Throbbers utilize a wxTimer so that normal processing
8 can 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 #
19 # 12/12/2003 - Jeff Grimmett (grimmtooth@softhome.net)
20 #
21 # o 2.5 compatability update.
22 #
23
24
25 import os
26 import wx
27
28 # ------------------------------------------------------------------------------
29
30 THROBBER_EVENT = wx.NewEventType()
31 EVT_UPDATE_THROBBER = wx.PyEventBinder(THROBBER_EVENT, 0)
32
33 class UpdateThrobberEvent(wx.PyEvent):
34 def __init__(self):
35 wx.PyEvent.__init__(self)
36 self.SetEventType(THROBBER_EVENT)
37
38 # ------------------------------------------------------------------------------
39
40 class Throbber(wx.PyPanel):
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"):
63 wx.PyPanel.__init__(self, parent, id, pos, size, style, name)
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
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)
130
131
132 def DoGetBestSize(self):
133 return (self.width, self.height)
134
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):
146 dc.DrawBitmap(self.submaps[self.sequence[self.current]], 0, 0, True)
147 if self.overlay and self.showOverlay:
148 dc.DrawBitmap(self.overlay, self.overlayX, self.overlayY, True)
149 if self.label and self.showLabel:
150 dc.DrawText(self.label, self.labelX, self.labelY)
151 dc.SetTextForeground(wx.WHITE)
152 dc.DrawText(self.label, self.labelX-1, self.labelY-1)
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
206 self.timer.Start(int(self.frameDelay * 1000))
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 # ------------------------------------------------------------------------------
254