Mercurial > fife-parpg
comparison engine/extensions/pychan/widgets.py @ 0:4a0efb7baf70
* Datasets becomes the new trunk and retires after that :-)
author | mvbarracuda@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Sun, 29 Jun 2008 18:44:17 +0000 |
parents | |
children | 0e39a20bdfb2 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4a0efb7baf70 |
---|---|
1 # coding: utf-8 | |
2 ### Widget/Container Base Classes ### | |
3 | |
4 """ | |
5 Widget wrappers. | |
6 | |
7 Please look at the documentation of L{Widget} for details. | |
8 """ | |
9 | |
10 import fife, pythonize | |
11 import tools | |
12 from exceptions import * | |
13 from attrs import Attr,PointAttr,ColorAttr,BoolAttr,IntAttr | |
14 | |
15 def get_manager(): | |
16 import pychan | |
17 return pychan.manager | |
18 | |
19 def _mungeText(text): | |
20 """ | |
21 This function is applied to all text set on widgets, currently only replacing tabs with four spaces. | |
22 """ | |
23 return text.replace('\t'," "*4).replace('[br]','\n') | |
24 | |
25 class _DummyImage(object): | |
26 def getWidth(self): return 0 | |
27 def getHeight(self): return 0 | |
28 | |
29 class Widget(object): | |
30 """ | |
31 This is the common widget base class, which provides most of the wrapping | |
32 functionality. | |
33 | |
34 Attributes | |
35 ========== | |
36 | |
37 Widgets are manipulated (mostly) through attributes - and these can all be set by XML attributes. | |
38 Derived widgets will have other attributes. Please see their B{New Attributes} sections. The types of the | |
39 attributes are pretty straightforward, but note that Position and Color attribute types will also accept | |
40 C{fife.Point} and C{fife.Color} values. | |
41 | |
42 - name: String: The identification of the widget, most useful if it is unique within a given widget hiarachy. | |
43 This is used to find widgets by L{mapEvents},L{distributeInitialData},L{distributeData} and L{collectData}. | |
44 - position: Position: The position relative to the parent widget - or on screen, if this is the root widget. | |
45 - size: Position: The real size of the widget (including border and margins). Usually you do not need to set this. | |
46 A notable exception is the L{ScrollArea}. | |
47 - min_size: Position: The minimal size this widget is allowed to have. This is enforced through the accessor methods | |
48 of the actual size attribute. | |
49 - max_size: Position: The maximal size this widget is allowed to have. This is enforced through the accessor methods | |
50 of the actual size attribute. | |
51 - base_color: Color | |
52 - background_color: Color | |
53 - foreground_color: Color | |
54 - font: String: This should identify a font that was loaded via L{loadFonts} before. | |
55 - border_size: Integer: The size of the border in pixels. | |
56 - position_technique: This can be either "automatic" or "explicit" - only L{Window} has this set to "automatic" which | |
57 results in new windows being centered on screen (for now). | |
58 If it is set to "explicit" the position attribute will not be touched. | |
59 | |
60 Convenience Attributes | |
61 ====================== | |
62 | |
63 These attributes are convenience/shorthand versions of above mentioned attributes and assignment will reflect | |
64 the associated attributes values. E.g. the following is equivalent:: | |
65 # Set X position, leave Y alone | |
66 widget.x = 10 | |
67 # Same here | |
68 posi = widget.position | |
69 widget.position = (10, posi[1]) | |
70 | |
71 Here they are. | |
72 | |
73 - x: Integer: The horizontal part of the position attribute. | |
74 - y: Integer: The vertical part of the position attribute. | |
75 - width: Integer: The horizontal part of the size attribute. | |
76 - height: Integer: The vertical part of the size attribute. | |
77 | |
78 """ | |
79 | |
80 ATTRIBUTES = [ Attr('name'), PointAttr('position'), | |
81 PointAttr('min_size'), PointAttr('size'), PointAttr('max_size'), | |
82 ColorAttr('base_color'),ColorAttr('background_color'),ColorAttr('foreground_color'), | |
83 Attr('style'), Attr('font'),IntAttr('border_size') | |
84 ] | |
85 | |
86 DEFAULT_NAME = '__unnamed__' | |
87 | |
88 HIDE_SHOW_ERROR = """\ | |
89 You can only show/hide the top widget of a hierachy. | |
90 Use 'addChild' or 'removeChild' to add/remove labels for example. | |
91 """ | |
92 | |
93 def __init__(self,parent = None, name = DEFAULT_NAME, | |
94 size = (-1,-1), min_size=(0,0), max_size=(5000,5000), | |
95 style = None, **kwargs): | |
96 | |
97 assert( hasattr(self,'real_widget') ) | |
98 self._has_listener = False | |
99 self._visible = False | |
100 | |
101 # Data distribution & retrieval settings | |
102 self.accepts_data = False | |
103 self.accepts_initial_data = False | |
104 | |
105 self._parent = parent | |
106 | |
107 # This will also set the _event_id and call real_widget.setActionEventId | |
108 self.name = name | |
109 | |
110 self.min_size = min_size | |
111 self.max_size = max_size | |
112 self.size = size | |
113 self.position_technique = "explicit" | |
114 self.font = 'default' | |
115 | |
116 # Inherit style | |
117 if style is None and parent: | |
118 style = parent.style | |
119 self.style = style or "default" | |
120 | |
121 # Not needed as attrib assignment will trigger manager.stylize call | |
122 #manager.stylize(self,self.style) | |
123 | |
124 def execute(self,bind): | |
125 """ | |
126 Execute a dialog synchronously. | |
127 | |
128 As argument a dictionary mapping widget names to return values | |
129 is expected. Events from these widgets will cause this function | |
130 to return with the associated return value. | |
131 | |
132 This function will not return until such an event occurs. | |
133 The widget will be shown before execution and hidden afterwards. | |
134 You can only execute root widgets. | |
135 | |
136 Note: This feature is not tested well, and the API will probably | |
137 change. Otherwise have fun:: | |
138 # Okay this a very condensed example :-) | |
139 return pychan.loadXML("contents/gui/dialog.xml").execute({ 'okButton' : True, 'closeButton' : False }) | |
140 | |
141 """ | |
142 if not get_manager().can_execute: | |
143 raise RuntimeError("Synchronous execution is not set up!") | |
144 if self._parent: | |
145 raise RuntimeError("You can only 'execute' root widgets, not %s!" % str(self)) | |
146 | |
147 for name,returnValue in bind.items(): | |
148 def _quitThisDialog(returnValue = returnValue ): | |
149 get_manager().breakFromMainLoop( returnValue ) | |
150 self.hide() | |
151 self.findChild(name=name).capture( _quitThisDialog ) | |
152 self.show() | |
153 return get_manager().mainLoop() | |
154 | |
155 def match(self,**kwargs): | |
156 """ | |
157 Matches the widget against a list of key-value pairs. | |
158 Only if all keys are attributes and their value is the same it returns True. | |
159 """ | |
160 for k,v in kwargs.items(): | |
161 if v != getattr(self,k,None): | |
162 return False | |
163 return True | |
164 | |
165 def capture(self, callback): | |
166 """ | |
167 Add a callback to be executed when the widget event occurs on this widget. | |
168 | |
169 The callback must be either a callable or None. | |
170 The old event handler (if any) will be overridden by the callback. | |
171 If None is given, the event will be disabled. You can query L{isCaptured} | |
172 wether this widgets events are currently captured. | |
173 | |
174 It might be useful to check out L{tools.callbackWithArguments}. | |
175 | |
176 """ | |
177 if callback is None: | |
178 if not get_manager().widgetEvents.has_key(self._event_id): | |
179 if get_manager().debug: | |
180 print "You passed None as parameter to %s.capture, which would normally remove a mapped event." % str(self) | |
181 print "But there was no event mapped. Did you accidently call a function instead of passing it?" | |
182 else: | |
183 del get_manager().widgetEvents[self._event_id] | |
184 if self._has_listener: | |
185 self.real_widget.removeActionListener(get_manager().guimanager) | |
186 self._has_listener = None | |
187 return | |
188 | |
189 if not callable(callback): | |
190 raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback)) | |
191 | |
192 def captured_f(event): | |
193 tools.applyOnlySuitable(callback,event=event,widget=self) | |
194 | |
195 get_manager().widgetEvents[self._event_id] = captured_f | |
196 if not self._has_listener: | |
197 self.real_widget.addActionListener(get_manager().guimanager) | |
198 self._has_listener = True | |
199 | |
200 def isCaptured(self): | |
201 """ | |
202 Check whether this widgets events are captured | |
203 (a callback is installed) or not. | |
204 """ | |
205 return self._has_listener | |
206 | |
207 def show(self): | |
208 """ | |
209 Show the widget and all contained widgets. | |
210 """ | |
211 if self._parent: | |
212 raise RuntimeError(Widget.HIDE_SHOW_ERROR) | |
213 if self._visible: return | |
214 self.adaptLayout() | |
215 self.beforeShow() | |
216 get_manager().show(self) | |
217 self._visible = True | |
218 | |
219 def hide(self): | |
220 """ | |
221 Hide the widget and all contained widgets. | |
222 """ | |
223 if self._parent: | |
224 raise RuntimeError(Widget.HIDE_SHOW_ERROR) | |
225 if not self._visible: return | |
226 get_manager().hide(self) | |
227 self.afterHide() | |
228 self._visible = False | |
229 | |
230 def isVisible(self): | |
231 """ | |
232 Check whether the widget is currently shown, | |
233 either directly or as part of a container widget. | |
234 """ | |
235 widget = self | |
236 while widget._parent: | |
237 widget = widget._parent | |
238 return widget._visible | |
239 | |
240 def adaptLayout(self): | |
241 """ | |
242 Execute the Layout engine. Automatically called by L{show}. | |
243 In case you want to relayout a visible widget, you have to call this function | |
244 on the root widget. | |
245 """ | |
246 self._recursiveResizeToContent() | |
247 self._recursiveExpandContent() | |
248 | |
249 def beforeShow(self): | |
250 """ | |
251 This method is called just before the widget is shown. | |
252 You can override this in derived widgets to add finalization | |
253 behaviour. | |
254 """ | |
255 | |
256 def afterHide(self): | |
257 """ | |
258 This method is called just before the widget is hidden. | |
259 You can override this in derived widgets to add finalization | |
260 behaviour. | |
261 """ | |
262 | |
263 def findChildren(self,**kwargs): | |
264 """ | |
265 Find all contained child widgets by attribute values. | |
266 | |
267 Usage:: | |
268 closeButtons = root_widget.findChildren(name='close') | |
269 """ | |
270 | |
271 children = [] | |
272 def _childCollector(widget): | |
273 if widget.match(**kwargs): | |
274 children.append(widget) | |
275 self.deepApply(_childCollector) | |
276 return children | |
277 | |
278 def findChild(self,**kwargs): | |
279 """ Find the first contained child widgets by attribute values. | |
280 | |
281 Usage:: | |
282 closeButton = root_widget.findChild(name='close') | |
283 """ | |
284 children = self.findChildren(**kwargs) | |
285 if children: | |
286 return children[0] | |
287 return None | |
288 | |
289 def addChild(self,widget): | |
290 """ | |
291 This function adds a widget as child widget and is only implemented | |
292 in container widgets. | |
293 """ | |
294 raise RuntimeError("Trying to add a widget to %s, which doesn't allow this." % repr(self)) | |
295 | |
296 def addChildren(self,*widgets): | |
297 for widget in widgets: | |
298 self.addChild(widget) | |
299 | |
300 def removeChild(self,widget): | |
301 """ | |
302 This function removes a direct child widget and is only implemented | |
303 in container widgets. | |
304 """ | |
305 raise RuntimeError("Trying to remove a widget from %s, which is not a container widget." % repr(self)) | |
306 | |
307 def removeChildren(self,*widgets): | |
308 for widget in widgets: | |
309 self.removeChild(widget) | |
310 | |
311 def mapEvents(self,eventMap,ignoreMissing = False): | |
312 """ | |
313 Convenience function to map widget events to functions | |
314 in a batch. | |
315 | |
316 Subsequent calls of mapEvents will merge events with different | |
317 widget names and override the previously set callback. | |
318 You can also pass C{None} instead of a callback, which will | |
319 disable the event completely. | |
320 | |
321 @param eventMap: A dictionary with widget names as keys and callbacks as values. | |
322 @param ignoreMissing: Normally this method raises an RuntimeError, when a widget | |
323 can not be found - this behaviour can be overriden by passing True here. | |
324 """ | |
325 for name,func in eventMap.items(): | |
326 widget = self.findChild(name=name) | |
327 if widget: | |
328 widget.capture( func ) | |
329 elif not ignoreMissing: | |
330 raise RuntimeError("No widget with the name: %s" % name) | |
331 | |
332 def setInitialData(self,data): | |
333 """ | |
334 Set the initial data on a widget, what this means depends on the Widget. | |
335 In case the widget does not accept initial data, a L{RuntimeError} is thrown. | |
336 """ | |
337 if not self.accepts_initial_data: | |
338 raise RuntimeError("Trying to set data on a widget that does not accept initial data. Widget: %s Data: %s " % (repr(self),repr(data))) | |
339 self._realSetInitialData(data) | |
340 | |
341 def setData(self,data): | |
342 """ | |
343 Set the user-mutable data on a widget, what this means depends on the Widget. | |
344 In case the widget does not accept data, a L{RuntimeError} is thrown. | |
345 This is inverse to L{getData}. | |
346 """ | |
347 if not self.accepts_data: | |
348 raise RuntimeError("Trying to set data on a widget that does not accept data.") | |
349 self._realSetData(data) | |
350 | |
351 def getData(self): | |
352 """ | |
353 Get the user-mutable data of a widget, what this means depends on the Widget. | |
354 In case the widget does not have user mutable data, a L{RuntimeError} is thrown. | |
355 This is inverse to L{setData}. | |
356 """ | |
357 if not self.accepts_data: | |
358 raise RuntimeError("Trying to retrieve data from a widget that does not accept data.") | |
359 return self._realGetData() | |
360 | |
361 def distributeInitialData(self,initialDataMap): | |
362 """ | |
363 Distribute B{initial} (not mutable by the user) data from a dictionary over the widgets in the hierachy | |
364 using the keys as names and the values as the data (which is set via L{setInitialData}). | |
365 If more than one widget matches - the data is set on ALL matching widgets. | |
366 By default a missing widget is just ignored. | |
367 | |
368 Use it like this:: | |
369 guiElement.distributeInitialData({ | |
370 'myTextField' : 'Hello World!', | |
371 'myListBox' : ["1","2","3"] | |
372 }) | |
373 | |
374 """ | |
375 for name,data in initialDataMap.items(): | |
376 widgetList = self.findChildren(name = name) | |
377 for widget in widgetList: | |
378 widget.setInitialData(data) | |
379 | |
380 def distributeData(self,dataMap): | |
381 """ | |
382 Distribute data from a dictionary over the widgets in the hierachy | |
383 using the keys as names and the values as the data (which is set via L{setData}). | |
384 This will only accept unique matches. | |
385 | |
386 Use it like this:: | |
387 guiElement.distributeData({ | |
388 'myTextField' : 'Hello World!', | |
389 'myListBox' : ["1","2","3"] | |
390 }) | |
391 | |
392 """ | |
393 for name,data in dataMap.items(): | |
394 widgetList = self.findChildren(name = name) | |
395 if len(widgetList) != 1: | |
396 raise RuntimeError("DistributeData can only handle widgets with unique names.") | |
397 widgetList[0].setData(data) | |
398 | |
399 def collectDataAsDict(self,widgetNames): | |
400 """ | |
401 Collect data from a widget hierachy by names into a dictionary. | |
402 This can only handle UNIQUE widget names (in the hierachy) | |
403 and will raise a RuntimeError if the number of matching widgets | |
404 is not equal to one. | |
405 | |
406 Usage:: | |
407 data = guiElement.collectDataAsDict(['myTextField','myListBox']) | |
408 print "You entered:",data['myTextField']," and selected ",data['myListBox'] | |
409 | |
410 """ | |
411 dataMap = {} | |
412 for name in widgetNames: | |
413 widgetList = self.findChildren(name = name) | |
414 if len(widgetList) != 1: | |
415 raise RuntimeError("CollectData can only handle widgets with unique names.") | |
416 | |
417 dataMap[name] = widgetList[0].getData() | |
418 return dataMap | |
419 | |
420 def collectData(self,*widgetNames): | |
421 """ | |
422 Collect data from a widget hierachy by names. | |
423 This can only handle UNIQUE widget names (in the hierachy) | |
424 and will raise a RuntimeError if the number of matching widgets | |
425 is not equal to one. | |
426 | |
427 This function takes an arbitrary number of widget names and | |
428 returns a list of the collected data in the same order. | |
429 | |
430 In case only one argument is given, it will return just the | |
431 data, with out putting it into a list. | |
432 | |
433 Usage:: | |
434 # Multiple element extraction: | |
435 text, selected = guiElement.collectData('myTextField','myListBox') | |
436 print "You entered:",text," and selected item nr",selected | |
437 # Single elements are handled gracefully, too: | |
438 test = guiElement.collectData('testElement') | |
439 | |
440 """ | |
441 dataList = [] | |
442 for name in widgetNames: | |
443 widgetList = self.findChildren(name = name) | |
444 if len(widgetList) != 1: | |
445 raise RuntimeError("CollectData can only handle widgets with unique names.") | |
446 dataList.append( widgetList[0].getData() ) | |
447 if len(dataList) == 1: | |
448 return dataList[0] | |
449 return dataList | |
450 | |
451 def listNamedWidgets(self): | |
452 """ | |
453 This function will print a list of all currently named child-widgets | |
454 to the standard output. This is useful for debugging purposes. | |
455 """ | |
456 def _printNamedWidget(widget): | |
457 if widget.name != Widget.DEFAULT_NAME: | |
458 print widget.name.ljust(20),repr(widget).ljust(50),repr(widget._parent) | |
459 print "Named child widgets of ",repr(self) | |
460 print "name".ljust(20),"widget".ljust(50),"parent" | |
461 self.deepApply(_printNamedWidget) | |
462 | |
463 | |
464 def stylize(self,style,**kwargs): | |
465 """ | |
466 Recursively apply a style to all widgets. | |
467 """ | |
468 def _restyle(widget): | |
469 get_manager().stylize(widget,style,**kwargs) | |
470 self.deepApply(_restyle) | |
471 | |
472 def resizeToContent(self,recurse = True): | |
473 """ | |
474 Try to shrink the widget, so that it fits closely around its content. | |
475 Do not call directly. | |
476 """ | |
477 | |
478 def expandContent(self,recurse = True): | |
479 """ | |
480 Try to expand any spacer in the widget within the current size. | |
481 Do not call directly. | |
482 """ | |
483 | |
484 | |
485 def _recursiveResizeToContent(self): | |
486 """ | |
487 Recursively call L{resizeToContent}. Uses L{deepApply}. | |
488 Do not call directly. | |
489 """ | |
490 def _callResizeToContent(widget): | |
491 #print "RTC:",widget | |
492 widget.resizeToContent() | |
493 self.deepApply(_callResizeToContent) | |
494 | |
495 def _recursiveExpandContent(self): | |
496 """ | |
497 Recursively call L{expandContent}. Uses L{deepApply}. | |
498 Do not call directly. | |
499 """ | |
500 def _callExpandContent(widget): | |
501 #print "ETC:",widget | |
502 widget.expandContent() | |
503 self.deepApply(_callExpandContent) | |
504 | |
505 def deepApply(self,visitorFunc): | |
506 """ | |
507 Recursively apply a callable to all contained widgets and then the widget itself. | |
508 """ | |
509 visitorFunc(self) | |
510 | |
511 def sizeChanged(self): | |
512 if self._parent: | |
513 self._parent.sizeChanged() | |
514 else: | |
515 self.adaptLayout() | |
516 | |
517 def __str__(self): | |
518 return "%s(name='%s')" % (self.__class__.__name__,self.name) | |
519 | |
520 def __repr__(self): | |
521 return "<%s(name='%s') at %x>" % (self.__class__.__name__,self.name,id(self)) | |
522 | |
523 def _setSize(self,size): | |
524 if isinstance(size,fife.Point): | |
525 self.width, self.height = size.x, size.y | |
526 else: | |
527 self.width, self.height = size | |
528 #self.sizeChanged() | |
529 | |
530 def _getSize(self): | |
531 return self.width, self.height | |
532 | |
533 def _setPosition(self,size): | |
534 if isinstance(size,fife.Point): | |
535 self.x, self.y = size.x, size.y | |
536 else: | |
537 self.x, self.y = size | |
538 | |
539 def _getPosition(self): | |
540 return self.x, self.y | |
541 | |
542 def _setX(self,x):self.real_widget.setX(x) | |
543 def _getX(self): return self.real_widget.getX() | |
544 def _setY(self,y): self.real_widget.setY(y) | |
545 def _getY(self): return self.real_widget.getY() | |
546 | |
547 def _setWidth(self,w): | |
548 w = max(self.min_size[0],w) | |
549 w = min(self.max_size[0],w) | |
550 self.real_widget.setWidth(w) | |
551 | |
552 def _getWidth(self): return self.real_widget.getWidth() | |
553 def _setHeight(self,h): | |
554 h = max(self.min_size[1],h) | |
555 h = min(self.max_size[1],h) | |
556 self.real_widget.setHeight(h) | |
557 | |
558 def _getHeight(self): return self.real_widget.getHeight() | |
559 | |
560 def _setFont(self, font): | |
561 self._font = font | |
562 self.real_font = get_manager().getFont(font) | |
563 self.real_widget.setFont(self.real_font) | |
564 def _getFont(self): | |
565 return self._font | |
566 | |
567 def _getBorderSize(self): return self.real_widget.getFrameSize() | |
568 def _setBorderSize(self,size): self.real_widget.setFrameSize(size) | |
569 | |
570 def _getBaseColor(self): return self.real_widget.getBaseColor() | |
571 def _setBaseColor(self,color): | |
572 if isinstance(color,type(())): | |
573 color = fife.Color(*color) | |
574 self.real_widget.setBaseColor(color) | |
575 base_color = property(_getBaseColor,_setBaseColor) | |
576 | |
577 def _getBackgroundColor(self): return self.real_widget.getBackgroundColor() | |
578 def _setBackgroundColor(self,color): | |
579 if isinstance(color,type(())): | |
580 color = fife.Color(*color) | |
581 self.real_widget.setBackgroundColor(color) | |
582 background_color = property(_getBackgroundColor,_setBackgroundColor) | |
583 | |
584 def _getForegroundColor(self): return self.real_widget.getForegroundColor() | |
585 def _setForegroundColor(self,color): | |
586 if isinstance(color,type(())): | |
587 color = fife.Color(*color) | |
588 self.real_widget.setForegroundColor(color) | |
589 foreground_color = property(_getForegroundColor,_setForegroundColor) | |
590 | |
591 def _getName(self): return self._name | |
592 def _setName(self,name): | |
593 from pychan import manager | |
594 self._name = name | |
595 # Do not change the event id while we are captured. | |
596 if not self.isCaptured(): | |
597 self._event_id = "%s(name=%s,id=%d)" % (str(self.__class__),name,id(self)) | |
598 else: | |
599 # Print some notfication, so obscure behaviour might get debugged. | |
600 print "%s already captured, but changing the name attribute. Just a notification :-)" % str(self) | |
601 self.real_widget.setActionEventId(self._event_id) | |
602 name = property(_getName,_setName) | |
603 | |
604 def _getStyle(self): return self._style | |
605 def _setStyle(self,style): | |
606 self._style = style | |
607 get_manager().stylize(self,style) | |
608 style = property(_getStyle,_setStyle) | |
609 | |
610 x = property(_getX,_setX) | |
611 y = property(_getY,_setY) | |
612 width = property(_getWidth,_setWidth) | |
613 height = property(_getHeight,_setHeight) | |
614 size = property(_getSize,_setSize) | |
615 position = property(_getPosition,_setPosition) | |
616 font = property(_getFont,_setFont) | |
617 border_size = property(_getBorderSize,_setBorderSize) | |
618 | |
619 ### Containers + Layout code ### | |
620 | |
621 class Container(Widget): | |
622 """ | |
623 This is the basic container class. It provides space in which child widgets can | |
624 be position via the position attribute. If you want to use the layout engine, | |
625 you have to use derived containers with vertical or horizontal orientation | |
626 (L{VBox} or L{HBox}) | |
627 | |
628 New Attributes | |
629 ============== | |
630 | |
631 - padding - Integer: Not used in the Container class istelf, distance between child widgets. | |
632 - background_image - Set this to a GuiImage or a resource location (simply a filename). | |
633 The image will be tiled over the background area. | |
634 - opaque - Boolean: Whether the background should be drawn at all. Set this to False | |
635 to make the widget transparent. | |
636 - children - Just contains the list of contained child widgets. Do NOT modify. | |
637 """ | |
638 | |
639 ATTRIBUTES = Widget.ATTRIBUTES + [ IntAttr('padding'), Attr('background_image'), BoolAttr('opaque'),PointAttr('margins') ] | |
640 | |
641 def __init__(self,padding=5,margins=(5,5),_real_widget=None, **kwargs): | |
642 self.real_widget = _real_widget or fife.Container() | |
643 self.children = [] | |
644 self.margins = margins | |
645 self.padding = padding | |
646 self._background = [] | |
647 self._background_image = None | |
648 super(Container,self).__init__(**kwargs) | |
649 | |
650 def addChild(self, widget): | |
651 widget._parent = self | |
652 self.children.append(widget) | |
653 self.real_widget.add(widget.real_widget) | |
654 | |
655 def removeChild(self,widget): | |
656 if not widget in self.children: | |
657 raise RuntimeError("%s does not have %s as direct child widget." % (str(self),str(widget))) | |
658 self.children.remove(widget) | |
659 self.real_widget.remove(widget.real_widget) | |
660 widget._parent = None | |
661 | |
662 def add(self,*widgets): | |
663 print "PyChan: Deprecation warning: Please use 'addChild' or 'addChildren' instead." | |
664 self.addChildren(*widgets) | |
665 | |
666 def getMaxChildrenWidth(self): | |
667 if not self.children: return 0 | |
668 return max(widget.width for widget in self.children) | |
669 | |
670 def getMaxChildrenHeight(self): | |
671 if not self.children: return 0 | |
672 return max(widget.height for widget in self.children) | |
673 | |
674 def deepApply(self,visitorFunc): | |
675 for child in self.children: | |
676 child.deepApply(visitorFunc) | |
677 visitorFunc(self) | |
678 | |
679 def beforeShow(self): | |
680 self._resetTiling() | |
681 | |
682 def _resetTiling(self): | |
683 image = self._background_image | |
684 if image is None: | |
685 return | |
686 | |
687 back_w,back_h = self.width, self.height | |
688 image_w, image_h = image.getWidth(), image.getHeight() | |
689 | |
690 map(self.real_widget.remove,self._background) | |
691 | |
692 # Now tile the background over the widget | |
693 self._background = [] | |
694 icon = fife.Icon(image) | |
695 x, w = 0, image_w | |
696 while x < back_w: | |
697 y, h = 0, image_h | |
698 while y < self.height: | |
699 icon = fife.Icon(image) | |
700 icon.setPosition(x,y) | |
701 self._background.append(icon) | |
702 y += h | |
703 x += w | |
704 map(self.real_widget.add,self._background) | |
705 for tile in self._background: | |
706 tile.requestMoveToBottom() | |
707 | |
708 def setBackgroundImage(self,image): | |
709 self._background = getattr(self,'_background',None) | |
710 if image is None: | |
711 self._background_image = image | |
712 map(self.real_widget.remove,self._background) | |
713 self._background = [] | |
714 | |
715 # Background generation is done in _resetTiling | |
716 | |
717 if not isinstance(image, fife.GuiImage): | |
718 image = get_manager().loadImage(image) | |
719 self._background_image = image | |
720 | |
721 def getBackgroundImage(self): return self._background_image | |
722 background_image = property(getBackgroundImage,setBackgroundImage) | |
723 | |
724 def _setOpaque(self,opaque): self.real_widget.setOpaque(opaque) | |
725 def _getOpaque(self): return self.real_widget.isOpaque() | |
726 opaque = property(_getOpaque,_setOpaque) | |
727 | |
728 AlignTop, AlignBottom, AlignLeft, AlignRight, AlignCenter = range(5) | |
729 | |
730 class LayoutBase(object): | |
731 """ | |
732 This class is at the core of the layout engine. The two MixIn classes L{VBoxLayoutMixin} | |
733 and L{HBoxLayoutMixin} specialise on this by reimplementing the C{resizeToContent} and | |
734 the C{expandContent} methods. | |
735 | |
736 At the core the layout engine works in two passes: | |
737 | |
738 Before a root widget loaded by the XML code is shown, its resizeToContent method | |
739 is called recursively (walking the widget containment relation in post order). | |
740 This shrinks all HBoxes and VBoxes to their minimum heigt and width. | |
741 After that the expandContent method is called recursively in the same order, | |
742 which will re-align the widgets if there is space left AND if a Spacer is contained. | |
743 | |
744 Inside bare Container instances (without a Layout MixIn) absolute positioning | |
745 can be used. | |
746 """ | |
747 def __init__(self,align = (AlignLeft,AlignTop), **kwargs): | |
748 self.align = align | |
749 self.spacer = None | |
750 super(LayoutBase,self).__init__(**kwargs) | |
751 | |
752 def addSpacer(self,spacer): | |
753 if self.spacer: | |
754 raise RuntimeException("Already a Spacer in %s!" % str(self)) | |
755 self.spacer = spacer | |
756 spacer.index = len(self.children) | |
757 | |
758 def xdelta(self,widget):return 0 | |
759 def ydelta(self,widget):return 0 | |
760 | |
761 def _adjustHeight(self): | |
762 | |
763 if self.align[1] == AlignTop:return #dy = 0 | |
764 if self.align[1] == AlignBottom: | |
765 y = self.height - self.childarea[1] - self.border_size - self.margins[1] | |
766 else: | |
767 y = (self.height - self.childarea[1] - self.border_size - self.margins[1])/2 | |
768 for widget in self.children: | |
769 widget.y = y | |
770 y += self.ydelta(widget) | |
771 | |
772 def _adjustHeightWithSpacer(self): | |
773 pass | |
774 | |
775 def _adjustWidth(self): | |
776 | |
777 if self.align[0] == AlignLeft:return #dx = 0 | |
778 if self.align[0] == AlignRight: | |
779 x = self.width - self.childarea[0] - self.border_size - self.margins[0] | |
780 else: | |
781 x = (self.width - self.childarea[0] - self.border_size - self.margins[0])/2 | |
782 for widget in self.children: | |
783 widget.x = x | |
784 x += self.xdelta(widget) | |
785 | |
786 def _expandWidthSpacer(self): | |
787 x = self.border_size + self.margins[0] | |
788 xdelta = map(self.xdelta,self.children) | |
789 | |
790 for widget in self.children[:self.spacer.index]: | |
791 widget.x = x | |
792 x += xdelta.pop(0) | |
793 | |
794 x = self.width - sum(xdelta) - self.border_size - self.margins[0] | |
795 for widget in self.children[self.spacer.index:]: | |
796 widget.x = x | |
797 x += xdelta.pop(0) | |
798 | |
799 def _expandHeightSpacer(self): | |
800 y = self.border_size + self.margins[1] | |
801 ydelta = map(self.ydelta,self.children) | |
802 | |
803 for widget in self.children[:self.spacer.index]: | |
804 widget.y = y | |
805 y += ydelta.pop(0) | |
806 | |
807 y = self.height - sum(ydelta) - self.border_size - self.margins[1] | |
808 for widget in self.children[self.spacer.index:]: | |
809 widget.y = y | |
810 y += ydelta.pop(0) | |
811 | |
812 | |
813 class VBoxLayoutMixin(LayoutBase): | |
814 """ | |
815 A mixin class for a vertical layout. Do not use directly. | |
816 """ | |
817 def __init__(self,**kwargs): | |
818 super(VBoxLayoutMixin,self).__init__(**kwargs) | |
819 | |
820 def resizeToContent(self, recurse = True): | |
821 max_w = self.getMaxChildrenWidth() | |
822 x = self.margins[0] + self.border_size | |
823 y = self.margins[1] + self.border_size | |
824 for widget in self.children: | |
825 widget.x = x | |
826 widget.y = y | |
827 widget.width = max_w | |
828 y += widget.height + self.padding | |
829 | |
830 #Add the padding for the spacer. | |
831 if self.spacer: | |
832 y += self.padding | |
833 | |
834 self.height = y + self.margins[1] - self.padding | |
835 self.width = max_w + 2*x | |
836 self.childarea = max_w, y - self.padding - self.margins[1] | |
837 | |
838 self._adjustHeight() | |
839 self._adjustWidth() | |
840 | |
841 def expandContent(self): | |
842 if self.spacer: | |
843 self._expandHeightSpacer() | |
844 | |
845 def ydelta(self,widget):return widget.height + self.padding | |
846 | |
847 class HBoxLayoutMixin(LayoutBase): | |
848 """ | |
849 A mixin class for a horizontal layout. Do not use directly. | |
850 """ | |
851 def __init__(self,**kwargs): | |
852 super(HBoxLayoutMixin,self).__init__(**kwargs) | |
853 | |
854 def resizeToContent(self, recurse = True): | |
855 max_h = self.getMaxChildrenHeight() | |
856 x = self.margins[0] + self.border_size | |
857 y = self.margins[1] + self.border_size | |
858 for widget in self.children: | |
859 widget.x = x | |
860 widget.y = y | |
861 widget.height = max_h | |
862 x += widget.width + self.padding | |
863 | |
864 #Add the padding for the spacer. | |
865 if self.spacer: | |
866 x += self.padding | |
867 | |
868 self.width = x + self.margins[0] - self.padding | |
869 self.height = max_h + 2*y | |
870 self.childarea = x - self.margins[0] - self.padding, max_h | |
871 | |
872 self._adjustHeight() | |
873 self._adjustWidth() | |
874 | |
875 def expandContent(self): | |
876 if self.spacer: | |
877 self._expandWidthSpacer() | |
878 | |
879 def xdelta(self,widget):return widget.width + self.padding | |
880 | |
881 | |
882 class VBox(VBoxLayoutMixin,Container): | |
883 """ | |
884 A vertically aligned box - for containement of child widgets. | |
885 | |
886 Widgets added to this container widget, will layout on top of each other. | |
887 Also the minimal width of the container will be the maximum of the minimal | |
888 widths of the contained widgets. | |
889 | |
890 The default alignment is to the top. This can be changed by adding a Spacer | |
891 to the widget at any point (but only one!). The spacer will expand, so that | |
892 widgets above the spacer are aligned to the top, while widgets below the spacer | |
893 are aligned to the bottom. | |
894 """ | |
895 def __init__(self,padding=5,**kwargs): | |
896 super(VBox,self).__init__(**kwargs) | |
897 self.padding = padding | |
898 | |
899 | |
900 class HBox(HBoxLayoutMixin,Container): | |
901 """ | |
902 A horizontally aligned box - for containement of child widgets. | |
903 | |
904 Please see L{VBox} for details - just change the directions :-). | |
905 """ | |
906 def __init__(self,padding=5,**kwargs): | |
907 super(HBox,self).__init__(**kwargs) | |
908 self.padding = padding | |
909 | |
910 class Window(VBoxLayoutMixin,Container): | |
911 """ | |
912 A L{VBox} with a draggable title bar aka a window | |
913 | |
914 New Attributes | |
915 ============== | |
916 | |
917 - title: The Caption of the window | |
918 - titlebar_height: The height of the window title bar | |
919 """ | |
920 | |
921 ATTRIBUTES = Container.ATTRIBUTES + [ Attr('title'), IntAttr('titlebar_height') ] | |
922 | |
923 def __init__(self,title="title",titlebar_height=0,**kwargs): | |
924 super(Window,self).__init__(_real_widget = fife.Window(), **kwargs) | |
925 if titlebar_height == 0: | |
926 titlebar_height = self.real_font.getHeight() + 4 | |
927 self.titlebar_height = titlebar_height | |
928 self.title = title | |
929 | |
930 # Override explicit positioning | |
931 self.position_technique = "automatic" | |
932 | |
933 | |
934 def _getTitle(self): return self.real_widget.getCaption() | |
935 def _setTitle(self,text): self.real_widget.setCaption(text) | |
936 title = property(_getTitle,_setTitle) | |
937 | |
938 def _getTitleBarHeight(self): return self.real_widget.getTitleBarHeight() | |
939 def _setTitleBarHeight(self,h): self.real_widget.setTitleBarHeight(h) | |
940 titlebar_height = property(_getTitleBarHeight,_setTitleBarHeight) | |
941 | |
942 # Hackish way of hiding that title bar height in the perceived height. | |
943 # Fixes VBox calculation | |
944 def _setHeight(self,h): | |
945 h = max(self.min_size[1],h) | |
946 h = min(self.max_size[1],h) | |
947 self.real_widget.setHeight(h + self.titlebar_height) | |
948 def _getHeight(self): return self.real_widget.getHeight() - self.titlebar_height | |
949 height = property(_getHeight,_setHeight) | |
950 | |
951 ### Basic Widgets ### | |
952 | |
953 class BasicTextWidget(Widget): | |
954 """ | |
955 The base class for widgets which display a string - L{Label},L{ClickLabel},L{Button}, etc. | |
956 Do not use directly. | |
957 | |
958 New Attributes | |
959 ============== | |
960 | |
961 - text: The text (depends on actual widget) | |
962 | |
963 Data | |
964 ==== | |
965 | |
966 The text can be set via the L{distributeInitialData} method. | |
967 """ | |
968 | |
969 ATTRIBUTES = Widget.ATTRIBUTES + [Attr('text')] | |
970 | |
971 def __init__(self, text = "",**kwargs): | |
972 self.margins = (5,5) | |
973 self.text = text | |
974 super(BasicTextWidget,self).__init__(**kwargs) | |
975 | |
976 # Prepare Data collection framework | |
977 self.accepts_initial_data = True | |
978 self._realSetInitialData = self._setText | |
979 | |
980 def _getText(self): return self.real_widget.getCaption() | |
981 def _setText(self,text): self.real_widget.setCaption(_mungeText(text)) | |
982 text = property(_getText,_setText) | |
983 | |
984 def resizeToContent(self, recurse = True): | |
985 self.height = self.real_font.getHeight() + self.margins[1]*2 | |
986 self.width = self.real_font.getWidth(self.text) + self.margins[0]*2 | |
987 | |
988 class Icon(Widget): | |
989 """ | |
990 An image icon. | |
991 | |
992 New Attributes | |
993 ============== | |
994 | |
995 - image: String or GuiImage: The source location of the Image or a direct GuiImage | |
996 """ | |
997 ATTRIBUTES = Widget.ATTRIBUTES + [Attr('image')] | |
998 | |
999 def __init__(self,image="",**kwargs): | |
1000 self.real_widget = fife.Icon(None) | |
1001 super(Icon,self).__init__(**kwargs) | |
1002 self._source = self._image = None | |
1003 if image: | |
1004 self.image = image | |
1005 | |
1006 def _setImage(self,source): | |
1007 if isinstance(source,str): | |
1008 self._source = source | |
1009 self._image = get_manager().loadImage(source) | |
1010 elif isinstance(source,fife.GuiImage): | |
1011 self._source = None | |
1012 self._image = source | |
1013 else: | |
1014 raise RuntimeError("Icon.image only accepts GuiImage and python strings, not '%s'" % repr(source)) | |
1015 self.real_widget.setImage( self._image ) | |
1016 | |
1017 # Set minimum size accoriding to image | |
1018 self.min_size = self.real_widget.getWidth(),self.real_widget.getHeight() | |
1019 self.size = self.max_size = self.min_size | |
1020 | |
1021 def _getImage(self): | |
1022 if self._source is not None: | |
1023 return self._source | |
1024 return self._image | |
1025 image = property(_getImage,_setImage) | |
1026 | |
1027 class Label(BasicTextWidget): | |
1028 """ | |
1029 A basic label - displaying a string. | |
1030 | |
1031 Also allows text wrapping. | |
1032 | |
1033 New Attributes | |
1034 ============== | |
1035 | |
1036 - wrap_text: Boolean: Enable/Disable automatic text wrapping. Disabled by default. | |
1037 Currently to actually see text wrapping you have to explicitly set a max_size with | |
1038 the desired width of the text, as the layout engine is not capable of deriving | |
1039 the maximum width from a parent container. | |
1040 """ | |
1041 | |
1042 ATTRIBUTES = BasicTextWidget.ATTRIBUTES + [BoolAttr('wrap_text')] | |
1043 | |
1044 def __init__(self,wrap_text=False,**kwargs): | |
1045 self.real_widget = fife.Label("") | |
1046 self.wrap_text = wrap_text | |
1047 super(Label,self).__init__(**kwargs) | |
1048 | |
1049 def resizeToContent(self): | |
1050 self.real_widget.setWidth( self.max_size[0] ) | |
1051 self.real_widget.adjustSize() | |
1052 self.height = self.real_widget.getHeight() + self.margins[1]*2 | |
1053 self.width = self.real_widget.getWidth() + self.margins[0]*2 | |
1054 #print self.width,self.max_size[0] | |
1055 | |
1056 def _setTextWrapping(self,wrapping): self.real_widget.setTextWrapping(wrapping) | |
1057 def _getTextWrapping(self): self.real_widget.isTextWrapping() | |
1058 wrap_text = property(_getTextWrapping,_setTextWrapping) | |
1059 | |
1060 class ClickLabel(Label): | |
1061 """ | |
1062 Deprecated - use L{Label} instead. | |
1063 """ | |
1064 __init__ = tools.this_is_deprecated(Label.__init__,message = "ClickLabel - Use Label instead") | |
1065 | |
1066 | |
1067 class Button(BasicTextWidget): | |
1068 """ | |
1069 A basic push button. | |
1070 """ | |
1071 def __init__(self,**kwargs): | |
1072 self.real_widget = fife.Button("") | |
1073 super(Button,self).__init__(**kwargs) | |
1074 | |
1075 class ImageButtonListener(fife.TwoButtonListener): | |
1076 def __init__(self, btn): | |
1077 fife.TwoButtonListener.__init__(self) | |
1078 self.btn = btn | |
1079 self.entercb = None | |
1080 self.exitcb = None | |
1081 | |
1082 def mouseEntered(self, btn): | |
1083 if self.entercb: | |
1084 self.entercb(self.btn) | |
1085 | |
1086 def mouseExited(self, btn): | |
1087 if self.exitcb: | |
1088 self.exitcb(self.btn) | |
1089 | |
1090 class ImageButton(BasicTextWidget): | |
1091 """ | |
1092 A basic push button with three different images for the up, down and hover state. | |
1093 | |
1094 B{Work in progress.} | |
1095 | |
1096 New Attributes | |
1097 ============== | |
1098 | |
1099 - up_image: String: The source location of the Image for the B{unpressed} state. | |
1100 - down_image: String: The source location of the Image for the B{pressed} state. | |
1101 - hover_image: String: The source location of the Image for the B{unpressed hovered} state. | |
1102 """ | |
1103 | |
1104 ATTRIBUTES = BasicTextWidget.ATTRIBUTES + [Attr('up_image'),Attr('down_image'),PointAttr('offset'),Attr('helptext'),Attr('hover_image')] | |
1105 | |
1106 def __init__(self,up_image="",down_image="",hover_image="",offset=(0,0),**kwargs): | |
1107 self.real_widget = fife.TwoButton() | |
1108 super(ImageButton,self).__init__(**kwargs) | |
1109 self.listener = ImageButtonListener(self) | |
1110 self.real_widget.setListener(self.listener) | |
1111 | |
1112 self.up_image = up_image | |
1113 self.down_image = down_image | |
1114 self.hover_image = hover_image | |
1115 self.offset = offset | |
1116 | |
1117 def _setUpImage(self,image): | |
1118 self._upimage_source = image | |
1119 try: | |
1120 self._upimage = get_manager().loadImage(image) | |
1121 self.real_widget.setUpImage( self._upimage ) | |
1122 except: | |
1123 self._upimage = _DummyImage() | |
1124 def _getUpImage(self): return self._upimage_source | |
1125 up_image = property(_getUpImage,_setUpImage) | |
1126 | |
1127 def _setDownImage(self,image): | |
1128 self._downimage_source = image | |
1129 try: | |
1130 self._downimage = get_manager().loadImage(image) | |
1131 self.real_widget.setDownImage( self._downimage ) | |
1132 except: | |
1133 self._downimage = _DummyImage() | |
1134 def _getDownImage(self): return self._downimage_source | |
1135 down_image = property(_getDownImage,_setDownImage) | |
1136 | |
1137 def _setHoverImage(self,image): | |
1138 self._hoverimage_source = image | |
1139 try: | |
1140 self._hoverimage = get_manager().loadImage(image) | |
1141 self.real_widget.setHoverImage( self._hoverimage ) | |
1142 except: | |
1143 self._hoverimage = _DummyImage() | |
1144 def _getHoverImage(self): return self._hoverimage_source | |
1145 hover_image = property(_getHoverImage,_setHoverImage) | |
1146 | |
1147 def _setOffset(self, offset): | |
1148 self.real_widget.setDownOffset(offset[0], offset[1]) | |
1149 def _getOffset(self): | |
1150 return (self.real_widget.getDownXOffset(), self.real_widget.getDownYOffset()) | |
1151 offset = property(_getOffset,_setOffset) | |
1152 | |
1153 def _setHelpText(self, txt): | |
1154 self.real_widget.setHelpText(txt) | |
1155 def _getHelpText(self): | |
1156 return self.real_widget.getHelpText() | |
1157 helptext = property(_getHelpText,_setHelpText) | |
1158 | |
1159 def resizeToContent(self): | |
1160 self.height = max(self._upimage.getHeight(),self._downimage.getHeight(),self._hoverimage.getHeight()) + self.margins[1]*2 | |
1161 self.width = max(self._upimage.getWidth(),self._downimage.getWidth(),self._hoverimage.getWidth()) + self.margins[1]*2 | |
1162 | |
1163 def setEnterCallback(self, cb): | |
1164 ''' | |
1165 Callback is called when mouse enters the area of ImageButton | |
1166 callback should have form of function(button) | |
1167 ''' | |
1168 self.listener.entercb = cb | |
1169 | |
1170 def setExitCallback(self, cb): | |
1171 ''' | |
1172 Callback is called when mouse enters the area of ImageButton | |
1173 callback should have form of function(button) | |
1174 ''' | |
1175 self.listener.exitcb = cb | |
1176 | |
1177 | |
1178 | |
1179 class CheckBox(BasicTextWidget): | |
1180 """ | |
1181 A basic checkbox. | |
1182 | |
1183 New Attributes | |
1184 ============== | |
1185 | |
1186 - marked: Boolean value, whether the checkbox is checked or not. | |
1187 | |
1188 Data | |
1189 ==== | |
1190 The marked status can be read and set via L{distributeData} and L{collectData} | |
1191 """ | |
1192 | |
1193 ATTRIBUTES = BasicTextWidget.ATTRIBUTES + [BoolAttr('marked')] | |
1194 | |
1195 def __init__(self,**kwargs): | |
1196 self.real_widget = fife.CheckBox() | |
1197 super(CheckBox,self).__init__(**kwargs) | |
1198 | |
1199 # Prepare Data collection framework | |
1200 self.accepts_data = True | |
1201 self._realGetData = self._isMarked | |
1202 self._realSetData = self._setMarked | |
1203 | |
1204 # Initial data stuff inherited. | |
1205 | |
1206 def _isMarked(self): return self.real_widget.isSelected() | |
1207 def _setMarked(self,mark): self.real_widget.setSelected(mark) | |
1208 marked = property(_isMarked,_setMarked) | |
1209 | |
1210 class RadioButton(BasicTextWidget): | |
1211 """ | |
1212 A basic radiobutton (an exclusive checkbox). | |
1213 | |
1214 New Attributes | |
1215 ============== | |
1216 | |
1217 - marked: Boolean: Whether the checkbox is checked or not. | |
1218 - group: String: All RadioButtons with the same group name | |
1219 can only be checked exclusively. | |
1220 | |
1221 Data | |
1222 ==== | |
1223 The marked status can be read and set via L{distributeData} and L{collectData} | |
1224 """ | |
1225 | |
1226 ATTRIBUTES = BasicTextWidget.ATTRIBUTES + [BoolAttr('marked'),Attr('group')] | |
1227 | |
1228 def __init__(self,group="_no_group_",**kwargs): | |
1229 self.real_widget = fife.RadioButton() | |
1230 super(RadioButton,self).__init__(**kwargs) | |
1231 | |
1232 self.group = group | |
1233 | |
1234 # Prepare Data collection framework | |
1235 self.accepts_data = True | |
1236 self._realGetData = self._isMarked | |
1237 self._realSetData = self._setMarked | |
1238 | |
1239 # Initial data stuff inherited. | |
1240 | |
1241 def _isMarked(self): return self.real_widget.isSelected() | |
1242 def _setMarked(self,mark): self.real_widget.setSelected(mark) | |
1243 marked = property(_isMarked,_setMarked) | |
1244 | |
1245 def _setGroup(self,group): self.real_widget.setGroup(group) | |
1246 def _getGroup(self): return self.real_widget.getGroup() | |
1247 group = property(_getGroup,_setGroup) | |
1248 | |
1249 def resizeToContent(self,recurse=True): | |
1250 self.width = self.real_font.getWidth(self.text) + 35# Size of the Checked box? | |
1251 self.height = self.real_font.getHeight() | |
1252 | |
1253 class GenericListmodel(fife.ListModel,list): | |
1254 """ | |
1255 A wrapper for the exported list model to behave more like a Python list. | |
1256 Don't use directly. | |
1257 """ | |
1258 def __init__(self,*args): | |
1259 super(GenericListmodel,self).__init__() | |
1260 map(self.append,args) | |
1261 def clear(self): | |
1262 while len(self): | |
1263 self.pop() | |
1264 def getNumberOfElements(self): | |
1265 return len(self) | |
1266 | |
1267 def getElementAt(self, i): | |
1268 i = max(0,min(i,len(self) - 1)) | |
1269 return str(self[i]) | |
1270 | |
1271 class ListBox(Widget): | |
1272 """ | |
1273 A basic list box widget for displaying lists of strings. It makes most sense to wrap | |
1274 this into a L{ScrollArea}. | |
1275 | |
1276 New Attributes | |
1277 ============== | |
1278 | |
1279 - items: A List of strings. This can be treated like an ordinary python list. | |
1280 but only strings are allowed. | |
1281 - selected: The index of the selected item in the list. Starting from C{0} to C{len(items)-1}. | |
1282 A negative value indicates, that no item is selected. | |
1283 - selected_item: The selected string itself, or C{None} - if no string is selected. | |
1284 | |
1285 Data | |
1286 ==== | |
1287 The selected attribute can be read and set via L{distributeData} and L{collectData}. | |
1288 The list items can be set via L{distributeInitialData}. | |
1289 """ | |
1290 def __init__(self,items=[],**kwargs): | |
1291 self._items = GenericListmodel(*items) | |
1292 self.real_widget = fife.ListBox(self._items) | |
1293 super(ListBox,self).__init__(**kwargs) | |
1294 | |
1295 # Prepare Data collection framework | |
1296 self.accepts_initial_data = True | |
1297 self._realSetInitialData = self._setItems | |
1298 | |
1299 self.accepts_data = True | |
1300 self._realSetData = self._setSelected | |
1301 self._realGetData = self._getSelected | |
1302 | |
1303 def resizeToContent(self,recurse=True): | |
1304 # We append a minimum value, so max() does not bail out, | |
1305 # if no items are in the list | |
1306 _item_widths = map(self.real_font.getWidth,map(str,self._items)) + [0] | |
1307 max_w = max(_item_widths) | |
1308 self.width = max_w | |
1309 self.height = (self.real_font.getHeight() + 2) * len(self._items) | |
1310 | |
1311 def _getItems(self): return self._items | |
1312 def _setItems(self,items): | |
1313 # Note we cannot use real_widget.setListModel | |
1314 # for some reason ??? | |
1315 | |
1316 # Also self assignment can kill you | |
1317 if id(items) != id(self._items): | |
1318 self._items.clear() | |
1319 self._items.extend(items) | |
1320 | |
1321 items = property(_getItems,_setItems) | |
1322 | |
1323 def _getSelected(self): return self.real_widget.getSelected() | |
1324 def _setSelected(self,index): self.real_widget.setSelected(index) | |
1325 selected = property(_getSelected,_setSelected) | |
1326 def _getSelectedItem(self): | |
1327 if 0 <= self.selected < len(self._items): | |
1328 return self._items[self.selected] | |
1329 return None | |
1330 selected_item = property(_getSelectedItem) | |
1331 | |
1332 class DropDown(Widget): | |
1333 """ | |
1334 A dropdown or combo box widget for selecting lists of strings. | |
1335 | |
1336 New Attributes | |
1337 ============== | |
1338 | |
1339 - items: A List of strings. This can be treated like an ordinary python list. | |
1340 but only strings are allowed. | |
1341 - selected: The index of the selected item in the list. Starting from C{0} to C{len(items)-1}. | |
1342 A negative value indicates, that no item is selected. | |
1343 - selected_item: The selected string itself, or C{None} - if no string is selected. | |
1344 | |
1345 Data | |
1346 ==== | |
1347 The selected attribute can be read and set via L{distributeData} and L{collectData}. | |
1348 The list items can be set via L{distributeInitialData}. | |
1349 """ | |
1350 def __init__(self,items=[],**kwargs): | |
1351 self._items = GenericListmodel(*items) | |
1352 self.real_widget = fife.DropDown(self._items) | |
1353 super(DropDown,self).__init__(**kwargs) | |
1354 | |
1355 # Prepare Data collection framework | |
1356 self.accepts_initial_data = True | |
1357 self._realSetInitialData = self._setItems | |
1358 | |
1359 self.accepts_data = True | |
1360 self._realSetData = self._setSelected | |
1361 self._realGetData = self._getSelected | |
1362 | |
1363 def resizeToContent(self,recurse=True): | |
1364 # We append a minimum value, so max() does not bail out, | |
1365 # if no items are in the list | |
1366 _item_widths = map(self.real_font.getWidth,map(str,self._items)) + [self.real_font.getHeight()] | |
1367 max_w = max(_item_widths) | |
1368 self.width = max_w | |
1369 self.height = (self.real_font.getHeight() + 2) | |
1370 | |
1371 def _getItems(self): return self._items | |
1372 def _setItems(self,items): | |
1373 # Note we cannot use real_widget.setListModel | |
1374 # for some reason ??? | |
1375 | |
1376 # Also self assignment can kill you | |
1377 if id(items) != id(self._items): | |
1378 self._items.clear() | |
1379 self._items.extend(items) | |
1380 items = property(_getItems,_setItems) | |
1381 | |
1382 def _getSelected(self): return self.real_widget.getSelected() | |
1383 def _setSelected(self,index): self.real_widget.setSelected(index) | |
1384 selected = property(_getSelected,_setSelected) | |
1385 def _getSelectedItem(self): | |
1386 if 0 <= self.selected < len(self._items): | |
1387 return self._items[self.selected] | |
1388 return None | |
1389 selected_item = property(_getSelectedItem) | |
1390 | |
1391 class TextBox(Widget): | |
1392 """ | |
1393 An editable B{multiline} text edit widget. | |
1394 | |
1395 New Attributes | |
1396 ============== | |
1397 | |
1398 - text: The text in the TextBox. | |
1399 - filename: A write-only attribute - assigning a filename will cause the widget to load it's text from it. | |
1400 | |
1401 Data | |
1402 ==== | |
1403 The text can be read and set via L{distributeData} and L{collectData}. | |
1404 """ | |
1405 | |
1406 ATTRIBUTES = Widget.ATTRIBUTES + [Attr('text'),Attr('filename')] | |
1407 | |
1408 def __init__(self,text="",filename = "", **kwargs): | |
1409 self.real_widget = fife.TextBox() | |
1410 self.text = text | |
1411 self.filename = filename | |
1412 super(TextBox,self).__init__(**kwargs) | |
1413 | |
1414 # Prepare Data collection framework | |
1415 self.accepts_data = True | |
1416 self.accepts_initial_data = True # Make sense in a way ... | |
1417 self._realSetInitialData = self._setText | |
1418 self._realSetData = self._setText | |
1419 self._realGetData = self._getText | |
1420 | |
1421 def _getFileName(self): return self._filename | |
1422 def _loadFromFile(self,filename): | |
1423 self._filename = filename | |
1424 if not filename: return | |
1425 try: | |
1426 self.text = open(filename).read() | |
1427 except Exception, e: | |
1428 self.text = str(e) | |
1429 filename = property(_getFileName, _loadFromFile) | |
1430 | |
1431 def resizeToContent(self,recurse=True): | |
1432 rows = [self.real_widget.getTextRow(i) for i in range(self.real_widget.getNumberOfRows())] | |
1433 max_w = max(map(self.real_font.getWidth,rows)) | |
1434 self.width = max_w | |
1435 self.height = (self.real_font.getHeight() + 2) * self.real_widget.getNumberOfRows() | |
1436 | |
1437 def _getText(self): return self.real_widget.getText() | |
1438 def _setText(self,text): self.real_widget.setText(_mungeText(text)) | |
1439 text = property(_getText,_setText) | |
1440 | |
1441 def _setOpaque(self,opaque): self.real_widget.setOpaque(opaque) | |
1442 def _getOpaque(self): return self.real_widget.isOpaque() | |
1443 opaque = property(_getOpaque,_setOpaque) | |
1444 | |
1445 class TextField(Widget): | |
1446 """ | |
1447 An editable B{single line} text edit widget. | |
1448 | |
1449 New Attributes | |
1450 ============== | |
1451 | |
1452 - text: The text in the TextBox. | |
1453 | |
1454 Data | |
1455 ==== | |
1456 The text can be read and set via L{distributeData} and L{collectData}. | |
1457 """ | |
1458 | |
1459 ATTRIBUTES = Widget.ATTRIBUTES + [Attr('text')] | |
1460 | |
1461 def __init__(self,text="", **kwargs): | |
1462 self.real_widget = fife.TextField() | |
1463 self.text = text | |
1464 super(TextField,self).__init__(**kwargs) | |
1465 | |
1466 # Prepare Data collection framework | |
1467 self.accepts_data = True | |
1468 self.accepts_inital_data = True | |
1469 self._realSetInitialData = self._setText | |
1470 self._realSetData = self._setText | |
1471 self._realGetData = self._getText | |
1472 | |
1473 def resizeToContent(self,recurse=True): | |
1474 max_w = self.real_font.getWidth(self.text) | |
1475 self.width = max_w | |
1476 self.height = (self.real_font.getHeight() + 2) | |
1477 def _getText(self): return self.real_widget.getText() | |
1478 def _setText(self,text): self.real_widget.setText(text) | |
1479 text = property(_getText,_setText) | |
1480 | |
1481 def _setOpaque(self,opaque): self.real_widget.setOpaque(opaque) | |
1482 def _getOpaque(self): return self.real_widget.isOpaque() | |
1483 opaque = property(_getOpaque,_setOpaque) | |
1484 | |
1485 | |
1486 # coding: utf-8 | |
1487 | |
1488 class ScrollArea(Widget): | |
1489 """ | |
1490 A wrapper around another (content) widget. | |
1491 | |
1492 New Attributes | |
1493 ============== | |
1494 | |
1495 - content: The wrapped widget. | |
1496 - vertical_scrollbar: Boolean: Set this to False to hide the Vertcial scrollbar | |
1497 - horizontal_scrollbar: Boolean: Set this to False to hide the Horizontal scrollbar | |
1498 | |
1499 """ | |
1500 | |
1501 ATTRIBUTES = Widget.ATTRIBUTES + [ BoolAttr("vertical_scrollbar"),BoolAttr("horizontal_scrollbar") ] | |
1502 | |
1503 def __init__(self,**kwargs): | |
1504 self.real_widget = fife.ScrollArea() | |
1505 self._content = None | |
1506 super(ScrollArea,self).__init__(**kwargs) | |
1507 | |
1508 def addChild(self,widget): | |
1509 self.content = widget | |
1510 | |
1511 def removeChild(self,widget): | |
1512 if self._content != widget: | |
1513 raise RuntimeError("%s does not have %s as direct child widget." % (str(self),str(widget))) | |
1514 self.content = None | |
1515 | |
1516 def _setContent(self,content): | |
1517 self.real_widget.setContent(content.real_widget) | |
1518 self._content = content | |
1519 def _getContent(self): return self._content | |
1520 content = property(_getContent,_setContent) | |
1521 | |
1522 def deepApply(self,visitorFunc): | |
1523 if self._content: visitorFunc(self._content) | |
1524 visitorFunc(self) | |
1525 | |
1526 def resizeToContent(self,recurse=True): | |
1527 if self._content is None: return | |
1528 if recurse: | |
1529 self.content.resizeToContent(recurse=True) | |
1530 self.content.width = max(self.content.width,self.width-5) | |
1531 self.content.height = max(self.content.height,self.height-5) | |
1532 | |
1533 def _visibilityToScrollPolicy(self,visibility): | |
1534 if visibility: | |
1535 return fife.ScrollArea.SHOW_AUTO | |
1536 return fife.ScrollArea.SHOW_NEVER | |
1537 | |
1538 def _scrollPolicyToVisibility(self,policy): | |
1539 if policy == fife.ScrollArea.SHOW_NEVER: | |
1540 return False | |
1541 return True | |
1542 | |
1543 def _setHorizontalScrollbar(self,visibility): | |
1544 self.real_widget.setHorizontalScrollPolicy( self._visibilityToScrollPolicy(visibility) ) | |
1545 | |
1546 def _setVerticalScrollbar(self,visibility): | |
1547 self.real_widget.setVerticalScrollPolicy( self._visibilityToScrollPolicy(visibility) ) | |
1548 | |
1549 def _getHorizontalScrollbar(self): | |
1550 return self._scrollPolicyToVisibility( self.real_widget.getHorizontalScrollPolicy() ) | |
1551 | |
1552 def _getVerticalScrollbar(self): | |
1553 return self._scrollPolicyToVisibility( self.real_widget.getVerticalScrollPolicy() ) | |
1554 | |
1555 vertical_scrollbar = property(_getVerticalScrollbar,_setVerticalScrollbar) | |
1556 horizontal_scrollbar = property(_getHorizontalScrollbar,_setHorizontalScrollbar) | |
1557 | |
1558 # Spacer | |
1559 | |
1560 class Spacer(object): | |
1561 """ A spacer represents expandable 'whitespace' in the GUI. | |
1562 | |
1563 In a XML file you can get this by adding a <Spacer /> inside a VBox or | |
1564 HBox element (Windows implicitly are VBox elements). | |
1565 | |
1566 The effect is, that elements before the spacer will be left (top) | |
1567 and elements after the spacer will be right (bottom) aligned. | |
1568 | |
1569 There can only be one spacer in VBox (HBox). | |
1570 """ | |
1571 def __init__(self,parent=None,**kwargs): | |
1572 self._parent = parent | |
1573 | |
1574 def __str__(self): | |
1575 return "Spacer(parent.name='%s')" % getattr(self._parent,'name','None') | |
1576 | |
1577 def __repr__(self): | |
1578 return "<Spacer(parent.name='%s') at %x>" % (getattr(self._parent,'name','None'),id(self)) | |
1579 | |
1580 | |
1581 # Global Widget Class registry | |
1582 | |
1583 WIDGETS = { | |
1584 # Containers | |
1585 "Container" : Container, | |
1586 "Window" : Window, | |
1587 "VBox" : VBox, | |
1588 "HBox" : HBox, | |
1589 "ScrollArea" :ScrollArea, | |
1590 | |
1591 # Simple Widgets | |
1592 "Icon" : Icon, | |
1593 "Label" : Label, | |
1594 "ClickLabel" : ClickLabel, | |
1595 | |
1596 # Button Widgets | |
1597 "Button" : Button, | |
1598 "CheckBox" : CheckBox, | |
1599 "RadioButton" : RadioButton, | |
1600 "ImageButton" : ImageButton, | |
1601 | |
1602 #Complexer Widgets / Text io | |
1603 "TextField" : TextField, | |
1604 "TextBox" : TextBox, | |
1605 "ListBox" : ListBox, | |
1606 "DropDown" : DropDown | |
1607 } | |
1608 | |
1609 def registerWidget(cls): | |
1610 """ | |
1611 Register a new Widget class for pychan. | |
1612 """ | |
1613 global WIDGETS | |
1614 name = cls.__name__ | |
1615 if name in WIDGETS: | |
1616 raise InitializationError("Widget class name '%s' already registered." % name) | |
1617 WIDGETS[name] = cls |