Commit before breaking everything
[matches/honours.git] / research / transmission_spectroscopy / simulator / pgu-0.18 / build / lib / pgu / gui / container.py
1 """Defines the Container class which is the base class for all widgets that contain other widgets.
2 """
3 import pygame
4 from pygame.locals import *
5
6 from .const import *
7 from . import widget, surface
8 from . import pguglobals
9 from .surface import ProxySurface
10 from .errors import StyleError
11
12 class Container(widget.Widget):
13     """The base container widget, can be used as a template as well as stand alone."""
14
15     # The widget in this container that the mouse is hovering over
16     myhover = None
17     # The widget that has input focus in this container
18     myfocus = None
19     # The currently open window
20     mywindow = None
21
22     def __init__(self,**params):
23         widget.Widget.__init__(self,**params)
24         self.widgets = []
25         self.windows = []
26         self.toupdate = {}
27         self.topaint = {}
28     
29     def update(self,s):
30         updates = []
31         
32         if self.myfocus: self.toupdate[self.myfocus] = self.myfocus
33         
34         # Paint all child widgets, skipping over the currently open window (if any)
35         for w in self.topaint:
36             if w is self.mywindow:
37                 continue
38             sub = surface.subsurface(s,w.rect)
39             #if (hasattr(w, "_container_bkgr")):
40             #    sub.blit(w._container_bkgr,(0,0))
41
42             # Check for the theme alpha hack. This is needed (for now) to accomodate rendering
43             # images with alpha blending (eg alpha value between fully opaque and fully transparent). 
44             # Normally in PGU when a widget changes state (eg normal to highlighted state) the new 
45             # widget appearance is rendered directly overtop of the old image. This usually works okay.
46             #
47             # However, if the images have a different shape, then the new image may fail to 
48             # completely paint over the old image, leaving bits behind. As well, if the images use
49             # alpha blending you have a similar situation where the old image isn't completely
50             # covered by the new image (since alpha blending preserves some of the pixel data). The
51             # work-around is to paint the background behind the image, then paint the new image.
52             # And that's what this hack does.
53             try:
54                 # This hack isn't perfect and so it's not enabled by default, but only by
55                 # themes that explicitly request it.
56                 alpha = pguglobals.app.theme.getstyle("pgu", "", "themealpha")
57             except StyleError,e:
58                 alpha = False
59
60             if (alpha):
61                 # Look for the first parent container that has a rendered background
62                 cnt = self
63                 (x, y) = w._rect_content.topleft
64                 while (cnt and not cnt.background):
65                     cnt = cnt.container
66
67 #                if (cnt and cnt.background):
68 #                    if (cnt._rect_content):
69 #                        x += cnt._rect_content.left
70 #                        y += cnt._rect_content.top
71 #                    r1 = cnt.get_abs_rect()
72 #                    r2 = w.get_abs_rect()
73 #                    x = r2.left - r1.left
74 #                    y = r2.top - r1.top
75 #                    subrect = (x, y, sub.get_width(), sub.get_height())
76 #                    tmp = pygame.Surface(r1.size).convert_alpha()
77 #                    tmp.set_clip(subrect)
78 #                    cnt.background.paint(tmp)
79 #                    tmp.set_clip()
80 #                    sub.blit(tmp, (0,0), subrect)
81                 if (cnt):
82                     # Paint the background. This works reasonably okay but it's not exactly correct.
83                     r1 = cnt.get_abs_rect()
84                     r2 = w.get_abs_rect()
85                     x = r2.left - r1.left
86                     y = r2.top - r1.top
87                     cnt.background.paint(sub) #, size=r1.size, offset=(x, y))
88
89             w.paint(sub)
90             updates.append(pygame.rect.Rect(w.rect))
91         
92         # Update the child widgets, excluding the open window
93         for w in self.toupdate:
94             if w is self.mywindow:
95                 continue
96             us = w.update(surface.subsurface(s,w.rect))
97             if us:
98                 for u in us:
99                     updates.append(pygame.rect.Rect(u.x + w.rect.x,u.y+w.rect.y,u.w,u.h))
100
101         # Now handle the open window (if any)        
102         if self.mywindow:
103             # Render the window
104             self.mywindow.paint(self.top_surface(s,self.mywindow))
105             updates.append(pygame.rect.Rect(self.mywindow.rect))
106             # Update the surface
107             us = self.mywindow.update(self.top_surface(s,self.mywindow))
108             if us:
109                 for u in us:
110                     updates.append(pygame.rect.Rect(
111                         u.x + self.mywindow.rect.x,
112                         u.y + self.mywindow.rect.y,
113                         u.w, u.h))
114         
115         self.topaint = {}
116         self.toupdate = {}
117         
118         return updates
119
120     def repaint(self,w=None):
121         if not w:
122             return widget.Widget.repaint(self)
123         self.topaint[w] = w
124         self.reupdate()
125     
126     def reupdate(self,w=None):
127         if not w:
128             return widget.Widget.reupdate(self)
129         self.toupdate[w] = w
130         self.reupdate()
131     
132     def paint(self,s):
133         self.toupdate = {}
134         self.topaint = {}
135         for w in self.widgets:
136             try:
137                 sub = surface.subsurface(s, w.rect)
138             except: 
139                 print('container.paint(): %s not inside %s' % (
140                     w.__class__.__name__,self.__class__.__name__))
141                 print(s.get_width(), s.get_height(), w.rect)
142                 print("")
143             else:
144                 w.paint(sub)
145
146         for w in self.windows:
147             w.paint(self.top_surface(s,w))
148     
149     def top_surface(self,s,w):
150         x,y = s.get_abs_offset()
151         s = s.get_abs_parent()
152         return surface.subsurface(s,(x+w.rect.x,y+w.rect.y,w.rect.w,w.rect.h))
153     
154     def event(self,e):
155         used = False
156         
157         if self.mywindow and e.type == MOUSEBUTTONDOWN:
158             w = self.mywindow
159             if self.myfocus is w:
160                 if not w.collidepoint(e.pos): self.blur(w)
161             if not self.myfocus:
162                 if w.collidepoint(e.pos): self.focus(w)
163         
164         if not self.mywindow:
165             #### by Gal Koren
166             ##
167             ## if e.type == FOCUS:
168             if e.type == FOCUS and not self.myfocus:
169                 #self.first()
170                 pass
171             elif e.type == EXIT:
172                 if self.myhover: self.exit(self.myhover)
173             elif e.type == BLUR:
174                 if self.myfocus: self.blur(self.myfocus)
175             elif e.type == MOUSEBUTTONDOWN:
176                 h = None
177                 for w in self.widgets:
178                     if not w.disabled: 
179                         # Focusable not considered, since that is only for tabs
180                         if w.collidepoint(e.pos):
181                             h = w
182                             if self.myfocus is not w: self.focus(w)
183                 if not h and self.myfocus:
184                     self.blur(self.myfocus)
185             elif e.type == MOUSEMOTION:
186                 if 1 in e.buttons:
187                     if self.myfocus: ws = [self.myfocus]
188                     else: ws = []
189                 else: ws = self.widgets
190                 
191                 h = None
192                 for w in ws:
193                     if w.collidepoint(e.pos):
194                         h = w
195                         if self.myhover is not w: 
196                             self.enter(w)
197                         break
198                 if not h and self.myhover:
199                     self.exit(self.myhover)
200                 w = self.myhover
201                 
202                 if w and w is not self.myfocus:
203                     sub = pygame.event.Event(e.type,{
204                         'buttons':e.buttons,
205                         'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y),
206                         'rel':e.rel})
207                     used = w._event(sub)
208         
209         w = self.myfocus
210         if w:
211             if e.type == MOUSEBUTTONUP or e.type == MOUSEBUTTONDOWN:
212                 sub = pygame.event.Event(e.type,{
213                     'button':e.button,
214                     'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y)})
215             elif e.type == CLICK and self.myhover is w:
216                 sub = pygame.event.Event(e.type,{
217                     'button':e.button,
218                     'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y)})
219             elif e.type == MOUSEMOTION:
220                 sub = pygame.event.Event(e.type,{
221                     'buttons':e.buttons,
222                     'pos':(e.pos[0]-w.rect.x,e.pos[1]-w.rect.y),
223                     'rel':e.rel})
224             elif (e.type == KEYDOWN or e.type == KEYUP):
225                 sub = e
226             else:
227                 sub = None
228
229             #elif e.type == CLICK: #a dead click
230             #    sub = None
231
232             if (sub):
233                 used = w._event(sub)
234
235         if not used and e.type is KEYDOWN:
236             if e.key is K_TAB and self.myfocus:
237                 if (e.mod&KMOD_SHIFT) == 0:
238                     self.myfocus.next()
239                 else:
240                     self.myfocus.previous()
241                     return True
242             elif e.key == K_UP: 
243                 self._move_focus(0,-1)
244                 return True
245             elif e.key == K_RIGHT:
246                 self._move_focus(1,0)
247                 return True
248             elif e.key == K_DOWN:
249                 self._move_focus(0,1)
250                 return True
251             elif e.key == K_LEFT:
252                 self._move_focus(-1,0)
253                 return True
254         return used
255
256     def _move_focus(self,dx_,dy_):
257         myfocus = self.myfocus
258         if not self.myfocus: return
259         
260         widgets = self._get_widgets(pguglobals.app)
261         #if myfocus not in widgets: return
262         #widgets.remove(myfocus)
263         if myfocus in widgets:
264             widgets.remove(myfocus)
265         rect = myfocus.get_abs_rect()
266         fx,fy = rect.centerx,rect.centery
267         
268         def sign(v):
269             if v < 0: return -1
270             if v > 0: return 1
271             return 0
272         
273         dist = []
274         for w in widgets:
275             wrect = w.get_abs_rect()
276             wx,wy = wrect.centerx,wrect.centery
277             dx,dy = wx-fx,wy-fy
278             if dx_ > 0 and wrect.left < rect.right: continue
279             if dx_ < 0 and wrect.right > rect.left: continue
280             if dy_ > 0 and wrect.top < rect.bottom: continue
281             if dy_ < 0 and wrect.bottom > rect.top: continue
282             dist.append((dx*dx+dy*dy,w))
283         if not len(dist): return
284         dist.sort()
285         d,w = dist.pop(0)
286         w.focus()
287         
288     def _get_widgets(self,c):
289         widgets = []
290         if c.mywindow:
291             widgets.extend(self._get_widgets(c.mywindow))
292         else:
293             for w in c.widgets:
294                 if isinstance(w,Container):
295                     widgets.extend(self._get_widgets(w))
296                 elif not w.disabled and w.focusable:
297                     widgets.append(w)
298         return widgets
299
300     def remove(self,w):
301         """Remove a widget from the container."""
302         self.blur(w)
303         self.widgets.remove(w)
304         #self.repaint()
305         self.chsize()
306     
307     def add(self,w,x,y):
308         """Add a widget to the container given the position."""
309         w.style.x = x
310         w.style.y = y 
311         w.container = self
312         #NOTE: this might fix it, sort of...
313         #but the thing is, we don't really want to resize
314         #something if it is going to get resized again later
315         #for no reason...
316         #w.rect.x,w.rect.y = w.style.x,w.style.y
317         #w.rect.w, w.rect.h = w.resize()
318         self.widgets.append(w)
319         self.chsize()
320     
321     def open(self,w=None,x=None,y=None):
322         if (not w):
323             w = self
324
325         if (x != None):
326             # The position is relative to this container
327             rect = self.get_abs_rect()
328             pos = (rect.x + x, rect.y + y)
329         else:
330             pos = None
331         # Have the application open the window
332         pguglobals.app.open(w, pos)
333         
334     def focus(self,w=None):
335         widget.Widget.focus(self) ### by Gal koren
336 #        if not w:
337 #            return widget.Widget.focus(self)
338         if not w: return
339         if self.myfocus: self.blur(self.myfocus)
340         if self.myhover is not w: self.enter(w)
341         self.myfocus = w
342         w._event(pygame.event.Event(FOCUS))
343         
344         #print self.myfocus,self.myfocus.__class__.__name__
345     
346     def blur(self,w=None):
347         if not w:
348             return widget.Widget.blur(self)
349         if self.myfocus is w:
350             if self.myhover is w: self.exit(w)
351             self.myfocus = None
352             w._event(pygame.event.Event(BLUR))
353     
354     def enter(self,w):
355         if self.myhover: self.exit(self.myhover)
356         self.myhover = w
357         w._event(pygame.event.Event(ENTER))
358     
359     def exit(self,w):
360         if self.myhover and self.myhover is w:
361             self.myhover = None
362             w._event(pygame.event.Event(EXIT))    
363     
364     
365 #     def first(self):
366 #         for w in self.widgets:
367 #             if w.focusable:
368 #                 self.focus(w)
369 #                 return
370 #         if self.container: self.container.next(self)
371     
372 #     def next(self,w):
373 #         if w not in self.widgets: return #HACK: maybe.  this happens in windows for some reason...
374 #         
375 #         for w in self.widgets[self.widgets.index(w)+1:]:
376 #             if w.focusable:
377 #                 self.focus(w)
378 #                 return
379 #         if self.container: return self.container.next(self)
380     
381     
382     def _next(self,orig=None):
383         start = 0
384         if orig in self.widgets: start = self.widgets.index(orig)+1
385         for w in self.widgets[start:]:
386             if not w.disabled and w.focusable:
387                 if isinstance(w,Container):
388                     if w._next():
389                         return True
390                 else:
391                     self.focus(w)
392                     return True
393         return False
394     
395     def _previous(self,orig=None):
396         end = len(self.widgets)
397         if orig in self.widgets: end = self.widgets.index(orig)
398         ws = self.widgets[:end]
399         ws.reverse()
400         for w in ws:
401             if not w.disabled and w.focusable:
402                 if isinstance(w,Container):
403                     if w._previous():
404                         return True
405                 else:
406                     self.focus(w)
407                     return True
408         return False
409                 
410     def next(self,w=None):
411         if w != None and w not in self.widgets: return #HACK: maybe.  this happens in windows for some reason...
412         
413         if self._next(w): return True
414         if self.container: return self.container.next(self)
415     
416     
417     def previous(self,w=None):
418         if w != None and w not in self.widgets: return #HACK: maybe.  this happens in windows for some reason...
419         
420         if self._previous(w): return True
421         if self.container: return self.container.previous(self)
422     
423     def resize(self,width=None,height=None):
424         #r = self.rect
425         #r.w,r.h = 0,0
426         ww,hh = 0,0
427         if self.style.width: ww = self.style.width
428         if self.style.height: hh = self.style.height
429         
430         for w in self.widgets:
431             #w.rect.w,w.rect.h = 0,0
432             w.rect.x,w.rect.y = w.style.x,w.style.y
433             w.rect.w, w.rect.h = w.resize()
434             #w._resize()
435             
436             ww = max(ww,w.rect.right)
437             hh = max(hh,w.rect.bottom)
438         return ww,hh
439
440     # Returns the widget with the given name
441     def find(self, name):
442         for w in self.widgets:
443             if (w.name == name):
444                 return w
445             elif (isinstance(w, Container)):
446                 tmp = w.find(name)
447                 if (tmp): return tmp
448         return None
449

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