f0f4b133161ef8cf04309a1619e1dccd0739e5a0
[minwii.git] / src / pgu / gui / widget.py
1 """
2 """
3 import pygame
4
5 import pguglobals
6 import style
7
8 class SignalCallback:
9 # The function to call
10 func = None
11 # The parameters to pass to the function (as a list)
12 params = None
13
14 class Widget:
15 """Template object - base for all widgets.
16
17 <pre>Widget(**params)</pre>
18
19 <p>A number of optional params may be passed to the Widget initializer.</p>
20
21 <dl>
22 <dt>decorate<dd>defaults to True. If true, will call <tt>theme.decorate(self)</tt> to allow the theme a chance to decorate the widget.
23 <dt>style<dd>a dict of style parameters.
24 <dt>x, y, width, height<dd>position and size parameters, passed along to style
25 <dt>align, valign<dd>alignment parameters, passed along to style
26 <dt>font, color, background<dd>other common parameters that are passed along to style
27 <dt>cls<dd>class name as used by Theme
28 <dt>name<dd>name of widget as used by Form. If set, will call <tt>form.add(self,name)</tt> to add the widget to the most recently created Form.
29 <dt>focusable<dd>True if this widget can receive focus via Tab, etc. Defaults to True.
30 <dt>disabled<dd>True of this widget is disabled. Defaults to False.
31 <dt>value<dd>initial value
32 </dl>
33
34 <strong>Example - Creating your own Widget</strong>
35 <p>This example shows which methods are template methods.</p>
36 <code>
37 class Draw(gui.Widget):
38 def paint(self,s):
39 #paint the pygame.Surface
40 return
41
42 def update(self,s):
43 #update the pygame.Surface and return the update rects
44 return [pygame.Rect(0,0,self.rect.w,self.rect.h)]
45
46 def event(self,e):
47 #handle the pygame.Event
48 return
49
50 def resize(self,width=None,height=None):
51 #return the width and height of this widget
52 return 256,256
53 </code>
54 """
55
56 # The name of the widget (or None if not defined)
57 name = None
58
59 def __init__(self,**params):
60 #object.Object.__init__(self)
61 self.connects = {}
62 params.setdefault('decorate',True)
63 params.setdefault('style',{})
64 params.setdefault('focusable',True)
65 params.setdefault('disabled',False)
66
67 self.focusable = params['focusable']
68 self.disabled = params['disabled']
69
70 self.rect = pygame.Rect(params.get('x',0),params.get('y',0),params.get('width',0),params.get('height',0))
71
72 s = params['style']
73 #some of this is a bit "theme-ish" but it is very handy, so these
74 #things don't have to be put directly into the style.
75 for att in ('align','valign','x','y','width','height','color','font','background'):
76 if att in params: s[att] = params[att]
77 self.style = style.Style(self,s)
78
79 self.cls = 'default'
80 if 'cls' in params: self.cls = params['cls']
81 if 'name' in params:
82 import form
83 self.name = params['name']
84 if hasattr(form.Form,'form') and form.Form.form != None:
85 form.Form.form.add(self)
86 self.form = form.Form.form
87 if 'value' in params: self.value = params['value']
88 self.pcls = ""
89
90 if params['decorate'] != False:
91 if (not pguglobals.app):
92 # TODO - fix this somehow
93 import app
94 print 'gui.widget: creating an App'
95 app.App()
96 pguglobals.app.theme.decorate(self,params['decorate'])
97
98 def focus(self):
99 """Focus this Widget.
100
101 <pre>Widget.focus()</pre>
102 """
103 if getattr(self,'container',None) != None:
104 if self.container.myfocus != self: ## by Gal Koren
105 self.container.focus(self)
106
107 def blur(self):
108 """Blur this Widget.
109
110 <pre>Widget.blur()</pre>
111 """
112 if getattr(self,'container',None) != None: self.container.blur(self)
113
114 def open(self):
115 """Open this Widget as a modal dialog.
116
117 <pre>Widget.open()</pre>
118 """
119 #if getattr(self,'container',None) != None: self.container.open(self)
120 pguglobals.app.open(self)
121
122 def close(self, w=None):
123 """Close this Widget (if it is a modal dialog.)
124
125 <pre>Widget.close()</pre>
126 """
127 #if getattr(self,'container',None) != None: self.container.close(self)
128 if (not w):
129 w = self
130 pguglobals.app.close(w)
131
132 def resize(self,width=None,height=None):
133 """Template method - return the size and width of this widget.
134
135 <p>Responsible for also resizing all sub-widgets.</p>
136
137 <pre>Widget.resize(width,height): return width,height</pre>
138
139 <dl>
140 <dt>width<dd>suggested width
141 <dt>height<dd>suggested height
142 </dl>
143
144 <p>If not overridden, will return self.style.width, self.style.height</p>
145 """
146 return self.style.width, self.style.height
147
148 def chsize(self):
149 """Change the size of this widget.
150
151 <p>Calling this method will cause a resize on all the widgets,
152 including this one.</p>
153
154 <pre>Widget.chsize()</pre>
155 """
156
157 if not hasattr(self,'_painted'): return
158
159 if not hasattr(self,'container'): return
160
161 if pguglobals.app:
162 if pguglobals.app._chsize:
163 return
164 pguglobals.app.chsize()
165 return
166
167 #if hasattr(app.App,'app'):
168 # w,h = self.rect.w,self.rect.h
169 # w2,h2 = self.resize()
170 # if w2 != w or h2 != h:
171 # app.App.app.chsize()
172 # else:
173 # self.repaint()
174
175
176 def update(self,s):
177 """Template method - update the surface
178
179 <pre>Widget.update(s): return list of pygame.Rect(s)</pre>
180
181 <dl>
182 <dt>s<dd>pygame.Surface to update
183 </dl>
184
185 <p>return - a list of the updated areas as pygame.Rect(s).</p>
186 """
187 return
188
189 def paint(self,s):
190 """Template method - paint the surface
191
192 <pre>Widget.paint(s)</pre>
193
194 <dl>
195 <dt>s<dd>pygame.Surface to paint
196 </dl>
197 """
198 return
199
200 def repaint(self):
201 """Request a repaint of this Widget.
202
203 <pre>Widget.repaint()</pre>
204 """
205 if getattr(self,'container',None) != None: self.container.repaint(self)
206 def repaintall(self):
207 """Request a repaint of all Widgets.
208
209 <pre>Widget.repaintall()</pre>
210 """
211 if getattr(self,'container',None) != None: self.container.repaintall()
212 def reupdate(self):
213 """Request a reupdate of this Widget
214
215 <pre>Widget.reupdate()</pre>
216 """
217 if getattr(self,'container',None) != None: self.container.reupdate(self)
218 def next(self):
219 """Pass focus to next Widget.
220
221 <p>Widget order determined by the order they were added to their container.</p>
222
223 <pre>Widget.next()</pre>
224 """
225 if getattr(self,'container',None) != None: self.container.next(self)
226 def previous(self):
227 """Pass focus to previous Widget.
228
229 <p>Widget order determined by the order they were added to their container.</p>
230
231 <pre>Widget.previous()</pre>
232 """
233
234 if getattr(self,'container',None) != None: self.container.previous(self)
235
236 def get_abs_rect(self):
237 """Get the absolute rect of this widget on the App screen
238
239 <pre>Widget.get_abs_rect(): return pygame.Rect</pre>
240 """
241 x, y = self.rect.x, self.rect.y
242 x += self._rect_content.x
243 y += self._rect_content.y
244 c = getattr(self,'container',None)
245 while c:
246 x += c.rect.x
247 y += c.rect.y
248 if hasattr(c,'_rect_content'):
249 x += c._rect_content.x
250 y += c._rect_content.y
251 c = getattr(c,'container',None)
252 return pygame.Rect(x, y, self.rect.w, self.rect.h)
253
254 def connect(self,code,func,*params):
255 """Connect a event code to a callback function.
256
257 <p>There may be multiple callbacks per event code.</p>
258
259 <pre>Object.connect(code,fnc,value)</pre>
260
261 <dl>
262 <dt>code<dd>event type [[gui-const]]
263 <dt>fnc<dd>callback function
264 <dt>*values<dd>values to pass to callback. Please note that callbacks may also have "magicaly" parameters. Such as:
265 <dl>
266 <dt>_event<dd>receive the event
267 <dt>_code<dd>receive the event code
268 <dt>_widget<dd>receive the sending widget
269 </dl>
270 </dl>
271
272 <strong>Example</strong>
273 <code>
274 def onclick(value):
275 print 'click',value
276
277 w = Button("PGU!")
278 w.connect(gui.CLICK,onclick,'PGU Button Clicked')
279 </code>
280 """
281 if (not code in self.connects):
282 self.connects[code] = []
283 for cb in self.connects[code]:
284 if (cb.func == func):
285 # Already connected to this callback function
286 return
287 # Wrap the callback function and add it to the list
288 cb = SignalCallback()
289 cb.func = func
290 cb.params = params
291 self.connects[code].append(cb)
292
293 # Remove signal handlers from the given event code. If func is specified,
294 # only those handlers will be removed. If func is None, all handlers
295 # will be removed.
296 def disconnect(self, code, func=None):
297 if (not code in self.connects):
298 return
299 if (not func):
300 # Remove all signal handlers
301 del self.connects[code]
302 else:
303 # Remove handlers that call 'func'
304 n = 0
305 callbacks = self.connects[code]
306 while (n < len(callbacks)):
307 if (callbacks[n].func == func):
308 # Remove this callback
309 del callbacks[n]
310 else:
311 n += 1
312
313 def send(self,code,event=None):
314 """Send a code, event callback trigger.
315
316 <pre>Object.send(code,event=None)</pre>
317
318 <dl>
319 <dt>code<dd>event code
320 <dt>event<dd>event
321 </dl>
322 """
323 if (not code in self.connects):
324 return
325 # Trigger all connected signal handlers
326 for cb in self.connects[code]:
327 func = cb.func
328 values = list(cb.params)
329
330 nargs = func.func_code.co_argcount
331 names = list(func.func_code.co_varnames)[:nargs]
332 if hasattr(func,'im_class'): names.pop(0)
333
334 args = []
335 magic = {'_event':event,'_code':code,'_widget':self}
336 for name in names:
337 if name in magic.keys():
338 args.append(magic[name])
339 elif len(values):
340 args.append(values.pop(0))
341 else:
342 break
343 args.extend(values)
344 func(*args)
345
346 def _event(self,e):
347 if self.disabled: return
348 self.send(e.type,e)
349 return self.event(e)
350 # return
351 # import app
352 # if hasattr(app.App,'app'):
353 # app.App.app.events.append((self,e))
354
355 def event(self,e):
356 """Template method - called when an event is passed to this object.
357
358 <p>Please note that if you use an event, returning the value True
359 will stop parent containers from also using the event. (For example, if
360 your widget handles TABs or arrow keys, and you don't want those to
361 also alter the focus.)</p>
362
363 <dl>
364 <dt>e<dd>event
365 </dl>
366 """
367
368 return
369
370 # Returns the top-level widget (usually the Desktop) by following the
371 # chain of 'container' references.
372 def get_toplevel(self):
373 top = self
374 while (getattr(top, "container", None)):
375 top = top.container
376 return top
377