Skip to content

Writing pygtk applications with style, using pygtkhelpers

24/05/2010

Introduction

pygtkhelpers is an awesome library for writing pygtk applications, it was developed by pida developers and makes the pygtk programming experience much better, let’s start with the tutorial.

I’ve used this library for my project, filesnake, I want to explain my workflow.

GUIs like template

In pygtkhelpers “glade” files and hand written GUI blends together in a wonderful manner, in particular each piece of the GUI is separate from the rest and give you a lot of control, flexibility and mantainablity.

I started with glade, writing a “skeleton gui”. It’s a window with a VBox inside (3 slots). I packed in each slot of the VBox a gtk.EventBox (also another gtk.VBox would have been fine for the purpose), these EventBoxes serves as “placeholders” for other pieces of the GUI, the menu, the userlist and the status bar.

I’ve written the very little code to make the GUI run:

class FileSnakeGUI(WindowView):
    builder_file = "main_win.glade"

def test_run():
    fs = FileSnakeGUI()
    fs.show_and_run()

if __name__ == '__main__':
    test_run()

It’s time to write the three components:

  • menu
  • userlist
  • statusbar

The Menu

I’ve written another glade file that contains  a window with the menu inside, the library will handle the extraction of the menu from the “container” window for you.

To connect the events I’ve used the signal handling facilities provided by pygtkhelpers.

The connection is made automatically with the naming convention on_widget__signal,  this simple convention let us to eliminate the boilerplate code related to the “connect” methods.

from pygtkhelpers.delegates import SlaveView

class Menu(SlaveView):

    builder_file = "menu.glade"

    def __init__(self, parent):
        SlaveView.__init__(self)
        self.parent = parent

    def on_quit__activate(self,*a):
        self.parent.hide_and_quit()

    def on_sendfile__activate(self, *a):
        self.parent.send_file()

Handwritten GUIs

I’ve written the statusbar and the userlist by hand, The code can be as complex as you want, demostrating the flexibility and “invisibility” of the framework, all the code is well packed and organized on its own place.

When subclassing a generic Slave/WindowView (subclasses of BaseDelegate) you can override this methods to customize the behaviour of the class  :

  • create_ui: in this method you can manually write your GUI, usually you put there all the “add_slave” code
  • on_mywidget__event: these are the signal handlers, facilities that let
    you write cleaner code without all the self.connect stuff
  • __init__: you can pass custom initializer, and various control
    code not related to the gui code (nothing stops you to do that in
    the create_ui, it’s just to add a bit of conventions)

Here’s the UserList code, you can add additional methods to simplify
external access, like add_user(). This “additional method” would be
used i.e. in the main controller (FileSnakeGUI). It’s a component, and it’s reusable.

# Defining a user container
User = namedtuple("User", "name icon address port")

# UserList section
class UserList(SlaveView):

    def create_ui(self):
        model = gtk.ListStore(object)
        treeview = gtk.TreeView(model)
        treeview.set_name("User List")

        iconrend = gtk.CellRendererPixbuf()
        inforend = gtk.CellRendererText()

        iconcol = gtk.TreeViewColumn('Icon', iconrend)
        infocol = gtk.TreeViewColumn('Info', inforend)

        iconcol.set_cell_data_func(iconrend, self._icon_data)
        infocol.set_cell_data_func(inforend, self._info_data)

        treeview.append_column(iconcol)
        treeview.append_column(infocol)
        treeview.set_headers_visible(False)

        self.store = model
        self.treeview = treeview
        self.widget.add(treeview)

    def _icon_data(self, column, cell, model, iter):

        row = model[iter]
        user = row[0]
        cell.set_property("pixbuf",gtk.gdk.pixbuf_new_from_file(user.icon))

    def _info_data(self, column, cell, model, iter):

        row = model[iter]
        user = row[0]

        template = "<big><b>{user}</b></big>\n<small><i>{address}:{port}</i></small>"
        label = template.format(user=user.name,
                                address=user.address,
                                port=user.port)
        cell.set_property("markup",label)

    def add_user(self, user):

        self.store.append([user])

The statusbar doesn’t introduce anything new. You can find the source following the link at the end of the article.

To pack together the gui, we’ll use the add_slave method, it adds to a container widget (the EventBoxes we placed)  the widget defined in the slave view, the resulting code is that:

class FileSnakeGUI(WindowView):

    builder_file = "main_win.glade"

    def create_ui(self):
        self.userlist = UserList()

        self.add_slave("statusbar_cont", Statusbar())
        self.add_slave("menu_cont", Menu(self))
        self.add_slave("userlist_cont", self.userlist)

    def on_window1__delete_event(self, *a):
        self.hide_and_quit()

pygtk signals facilities, now you haven’t any excuse

It’s trivial to add your own signals to a BaseDelegate instance, let’s see
how to add a “user-added” signal to my UserList:

from pygtkhelpers.utils import gsignal

class UserList(SlaveView):

   gsignal("user-added",object)

   #   ... source code defined before...
   def add_user(self, user):
      self.emit("user-added", user)
      self.store.append([user])

It’s just one line of code and you can connect it like a gtk widget,
implementing in the easiest way I’ve seen the observer pattern. It’s
cool!

Refactoring the UserList with pygtkhelpers.ui.objectlist.ObjectList widget

One of the most useful feature of pygtkhelpers are ObjectList and ObjectTree, pythonic versions of the common gtk list/tree widgets. They automate the tedious task of setting up treeview widgets using a very powerful (yet customizable) manner.

In a few words, given an object, his attributes can be mapped to the treeview through Columns that almost automatically select the correct renderer for the data to be displayed, like in the following scheme:

The resulting code, is much cleaner and readable:

from pygtkhelpers.ui.objectlist import Column,ObjectList
from pygtkhelpers.delegates import SlaveView
import gtk

from collections import namedtuple
# This is the "user object"
User = namedtuple("User", "name address port icon")

class UserList(SlaveView):

   def create_ui(self):
      # Columns are intended to map UserEntry object, with this signature: Column("attribute", type)
      self.users = ObjectList(columns = [Column("icon",gtk.gdk.Pixbuf),
                                         Column("info",str, use_markup=True)])

      self.users.set_headers_visible(False)
      self.widget.add(self.users)

   def add_user(self, user):
      self.users.append(UserEntry(user))

# The UserEntry is mapped by the treeview with the attributes info and icon
class UserEntry(object):

    def __init__(self, user):
        template = "<big><b>{user}</b></big>\n<small><i>{address}:{port}</i></small>"
        self.info = template.format(user=user.name,
                                    address=user.address,
                                    port=user.port)
        self.icon = gtk.gdk.pixbuf_new_from_file(user.icon)

There are also other interesting features that really boost the pygk GUI programming expecially regarding ObjectList, but also with the pygtkhelpers.ui.dialogs module: well cooked dialogs for common uses.

You can find the full sourcecode at the blog repo:

http://bitbucket.org/gabriele/pygabriel-blogging/src/tip/pygtkhelpers/source/

About these ads
3 Comments leave one →
  1. Ronny Pfannschmidt permalink
    03/06/2010 9:16 pm

    it would be really nice to show the pygtkhelpers.ui.objectlist use instead of normal treeviews

    • 03/06/2010 9:31 pm

      Thank you very much for commenting! I’ve used an hand written treeview just to show some sort of hand-written code. Anyway, I’ll take up your hints and update the blog post with a “refactoring” using objectlist. Bye!

  2. 03/06/2010 8:59 pm

    Glad you like PyGTKHelpers, did you consider using the ObjectList for your userlist? Would have saved a bit more code there.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: