Mercurial > fife-parpg
changeset 248:a2d5e2721489
widgets.py split up.
author | phoku@33b003aa-7bff-0310-803a-e67f0ece8222 |
---|---|
date | Thu, 26 Mar 2009 16:20:16 +0000 |
parents | 040387b7167f |
children | 1cc51d145af9 |
files | engine/extensions/pychan/widgets.py engine/extensions/pychan/widgets/__init__.py engine/extensions/pychan/widgets/basictextwidget.py engine/extensions/pychan/widgets/buttons.py engine/extensions/pychan/widgets/checkbox.py engine/extensions/pychan/widgets/common.py engine/extensions/pychan/widgets/containers.py engine/extensions/pychan/widgets/dropdown.py engine/extensions/pychan/widgets/icon.py engine/extensions/pychan/widgets/label.py engine/extensions/pychan/widgets/listbox.py engine/extensions/pychan/widgets/radiobutton.py engine/extensions/pychan/widgets/scrollarea.py engine/extensions/pychan/widgets/slider.py engine/extensions/pychan/widgets/textbox.py engine/extensions/pychan/widgets/textfield.py engine/extensions/pychan/widgets/widget.py |
diffstat | 17 files changed, 1989 insertions(+), 1915 deletions(-) [+] |
line wrap: on
line diff
--- a/engine/extensions/pychan/widgets.py Wed Mar 25 21:41:43 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1915 +0,0 @@ -# -*- coding: utf-8 -*- -### Widget/Container Base Classes ### - -""" -Widget wrappers. - -Please look at the documentation of L{Widget} for details. -""" - -import fife, pythonize -import tools -import events -from exceptions import * -from attrs import Attr,UnicodeAttr, PointAttr,ColorAttr,BoolAttr,IntAttr,FloatAttr -from properties import ColorProperty - -def get_manager(): - import pychan - return pychan.manager - -def _text2gui(text): - """ - This function is applied to all text set on widgets. - It replaces tabs by four spaces. - It assumes the text to be a unicode object. - """ - if not isinstance(text,unicode): - print "Widget text needs to be set from an unicode object. Got: '%s'" % repr(text) - text = unicode(text,"utf8") - return text.encode("utf8",*get_manager().unicodePolicy).replace("\t"," "*4).replace("[br]","\n") - -def _gui2text(text): - """ - This function is applied to all text get from widgets. - Translates the encoded string into a unicode object. - """ - return unicode(text,"utf8",*get_manager().unicodePolicy) - - -def isLayouted(widget): - return isinstance(widget,LayoutBase) - -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 - - selection_color: Color - - font: String: This should identify a font that was loaded via L{loadFonts} before. - - helptext: Unicode: Text which can be used for e.g. tooltips. - - 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'),ColorAttr('selection_color'), - Attr('style'), Attr('font'),IntAttr('border_size'),Attr('position_technique'), - UnicodeAttr('helptext') - ] - - 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), - helptext=u"", - style = None, **kwargs): - - assert( hasattr(self,'real_widget') ) - self.event_mapper = events.EventMapper(self) - 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" - - self.helptext = helptext - # 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, event_name="action", group_name="default"): - """ - 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}. - - @param callback: Event callback - may accept keyword arguments event and widget. - @paran event_name: The event to capture - may be one of L{events.EVENTS} and defaults to "action" - @paran group_name: Event group. - - Event groups are used to have different B{channels} which don't interfere with each other. - For derived widgets that need to capture events it's advised to use the group_name 'widget'. - The 'default' group is used by default, and should be reserved for the application programmers. - """ - self.event_mapper.capture( event_name, callback, group_name ) - - def isCaptured(self): - """ - Check whether this widgets events are captured - (a callback is installed) or not. - """ - return bool(self.event_mapper.getCapturedEvents()) - - 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,recurse=True): - """ - Execute the Layout engine. Automatically called by L{show}. - In case you want to relayout a visible widget. - This function will automatically perform the layout adaption - from the top-most layouted widget. - - To make this clear consider this arrangement:: - VBox 1 - - Container - - VBox 2 - - HBox - - Label - - If you call adaptLayout on the Label the layout from the VBox 2 - will get recalculated, while the VBox 1 stays untouched. - - @param recurse Pass False here to force the layout to start from - this widget. - """ - widget = self - while widget.parent and recurse: - if not isLayouted(widget.parent): - break - widget = widget.parent - widget._recursiveResizeToContent() - widget._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') - buttons = root_widget.findChildren(__class__=pychan.widgets.Button) - """ - - 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. - - You'll need to call L{adaptLayout} if the container is already shown, - to adapt the layout to the new widget. This doesn't happen - automatically. - """ - raise RuntimeError("Trying to add a widget to %s, which doesn't allow this." % repr(self)) - - def addChildren(self,*widgets): - """ - Add multiple widgets as children. - Only implemented for container widgets. See also L{addChild} - - Usage:: - container.addChildren( widget1, widget2, ... ) - # or you can use this on a list - container.addChildren( [widget1,widget2,...] ) - """ - if len(widgets) == 1 and not isinstance(widgets[0],Widget): - widgets = widgets[0] - 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. - - You'll need to call L{adaptLayout} if the container is already shown, - to adapt the layout to the removed widget. This doesn't happen - automatically. - """ - raise RuntimeError("Trying to remove a widget from %s, which is not a container widget." % repr(self)) - - def removeChildren(self,*widgets): - """ - Remove a list of direct child widgets. - All widgets have to be direct child widgets. - To 'clear' a container take a look at L{removeAllChildren}. - See also L{removeChild}. - - Usage:: - container.removeChildren( widget1, widget2, ... ) - # or you can use this on a list - container.removeChildren( [widget1,widget2,...] ) - """ - if len(widgets) == 1 and not isinstance(widgets[0],Widget): - widgets = widgets[0] - for widget in widgets: - self.removeChild(widget) - - def removeAllChildren(self): - """ - This function will remove all direct child widgets. - This will work even for non-container widgets. - """ - children = self.findChildren(parent=self) - for widget in children: - 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/event 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. - - The keys in the dictionary are parsed as C{"widgetName/eventName"} with the slash - separating the two. If no slash is found the eventName is assumed to be "action". - - Additionally you can supply a group name or channel C{"widgetName/eventName/groupName"}. - Event handlers from one group are not overridden by handlers from another group. - The default group name is C{"default"}. - - Example:: - guiElement.mapEvents({ - "button" : guiElement.hide, - "button/mouseEntered" : toggleButtonColorGreen, - "button/mouseExited" : toggleButtonColorBlue, - }) - - """ - for descr,func in eventMap.items(): - name, event_name, group_name = events.splitEventDescriptor(descr) - #print name, event_name, group_name - widget = self.findChild(name=name) - if widget: - widget.capture( func, event_name = event_name, group_name = group_name ) - 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: - if get_manager().debug: - self.listNamedWidgets() - 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: - if get_manager().debug: - self.listNamedWidgets() - 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: - if get_manager().debug: - self.listNamedWidgets() - 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) - - base_color = ColorProperty("BaseColor") - background_color = ColorProperty("BackgroundColor") - foreground_color = ColorProperty("ForegroundColor") - selection_color = ColorProperty("SelectionColor") - - def _getStyle(self): return self._style - def _setStyle(self,style): - self._style = style - get_manager().stylize(self,style) - style = property(_getStyle,_setStyle) - - def _getParent(self): return self._parent - def _setParent(self,parent): - self._parent = parent - parent = property(_getParent,_setParent) - - def _setName(self,name): self._name = name - def _getName(self): return self._name - name = property(_getName,_setName) - - 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) - - def setEnterCallback(self, cb): - """ - *DEPRECATED* - - Callback is called when mouse enters the area of Widget - callback should have form of function(button) - """ - if cb is None: - self.capture(None, event_name = "mouseEntered" ) - return - - def callback(widget=None): - return cb(widget) - print "PyChan: You are using the DEPRECATED functionality: setEnterCallback." - self.capture(callback, event_name = "mouseEntered" ) - - def setExitCallback(self, cb): - """ - *DEPRECATED* - - Callback is called when mouse exits the area of Widget - callback should have form of function(button) - """ - if cb is None: - self.capture(None, event_name = "mouseExited" ) - return - - def callback(widget=None): - return cb(widget) - print "PyChan: You are using the DEPRECATED functionality: setExitCallback." - self.capture(callback, event_name = "mouseExited" ) - - - -### 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. - - Dynamic Layouting - ----------------- - - The layout is calculated in the L{Widget.show} method. Thus if you modify the layout, - by adding or removing child widgets for example, you have to call L{Widget.adaptLayout} - so that the changes ripple through the widget hierachy. - - Internals - --------- - - 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 + [ UnicodeAttr('title'), IntAttr('titlebar_height') ] - - def __init__(self,title=u"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 _gui2text(self.real_widget.getCaption()) - def _setTitle(self,text): self.real_widget.setCaption(_text2gui(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 + [UnicodeAttr('text')] - - def __init__(self, text = u"",**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 _gui2text(self.real_widget.getCaption()) - def _setText(self,text): self.real_widget.setCaption(_text2gui(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(_text2gui(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 and onMouse hover callbacks. - - 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 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('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.up_image = up_image - self.down_image = down_image - self.hover_image = hover_image - self.offset = offset - - def _setUpImage(self, source): - if isinstance(source,str): - self._upimage_source = source - try: - self._upimage = get_manager().loadImage(source) - self.real_widget.setUpImage( self._upimage ) - except: - self._upimage = _DummyImage() - elif isinstance(source,fife.GuiImage): - self._upimage_source = None - self._upimage = source - self.real_widget.setUpImage( self._upimage ) - else: - raise RuntimeError("Icon.image only accepts GuiImage and python strings, not '%s'" % repr(source)) - - # 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 _getUpImage(self): return self._upimage_source - up_image = property(_getUpImage,_setUpImage) - - def _setDownImage(self, source): - if isinstance(source,str): - self._downimage_source = source - try: - self._downimage = get_manager().loadImage(source) - self.real_widget.setDownImage( self._downimage ) - except: - self._downimage = _DummyImage() - elif isinstance(source,fife.GuiImage): - self._downimage_source = None - self._downimage = source - self.real_widget.setDownImage( self._downimage ) - else: - raise RuntimeError("Icon.image only accepts GuiImage and python strings, not '%s'" % repr(source)) - - def _getDownImage(self): return self._downimage_source - down_image = property(_getDownImage,_setDownImage) - - def _setHoverImage(self, source): - if isinstance(source, str): - self._hoverimage_source = source - try: - self._hoverimage = get_manager().loadImage(source) - self.real_widget.setHoverImage( self._hoverimage ) - except: - self._hoverimage = _DummyImage() - elif isinstance(source,fife.GuiImage): - self._hoverimage_source = None - self._hoverimage = source - self.real_widget.setHoverImage( self._hoverimage ) - else: - raise RuntimeError("Icon.image only accepts GuiImage and python strings, not '%s'" % repr(source)) - - 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 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 - -class ToggleButton(BasicTextWidget): - """ - A basic push button that can be toggled. - - Unfortunately a bit of code duplication from ImageButton. - - New Attributes - ============== - - - group: String: The group the button belongs to. Only one button in each group will be toggled at one time. - - toggled: Boolean: Whether the button is toggled or not. - """ - - ATTRIBUTES = BasicTextWidget.ATTRIBUTES + [ - Attr('up_image'),Attr('down_image'),Attr('hover_image'), - PointAttr('offset'),Attr('group') - ] - - def __init__(self,up_image="",down_image="",hover_image="",offset=(0,0),group="",**kwargs): - - self.real_widget = fife.ToggleButton() - super(ToggleButton,self).__init__(**kwargs) - self.group = group - self.up_image = up_image - self.down_image = down_image - self.hover_image = hover_image - self.offset = offset - - def _setGroup(self,group): - self.real_widget.setGroup( group ) - - def _getGroup(self): - return self.real_widget.getGroup() - group = property(_getGroup,_setGroup) - - def _setToggled(self, toggled): - self.real_widget.setToggled( toggled ) - - def _isToggled(self): - return self.real_widget.isToggled() - toggled = property(_isToggled, _setToggled) - - ### - # I didn't want to do this, but this is just cut and paste from the ImageButton class: - ### - - 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 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 - -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(_text2gui(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 + [UnicodeAttr('text'),Attr('filename')] - - def __init__(self,text=u"",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: - # FIXME needs encoding detection. - self.text = unicode(open(filename).read(),"utf8") - 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 _gui2text(self.real_widget.getText()) - def _setText(self,text): self.real_widget.setText(_text2gui(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 + [UnicodeAttr('text')] - - def __init__(self,text=u"", **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(_text2gui(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 Vertical 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 - widget.parent = self - - def removeChild(self,widget): - if self._content != widget: - raise RuntimeError("%s does not have %s as direct child widget." % (str(self),str(widget))) - # Set a Container here, as this can not be set to None - # Might be possible to find a better solution, needs peer review - self.content = Container() - widget.parent = 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: self._content.deepApply(visitorFunc) - 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)) - -class Slider(Widget): - """ A slider widget - - Use a callback to read out the slider value every time the marker - is moved. - - New Attributes - ============== - - - orientation: 1 = horizontal, 0=vertical - - scale_start: float: default 0.0 - - scale_end: float: default 1.0 - - FIXME: - - set new attributes for marker & step length, value - - update docstrings - """ - - HORIZONTAL = fife.Slider.HORIZONTAL - VERTICAL = fife.Slider.VERTICAL - - ATTRIBUTES = Widget.ATTRIBUTES + [IntAttr('orientation'), FloatAttr('scale_start'), FloatAttr('scale_end')] - - def __init__(self, scaleStart=0.0, scaleEnd=1.0, orientation=HORIZONTAL, **kwargs): - self.real_widget = fife.Slider(scaleStart, scaleEnd) - self.orientation = orientation - self.setOrientation(self.orientation) - super(Slider, self).__init__(**kwargs) - - def _setScale(self, start, end): - """setScale(self, double scaleStart, double scaleEnd)""" - if type(start) != float: - raise RuntimeError("Slider expects float for start scale") - if type(end) != float: - raise RuntimeError("Slider expects float for end scale") - self.real_widget.setScale(start, end) - - def getScaleStart(self): - """getScaleStart(self) -> double""" - return self.real_widget.getScaleStart() - - def setScaleStart(self, start): - """setScaleStart(self, double scaleStart)""" - if type(start) != float: - raise RuntimeError("Slider expects float for start scale") - self.real_widget.setScaleStart(start) - scale_start = property(getScaleStart, setScaleStart) - - def getScaleEnd(self): - """getScaleEnd(self) -> double""" - return self.real_widget.getScaleEnd() - - def setScaleEnd(self, end): - """setScaleEnd(self, double scaleEnd)""" - if type(end) != float: - raise RuntimeError("Slider expects float for end scale") - self.real_widget.setScaleEnd(end) - scale_end = property(getScaleEnd, setScaleEnd) - - def getValue(self): - """getValue(self) -> double""" - return self.real_widget.getValue() - - def setValue(self, value): - """setValue(self, double value)""" - if type(value) != float: - raise RuntimeError("Slider only accepts float values") - self.real_widget.setValue(value) - - def setMarkerLength(self, length): - """setMarkerLength(self, int length)""" - if type(length) != int: - raise RuntimeError("Slider only accepts int for Marker lenght") - self.real_widget.setMarkerLength(length) - - def getMarkerLength(self): - """getMarkerLength(self) -> int""" - return self.real_widget.getMarkerLength() - - def setOrientation(self, orientation): - """setOrientation(self, Orientation orientation)""" - self.real_widget.setOrientation(orientation) - - def getOrientation(self): - """getOrientation(self) -> int""" - return self.real_widget.getOrientation() - orientation = property(getOrientation, setOrientation) - - def setStepLength(self, length): - """setStepLength(self, double length)""" - if type(length) != float: - raise RuntimeError("Slider only accepts floats for step length") - self.real_widget.setStepLength(length) - - def getStepLength(self): - """getStepLength(self) -> double""" - return self.real_widget.getStepLength() - -# 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, - "ToggleButton" : ToggleButton, - - #Complexer Widgets / Text io - "TextField" : TextField, - "TextBox" : TextBox, - "ListBox" : ListBox, - "DropDown" : DropDown, - "Slider" : Slider -} - -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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/__init__.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +### Widget/Container Base Classes ### + +""" +Widget wrappers. + +Please look at the documentation of L{Widget} for details. +""" + +from widget import Widget + +from containers import Container, VBox, HBox, Window, Spacer +from label import Label, ClickLabel +from icon import Icon +from buttons import Button, ToggleButton, ImageButton +from checkbox import CheckBox +from radiobutton import RadioButton +from textfield import TextField +from textbox import TextBox +from listbox import ListBox +from dropdown import DropDown +from scrollarea import ScrollArea +from slider import Slider + +# 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, + "ToggleButton" : ToggleButton, + + #Complexer Widgets / Text io + "TextField" : TextField, + "TextBox" : TextBox, + "ListBox" : ListBox, + "DropDown" : DropDown, + "Slider" : Slider +} + +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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/basictextwidget.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from widget import Widget +from common import * + +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 + [UnicodeAttr('text')] + + def __init__(self, text = u"",**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 gui2text(self.real_widget.getCaption()) + def _setText(self,text): self.real_widget.setCaption(text2gui(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(text2gui(self.text)) + self.margins[0]*2
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/buttons.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,191 @@ +# -*- coding: utf-8 -*- + +from common import * +from basictextwidget import BasicTextWidget + +class Button(BasicTextWidget): + """ + A basic push button. + """ + def __init__(self,**kwargs): + self.real_widget = fife.Button("") + super(Button,self).__init__(**kwargs) + +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('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.up_image = up_image + self.down_image = down_image + self.hover_image = hover_image + self.offset = offset + + def _setUpImage(self, source): + if isinstance(source,str): + self._upimage_source = source + try: + self._upimage = get_manager().loadImage(source) + self.real_widget.setUpImage( self._upimage ) + except: + self._upimage = DummyImage() + elif isinstance(source,fife.GuiImage): + self._upimage_source = None + self._upimage = source + self.real_widget.setUpImage( self._upimage ) + else: + raise RuntimeError("Icon.image only accepts GuiImage and python strings, not '%s'" % repr(source)) + + # 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 _getUpImage(self): return self._upimage_source + up_image = property(_getUpImage,_setUpImage) + + def _setDownImage(self, source): + if isinstance(source,str): + self._downimage_source = source + try: + self._downimage = get_manager().loadImage(source) + self.real_widget.setDownImage( self._downimage ) + except: + self._downimage = DummyImage() + elif isinstance(source,fife.GuiImage): + self._downimage_source = None + self._downimage = source + self.real_widget.setDownImage( self._downimage ) + else: + raise RuntimeError("Icon.image only accepts GuiImage and python strings, not '%s'" % repr(source)) + + def _getDownImage(self): return self._downimage_source + down_image = property(_getDownImage,_setDownImage) + + def _setHoverImage(self, source): + if isinstance(source, str): + self._hoverimage_source = source + try: + self._hoverimage = get_manager().loadImage(source) + self.real_widget.setHoverImage( self._hoverimage ) + except: + self._hoverimage = DummyImage() + elif isinstance(source,fife.GuiImage): + self._hoverimage_source = None + self._hoverimage = source + self.real_widget.setHoverImage( self._hoverimage ) + else: + raise RuntimeError("Icon.image only accepts GuiImage and python strings, not '%s'" % repr(source)) + + 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 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 + +class ToggleButton(BasicTextWidget): + """ + A basic push button that can be toggled. + + Unfortunately a bit of code duplication from ImageButton. + + New Attributes + ============== + + - group: String: The group the button belongs to. Only one button in each group will be toggled at one time. + - toggled: Boolean: Whether the button is toggled or not. + """ + + ATTRIBUTES = BasicTextWidget.ATTRIBUTES + [ + Attr('up_image'),Attr('down_image'),Attr('hover_image'), + PointAttr('offset'),Attr('group') + ] + + def __init__(self,up_image="",down_image="",hover_image="",offset=(0,0),group="",**kwargs): + + self.real_widget = fife.ToggleButton() + super(ToggleButton,self).__init__(**kwargs) + self.group = group + self.up_image = up_image + self.down_image = down_image + self.hover_image = hover_image + self.offset = offset + + def _setGroup(self,group): + self.real_widget.setGroup( group ) + + def _getGroup(self): + return self.real_widget.getGroup() + group = property(_getGroup,_setGroup) + + def _setToggled(self, toggled): + self.real_widget.setToggled( toggled ) + + def _isToggled(self): + return self.real_widget.isToggled() + toggled = property(_isToggled, _setToggled) + + ### + # I didn't want to do this, but this is just cut and paste from the ImageButton class: + ### + + 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 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/checkbox.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +from common import * +from basictextwidget import BasicTextWidget + +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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/common.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +import fife +import pychan.tools as tools +import pychan.events as events +from pychan.exceptions import * +from pychan.attrs import Attr,UnicodeAttr, PointAttr,ColorAttr,BoolAttr,IntAttr,FloatAttr +from pychan.properties import ColorProperty + + +def get_manager(): + import pychan + return pychan.manager + +def text2gui(text): + """ + This function is applied to all text set on widgets. + It replaces tabs by four spaces. + It assumes the text to be a unicode object. + """ + if not isinstance(text,unicode): + print "Widget text needs to be set from an unicode object. Got: '%s'" % repr(text) + text = unicode(text,"utf8") + return text.encode("utf8",*get_manager().unicodePolicy).replace("\t"," "*4).replace("[br]","\n") + +def gui2text(text): + """ + This function is applied to all text get from widgets. + Translates the encoded string into a unicode object. + """ + return unicode(text,"utf8",*get_manager().unicodePolicy) + +def isLayouted(widget): + from containers import LayoutBase + return isinstance(widget,LayoutBase) + +class DummyImage(object): + def getWidth(self): return 0 + def getHeight(self): return 0
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/containers.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,367 @@ +# -*- coding: utf-8 -*- + +from common import * +from widget import Widget + +### 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. + + Dynamic Layouting + ----------------- + + The layout is calculated in the L{Widget.show} method. Thus if you modify the layout, + by adding or removing child widgets for example, you have to call L{Widget.adaptLayout} + so that the changes ripple through the widget hierachy. + + Internals + --------- + + 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 + [ UnicodeAttr('title'), IntAttr('titlebar_height') ] + + def __init__(self,title=u"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 gui2text(self.real_widget.getCaption()) + def _setTitle(self,text): self.real_widget.setCaption(text2gui(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) + + +# 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))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/dropdown.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +from common import * +from widget import Widget +from listbox import GenericListmodel + +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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/icon.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +from common import * +from widget import Widget + +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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/label.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +from common import * +from basictextwidget import BasicTextWidget + +class Label(BasicTextWidget): + """ + A basic label - displaying a string. + + Also allows text wrapping and onMouse hover callbacks. + + 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")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/listbox.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- + +from common import * +from widget import Widget + +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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/radiobutton.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +from common import * +from basictextwidget import BasicTextWidget + +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(_text2gui(self.text)) + 35# Size of the Checked box? + self.height = self.real_font.getHeight()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/scrollarea.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +from common import * +from widget import Widget + +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 Vertical 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 + widget.parent = self + + 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 + widget.parent = None + + def _setContent(self,content): + if content is None: + self.real_widget.setContent(content) + else: + 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: self._content.deepApply(visitorFunc) + 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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/slider.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +from common import * +from widget import Widget + +class Slider(Widget): + """ A slider widget + + Use a callback to read out the slider value every time the marker + is moved. + + New Attributes + ============== + + - orientation: 1 = horizontal, 0=vertical + - scale_start: float: default 0.0 + - scale_end: float: default 1.0 + + FIXME: + - set new attributes for marker & step length, value + - update docstrings + """ + + HORIZONTAL = fife.Slider.HORIZONTAL + VERTICAL = fife.Slider.VERTICAL + + ATTRIBUTES = Widget.ATTRIBUTES + [IntAttr('orientation'), FloatAttr('scale_start'), FloatAttr('scale_end')] + + def __init__(self, scaleStart=0.0, scaleEnd=1.0, orientation=HORIZONTAL, **kwargs): + self.real_widget = fife.Slider(scaleStart, scaleEnd) + self.orientation = orientation + self.setOrientation(self.orientation) + super(Slider, self).__init__(**kwargs) + + def _setScale(self, start, end): + """setScale(self, double scaleStart, double scaleEnd)""" + if type(start) != float: + raise RuntimeError("Slider expects float for start scale") + if type(end) != float: + raise RuntimeError("Slider expects float for end scale") + self.real_widget.setScale(start, end) + + def getScaleStart(self): + """getScaleStart(self) -> double""" + return self.real_widget.getScaleStart() + + def setScaleStart(self, start): + """setScaleStart(self, double scaleStart)""" + if type(start) != float: + raise RuntimeError("Slider expects float for start scale") + self.real_widget.setScaleStart(start) + scale_start = property(getScaleStart, setScaleStart) + + def getScaleEnd(self): + """getScaleEnd(self) -> double""" + return self.real_widget.getScaleEnd() + + def setScaleEnd(self, end): + """setScaleEnd(self, double scaleEnd)""" + if type(end) != float: + raise RuntimeError("Slider expects float for end scale") + self.real_widget.setScaleEnd(end) + scale_end = property(getScaleEnd, setScaleEnd) + + def getValue(self): + """getValue(self) -> double""" + return self.real_widget.getValue() + + def setValue(self, value): + """setValue(self, double value)""" + if type(value) != float: + raise RuntimeError("Slider only accepts float values") + self.real_widget.setValue(value) + + def setMarkerLength(self, length): + """setMarkerLength(self, int length)""" + if type(length) != int: + raise RuntimeError("Slider only accepts int for Marker lenght") + self.real_widget.setMarkerLength(length) + + def getMarkerLength(self): + """getMarkerLength(self) -> int""" + return self.real_widget.getMarkerLength() + + def setOrientation(self, orientation): + """setOrientation(self, Orientation orientation)""" + self.real_widget.setOrientation(orientation) + + def getOrientation(self): + """getOrientation(self) -> int""" + return self.real_widget.getOrientation() + orientation = property(getOrientation, setOrientation) + + def setStepLength(self, length): + """setStepLength(self, double length)""" + if type(length) != float: + raise RuntimeError("Slider only accepts floats for step length") + self.real_widget.setStepLength(length) + + def getStepLength(self): + """getStepLength(self) -> double""" + return self.real_widget.getStepLength()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/textbox.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +from common import * +from widget import Widget + +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 + [UnicodeAttr('text'),Attr('filename')] + + def __init__(self,text=u"",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: + # FIXME needs encoding detection. + self.text = unicode(open(filename).read(),"utf8") + 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 gui2text(self.real_widget.getText()) + def _setText(self,text): self.real_widget.setText(text2gui(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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/textfield.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +from common import * +from widget import Widget + +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 + [UnicodeAttr('text')] + + def __init__(self,text=u"", **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(text2gui(self.text)) + self.width = max_w + self.height = (self.real_font.getHeight() + 2) + def _getText(self): return gui2text(self.real_widget.getText()) + def _setText(self,text): self.real_widget.setText(text2gui(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)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/engine/extensions/pychan/widgets/widget.py Thu Mar 26 16:20:16 2009 +0000 @@ -0,0 +1,691 @@ +# -*- coding: utf-8 -*- + +import fife, pythonize +import pychan.tools as tools +import pychan.events as events +from pychan.exceptions import * +from pychan.attrs import Attr,UnicodeAttr, PointAttr,ColorAttr,BoolAttr,IntAttr,FloatAttr +from pychan.properties import ColorProperty + +from pychan.widgets.common import * + +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 + - selection_color: Color + - font: String: This should identify a font that was loaded via L{loadFonts} before. + - helptext: Unicode: Text which can be used for e.g. tooltips. + - 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'),ColorAttr('selection_color'), + Attr('style'), Attr('font'),IntAttr('border_size'),Attr('position_technique'), + UnicodeAttr('helptext') + ] + + 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), + helptext=u"", + style = None, **kwargs): + + assert( hasattr(self,'real_widget') ) + self.event_mapper = events.EventMapper(self) + 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" + + self.helptext = helptext + # 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, event_name="action", group_name="default"): + """ + 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}. + + @param callback: Event callback - may accept keyword arguments event and widget. + @paran event_name: The event to capture - may be one of L{events.EVENTS} and defaults to "action" + @paran group_name: Event group. + + Event groups are used to have different B{channels} which don't interfere with each other. + For derived widgets that need to capture events it's advised to use the group_name 'widget'. + The 'default' group is used by default, and should be reserved for the application programmers. + """ + self.event_mapper.capture( event_name, callback, group_name ) + + def isCaptured(self): + """ + Check whether this widgets events are captured + (a callback is installed) or not. + """ + return bool(self.event_mapper.getCapturedEvents()) + + 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,recurse=True): + """ + Execute the Layout engine. Automatically called by L{show}. + In case you want to relayout a visible widget. + This function will automatically perform the layout adaption + from the top-most layouted widget. + + To make this clear consider this arrangement:: + VBox 1 + - Container + - VBox 2 + - HBox + - Label + + If you call adaptLayout on the Label the layout from the VBox 2 + will get recalculated, while the VBox 1 stays untouched. + + @param recurse Pass False here to force the layout to start from + this widget. + """ + widget = self + while widget.parent and recurse: + if not isLayouted(widget.parent): + break + widget = widget.parent + widget._recursiveResizeToContent() + widget._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') + buttons = root_widget.findChildren(__class__=pychan.widgets.Button) + """ + + 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. + + You'll need to call L{adaptLayout} if the container is already shown, + to adapt the layout to the new widget. This doesn't happen + automatically. + """ + raise RuntimeError("Trying to add a widget to %s, which doesn't allow this." % repr(self)) + + def addChildren(self,*widgets): + """ + Add multiple widgets as children. + Only implemented for container widgets. See also L{addChild} + + Usage:: + container.addChildren( widget1, widget2, ... ) + # or you can use this on a list + container.addChildren( [widget1,widget2,...] ) + """ + if len(widgets) == 1 and not isinstance(widgets[0],Widget): + widgets = widgets[0] + 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. + + You'll need to call L{adaptLayout} if the container is already shown, + to adapt the layout to the removed widget. This doesn't happen + automatically. + """ + raise RuntimeError("Trying to remove a widget from %s, which is not a container widget." % repr(self)) + + def removeChildren(self,*widgets): + """ + Remove a list of direct child widgets. + All widgets have to be direct child widgets. + To 'clear' a container take a look at L{removeAllChildren}. + See also L{removeChild}. + + Usage:: + container.removeChildren( widget1, widget2, ... ) + # or you can use this on a list + container.removeChildren( [widget1,widget2,...] ) + """ + if len(widgets) == 1 and not isinstance(widgets[0],Widget): + widgets = widgets[0] + for widget in widgets: + self.removeChild(widget) + + def removeAllChildren(self): + """ + This function will remove all direct child widgets. + This will work even for non-container widgets. + """ + children = self.findChildren(parent=self) + for widget in children: + 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/event 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. + + The keys in the dictionary are parsed as C{"widgetName/eventName"} with the slash + separating the two. If no slash is found the eventName is assumed to be "action". + + Additionally you can supply a group name or channel C{"widgetName/eventName/groupName"}. + Event handlers from one group are not overridden by handlers from another group. + The default group name is C{"default"}. + + Example:: + guiElement.mapEvents({ + "button" : guiElement.hide, + "button/mouseEntered" : toggleButtonColorGreen, + "button/mouseExited" : toggleButtonColorBlue, + }) + + """ + for descr,func in eventMap.items(): + name, event_name, group_name = events.splitEventDescriptor(descr) + #print name, event_name, group_name + widget = self.findChild(name=name) + if widget: + widget.capture( func, event_name = event_name, group_name = group_name ) + 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: + if get_manager().debug: + self.listNamedWidgets() + 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: + if get_manager().debug: + self.listNamedWidgets() + 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: + if get_manager().debug: + self.listNamedWidgets() + 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): + pass + + 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 + + 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): + old_width = self.width + w = max(self.min_size[0],w) + w = min(self.max_size[0],w) + self.real_widget.setWidth(w) + if w != old_width: + self.sizeChanged() + + def _getWidth(self): return self.real_widget.getWidth() + def _setHeight(self,h): + old_height = self.height + h = max(self.min_size[1],h) + h = min(self.max_size[1],h) + self.real_widget.setHeight(h) + if h != old_height: + self.sizeChanged() + + 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) + + base_color = ColorProperty("BaseColor") + background_color = ColorProperty("BackgroundColor") + foreground_color = ColorProperty("ForegroundColor") + selection_color = ColorProperty("SelectionColor") + + def _getStyle(self): return self._style + def _setStyle(self,style): + self._style = style + get_manager().stylize(self,style) + style = property(_getStyle,_setStyle) + + def _getParent(self): return self._parent + def _setParent(self,parent): + self._parent = parent + parent = property(_getParent,_setParent) + + def _setName(self,name): self._name = name + def _getName(self): return self._name + name = property(_getName,_setName) + + 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) + + def setEnterCallback(self, cb): + """ + *DEPRECATED* + + Callback is called when mouse enters the area of Widget + callback should have form of function(button) + """ + if cb is None: + self.capture(None, event_name = "mouseEntered" ) + return + + def callback(widget=None): + return cb(widget) + print "PyChan: You are using the DEPRECATED functionality: setEnterCallback." + self.capture(callback, event_name = "mouseEntered" ) + + def setExitCallback(self, cb): + """ + *DEPRECATED* + + Callback is called when mouse exits the area of Widget + callback should have form of function(button) + """ + if cb is None: + self.capture(None, event_name = "mouseExited" ) + return + + def callback(widget=None): + return cb(widget) + print "PyChan: You are using the DEPRECATED functionality: setExitCallback." + self.capture(callback, event_name = "mouseExited" ) +