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)
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.