diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/engine/extensions/pychan/widgets.py	Sun Jun 29 18:44:17 2008 +0000
@@ -0,0 +1,1617 @@
+# coding: utf-8
+### Widget/Container Base Classes ###
+
+"""
+Widget wrappers.
+
+Please look at the documentation of L{Widget} for details.
+"""
+
+import fife, pythonize
+import tools
+from exceptions import *
+from attrs import Attr,PointAttr,ColorAttr,BoolAttr,IntAttr
+
+def get_manager():
+    import pychan
+    return pychan.manager
+
+def _mungeText(text):
+	"""
+	This function is applied to all text set on widgets, currently only replacing tabs with four spaces.
+	"""
+	return text.replace('\t'," "*4).replace('[br]','\n')
+
+class _DummyImage(object):
+	def getWidth(self): return 0
+	def getHeight(self): return 0
+
+class Widget(object):
+	"""
+	This is the common widget base class, which provides most of the wrapping
+	functionality.
+	
+	Attributes
+	==========
+	
+	Widgets are manipulated (mostly) through attributes - and these can all be set by XML attributes.
+	Derived widgets will have other attributes. Please see their B{New Attributes} sections. The types of the
+	attributes are pretty straightforward, but note that Position and Color attribute types will also accept
+	C{fife.Point} and C{fife.Color} values.
+	
+	  - name: String: The identification of the widget, most useful if it is unique within a given widget hiarachy.
+	  This is used to find widgets by L{mapEvents},L{distributeInitialData},L{distributeData} and L{collectData}.
+	  - position: Position: The position relative to the parent widget - or on screen, if this is the root widget.
+	  - size: Position: The real size of the widget (including border and margins). Usually you do not need to set this.
+	  A notable exception is the L{ScrollArea}.
+	  - min_size: Position: The minimal size this widget is allowed to have. This is enforced through the accessor methods
+	  of the actual size attribute.
+	  - max_size: Position: The maximal size this widget is allowed to have. This is enforced through the accessor methods
+	  of the actual size attribute.
+	  - base_color: Color
+	  - background_color: Color
+	  - foreground_color: Color
+	  - font: String: This should identify a font that was loaded via L{loadFonts} before.
+	  - border_size: Integer: The size of the border in pixels.
+	  - position_technique: This can be either "automatic" or "explicit" - only L{Window} has this set to "automatic" which
+	  results in new windows being centered on screen (for now).
+	  If it is set to "explicit" the position attribute will not be touched.
+
+	Convenience Attributes
+	======================
+	
+	These attributes are convenience/shorthand versions of above mentioned attributes and assignment will reflect
+	the associated attributes values. E.g. the following is equivalent::
+	   # Set X position, leave Y alone
+	   widget.x = 10
+	   # Same here
+	   posi = widget.position
+	   widget.position = (10, posi[1])
+
+	Here they are.
+
+	   - x: Integer: The horizontal part of the position attribute.
+	   - y: Integer: The vertical part of the position attribute.
+	   - width: Integer: The horizontal part of the size attribute.
+	   - height: Integer: The vertical part of the size attribute.
+
+	"""
+	
+	ATTRIBUTES = [ Attr('name'), PointAttr('position'),
+		PointAttr('min_size'), PointAttr('size'), PointAttr('max_size'),
+		ColorAttr('base_color'),ColorAttr('background_color'),ColorAttr('foreground_color'),
+		Attr('style'), Attr('font'),IntAttr('border_size')
+		]
+	
+	DEFAULT_NAME = '__unnamed__'
+	
+	HIDE_SHOW_ERROR = """\
+	You can only show/hide the top widget of a hierachy.
+	Use 'addChild' or 'removeChild' to add/remove labels for example.
+	"""
+	
+	def __init__(self,parent = None, name = DEFAULT_NAME,
+			size = (-1,-1), min_size=(0,0), max_size=(5000,5000),
+			style = None, **kwargs):
+		
+		assert( hasattr(self,'real_widget') )
+		self._has_listener = False
+		self._visible = False
+		
+		# Data distribution & retrieval settings
+		self.accepts_data = False
+		self.accepts_initial_data = False
+
+		self._parent = parent
+		
+		# This will also set the _event_id and call real_widget.setActionEventId
+		self.name = name
+		
+		self.min_size = min_size
+		self.max_size = max_size
+		self.size = size
+		self.position_technique = "explicit"
+		self.font = 'default'
+		
+		# Inherit style
+		if style is None and parent:
+			style = parent.style
+		self.style = style or "default"
+		
+		# Not needed as attrib assignment will trigger manager.stylize call
+		#manager.stylize(self,self.style)
+
+	def execute(self,bind):
+		"""
+		Execute a dialog synchronously.
+		
+		As argument a dictionary mapping widget names to return values
+		is expected. Events from these widgets will cause this function
+		to return with the associated return value.
+		
+		This function will not return until such an event occurs.
+		The widget will be shown before execution and hidden afterwards.
+		You can only execute root widgets.
+		
+		Note: This feature is not tested well, and the API will probably
+		change. Otherwise have fun::
+		  # Okay this a very condensed example :-)
+		  return pychan.loadXML("contents/gui/dialog.xml").execute({ 'okButton' : True, 'closeButton' : False })
+		
+		"""
+		if not get_manager().can_execute:
+			raise RuntimeError("Synchronous execution is not set up!")
+		if self._parent:
+			raise RuntimeError("You can only 'execute' root widgets, not %s!" % str(self))
+		
+		for name,returnValue in bind.items():
+			def _quitThisDialog(returnValue = returnValue ):
+				get_manager().breakFromMainLoop( returnValue )
+				self.hide()
+			self.findChild(name=name).capture( _quitThisDialog )
+		self.show()
+		return get_manager().mainLoop()
+
+	def match(self,**kwargs):
+		"""
+		Matches the widget against a list of key-value pairs.
+		Only if all keys are attributes and their value is the same it returns True.
+		"""
+		for k,v in kwargs.items():
+			if v != getattr(self,k,None):
+				return False
+		return True
+
+	def capture(self, callback):
+		"""
+		Add a callback to be executed when the widget event occurs on this widget.
+		
+		The callback must be either a callable or None.
+		The old event handler (if any) will be overridden by the callback.
+		If None is given, the event will be disabled. You can query L{isCaptured}
+		wether this widgets events are currently captured.
+		
+		It might be useful to check out L{tools.callbackWithArguments}.
+		
+		"""
+		if callback is None:
+			if not get_manager().widgetEvents.has_key(self._event_id):
+				if get_manager().debug:
+					print "You passed None as parameter to %s.capture, which would normally remove a mapped event." % str(self)
+					print "But there was no event mapped. Did you accidently call a function instead of passing it?"
+			else:
+				del get_manager().widgetEvents[self._event_id]
+			if self._has_listener:
+				self.real_widget.removeActionListener(get_manager().guimanager)
+			self._has_listener = None
+			return
+
+		if not callable(callback):
+			raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback))
+
+		def captured_f(event):
+			tools.applyOnlySuitable(callback,event=event,widget=self)
+
+		get_manager().widgetEvents[self._event_id] = captured_f
+		if not self._has_listener:
+			self.real_widget.addActionListener(get_manager().guimanager)
+		self._has_listener = True
+
+	def isCaptured(self):
+		"""
+		Check whether this widgets events are captured
+		(a callback is installed) or not.
+		"""
+		return self._has_listener
+
+	def show(self):
+		"""
+		Show the widget and all contained widgets.
+		"""
+		if self._parent:
+			raise RuntimeError(Widget.HIDE_SHOW_ERROR)
+		if self._visible: return
+		self.adaptLayout()
+		self.beforeShow()
+		get_manager().show(self)
+		self._visible = True
+
+	def hide(self):
+		"""
+		Hide the widget and all contained widgets.
+		"""
+		if self._parent:
+			raise RuntimeError(Widget.HIDE_SHOW_ERROR)
+		if not self._visible: return
+		get_manager().hide(self)
+		self.afterHide()
+		self._visible = False
+	
+	def isVisible(self):
+		"""
+		Check whether the widget is currently shown,
+		either directly or as part of a container widget.
+		"""
+		widget = self
+		while widget._parent:
+			widget = widget._parent
+		return widget._visible
+	
+	def adaptLayout(self):
+		"""
+		Execute the Layout engine. Automatically called by L{show}.
+		In case you want to relayout a visible widget, you have to call this function
+		on the root widget.
+		"""
+		self._recursiveResizeToContent()
+		self._recursiveExpandContent()
+	
+	def beforeShow(self):
+		"""
+		This method is called just before the widget is shown.
+		You can override this in derived widgets to add finalization
+		behaviour.
+		"""
+
+	def afterHide(self):
+		"""
+		This method is called just before the widget is hidden.
+		You can override this in derived widgets to add finalization
+		behaviour.
+		"""
+
+	def findChildren(self,**kwargs):
+		"""
+		Find all contained child widgets by attribute values.
+		
+		Usage::
+		  closeButtons = root_widget.findChildren(name='close')
+		"""
+
+		children = []
+		def _childCollector(widget):
+			if widget.match(**kwargs):
+				children.append(widget)
+		self.deepApply(_childCollector)
+		return children
+
+	def findChild(self,**kwargs):
+		""" Find the first contained child widgets by attribute values.
+		
+		Usage::
+		  closeButton = root_widget.findChild(name='close')
+		"""
+		children = self.findChildren(**kwargs)
+		if children:
+			return children[0]
+		return None
+
+	def addChild(self,widget):
+		"""
+		This function adds a widget as child widget and is only implemented
+		in container widgets.
+		"""
+		raise RuntimeError("Trying to add a widget to %s, which doesn't allow this." % repr(self))
+
+	def addChildren(self,*widgets):
+		for widget in widgets:
+			self.addChild(widget)
+
+	def removeChild(self,widget):
+		"""
+		This function removes a direct child widget and is only implemented
+		in container widgets.
+		"""
+		raise RuntimeError("Trying to remove a widget from %s, which is not a container widget." % repr(self))
+
+	def removeChildren(self,*widgets):
+		for widget in widgets:
+			self.removeChild(widget)
+
+	def mapEvents(self,eventMap,ignoreMissing = False):
+		"""
+		Convenience function to map widget events to functions
+		in a batch.
+		
+		Subsequent calls of mapEvents will merge events with different
+		widget names and override the previously set callback.
+		You can also pass C{None} instead of a callback, which will
+		disable the event completely.
+		
+		@param eventMap: A dictionary with widget names as keys and callbacks as values.
+		@param ignoreMissing: Normally this method raises an RuntimeError, when a widget
+		can not be found - this behaviour can be overriden by passing True here.
+		"""
+		for name,func in eventMap.items():
+			widget = self.findChild(name=name)
+			if widget:
+				widget.capture( func )
+			elif not ignoreMissing:
+				raise RuntimeError("No widget with the name: %s" % name)
+
+	def setInitialData(self,data):
+		"""
+		Set the initial data on a widget, what this means depends on the Widget.
+		In case the widget does not accept initial data, a L{RuntimeError} is thrown.
+		"""
+		if not self.accepts_initial_data:
+			raise RuntimeError("Trying to set data on a widget that does not accept initial data. Widget: %s Data: %s " % (repr(self),repr(data)))
+		self._realSetInitialData(data)
+	
+	def setData(self,data):
+		"""
+		Set the user-mutable data on a widget, what this means depends on the Widget.
+		In case the widget does not accept data, a L{RuntimeError} is thrown.
+		This is inverse to L{getData}.
+		"""
+		if not self.accepts_data:
+			raise RuntimeError("Trying to set data on a widget that does not accept data.")
+		self._realSetData(data)
+
+	def getData(self):
+		"""
+		Get the user-mutable data of a widget, what this means depends on the Widget.
+		In case the widget does not have user mutable data, a L{RuntimeError} is thrown.
+		This is inverse to L{setData}.
+		"""
+		if not self.accepts_data:
+			raise RuntimeError("Trying to retrieve data from a widget that does not accept data.")
+		return self._realGetData()
+
+	def distributeInitialData(self,initialDataMap):
+		"""
+		Distribute B{initial} (not mutable by the user) data from a dictionary over the widgets in the hierachy
+		using the keys as names and the values as the data (which is set via L{setInitialData}).
+		If more than one widget matches - the data is set on ALL matching widgets.
+		By default a missing widget is just ignored.
+		
+		Use it like this::
+		  guiElement.distributeInitialData({
+		       'myTextField' : 'Hello World!',
+		       'myListBox' : ["1","2","3"]
+		  })
+		
+		"""
+		for name,data in initialDataMap.items():
+			widgetList = self.findChildren(name = name)
+			for widget in widgetList:
+				widget.setInitialData(data)
+	
+	def distributeData(self,dataMap):
+		"""
+		Distribute data from a dictionary over the widgets in the hierachy
+		using the keys as names and the values as the data (which is set via L{setData}).
+		This will only accept unique matches.
+		
+		Use it like this::
+		  guiElement.distributeData({
+		       'myTextField' : 'Hello World!',
+		       'myListBox' : ["1","2","3"]
+		  })
+		
+		"""
+		for name,data in dataMap.items():
+			widgetList = self.findChildren(name = name)
+			if len(widgetList) != 1:
+				raise RuntimeError("DistributeData can only handle widgets with unique names.")
+			widgetList[0].setData(data)
+
+	def collectDataAsDict(self,widgetNames):
+		"""
+		Collect data from a widget hierachy by names into a dictionary.
+		This can only handle UNIQUE widget names (in the hierachy)
+		and will raise a RuntimeError if the number of matching widgets
+		is not equal to one.
+		
+		Usage::
+		  data = guiElement.collectDataAsDict(['myTextField','myListBox'])
+		  print "You entered:",data['myTextField']," and selected ",data['myListBox']
+		
+		"""
+		dataMap = {}
+		for name in widgetNames:
+			widgetList = self.findChildren(name = name)
+			if len(widgetList) != 1:
+				raise RuntimeError("CollectData can only handle widgets with unique names.")
+			
+			dataMap[name] = widgetList[0].getData()
+		return dataMap
+
+	def collectData(self,*widgetNames):
+		"""
+		Collect data from a widget hierachy by names.
+		This can only handle UNIQUE widget names (in the hierachy)
+		and will raise a RuntimeError if the number of matching widgets
+		is not equal to one.
+		
+		This function takes an arbitrary number of widget names and
+		returns a list of the collected data in the same order.
+		
+		In case only one argument is given, it will return just the
+		data, with out putting it into a list.
+		
+		Usage::
+		  # Multiple element extraction:
+		  text, selected = guiElement.collectData('myTextField','myListBox')
+		  print "You entered:",text," and selected item nr",selected
+		  # Single elements are handled gracefully, too:
+		  test = guiElement.collectData('testElement')
+		
+		"""
+		dataList = []
+		for name in widgetNames:
+			widgetList = self.findChildren(name = name)
+			if len(widgetList) != 1:
+				raise RuntimeError("CollectData can only handle widgets with unique names.")
+			dataList.append( widgetList[0].getData() )
+		if len(dataList) == 1:
+			return dataList[0]
+		return dataList
+
+	def listNamedWidgets(self):
+		"""
+		This function will print a list of all currently named child-widgets
+		to the standard output. This is useful for debugging purposes.
+		"""
+		def _printNamedWidget(widget):
+			if widget.name != Widget.DEFAULT_NAME:
+				print widget.name.ljust(20),repr(widget).ljust(50),repr(widget._parent)
+		print "Named child widgets of ",repr(self)
+		print "name".ljust(20),"widget".ljust(50),"parent"
+		self.deepApply(_printNamedWidget)
+
+
+	def stylize(self,style,**kwargs):
+		"""
+		Recursively apply a style to all widgets.
+		"""
+		def _restyle(widget):
+			get_manager().stylize(widget,style,**kwargs)
+		self.deepApply(_restyle)
+
+	def resizeToContent(self,recurse = True):
+		"""
+		Try to shrink the widget, so that it fits closely around its content.
+		Do not call directly.
+		"""
+
+	def expandContent(self,recurse = True):
+		"""
+		Try to expand any spacer in the widget within the current size.
+		Do not call directly.
+		"""
+		
+
+	def _recursiveResizeToContent(self):
+		"""
+		Recursively call L{resizeToContent}. Uses L{deepApply}.
+		Do not call directly.
+		"""
+		def _callResizeToContent(widget):
+			#print "RTC:",widget
+			widget.resizeToContent()
+		self.deepApply(_callResizeToContent)
+
+	def _recursiveExpandContent(self):
+		"""
+		Recursively call L{expandContent}. Uses L{deepApply}.
+		Do not call directly.
+		"""
+		def _callExpandContent(widget):
+			#print "ETC:",widget
+			widget.expandContent()
+		self.deepApply(_callExpandContent)
+
+	def deepApply(self,visitorFunc):
+		"""
+		Recursively apply a callable to all contained widgets and then the widget itself.
+		"""
+		visitorFunc(self)
+
+	def sizeChanged(self):
+		if self._parent:
+			self._parent.sizeChanged()
+		else:
+			self.adaptLayout()
+
+	def __str__(self):
+		return "%s(name='%s')" % (self.__class__.__name__,self.name)
+	
+	def __repr__(self):
+		return "<%s(name='%s') at %x>" % (self.__class__.__name__,self.name,id(self))
+
+	def _setSize(self,size):
+		if isinstance(size,fife.Point):
+			self.width, self.height = size.x, size.y
+		else:
+			self.width, self.height = size
+		#self.sizeChanged()
+
+	def _getSize(self):
+		return self.width, self.height
+
+	def _setPosition(self,size):
+		if isinstance(size,fife.Point):
+			self.x, self.y = size.x, size.y
+		else:
+			self.x, self.y = size
+
+	def _getPosition(self):
+		return self.x, self.y
+
+	def _setX(self,x):self.real_widget.setX(x)
+	def _getX(self): return self.real_widget.getX()
+	def _setY(self,y): self.real_widget.setY(y)
+	def _getY(self): return self.real_widget.getY()
+
+	def _setWidth(self,w):
+		w = max(self.min_size[0],w)
+		w = min(self.max_size[0],w)
+		self.real_widget.setWidth(w)
+
+	def _getWidth(self): return self.real_widget.getWidth()
+	def _setHeight(self,h):
+		h = max(self.min_size[1],h)
+		h = min(self.max_size[1],h)
+		self.real_widget.setHeight(h)
+
+	def _getHeight(self): return self.real_widget.getHeight()
+
+	def _setFont(self, font):
+		self._font = font
+		self.real_font = get_manager().getFont(font)
+		self.real_widget.setFont(self.real_font)
+	def _getFont(self):
+		return self._font
+
+	def _getBorderSize(self): return self.real_widget.getFrameSize()
+	def _setBorderSize(self,size): self.real_widget.setFrameSize(size)
+
+	def _getBaseColor(self): return self.real_widget.getBaseColor()
+	def _setBaseColor(self,color):
+		if isinstance(color,type(())):
+			color = fife.Color(*color)
+		self.real_widget.setBaseColor(color)
+	base_color = property(_getBaseColor,_setBaseColor)
+	
+	def _getBackgroundColor(self): return self.real_widget.getBackgroundColor()
+	def _setBackgroundColor(self,color):
+		if isinstance(color,type(())):
+			color = fife.Color(*color)
+		self.real_widget.setBackgroundColor(color)
+	background_color = property(_getBackgroundColor,_setBackgroundColor)
+
+	def _getForegroundColor(self): return self.real_widget.getForegroundColor()
+	def _setForegroundColor(self,color):
+		if isinstance(color,type(())):
+			color = fife.Color(*color)
+		self.real_widget.setForegroundColor(color)
+	foreground_color = property(_getForegroundColor,_setForegroundColor)
+	
+	def _getName(self): return self._name
+	def _setName(self,name):
+		from pychan import manager
+		self._name = name
+		# Do not change the event id while we are captured.
+		if not self.isCaptured():
+			self._event_id = "%s(name=%s,id=%d)" % (str(self.__class__),name,id(self))
+		else:
+			# Print some notfication, so obscure behaviour might get debugged.
+			print "%s already captured, but changing the name attribute. Just a notification :-)" % str(self)
+		self.real_widget.setActionEventId(self._event_id)
+	name = property(_getName,_setName)
+
+	def _getStyle(self): return self._style
+	def _setStyle(self,style):
+		self._style = style
+		get_manager().stylize(self,style)
+	style = property(_getStyle,_setStyle)
+
+	x = property(_getX,_setX)
+	y = property(_getY,_setY)
+	width = property(_getWidth,_setWidth)
+	height = property(_getHeight,_setHeight)
+	size = property(_getSize,_setSize)
+	position = property(_getPosition,_setPosition)
+	font = property(_getFont,_setFont)
+	border_size = property(_getBorderSize,_setBorderSize)
+
+### Containers + Layout code ###
+
+class Container(Widget):
+	"""
+	This is the basic container class. It provides space in which child widgets can
+	be position via the position attribute. If you want to use the layout engine,
+	you have to use derived containers with vertical or horizontal orientation
+	(L{VBox} or L{HBox})
+	
+	New Attributes
+	==============
+	
+	  - padding - Integer: Not used in the Container class istelf, distance between child widgets.
+	  - background_image - Set this to a GuiImage or a resource location (simply a filename).
+	    The image will be tiled over the background area.
+	  - opaque - Boolean: Whether the background should be drawn at all. Set this to False
+	    to make the widget transparent.
+	  - children - Just contains the list of contained child widgets. Do NOT modify.
+	"""
+	
+	ATTRIBUTES = Widget.ATTRIBUTES + [ IntAttr('padding'), Attr('background_image'), BoolAttr('opaque'),PointAttr('margins') ]
+	
+	def __init__(self,padding=5,margins=(5,5),_real_widget=None, **kwargs):
+		self.real_widget = _real_widget or fife.Container()
+		self.children = []
+		self.margins = margins
+		self.padding = padding
+		self._background = []
+		self._background_image = None
+		super(Container,self).__init__(**kwargs)
+
+	def addChild(self, widget):
+		widget._parent = self
+		self.children.append(widget)
+		self.real_widget.add(widget.real_widget)
+
+	def removeChild(self,widget):
+		if not widget in self.children:
+			raise RuntimeError("%s does not have %s as direct child widget." % (str(self),str(widget)))
+		self.children.remove(widget)
+		self.real_widget.remove(widget.real_widget)
+		widget._parent = None
+
+	def add(self,*widgets):
+		print "PyChan: Deprecation warning: Please use 'addChild' or 'addChildren' instead."
+		self.addChildren(*widgets)
+
+	def getMaxChildrenWidth(self):
+		if not self.children: return 0
+		return max(widget.width for widget in self.children)
+
+	def getMaxChildrenHeight(self):
+		if not self.children: return 0
+		return max(widget.height for widget in self.children)
+
+	def deepApply(self,visitorFunc):
+		for child in self.children:
+			child.deepApply(visitorFunc)
+		visitorFunc(self)
+
+	def beforeShow(self):
+		self._resetTiling()
+
+	def _resetTiling(self):
+		image = self._background_image
+		if image is None:
+			return
+		
+		back_w,back_h = self.width, self.height
+		image_w, image_h = image.getWidth(), image.getHeight()
+
+		map(self.real_widget.remove,self._background)
+		
+		# Now tile the background over the widget
+		self._background = []
+		icon = fife.Icon(image)
+		x, w = 0, image_w
+		while x < back_w:
+			y, h = 0, image_h
+			while y < self.height:
+				icon = fife.Icon(image)
+				icon.setPosition(x,y)
+				self._background.append(icon)
+				y += h
+			x += w
+		map(self.real_widget.add,self._background)
+		for tile in self._background:
+			tile.requestMoveToBottom()
+
+	def setBackgroundImage(self,image):
+		self._background = getattr(self,'_background',None)
+		if image is None:
+			self._background_image = image
+			map(self.real_widget.remove,self._background)
+			self._background = []
+
+		# Background generation is done in _resetTiling
+
+		if not isinstance(image, fife.GuiImage):
+			image = get_manager().loadImage(image)
+		self._background_image = image
+	
+	def getBackgroundImage(self): return self._background_image
+	background_image = property(getBackgroundImage,setBackgroundImage)
+
+	def _setOpaque(self,opaque): self.real_widget.setOpaque(opaque)
+	def _getOpaque(self): return self.real_widget.isOpaque()
+	opaque = property(_getOpaque,_setOpaque)
+
+AlignTop, AlignBottom, AlignLeft, AlignRight, AlignCenter = range(5)
+
+class LayoutBase(object):
+	"""
+	This class is at the core of the layout engine. The two MixIn classes L{VBoxLayoutMixin}
+	and L{HBoxLayoutMixin} specialise on this by reimplementing the C{resizeToContent} and
+	the C{expandContent} methods.
+	
+	At the core the layout engine works in two passes:
+	
+	Before a root widget loaded by the XML code is shown, its resizeToContent method
+	is called recursively (walking the widget containment relation in post order).
+	This shrinks all HBoxes and VBoxes to their minimum heigt and width.
+	After that the expandContent method is called recursively in the same order,
+	which will re-align the widgets if there is space left AND if a Spacer is contained.
+	
+	Inside bare Container instances (without a Layout MixIn) absolute positioning
+	can be used.
+	"""
+	def __init__(self,align = (AlignLeft,AlignTop), **kwargs):
+		self.align = align
+		self.spacer = None
+		super(LayoutBase,self).__init__(**kwargs)
+
+	def addSpacer(self,spacer):
+		if self.spacer:
+			raise RuntimeException("Already a Spacer in %s!" % str(self))
+		self.spacer = spacer
+		spacer.index = len(self.children)
+
+	def xdelta(self,widget):return 0
+	def ydelta(self,widget):return 0
+
+	def _adjustHeight(self):
+		
+		if self.align[1] == AlignTop:return #dy = 0
+		if self.align[1] == AlignBottom:
+			y = self.height - self.childarea[1] - self.border_size - self.margins[1]
+		else:
+			y = (self.height - self.childarea[1] - self.border_size - self.margins[1])/2
+		for widget in self.children:
+			widget.y = y
+			y += self.ydelta(widget)
+
+	def _adjustHeightWithSpacer(self):
+		pass
+
+	def _adjustWidth(self):
+
+		if self.align[0] == AlignLeft:return #dx = 0
+		if self.align[0] == AlignRight:
+			x = self.width - self.childarea[0] - self.border_size - self.margins[0]
+		else:
+			x = (self.width - self.childarea[0] - self.border_size - self.margins[0])/2
+		for widget in self.children:
+			widget.x = x
+			x += self.xdelta(widget)
+
+	def _expandWidthSpacer(self):
+		x = self.border_size + self.margins[0]
+		xdelta = map(self.xdelta,self.children)
+		
+		for widget in self.children[:self.spacer.index]:
+			widget.x = x
+			x += xdelta.pop(0)
+		
+		x = self.width - sum(xdelta) - self.border_size - self.margins[0]
+		for widget in self.children[self.spacer.index:]:
+			widget.x = x
+			x += xdelta.pop(0)
+
+	def _expandHeightSpacer(self):
+		y = self.border_size + self.margins[1]
+		ydelta = map(self.ydelta,self.children)
+		
+		for widget in self.children[:self.spacer.index]:
+			widget.y = y
+			y += ydelta.pop(0)
+		
+		y = self.height - sum(ydelta) - self.border_size - self.margins[1]
+		for widget in self.children[self.spacer.index:]:
+			widget.y = y
+			y += ydelta.pop(0)
+
+
+class VBoxLayoutMixin(LayoutBase):
+	"""
+	A mixin class for a vertical layout. Do not use directly.
+	"""
+	def __init__(self,**kwargs):
+		super(VBoxLayoutMixin,self).__init__(**kwargs)
+
+	def resizeToContent(self, recurse = True):
+		max_w = self.getMaxChildrenWidth()
+		x = self.margins[0] + self.border_size
+		y = self.margins[1] + self.border_size
+		for widget in self.children:
+			widget.x = x
+			widget.y = y
+			widget.width = max_w
+			y += widget.height + self.padding
+		
+		#Add the padding for the spacer.
+		if self.spacer:
+			y += self.padding
+
+		self.height = y + self.margins[1] - self.padding
+		self.width = max_w + 2*x
+		self.childarea = max_w, y - self.padding - self.margins[1]
+
+		self._adjustHeight()
+		self._adjustWidth()
+
+	def expandContent(self):
+		if self.spacer:
+			self._expandHeightSpacer()
+
+	def ydelta(self,widget):return widget.height + self.padding
+
+class HBoxLayoutMixin(LayoutBase):
+	"""
+	A mixin class for a horizontal layout. Do not use directly.
+	"""
+	def __init__(self,**kwargs):
+		super(HBoxLayoutMixin,self).__init__(**kwargs)
+
+	def resizeToContent(self, recurse = True):
+		max_h = self.getMaxChildrenHeight()
+		x = self.margins[0] + self.border_size
+		y = self.margins[1] + self.border_size
+		for widget in self.children:
+			widget.x = x
+			widget.y = y
+			widget.height = max_h
+			x += widget.width + self.padding
+		
+		#Add the padding for the spacer.
+		if self.spacer:
+			x += self.padding
+
+		self.width = x + self.margins[0] - self.padding
+		self.height = max_h + 2*y
+		self.childarea = x - self.margins[0] - self.padding, max_h
+		
+		self._adjustHeight()
+		self._adjustWidth()
+
+	def expandContent(self):
+		if self.spacer:
+			self._expandWidthSpacer()
+
+	def xdelta(self,widget):return widget.width + self.padding
+
+
+class VBox(VBoxLayoutMixin,Container):
+	"""
+	A vertically aligned box - for containement of child widgets.
+	
+	Widgets added to this container widget, will layout on top of each other.
+	Also the minimal width of the container will be the maximum of the minimal
+	widths of the contained widgets.
+	
+	The default alignment is to the top. This can be changed by adding a Spacer
+	to the widget at any point (but only one!). The spacer will expand, so that
+	widgets above the spacer are aligned to the top, while widgets below the spacer
+	are aligned to the bottom.
+	"""
+	def __init__(self,padding=5,**kwargs):
+		super(VBox,self).__init__(**kwargs)
+		self.padding = padding
+
+
+class HBox(HBoxLayoutMixin,Container):
+	"""
+	A horizontally aligned box - for containement of child widgets.
+	
+	Please see L{VBox} for details - just change the directions :-).
+	"""	
+	def __init__(self,padding=5,**kwargs):
+		super(HBox,self).__init__(**kwargs)
+		self.padding = padding
+
+class Window(VBoxLayoutMixin,Container):
+	"""
+	A L{VBox} with a draggable title bar aka a window
+	
+	New Attributes
+	==============
+	
+	  - title: The Caption of the window
+	  - titlebar_height: The height of the window title bar
+	"""
+	
+	ATTRIBUTES = Container.ATTRIBUTES + [ Attr('title'), IntAttr('titlebar_height') ]
+	
+	def __init__(self,title="title",titlebar_height=0,**kwargs):
+		super(Window,self).__init__(_real_widget = fife.Window(), **kwargs)
+		if titlebar_height == 0:
+			titlebar_height = self.real_font.getHeight() + 4
+		self.titlebar_height = titlebar_height
+		self.title = title
+
+		# Override explicit positioning
+		self.position_technique = "automatic"
+
+
+	def _getTitle(self): return self.real_widget.getCaption()
+	def _setTitle(self,text): self.real_widget.setCaption(text)
+	title = property(_getTitle,_setTitle)
+	
+	def _getTitleBarHeight(self): return self.real_widget.getTitleBarHeight()
+	def _setTitleBarHeight(self,h): self.real_widget.setTitleBarHeight(h)
+	titlebar_height = property(_getTitleBarHeight,_setTitleBarHeight)
+
+	# Hackish way of hiding that title bar height in the perceived height.
+	# Fixes VBox calculation
+	def _setHeight(self,h):
+		h = max(self.min_size[1],h)
+		h = min(self.max_size[1],h)
+		self.real_widget.setHeight(h + self.titlebar_height)
+	def _getHeight(self): return self.real_widget.getHeight() - self.titlebar_height
+	height = property(_getHeight,_setHeight)
+
+### Basic Widgets ###
+
+class BasicTextWidget(Widget):
+	"""
+	The base class for widgets which display a string - L{Label},L{ClickLabel},L{Button}, etc.
+	Do not use directly.
+	
+	New Attributes
+	==============
+	
+	  - text: The text (depends on actual widget)
+	
+	Data
+	====
+	
+	The text can be set via the L{distributeInitialData} method.
+	"""
+	
+	ATTRIBUTES = Widget.ATTRIBUTES + [Attr('text')]
+	
+	def __init__(self, text = "",**kwargs):
+		self.margins = (5,5)
+		self.text = text
+		super(BasicTextWidget,self).__init__(**kwargs)
+		
+		# Prepare Data collection framework
+		self.accepts_initial_data = True
+		self._realSetInitialData = self._setText
+
+	def _getText(self): return self.real_widget.getCaption()
+	def _setText(self,text): self.real_widget.setCaption(_mungeText(text))
+	text = property(_getText,_setText)
+
+	def resizeToContent(self, recurse = True):
+		self.height = self.real_font.getHeight() + self.margins[1]*2
+		self.width = self.real_font.getWidth(self.text) + self.margins[0]*2
+
+class Icon(Widget):
+	"""
+	An image icon.
+	
+	New Attributes
+	==============
+	
+	  - image: String or GuiImage: The source location of the Image or a direct GuiImage
+	"""
+	ATTRIBUTES = Widget.ATTRIBUTES + [Attr('image')]
+
+	def __init__(self,image="",**kwargs):
+		self.real_widget = fife.Icon(None)
+		super(Icon,self).__init__(**kwargs)
+		self._source = self._image = None
+		if image:
+			self.image = image
+
+	def _setImage(self,source):
+		if isinstance(source,str):
+			self._source = source
+			self._image = get_manager().loadImage(source)
+		elif isinstance(source,fife.GuiImage):
+			self._source = None
+			self._image = source
+		else:
+			raise RuntimeError("Icon.image only accepts GuiImage and python strings, not '%s'" % repr(source))
+		self.real_widget.setImage( self._image )
+
+		# Set minimum size accoriding to image
+		self.min_size = self.real_widget.getWidth(),self.real_widget.getHeight()
+		self.size  = self.max_size = self.min_size
+
+	def _getImage(self):
+		if self._source is not None:
+			return self._source
+		return self._image
+	image = property(_getImage,_setImage)
+
+class Label(BasicTextWidget):
+	"""
+	A basic label - displaying a string.
+	
+	Also allows text wrapping.
+	
+	New Attributes
+	==============
+	
+	 - wrap_text: Boolean: Enable/Disable automatic text wrapping. Disabled by default.
+	 Currently to actually see text wrapping you have to explicitly set a max_size with
+	 the desired width of the text, as the layout engine is not capable of deriving
+	 the maximum width from a parent container.
+	"""
+	
+	ATTRIBUTES = BasicTextWidget.ATTRIBUTES + [BoolAttr('wrap_text')]
+	
+	def __init__(self,wrap_text=False,**kwargs):
+		self.real_widget = fife.Label("")
+		self.wrap_text = wrap_text
+		super(Label,self).__init__(**kwargs)
+
+	def resizeToContent(self):
+		self.real_widget.setWidth( self.max_size[0] )
+		self.real_widget.adjustSize()
+		self.height = self.real_widget.getHeight() + self.margins[1]*2
+		self.width  = self.real_widget.getWidth()  + self.margins[0]*2
+		#print self.width,self.max_size[0]
+	
+	def _setTextWrapping(self,wrapping): self.real_widget.setTextWrapping(wrapping)
+	def _getTextWrapping(self): self.real_widget.isTextWrapping()
+	wrap_text = property(_getTextWrapping,_setTextWrapping)
+
+class ClickLabel(Label):
+	"""
+	Deprecated - use L{Label} instead.
+	"""
+	__init__ = tools.this_is_deprecated(Label.__init__,message = "ClickLabel - Use Label instead")
+
+
+class Button(BasicTextWidget):
+	"""
+	A basic push button.
+	"""
+	def __init__(self,**kwargs):
+		self.real_widget = fife.Button("")
+		super(Button,self).__init__(**kwargs)
+
+class ImageButtonListener(fife.TwoButtonListener):
+	def __init__(self, btn):
+		fife.TwoButtonListener.__init__(self)
+		self.btn = btn
+		self.entercb = None
+		self.exitcb = None
+	
+	def mouseEntered(self, btn):
+		if self.entercb:
+			self.entercb(self.btn)
+	
+	def mouseExited(self, btn):
+		if self.exitcb:
+			self.exitcb(self.btn)
+	
+class ImageButton(BasicTextWidget):
+	"""
+	A basic push button with three different images for the up, down and hover state.
+	
+	B{Work in progress.}
+	
+	New Attributes
+	==============
+	
+	  - up_image: String: The source location of the Image for the B{unpressed} state.
+	  - down_image: String: The source location of the Image for the B{pressed} state.
+	  - hover_image: String: The source location of the Image for the B{unpressed hovered} state.
+	"""
+	
+	ATTRIBUTES = BasicTextWidget.ATTRIBUTES + [Attr('up_image'),Attr('down_image'),PointAttr('offset'),Attr('helptext'),Attr('hover_image')]
+	
+	def __init__(self,up_image="",down_image="",hover_image="",offset=(0,0),**kwargs):
+		self.real_widget = fife.TwoButton()
+		super(ImageButton,self).__init__(**kwargs)
+		self.listener = ImageButtonListener(self)
+		self.real_widget.setListener(self.listener)
+		
+		self.up_image = up_image
+		self.down_image = down_image
+		self.hover_image = hover_image
+		self.offset = offset
+
+	def _setUpImage(self,image):
+		self._upimage_source = image
+		try:
+			self._upimage = get_manager().loadImage(image)
+			self.real_widget.setUpImage( self._upimage )
+		except:
+			self._upimage = _DummyImage()
+	def _getUpImage(self): return self._upimage_source
+	up_image = property(_getUpImage,_setUpImage)
+
+	def _setDownImage(self,image):
+		self._downimage_source = image
+		try:
+			self._downimage = get_manager().loadImage(image)
+			self.real_widget.setDownImage( self._downimage )
+		except:
+			self._downimage = _DummyImage()
+	def _getDownImage(self): return self._downimage_source
+	down_image = property(_getDownImage,_setDownImage)
+
+	def _setHoverImage(self,image):
+		self._hoverimage_source = image
+		try:
+			self._hoverimage = get_manager().loadImage(image)
+			self.real_widget.setHoverImage( self._hoverimage )
+		except:
+			self._hoverimage = _DummyImage()
+	def _getHoverImage(self): return self._hoverimage_source
+	hover_image = property(_getHoverImage,_setHoverImage)	
+	
+	def _setOffset(self, offset):
+		self.real_widget.setDownOffset(offset[0], offset[1])
+	def _getOffset(self):
+		return (self.real_widget.getDownXOffset(), self.real_widget.getDownYOffset())
+	offset = property(_getOffset,_setOffset)
+
+	def _setHelpText(self, txt):
+		self.real_widget.setHelpText(txt)
+	def _getHelpText(self):
+		return self.real_widget.getHelpText()
+	helptext = property(_getHelpText,_setHelpText)
+	
+	def resizeToContent(self):
+		self.height = max(self._upimage.getHeight(),self._downimage.getHeight(),self._hoverimage.getHeight()) + self.margins[1]*2
+		self.width = max(self._upimage.getWidth(),self._downimage.getWidth(),self._hoverimage.getWidth()) + self.margins[1]*2
+
+	def setEnterCallback(self, cb):
+		'''
+		Callback is called when mouse enters the area of ImageButton
+		callback should have form of function(button)
+		'''
+		self.listener.entercb = cb
+	
+	def setExitCallback(self, cb):
+		'''
+		Callback is called when mouse enters the area of ImageButton
+		callback should have form of function(button)
+		'''
+		self.listener.exitcb = cb
+	
+
+
+class CheckBox(BasicTextWidget):
+	"""
+	A basic checkbox.
+	
+	New Attributes
+	==============
+	
+	  - marked: Boolean value, whether the checkbox is checked or not.
+	
+	Data
+	====
+	The marked status can be read and set via L{distributeData} and L{collectData}
+	"""
+	
+	ATTRIBUTES = BasicTextWidget.ATTRIBUTES + [BoolAttr('marked')]
+	
+	def __init__(self,**kwargs):
+		self.real_widget = fife.CheckBox()
+		super(CheckBox,self).__init__(**kwargs)
+
+		# Prepare Data collection framework
+		self.accepts_data = True
+		self._realGetData = self._isMarked
+		self._realSetData = self._setMarked
+		
+		# Initial data stuff inherited.
+	
+	def _isMarked(self): return self.real_widget.isSelected()
+	def _setMarked(self,mark): self.real_widget.setSelected(mark)
+	marked = property(_isMarked,_setMarked)
+
+class RadioButton(BasicTextWidget):
+	"""
+	A basic radiobutton (an exclusive checkbox).
+	
+	New Attributes
+	==============
+	
+	  - marked: Boolean: Whether the checkbox is checked or not.
+	  - group: String: All RadioButtons with the same group name
+	  can only be checked exclusively.
+	
+	Data
+	====
+	The marked status can be read and set via L{distributeData} and L{collectData}
+	"""
+	
+	ATTRIBUTES = BasicTextWidget.ATTRIBUTES + [BoolAttr('marked'),Attr('group')]
+	
+	def __init__(self,group="_no_group_",**kwargs):
+		self.real_widget = fife.RadioButton()
+		super(RadioButton,self).__init__(**kwargs)
+
+		self.group = group
+
+		# Prepare Data collection framework
+		self.accepts_data = True
+		self._realGetData = self._isMarked
+		self._realSetData = self._setMarked
+		
+		# Initial data stuff inherited.
+	
+	def _isMarked(self): return self.real_widget.isSelected()
+	def _setMarked(self,mark): self.real_widget.setSelected(mark)
+	marked = property(_isMarked,_setMarked)
+
+	def _setGroup(self,group): self.real_widget.setGroup(group)
+	def _getGroup(self): return self.real_widget.getGroup()
+	group = property(_getGroup,_setGroup)
+
+	def resizeToContent(self,recurse=True):
+		self.width = self.real_font.getWidth(self.text) + 35# Size of the Checked box?
+		self.height = self.real_font.getHeight()
+
+class GenericListmodel(fife.ListModel,list):
+	"""
+	A wrapper for the exported list model to behave more like a Python list.
+	Don't use directly.
+	"""
+	def __init__(self,*args):
+		super(GenericListmodel,self).__init__()
+		map(self.append,args)
+	def clear(self):
+		while len(self):
+			self.pop()
+	def getNumberOfElements(self):
+		return len(self)
+		
+	def getElementAt(self, i):
+		i = max(0,min(i,len(self) - 1))
+		return str(self[i])
+
+class ListBox(Widget):
+	"""
+	A basic list box widget for displaying lists of strings. It makes most sense to wrap
+	this into a L{ScrollArea}.
+	
+	New Attributes
+	==============
+	
+	  - items: A List of strings. This can be treated like an ordinary python list.
+	    but only strings are allowed.
+	  - selected: The index of the selected item in the list. Starting from C{0} to C{len(items)-1}.
+	    A negative value indicates, that no item is selected.
+	  - selected_item: The selected string itself, or C{None} - if no string is selected.
+	
+	Data
+	====
+	The selected attribute can be read and set via L{distributeData} and L{collectData}.
+	The list items can be set via L{distributeInitialData}.
+	"""
+	def __init__(self,items=[],**kwargs):
+		self._items = GenericListmodel(*items)
+		self.real_widget = fife.ListBox(self._items)
+		super(ListBox,self).__init__(**kwargs)
+
+		# Prepare Data collection framework
+		self.accepts_initial_data = True
+		self._realSetInitialData = self._setItems
+		
+		self.accepts_data = True
+		self._realSetData = self._setSelected
+		self._realGetData = self._getSelected
+
+	def resizeToContent(self,recurse=True):
+		# We append a minimum value, so max() does not bail out,
+		# if no items are in the list
+		_item_widths = map(self.real_font.getWidth,map(str,self._items)) + [0]
+		max_w = max(_item_widths)
+		self.width = max_w
+		self.height = (self.real_font.getHeight() + 2) * len(self._items)
+
+	def _getItems(self): return self._items
+	def _setItems(self,items):
+		# Note we cannot use real_widget.setListModel
+		# for some reason ???
+		
+		# Also self assignment can kill you
+		if id(items) != id(self._items):
+			self._items.clear()
+			self._items.extend(items)
+
+	items = property(_getItems,_setItems)
+
+	def _getSelected(self): return self.real_widget.getSelected()
+	def _setSelected(self,index): self.real_widget.setSelected(index)
+	selected = property(_getSelected,_setSelected)
+	def _getSelectedItem(self):
+		if 0 <= self.selected < len(self._items):
+			return self._items[self.selected]
+		return None
+	selected_item = property(_getSelectedItem)
+
+class DropDown(Widget):
+	"""
+	A dropdown or combo box widget for selecting lists of strings.
+	
+	New Attributes
+	==============
+	
+	  - items: A List of strings. This can be treated like an ordinary python list.
+	    but only strings are allowed.
+	  - selected: The index of the selected item in the list. Starting from C{0} to C{len(items)-1}.
+	    A negative value indicates, that no item is selected.
+	  - selected_item: The selected string itself, or C{None} - if no string is selected.
+	
+	Data
+	====
+	The selected attribute can be read and set via L{distributeData} and L{collectData}.
+	The list items can be set via L{distributeInitialData}.
+	"""
+	def __init__(self,items=[],**kwargs):
+		self._items = GenericListmodel(*items)
+		self.real_widget = fife.DropDown(self._items)
+		super(DropDown,self).__init__(**kwargs)
+
+		# Prepare Data collection framework
+		self.accepts_initial_data = True
+		self._realSetInitialData = self._setItems
+		
+		self.accepts_data = True
+		self._realSetData = self._setSelected
+		self._realGetData = self._getSelected
+
+	def resizeToContent(self,recurse=True):
+		# We append a minimum value, so max() does not bail out,
+		# if no items are in the list
+		_item_widths = map(self.real_font.getWidth,map(str,self._items)) + [self.real_font.getHeight()]
+		max_w = max(_item_widths)
+		self.width = max_w
+		self.height = (self.real_font.getHeight() + 2)
+
+	def _getItems(self): return self._items
+	def _setItems(self,items):
+		# Note we cannot use real_widget.setListModel
+		# for some reason ???
+		
+		# Also self assignment can kill you
+		if id(items) != id(self._items):
+			self._items.clear()
+			self._items.extend(items)
+	items = property(_getItems,_setItems)
+	
+	def _getSelected(self): return self.real_widget.getSelected()
+	def _setSelected(self,index): self.real_widget.setSelected(index)
+	selected = property(_getSelected,_setSelected)
+	def _getSelectedItem(self):
+		if 0 <= self.selected < len(self._items):
+			return self._items[self.selected]
+		return None
+	selected_item = property(_getSelectedItem)
+
+class TextBox(Widget):
+	"""
+	An editable B{multiline} text edit widget.
+	
+	New Attributes
+	==============
+	
+	  - text: The text in the TextBox.
+	  - filename: A write-only attribute - assigning a filename will cause the widget to load it's text from it.
+	
+	Data
+	====
+	The text can be read and set via L{distributeData} and L{collectData}.
+	"""
+
+	ATTRIBUTES = Widget.ATTRIBUTES + [Attr('text'),Attr('filename')]
+
+	def __init__(self,text="",filename = "", **kwargs):
+		self.real_widget = fife.TextBox()
+		self.text = text
+		self.filename = filename
+		super(TextBox,self).__init__(**kwargs)
+
+		# Prepare Data collection framework
+		self.accepts_data = True
+		self.accepts_initial_data = True # Make sense in a way ...
+		self._realSetInitialData = self._setText
+		self._realSetData = self._setText
+		self._realGetData = self._getText
+
+	def _getFileName(self): return self._filename
+	def _loadFromFile(self,filename):
+		self._filename = filename
+		if not filename: return
+		try:
+			self.text = open(filename).read()
+		except Exception, e:
+			self.text = str(e)
+	filename = property(_getFileName, _loadFromFile)
+
+	def resizeToContent(self,recurse=True):
+		rows = [self.real_widget.getTextRow(i) for i in range(self.real_widget.getNumberOfRows())]
+		max_w = max(map(self.real_font.getWidth,rows))
+		self.width = max_w
+		self.height = (self.real_font.getHeight() + 2) * self.real_widget.getNumberOfRows()
+
+	def _getText(self): return self.real_widget.getText()
+	def _setText(self,text): self.real_widget.setText(_mungeText(text))
+	text = property(_getText,_setText)
+
+	def _setOpaque(self,opaque): self.real_widget.setOpaque(opaque)
+	def _getOpaque(self): return self.real_widget.isOpaque()
+	opaque = property(_getOpaque,_setOpaque)
+
+class TextField(Widget):
+	"""
+	An editable B{single line} text edit widget.
+	
+	New Attributes
+	==============
+	
+	  - text: The text in the TextBox.
+	
+	Data
+	====
+	The text can be read and set via L{distributeData} and L{collectData}.
+	"""
+
+	ATTRIBUTES = Widget.ATTRIBUTES + [Attr('text')]
+
+	def __init__(self,text="", **kwargs):
+		self.real_widget = fife.TextField()
+		self.text = text
+		super(TextField,self).__init__(**kwargs)
+
+		# Prepare Data collection framework
+		self.accepts_data = True
+		self.accepts_inital_data = True
+		self._realSetInitialData = self._setText
+		self._realSetData = self._setText
+		self._realGetData = self._getText
+
+	def resizeToContent(self,recurse=True):
+		max_w = self.real_font.getWidth(self.text)
+		self.width = max_w
+		self.height = (self.real_font.getHeight() + 2)
+	def _getText(self): return self.real_widget.getText()
+	def _setText(self,text): self.real_widget.setText(text)
+	text = property(_getText,_setText)
+
+	def _setOpaque(self,opaque): self.real_widget.setOpaque(opaque)
+	def _getOpaque(self): return self.real_widget.isOpaque()
+	opaque = property(_getOpaque,_setOpaque)
+
+
+# coding: utf-8
+
+class ScrollArea(Widget):
+	"""
+	A wrapper around another (content) widget.
+	
+	New Attributes
+	==============
+	
+	  - content: The wrapped widget.
+	  - vertical_scrollbar: Boolean: Set this to False to hide the Vertcial scrollbar
+	  - horizontal_scrollbar: Boolean: Set this to False to hide the Horizontal scrollbar
+	
+	"""
+	
+	ATTRIBUTES = Widget.ATTRIBUTES + [ BoolAttr("vertical_scrollbar"),BoolAttr("horizontal_scrollbar") ]
+	
+	def __init__(self,**kwargs):
+		self.real_widget = fife.ScrollArea()
+		self._content = None
+		super(ScrollArea,self).__init__(**kwargs)
+
+	def addChild(self,widget):
+		self.content = widget
+
+	def removeChild(self,widget):
+		if self._content != widget:
+			raise RuntimeError("%s does not have %s as direct child widget." % (str(self),str(widget)))
+		self.content = None
+
+	def _setContent(self,content):
+		self.real_widget.setContent(content.real_widget)
+		self._content = content
+	def _getContent(self): return self._content
+	content = property(_getContent,_setContent)
+
+	def deepApply(self,visitorFunc):
+		if self._content: visitorFunc(self._content)
+		visitorFunc(self)
+
+	def resizeToContent(self,recurse=True):
+		if self._content is None: return
+		if recurse:
+			self.content.resizeToContent(recurse=True)
+		self.content.width = max(self.content.width,self.width-5)
+		self.content.height = max(self.content.height,self.height-5)
+
+	def _visibilityToScrollPolicy(self,visibility):
+		if visibility: 
+			return fife.ScrollArea.SHOW_AUTO
+		return fife.ScrollArea.SHOW_NEVER
+	
+	def _scrollPolicyToVisibility(self,policy):
+		if policy == fife.ScrollArea.SHOW_NEVER:
+			return False
+		return True
+
+	def _setHorizontalScrollbar(self,visibility):
+		self.real_widget.setHorizontalScrollPolicy( self._visibilityToScrollPolicy(visibility) )
+	
+	def _setVerticalScrollbar(self,visibility):
+		self.real_widget.setVerticalScrollPolicy( self._visibilityToScrollPolicy(visibility) )
+
+	def _getHorizontalScrollbar(self):
+		return self._scrollPolicyToVisibility( self.real_widget.getHorizontalScrollPolicy() )
+	
+	def _getVerticalScrollbar(self):
+		return self._scrollPolicyToVisibility( self.real_widget.getVerticalScrollPolicy() )
+
+	vertical_scrollbar = property(_getVerticalScrollbar,_setVerticalScrollbar)
+	horizontal_scrollbar = property(_getHorizontalScrollbar,_setHorizontalScrollbar)
+
+# Spacer
+
+class Spacer(object):
+	""" A spacer represents expandable 'whitespace' in the GUI.
+	
+	In a XML file you can get this by adding a <Spacer /> inside a VBox or
+	HBox element (Windows implicitly are VBox elements).
+	
+	The effect is, that elements before the spacer will be left (top)
+	and elements after the spacer will be right (bottom) aligned.
+	
+	There can only be one spacer in VBox (HBox).
+	"""
+	def __init__(self,parent=None,**kwargs):
+		self._parent = parent
+
+	def __str__(self):
+		return "Spacer(parent.name='%s')" % getattr(self._parent,'name','None')
+	
+	def __repr__(self):
+		return "<Spacer(parent.name='%s') at %x>" % (getattr(self._parent,'name','None'),id(self))
+
+
+# Global Widget Class registry
+
+WIDGETS = {
+	# Containers
+	"Container" : Container,
+	"Window" : Window,
+	"VBox" : VBox,
+	"HBox" : HBox,
+	"ScrollArea" :ScrollArea,
+	
+	# Simple Widgets
+	"Icon" : Icon,
+	"Label" : Label,
+	"ClickLabel" : ClickLabel,
+	
+	# Button Widgets
+	"Button" : Button,
+	"CheckBox" : CheckBox,
+	"RadioButton" : RadioButton,
+	"ImageButton" : ImageButton,
+	
+	#Complexer Widgets / Text io
+	"TextField" : TextField,
+	"TextBox" : TextBox,
+	"ListBox" : ListBox,
+	"DropDown" : DropDown
+}
+
+def registerWidget(cls):
+	"""
+	Register a new Widget class for pychan.
+	"""
+	global WIDGETS
+	name = cls.__name__
+	if name in WIDGETS:
+		raise InitializationError("Widget class name '%s' already registered." % name)
+	WIDGETS[name] = cls