Commit before breaking everything
[matches/honours.git] / research / transmission_spectroscopy / simulator / pgu-0.18 / build / lib / pgu / gui / widget.py
1 """This modules defines the Widget class, which is the base of the PGU widget
2 hierarchy."""
3
4 import pygame
5
6 from . import pguglobals
7 from . import style
8 from .errors import PguError
9
10 class SignalCallback:
11     # The function to call
12     func = None
13     # The parameters to pass to the function (as a list)
14     params = None
15
16 class Widget(object):
17     """Base class for all PGU graphical objects.
18
19     Example - Creating your own Widget:
20
21         class Draw(gui.Widget):
22             def paint(self,s):
23                 # Paint the pygame.Surface
24                 return
25             
26             def update(self,s):
27                 # Update the pygame.Surface and return the update rects
28                 return [pygame.Rect(0,0,self.rect.w,self.rect.h)]
29                 
30             def event(self,e):
31                 # Handle the pygame.Event
32                 return
33                 
34             def resize(self,width=None,height=None):
35                 # Return the width and height of this widget
36                 return 256,256
37     """
38
39     # The name of the widget (or None if not defined)
40     name = None
41     # The container this widget belongs to
42     container = None
43     # Whether this widget has been painted yet
44     _painted = False
45     # The widget used to paint the background
46     background = None
47     # ...
48     _rect_margin = None
49     _rect_border = None
50     _rect_padding = None
51     _rect_content = None
52     # A dictionary of signal callbacks, hashed by signal ID
53     connects = None
54     # The area covered by the widget, relative to the parent widget
55     rect = None
56     
57     def __init__(self, **params): 
58         """Create a new Widget instance given the style parameters.
59
60         Keyword arguments:
61             decorate -- whether to call theme.decorate(self) to allow the 
62                 theme a chance to decorate the widget. (default is true)
63             style -- a dict of style parameters.
64             x, y -- position parameters
65             width, height -- size parameters
66             align, valign -- alignment parameters, passed along to style
67             font -- the font to use with this widget
68             color -- the color property, if applicable
69             background -- the widget used to paint the background
70             cls -- class name as used by Theme
71             name -- name of widget as used by Form.  If set, will call 
72                 form.add(self,name) to add the widget to the most recently 
73                 created Form.
74             focusable -- True if this widget can receive focus via Tab, etc.
75                 (default is True)
76             disabled -- True of this widget is disabled (defaults is False)
77             value -- initial value
78
79         """
80         #object.Object.__init__(self) 
81         self.connects = {}
82         params.setdefault('decorate',True)
83         params.setdefault('style',{})
84         params.setdefault('focusable',True)
85         params.setdefault('disabled',False)
86         
87         self.focusable = params['focusable']
88         self.disabled = params['disabled']
89         
90         self.rect = pygame.Rect(params.get('x',0),
91                                 params.get('y',0),
92                                 params.get('width',0),
93                                 params.get('height',0))
94         
95         s = params['style']
96         #some of this is a bit "theme-ish" but it is very handy, so these
97         #things don't have to be put directly into the style.
98         for att in ('align','valign','x','y','width','height','color','font','background'):
99             if att in params: s[att] = params[att]
100         self.style = style.Style(self,s)
101         
102         self.cls = 'default'
103         if 'cls' in params: self.cls = params['cls']
104         if 'name' in params:    
105             from . import form
106             self.name = params['name']
107             if form.Form.form:
108                 form.Form.form.add(self)
109                 self.form = form.Form.form
110         if 'value' in params: self.value = params['value']
111         self.pcls = ""
112         
113         if params['decorate'] != False:
114             if (not pguglobals.app):
115                 # TODO - fix this somehow
116                 from . import app
117                 app.App()
118             pguglobals.app.theme.decorate(self,params['decorate'])
119
120     def focus(self):
121         """Focus this Widget."""
122         if self.container: 
123             if self.container.myfocus != self:  ## by Gal Koren
124                 self.container.focus(self)
125
126     def blur(self): 
127         """Blur this Widget."""
128         if self.container: self.container.blur(self)
129
130     def open(self):
131         """Open this widget as a modal dialog."""
132         #if getattr(self,'container',None) != None: self.container.open(self)
133         pguglobals.app.open(self)
134
135     def close(self, w=None):
136         """Close this widget, if it is currently an open dialog."""
137         #if getattr(self,'container',None) != None: self.container.close(self)
138         if (not w):
139             w = self
140         pguglobals.app.close(w)
141
142     def is_open(self):
143         return (self in pguglobals.app.windows)
144
145     def is_hovering(self):
146         """Returns true if the mouse is hovering over this widget."""
147         if self.container:
148             return (self.container.myhover is self)
149         return False
150
151     def resize(self,width=None,height=None):
152         """Resize this widget and all sub-widgets, returning the new size.
153
154         This should be implemented by a subclass.
155
156         """
157         return (self.style.width, self.style.height)
158
159     def chsize(self):
160         """Signal that this widget has changed its size."""
161         
162         if (not self._painted): 
163             return
164         
165         if (not self.container): 
166             return
167         
168         if (pguglobals.app):
169             pguglobals.app.chsize()
170
171     def update(self,s):
172         """Updates the surface and returns a rect list of updated areas
173
174         This should be implemented by a subclass.
175
176         """
177         return
178         
179     def paint(self,s):
180         """Render this widget onto the given surface
181
182         This should be implemented by a subclass.
183
184         """
185         return
186
187     def repaint(self): 
188         """Request a repaint of this Widget."""
189         if self.container: self.container.repaint(self)
190         #pguglobals.app.repaint_widget(self)
191
192     def repaintall(self):
193         """Request a repaint of all Widgets."""
194         if self.container: self.container.repaintall()
195
196     def reupdate(self): 
197         """Request a reupdate of this Widget."""
198         if self.container: self.container.reupdate(self)
199
200     def next(self): 
201         """Pass focus to next Widget.
202         
203         Widget order determined by the order they were added to their container.
204
205         """
206         if self.container: self.container.next(self)
207
208     def previous(self): 
209         """Pass focus to previous Widget.
210         
211         Widget order determined by the order they were added to their container.
212
213         """
214         
215         if self.container: self.container.previous(self)
216     
217     def get_abs_rect(self):
218         """Returns the absolute rect of this widget on the App screen."""
219         x, y = self.rect.x, self.rect.y
220         cnt = self.container
221         while cnt:
222             x += cnt.rect.x
223             y += cnt.rect.y
224             if cnt._rect_content:
225                 x += cnt._rect_content.x
226                 y += cnt._rect_content.y
227             cnt = cnt.container
228         return pygame.Rect(x, y, self.rect.w, self.rect.h)
229
230     def connect(self,code,func,*params):
231         """Connect an event code to a callback function.
232         
233         Note that there may be multiple callbacks per event code.
234
235         Arguments:
236             code -- event type
237             fnc -- callback function
238             *values -- values to pass to callback.  
239
240         Please note that callbacks may also have "magicaly" parameters.  
241         Such as:
242
243             _event -- receive the event
244             _code -- receive the event code
245             _widget -- receive the sending widget
246         
247         Example:
248             def onclick(value):
249                 print ('click', value)
250             
251             w = Button("PGU!")
252             w.connect(gui.CLICK,onclick,'PGU Button Clicked')
253
254         """
255         if (not code in self.connects):
256             self.connects[code] = []
257         for cb in self.connects[code]:
258             if (cb.func == func):
259                 # Already connected to this callback function
260                 return
261         # Wrap the callback function and add it to the list
262         cb = SignalCallback()
263         cb.func = func
264         cb.params = params
265         self.connects[code].append(cb)
266
267     # Remove signal handlers from the given event code. If func is specified,
268     # only those handlers will be removed. If func is None, all handlers
269     # will be removed.
270     def disconnect(self, code, func=None):
271         if (not code in self.connects):
272             return
273         if (not func):
274             # Remove all signal handlers
275             del self.connects[code]
276         else:
277             # Remove handlers that call 'func'
278             n = 0
279             callbacks = self.connects[code]
280             while (n < len(callbacks)):
281                 if (callbacks[n].func == func):
282                     # Remove this callback
283                     del callbacks[n]
284                 else:
285                     n += 1
286
287     def send(self,code,event=None):
288         """Send a code, event callback trigger."""
289         if (not code in self.connects):
290             return
291         # Trigger all connected signal handlers
292         for cb in self.connects[code]:
293             func = cb.func
294             values = list(cb.params)
295
296             # Attempt to be compatible with previous versions of python
297             try:
298                 code = func.__code__
299             except:
300                 code = func.func_code
301
302             nargs = code.co_argcount
303             names = list(code.co_varnames)[:nargs]
304
305             # If the function is bound to an instance, remove the first argument name. Again
306             # we keep compatibility with older versions of python.
307             if (hasattr(func, "__self__") and hasattr(func.__self__, "__class__") or 
308                 hasattr(func,'im_class')): 
309                 names.pop(0)
310             
311             args = []
312             magic = {'_event':event,'_code':code,'_widget':self}
313             for name in names:
314                 if name in magic.keys():
315                     args.append(magic[name])
316                 elif len(values):
317                     args.append(values.pop(0))
318                 else:
319                     break
320             args.extend(values)
321             func(*args)
322     
323     def _event(self,e):
324         if self.disabled: return
325         self.send(e.type,e)
326         return self.event(e)
327         
328     def event(self,e):
329         """Called when an event is passed to this object.
330         
331         Please note that if you use an event, returning the value True
332         will stop parent containers from also using the event.  (For example, if
333         your widget handles TABs or arrow keys, and you don't want those to 
334         also alter the focus.)
335
336         This should be implemented by a subclass.
337
338         """
339         return
340
341     def get_toplevel(self):
342         """Returns the top-level widget (usually the Desktop) by following the
343         chain of 'container' references."""
344         top = self
345         while (top.container):
346             top = top.container
347         return top
348
349     def collidepoint(self, pos):
350         """Test if the given point hits this widget. Over-ride this function
351         for more advanced collision testing."""
352         return self.rect.collidepoint(pos)
353

UCC git Repository :: git.ucc.asn.au