Re: PicoGUI backend
Lalo Martins <[email protected]>
| Newsgroups | gmane.comp.python.anygui.devel |
|---|---|
| Message-ID | <[email protected]> |
Oops.
[]s,
|alo
+----
--
Those who trade freedom for security
lose both and deserve neither.
--
http://www.laranja.org/ mailto:[email protected]
pgp key: http://www.laranja.org/pessoal/pgp
Eu jogo RPG! (I play RPG) http://www.eujogorpg.com.br/
Python Foundry Guide http://www.sf.net/foundry/python-foundry/
picogui.py
(text/plain, 19.2 KB)
""" PicoGUI backend by Lalo Martins <[email protected]> """ __all__ = ''' Application ButtonWrapper WindowWrapper LabelWrapper TextFieldWrapper TextAreaWrapper ListBoxWrapper FrameWrapper RadioButtonWrapper CheckBoxWrapper '''.split() # Import Anygui infrastructure. from anygui.backends import * from anygui.Applications import AbstractApplication from anygui.Wrappers import AbstractWrapper from anygui.Events import * from anygui.Exceptions import Error from anygui.Utils import log, getSetter from anygui import application # End Anygui imports. # Import anything needed to access the backend API. import PicoGUI _propnames = PicoGUI.server.constants['set'].keys() # End backend API imports. temporary_window_title = 'Anygui application' class ComponentWrapper(AbstractWrapper): """ The ComponentWrapper class should abstract away all behavior that (nearly) all backend widgets perform similarly. Normally, this will include geometry and visibility management, and in some cases widget creation can be handled here as well (see wxgui.py for example). """ _pg_type = None _prop_map = {} def __init__(self, *args, **kwds): """ Common widget wrapper initialization code. The main thing that we would normally do here is set any backend-specific attribute constraints. Whenever the application code alters a widget proxy, the front end will "push" any changed widget attributes into the backend by calling the wrapper.set<Attribute>(self,new_value) method for any attribute that changes. The setContraints() method allows the backend to specify the order in which the set<Attribute>() method calls are made, by specifying the order in which attribute names are set. The constraints set here are just examples. However, it is almost certainly a good idea to ensure that 'container' is set before any other attribute, so at minimum you should call "self.setConstraints('container')" here. """ AbstractWrapper.__init__(self, *args, **kwds) # 'container' before everything, then geometry. #self.setConstraints('container','x','y','width','height') # addConstraint(self,before,after) adds a constraint that attribute # 'before' must be set before 'after', when both appear in the # same push operation. An attempt to set mutually contradictory # constraints will result in an exception. #self.addConstraint('geometry', 'visible') self._on_hold = {} self._app = None def widgetFactory(self, container, *args, **kws): """ Create and return the backend widget for this wrapper. For example, mswgui.py uses the win32gui.CreateWindowEx() call here to create a Windows native widget. The value returned will be immediately assigned to self.widget by the Anygui framework; henceforth you should refer to self.widget. """ if self._pg_type and container: w = container.wrapper._pg_last.addWidget(self._pg_type) container.wrapper._pg_last = w self._pg_last = w return w def enterMainLoop(self): # ... """ enterMainLoop() is called when the application event loop is started for the first time. """ pass def destroy(self): """ destroy() is called when the application needs to destroy the native widget. You can also call it within the wrapper code if you need to destroy your native widget for some reason. You should set self.widget to None here, after destroying the native widget. """ self.widget = None pass # From here on, all methods in this class are getters and setters. # Setters must be implemented; they are called automatically in # response to application-code manipulation of the associated # proxy object, and perform the required magic on self.widget # to implement the change. # # Getters need not be implemented unless the backend requires # special handling, or if the attribute in question can be changed # by user actions as well as by application code. Getters are # called automatically during proxy attribute access, if they # exist; otherwise the last value set in the proxy is returned. # This file provides empty getter definitions for those attributes # that will nearly always require special handling for get # operations. # # The setters and getters here are a typical example of # attribute handling that's common to all of a backend's # widgets. For example, getGeometry() and setGeometry() work # the same for any backend widget, thus they're included # here in the common wrapper base class. # # Note that you must implement all the setter and getter # methods in this file in order for your backend to work # properly! def pg_set(self, name, value): if self.widget: try: self.widget.server.set(self.widget.handle, name, value) except PicoGUI.responses.ParameterError: pass else: self._on_hold[name] = value def push(self, state): """ Try to use cli_python smart property setting """ # first correct names for map_from, map_to in self._prop_map.items(): if state.has_key(map_from): state[map_to] = state[map_from] del state[map_from] elif state.has_key(map_to): del state[map_to] setters, unhandled = self.getSetters(state.keys()) for setter, params in setters: kwds = {} for key in params: kwds[key] = state[key] setter(**kwds) names = unhandled unhandled = {} for name in names: pname = name.lower().replace('_', ' ') value = state[name] if pname in _propnames: print 'setting %s.%s = %s' % (self, pname, repr(value)), self.pg_set(pname, value) else: unhandled[name] = value if self.widget: self.widget.server.update() def internalProd(self): """set properties we left for later""" if self.widget: print 'updating', self props = self._on_hold self._on_hold = {} self.push(props) def setContainer(self,container): """ setContainer() is called whenever the proxy's container (the Frame or top-level Window) in which the widget will appear is set. It's called implicitly when a widget is added to a container via the container.add(widget) method. In most backends, you'll call self.create() here in order to actually create the backend widget. create() is a template method that calls self.widgetFactory() to create the widget, and then performs some bookkeeping for the wrapper. When the widget is removed from its container, setContainer() will be called with container==None; you must handle that case correctly, whatever that means for your backend. """ if not self.widget is None: # If the container has changed, and there's already a native # widget, it may be necessary to take special action here. self.destroy() if container is None: # Handle "removed from container" case. self.destroy() self.create(container) self.post_setContainer(container) # Be sure to handle any backend container/contents protocol # here! # ... # Ensure native widget is brought up to date wrt the proxy # state. self.proxy.push(blocked=['container']) def post_setContainer(self, container): """Do stuff after setContainer()""" pass def setGeometry(self,x,y,width,height): """ Set the native widget's geometry. Note that we call self.widget is None here to see if the wrapper has a native widget to manage. You should probably use this idiom at the start of all setter and getter methods, unless you have a very good reason not to. """ #if self.widget is None: return pass def getGeometry(self): """ Get the native widget's geometry as an (x,y,w,h) tuple. Since the geometry can be changed by the user dragging the window frame, you must implement this method. """ # geometry doesn't really apply to PicoGUI... return (0, 0, 0, 0) def setVisible(self,visible): """ Set/get the native widget's visibility. """ if self.widget is None: return pass def setEnabled(self,enabled): """ Set/get the native widget's enabled/disabled state. """ self.pg_set('disabled', not enabled) def getEnabled(self): return self.widget and not self.widget.disabled class LabelWrapper(ComponentWrapper): """ Wraps a backend "label" widget - static text. You may need to implement setText() here. """ _pg_type = 'Label' class ListBoxWrapper(ComponentWrapper): """ Wraps a backend listbox. At the moment, Anygui supports only single-select mode. """ _pg_type = 'Box' _selection = None _items = () def setItems(self,items): """ Set the contents of the listbox widget. 'items' is a list of strings, or of str()-able objects. """ if self.widget is None: self._on_hold['items'] = items else: # FIXME: remove existing items first self._items = items self._children = [] for text in items: item = self.widget.addWidget('ListItem') item.text = text application().pg_link(self._select, item, 'activate') self._children.append(item) def getItems(self): """ Return ths listbox contents, in order, as a list of strings. """ if self.widget is None: return return self._items def setSelection(self,selection): """ Set the selection. 'selection' is an integer indicating the item to be selected. """ # FIXME: interact with pgui! self._selection = selection def getSelection(self): """ Return the selected listbox item index as an integer. """ return self._selection def widgetSetUp(self): """ widgetSetUp() is called by the Anygui framework immediately after the native widget has been created and assigned to self.widget. The most common use of widgetSetUp() is to register any event handlers necessary to deal with user actions. The ListBox widget requires that an Anygui 'select' event be fired whenever the user selects an item of the listbox. self._select() sends the event, so here you should associate the back-end's selection event with the self._select method. """ pass def _select(self, event, child): """ Send an Anygui 'select' event when the user clicks or otherwise selects a listbox item. Note that the source of the event is self.proxy, not self; that's because application code only knows about proxies, not wrappers, so the source of the Anygui event must be a proxy. """ self._selection = self._children.index(child) send(self.proxy,'select') #class CanvasWrapper(ComponentWrapper): # Fix me! # _twclass = tw.Canvas class ButtonWrapper(ComponentWrapper): """ Wraps a backend command-button widget - the kind you click on to initiate an action, eg "OK", "Cancel", etc. """ _pg_type = 'Button' def widgetSetUp(self): """ Register a backend event handler to call self.click when the user clicks the button. """ application().pg_link(self._click, self.widget, 'activate') def _click(self,*args,**kws): send(self.proxy,'click') class CheckBoxWrapper(ButtonWrapper): """ Button that can be on and off """ _pg_type = 'CheckBox' class RadioButtonWrapper(ButtonWrapper): """ Radio buttons are a pain. Only one member of an RB "group" may be active at a time. Anygui provides the RadioGroup front-end class, which takes care of querying the state of radiobuttons and setting their state in a mutually exclusive manner. However, many backends also enforce this mutual exclusion, which means that things can get complicated. The RadioGroup class is implemented in as non-intrusive a way as possible, but it can be a challenge to arrange for them to act in the correct way, backend-wise. Look at the other backend implementations for some clues. In the case where a backend implements radiobuttons as a simple visual variant of checkboxes with no mutual-exclusion behavior, this class already does everything you need; just create the proper backend widget in RadioButtonWrapper.widgetFactory(). You can usually fake that kind of backend implementation by playing tricks with the backend's mutual-exclusion mechanism. For example, create a tiny frame to encapsulate the backend radiobutton, if your backend enforces mutual exclusion on a per-frame basis. """ _pg_type = 'RadioButton' def _click(self,*args,**kws): try: # Ensure the other buttons in the group are updated # properly. Note that if for some reason you need to # implement getValue(), this code will no longer # work due to the pull mechanism. self.proxy.group.value = self.proxy.value except AttributeError: pass send(self.proxy,'click') class TextControlMixin: """ Single-line entry fields and multiline text controls usually have a lot of common behavior that can be abstracted away. Do that here. """ def setEditable(self,editable): """ Set the editable state of the widget. If 'editable' is 0, the widget should allow selection and copying of its text, but should not accept user input. """ if self.widget is None: return pass def getSelection(self): """ Return the first and last+1 character indexes covered by the selection, as a tuple; or (0,0) if there's no selection. """ if self.widget is None: return pass def setSelection(self,selection): """ Select the indicated text. 'selection' is a tuple of two integers indicating the first and last+1 indexes within the widget text that should be covered by the selection. """ if self.widget is None: return pass class TextFieldWrapper(TextControlMixin,ComponentWrapper): """ Wraps a native single-line entry field. """ _pg_type = 'Field' def widgetSetup(self): """ Arrange for a press of the "Enter" key to call self._return. """ pass def _return(self,*args,**kws): send(self.proxy, 'enterkey') class TextAreaWrapper(TextControlMixin,ComponentWrapper): """ Wraps a native multiline text area. If TextControlMixin works for your backend, you shouldn't need to change anything here. """ _pg_type = 'TextBox' # Incomplete: fix the remainder of this file! class ContainerWrapper(ComponentWrapper): """ Frames - that is, widgets whose job is to visually group other widgets - often have a lot of behavior in common with top-level windows. Abstract that behavior here. """ def post_setContainer(self, container): """ Most backends create native widgets when the front-end widget is added to a container. That means that containers must recursively ensure that their contents are created when they are added to a higher-level container. For example, a Frame being added to a Window must ensure that all of its contents are created and updated to match the front-end state. The easiest way to handle that is to simply call all of the contents' setContainer() methods. """ # Add self to the back-end container in the proper way. This # operation will be different for frames and top-level # windows, so we call a method to handle it. self.addToContainer(container) # Ensure all contents are created. for comp in self.proxy.contents: comp.container = self.proxy class FrameWrapper(ContainerWrapper): def __init__(self,*args,**kws): ComponentWrapper.__init__(self,*args,**kws) def addToContainer(self,container): """ Add the Frame to its back-end container (another Frame or a Window). """ #container.wrapper.widget.add(self.widget) pass class WindowWrapper(ContainerWrapper): """ Wraps a top-level window frame. """ _prop_map = {'title': 'text'} def widgetFactory(self, container, *args, **kws): """ Create and return the backend widget for this wrapper. For example, mswgui.py uses the win32gui.CreateWindowEx() call here to create a Windows native widget. The value returned will be immediately assigned to self.widget by the Anygui framework; henceforth you should refer to self.widget. """ if container and hasattr(container, '_pgserver'): title = self._on_hold.get('text', temporary_window_title) self._pg_last = PicoGUI.Application(title, container._pgserver) return self._pg_last def addToContainer(self, container): """ Add self to the backend application, if required. """ if container: container.manage(self) self._container = container def internalProd(self): # actually create the widget ContainerWrapper.internalProd(self) if self.inMainLoop: self.setContainer(self._container) def widgetSetUp(self): """ Arrange for self.resize() to be called whenever the user interactively resizes the window. """ pass def resize(self,dw,dh): """ Inform the proxy of a resize event. The proxy then takes care of laying out the container contents. Don't change this method, just call it from an event handler. """ self.proxy.resized(dw, dh) class Application(AbstractApplication): """ Wraps a backend Application object (or implements one from scratch if necessary). wxgui's Application class inherits wxPython's Application class. On the other hand, Tk has no Application class, so tkgui's Application class simply calls Tk.mainloop() in its Application.internalRun() method. """ def __init__(self): AbstractApplication.__init__(self) self._pgserver = PicoGUI.Server() def internalRun(self): """ Do whatever is necessary to start your backend's event- handling loop. """ self._windows[0].wrapper.widget.run() def pg_link(self, *args): self._windows[0].wrapper.widget.link(*args)