comparison engine/extensions/pychan/widgets/containers.py @ 248:a2d5e2721489

widgets.py split up.
author phoku@33b003aa-7bff-0310-803a-e67f0ece8222
date Thu, 26 Mar 2009 16:20:16 +0000
parents
children 1cc51d145af9
comparison
equal deleted inserted replaced
247:040387b7167f 248:a2d5e2721489
1 # -*- coding: utf-8 -*-
2
3 from common import *
4 from widget import Widget
5
6 ### Containers + Layout code ###
7
8 class Container(Widget):
9 """
10 This is the basic container class. It provides space in which child widgets can
11 be position via the position attribute. If you want to use the layout engine,
12 you have to use derived containers with vertical or horizontal orientation
13 (L{VBox} or L{HBox})
14
15 New Attributes
16 ==============
17
18 - padding - Integer: Not used in the Container class istelf, distance between child widgets.
19 - background_image - Set this to a GuiImage or a resource location (simply a filename).
20 The image will be tiled over the background area.
21 - opaque - Boolean: Whether the background should be drawn at all. Set this to False
22 to make the widget transparent.
23 - children - Just contains the list of contained child widgets. Do NOT modify.
24 """
25
26 ATTRIBUTES = Widget.ATTRIBUTES + [ IntAttr('padding'), Attr('background_image'), BoolAttr('opaque'),PointAttr('margins') ]
27
28 def __init__(self,padding=5,margins=(5,5),_real_widget=None, **kwargs):
29 self.real_widget = _real_widget or fife.Container()
30 self.children = []
31 self.margins = margins
32 self.padding = padding
33 self._background = []
34 self._background_image = None
35 super(Container,self).__init__(**kwargs)
36
37 def addChild(self, widget):
38 widget.parent = self
39 self.children.append(widget)
40 self.real_widget.add(widget.real_widget)
41
42 def removeChild(self,widget):
43 if not widget in self.children:
44 raise RuntimeError("%s does not have %s as direct child widget." % (str(self),str(widget)))
45 self.children.remove(widget)
46 self.real_widget.remove(widget.real_widget)
47 widget.parent = None
48
49 def add(self,*widgets):
50 print "PyChan: Deprecation warning: Please use 'addChild' or 'addChildren' instead."
51 self.addChildren(*widgets)
52
53 def getMaxChildrenWidth(self):
54 if not self.children: return 0
55 return max(widget.width for widget in self.children)
56
57 def getMaxChildrenHeight(self):
58 if not self.children: return 0
59 return max(widget.height for widget in self.children)
60
61 def deepApply(self,visitorFunc):
62 for child in self.children:
63 child.deepApply(visitorFunc)
64 visitorFunc(self)
65
66 def beforeShow(self):
67 self._resetTiling()
68
69 def _resetTiling(self):
70 image = self._background_image
71 if image is None:
72 return
73
74 back_w,back_h = self.width, self.height
75 image_w, image_h = image.getWidth(), image.getHeight()
76
77 map(self.real_widget.remove,self._background)
78
79 # Now tile the background over the widget
80 self._background = []
81 icon = fife.Icon(image)
82 x, w = 0, image_w
83 while x < back_w:
84 y, h = 0, image_h
85 while y < self.height:
86 icon = fife.Icon(image)
87 icon.setPosition(x,y)
88 self._background.append(icon)
89 y += h
90 x += w
91 map(self.real_widget.add,self._background)
92 for tile in self._background:
93 tile.requestMoveToBottom()
94
95 def setBackgroundImage(self,image):
96 self._background = getattr(self,'_background',None)
97 if image is None:
98 self._background_image = image
99 map(self.real_widget.remove,self._background)
100 self._background = []
101
102 # Background generation is done in _resetTiling
103
104 if not isinstance(image, fife.GuiImage):
105 image = get_manager().loadImage(image)
106 self._background_image = image
107
108 def getBackgroundImage(self): return self._background_image
109 background_image = property(getBackgroundImage,setBackgroundImage)
110
111 def _setOpaque(self,opaque): self.real_widget.setOpaque(opaque)
112 def _getOpaque(self): return self.real_widget.isOpaque()
113 opaque = property(_getOpaque,_setOpaque)
114
115 AlignTop, AlignBottom, AlignLeft, AlignRight, AlignCenter = range(5)
116
117 class LayoutBase(object):
118 """
119 This class is at the core of the layout engine. The two MixIn classes L{VBoxLayoutMixin}
120 and L{HBoxLayoutMixin} specialise on this by reimplementing the C{resizeToContent} and
121 the C{expandContent} methods.
122
123 Dynamic Layouting
124 -----------------
125
126 The layout is calculated in the L{Widget.show} method. Thus if you modify the layout,
127 by adding or removing child widgets for example, you have to call L{Widget.adaptLayout}
128 so that the changes ripple through the widget hierachy.
129
130 Internals
131 ---------
132
133 At the core the layout engine works in two passes:
134
135 Before a root widget loaded by the XML code is shown, its resizeToContent method
136 is called recursively (walking the widget containment relation in post order).
137 This shrinks all HBoxes and VBoxes to their minimum heigt and width.
138 After that the expandContent method is called recursively in the same order,
139 which will re-align the widgets if there is space left AND if a Spacer is contained.
140
141 Inside bare Container instances (without a Layout MixIn) absolute positioning
142 can be used.
143 """
144 def __init__(self,align = (AlignLeft,AlignTop), **kwargs):
145 self.align = align
146 self.spacer = None
147 super(LayoutBase,self).__init__(**kwargs)
148
149 def addSpacer(self,spacer):
150 if self.spacer:
151 raise RuntimeException("Already a Spacer in %s!" % str(self))
152 self.spacer = spacer
153 spacer.index = len(self.children)
154
155 def xdelta(self,widget):return 0
156 def ydelta(self,widget):return 0
157
158 def _adjustHeight(self):
159 if self.align[1] == AlignTop:return #dy = 0
160 if self.align[1] == AlignBottom:
161 y = self.height - self.childarea[1] - self.border_size - self.margins[1]
162 else:
163 y = (self.height - self.childarea[1] - self.border_size - self.margins[1])/2
164 for widget in self.children:
165 widget.y = y
166 y += self.ydelta(widget)
167
168 def _adjustHeightWithSpacer(self):
169 pass
170
171 def _adjustWidth(self):
172 if self.align[0] == AlignLeft:return #dx = 0
173 if self.align[0] == AlignRight:
174 x = self.width - self.childarea[0] - self.border_size - self.margins[0]
175 else:
176 x = (self.width - self.childarea[0] - self.border_size - self.margins[0])/2
177 for widget in self.children:
178 widget.x = x
179 x += self.xdelta(widget)
180
181 def _expandWidthSpacer(self):
182 x = self.border_size + self.margins[0]
183 xdelta = map(self.xdelta,self.children)
184
185 for widget in self.children[:self.spacer.index]:
186 widget.x = x
187 x += xdelta.pop(0)
188
189 x = self.width - sum(xdelta) - self.border_size - self.margins[0]
190 for widget in self.children[self.spacer.index:]:
191 widget.x = x
192 x += xdelta.pop(0)
193
194 def _expandHeightSpacer(self):
195 y = self.border_size + self.margins[1]
196 ydelta = map(self.ydelta,self.children)
197
198 for widget in self.children[:self.spacer.index]:
199 widget.y = y
200 y += ydelta.pop(0)
201
202 y = self.height - sum(ydelta) - self.border_size - self.margins[1]
203 for widget in self.children[self.spacer.index:]:
204 widget.y = y
205 y += ydelta.pop(0)
206
207
208 class VBoxLayoutMixin(LayoutBase):
209 """
210 A mixin class for a vertical layout. Do not use directly.
211 """
212 def __init__(self,**kwargs):
213 super(VBoxLayoutMixin,self).__init__(**kwargs)
214
215 def resizeToContent(self, recurse = True):
216 max_w = self.getMaxChildrenWidth()
217 x = self.margins[0] + self.border_size
218 y = self.margins[1] + self.border_size
219 for widget in self.children:
220 widget.x = x
221 widget.y = y
222 widget.width = max_w
223 y += widget.height + self.padding
224
225 #Add the padding for the spacer.
226 if self.spacer:
227 y += self.padding
228
229 self.height = y + self.margins[1] - self.padding
230 self.width = max_w + 2*x
231 self.childarea = max_w, y - self.padding - self.margins[1]
232
233 self._adjustHeight()
234 self._adjustWidth()
235
236 def expandContent(self):
237 if self.spacer:
238 self._expandHeightSpacer()
239
240 def ydelta(self,widget):return widget.height + self.padding
241
242 class HBoxLayoutMixin(LayoutBase):
243 """
244 A mixin class for a horizontal layout. Do not use directly.
245 """
246 def __init__(self,**kwargs):
247 super(HBoxLayoutMixin,self).__init__(**kwargs)
248
249 def resizeToContent(self, recurse = True):
250 max_h = self.getMaxChildrenHeight()
251 x = self.margins[0] + self.border_size
252 y = self.margins[1] + self.border_size
253 for widget in self.children:
254 widget.x = x
255 widget.y = y
256 widget.height = max_h
257 x += widget.width + self.padding
258
259 #Add the padding for the spacer.
260 if self.spacer:
261 x += self.padding
262
263 self.width = x + self.margins[0] - self.padding
264 self.height = max_h + 2*y
265 self.childarea = x - self.margins[0] - self.padding, max_h
266
267 self._adjustHeight()
268 self._adjustWidth()
269
270 def expandContent(self):
271 if self.spacer:
272 self._expandWidthSpacer()
273
274 def xdelta(self,widget):return widget.width + self.padding
275
276
277 class VBox(VBoxLayoutMixin,Container):
278 """
279 A vertically aligned box - for containement of child widgets.
280
281 Widgets added to this container widget, will layout on top of each other.
282 Also the minimal width of the container will be the maximum of the minimal
283 widths of the contained widgets.
284
285 The default alignment is to the top. This can be changed by adding a Spacer
286 to the widget at any point (but only one!). The spacer will expand, so that
287 widgets above the spacer are aligned to the top, while widgets below the spacer
288 are aligned to the bottom.
289 """
290 def __init__(self,padding=5,**kwargs):
291 super(VBox,self).__init__(**kwargs)
292 self.padding = padding
293
294
295 class HBox(HBoxLayoutMixin,Container):
296 """
297 A horizontally aligned box - for containement of child widgets.
298
299 Please see L{VBox} for details - just change the directions :-).
300 """
301 def __init__(self,padding=5,**kwargs):
302 super(HBox,self).__init__(**kwargs)
303 self.padding = padding
304
305 class Window(VBoxLayoutMixin,Container):
306 """
307 A L{VBox} with a draggable title bar aka a window
308
309 New Attributes
310 ==============
311
312 - title: The Caption of the window
313 - titlebar_height: The height of the window title bar
314 """
315
316 ATTRIBUTES = Container.ATTRIBUTES + [ UnicodeAttr('title'), IntAttr('titlebar_height') ]
317
318 def __init__(self,title=u"title",titlebar_height=0,**kwargs):
319 super(Window,self).__init__(_real_widget = fife.Window(), **kwargs)
320 if titlebar_height == 0:
321 titlebar_height = self.real_font.getHeight() + 4
322 self.titlebar_height = titlebar_height
323 self.title = title
324
325 # Override explicit positioning
326 self.position_technique = "automatic"
327
328
329 def _getTitle(self): return gui2text(self.real_widget.getCaption())
330 def _setTitle(self,text): self.real_widget.setCaption(text2gui(text))
331 title = property(_getTitle,_setTitle)
332
333 def _getTitleBarHeight(self): return self.real_widget.getTitleBarHeight()
334 def _setTitleBarHeight(self,h): self.real_widget.setTitleBarHeight(h)
335 titlebar_height = property(_getTitleBarHeight,_setTitleBarHeight)
336
337 # Hackish way of hiding that title bar height in the perceived height.
338 # Fixes VBox calculation
339 def _setHeight(self,h):
340 h = max(self.min_size[1],h)
341 h = min(self.max_size[1],h)
342 self.real_widget.setHeight(h + self.titlebar_height)
343 def _getHeight(self): return self.real_widget.getHeight() - self.titlebar_height
344 height = property(_getHeight,_setHeight)
345
346
347 # Spacer
348
349 class Spacer(object):
350 """ A spacer represents expandable 'whitespace' in the GUI.
351
352 In a XML file you can get this by adding a <Spacer /> inside a VBox or
353 HBox element (Windows implicitly are VBox elements).
354
355 The effect is, that elements before the spacer will be left (top)
356 and elements after the spacer will be right (bottom) aligned.
357
358 There can only be one spacer in VBox (HBox).
359 """
360 def __init__(self,parent=None,**kwargs):
361 self._parent = parent
362
363 def __str__(self):
364 return "Spacer(parent.name='%s')" % getattr(self._parent,'name','None')
365
366 def __repr__(self):
367 return "<Spacer(parent.name='%s') at %x>" % (getattr(self._parent,'name','None'),id(self))