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