[pygtk] Glade XML wrapper and easy callbacks

Sridhar R sridharinfinity at gmail.com
Sun Jul 25 16:23:08 WST 2004


Doug Quale <quale1 at charter.net> wrote:
> Sridhar R <sridharinfinity at gmail.com> writes:
> 
> > I have made an utility class for making the job of using glade XML
> > file much easier.  Can this be added to pygtk?
> >
> > Main class:
> > http://cs.annauniv.edu/~rsridhar/pub/python/snippets/glade/gwidget.py
> >
> > Example (not standalone, pulled from source tree):
> > http://cs.annauniv.edu/~rsridhar/pub/python/snippets/glade/gwidget-example.py
> >
> > Thoughts?
> 
> About class GWidget itself, I have a few thoughts.  The __init__()
> method lets the caller optionally turn off autoconnection of signals.
> This does no harm, but are there cases in which this is useful?  I
> thought about this but decided that if you derive from a glade
> controller class you do it because you expect the on_widget__signal()
> methods to be connected as signal handlers.

It will be useful if the some handlers need to be called before the
on_widget__signal methods.

super(......
connect_my_custom_first_handlers()
self.autoconnect_signals()


> 
> Specifying the glade file and toplevel widget name as class attributes
> is inflexible.  It's also hard to overide in subclasses since you have
> to completely override the __init__() method if you want to specify
> the widget name dynamically.  I think the glade file and toplevel
> widget name should be parameters to __init__().

To override that create similar Class Variables in derived classes
too.  I used class variables purposedly.  For each GWidget class, the
GLADE_FILE attribute is supposed to be constant for *all* instances,
bcoz the methods are written assuming that glade file, so the glade
file can't be changed for different instances as the methods remain
the same.

> For the signal handler method names I prefer to use
> after_widget__signal() instead of on_widget__signal__().  This is just
> a matter of taste.  Using 'after_' has the disadvantage of taking
> another prefix in addition to 'on_', but it is more explicit.  One
> tiny additional advantage that we don't often think of is that the
> on/after prefixes are easier to read aloud than the silent trailing
> double underscore.

That's probably right.  Using 'after_' is more readable than suffix '__'

> 
> Making the widgets available as attributes is a good idea.  The
> Pythonic way to do this is to set the attribute when it is first
> accessed.  Subsequent accesses won't trigger __getattr__() so you
> don't need a cache:
> 
>     def __getattr__(self, attr):
>         new_widget = self._gladexml.get_widget(attr)
>         if new_widget is None:
>             raise AttributeError, 'Widget %r not found' % attr
>         setattr(self, attr, new_widget)
>         return new_widget
> 
> Instead of making the widgets available as attributes, I use
> __getitem__() to allow dictionary access.  This again is a matter of
> taste.  Dictionary syntax is uglier than attribute syntax but it
> avoids the chance of stepping on a name that you want to use for
> another purpose.

I feel dictionary acess is approriate.  I though self.entry1 is much
easier than self['entry']

> 
> The real reason I chose dictionary syntax over attribute syntax is
> that I use the widget names as attributes for another purpose.  The
> Python 2.2+ data descriptor facility allows using descriptors to
> provide attribute syntax to get and set widget values.
> 
>     class widget(object):
>         """A Python data descriptor that gets and sets values from widgets.
> 
>         Use inside a class that derives from Gwidget to allow getting
>         and setting widget values as attribute values.  This works
>         only for widget types that have a reasonable notion of value
>         like gtk.Entry, gtk.SpinButton, gtk.Combo, gtk.ToggleButton,
>         gtk.Label and gtk.TextView.  Widgets that don't have an
>         obvious value like gtk.TreeView are not supported.  (Generally
>         tree views will be given their own controllers or
>         subcontrollers.)
>         """
> 
>     def __init__(self, widget_name):
>         self.widget_name = widget_name
> 
>     def __get__(self, obj, objtype):
>         widget = obj[self.widget_name]
> 
>         if isinstance(widget, gtk.Label):
>             return widget.get_label()
>         elif isinstance(widget, gtk.SpinButton):
>             return widget.get_value()
>         elif isinstance(widget, gtk.Entry):
>             return widget.get_text()
>         elif isinstance(widget, gtk.Combo):
>             return widget.get_entry().get_text()
>         elif isinstance(widget, gtk.ToggleButton):
>             if widget.get_inconsistent():
>                 return None
>             else:
>                 return widget.get_active()
>         elif isinstance(widget, gtk.TextView):
>             buf = widget.get_buffer()
>             start, end = buf.get_bounds()
>             return buf.get_text(start, end, include_hidden_chars=True)
>         else:
>             raise ValueError, 'Unrecognized widget type %r' % (type(widget),)
> 
>     def __set__(self, obj, value):
>         widget = obj[self.widget_name]
> 
>         if isinstance(widget, gtk.Label):
>             widget.set_label(str(value))
>         elif isinstance(widget, gtk.SpinButton):
>             widget.set_value(value)
>         elif isinstance(widget, gtk.Entry):
>             widget.set_text(str(value))
>         elif isinstance(widget, gtk.Combo):
>             widget.get_entry().set_text(value)
>         elif isinstance(widget, gtk.ToggleButton):
>             widget.set_active(value)
>             widget.set_inconsistent(value is None)
>         elif isinstance(widget, gtk.TextView):
>             buffer = widget.get_buffer()
>             buffer.set_text(value)
>         else:
>             raise ValueError, 'Unrecognized widget type %r' % (type(widget),)

Introspection can be used to remove those if..else part.

> 
> To use this, you put something like this in your class:
> 
>     class MyController(GWidget):
> 
>         name = widget('name')  # name entry
>         age = widget('age')    # age spinbutton
> 
>         ...
> 
>         def somemethod(self, ...):
> 
>             # Here self.name will return the value in the name entry widget
>             # and self.name = 'somebody' will set the value in the name entry
> 
> _______________________________________________
> pygtk mailing list   pygtk at daa.com.au
> http://www.daa.com.au/mailman/listinfo/pygtk
> Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/
> 


-- 
Sridhar - http://www.cs.annauniv.edu/~rsridhar
Blog: http://www.livejournal.com/users/sridharinfinity


More information about the pygtk mailing list