2.5 UI Composition

The examples we have seen so far use a simple mapping: one View per window. While simple applications will use this and nothing else, wouldn't it be interesting to reuse parts of your interface just like you reuse code when programming?

UI Composition allows you to create components of your application UI, which in Kiwi are named slaves, and attach them to other Views. This allows you to embed widgets in any way you like. A slave is really just another View (there is a class called SlaveView, which is for interfaces without a toplevel window that do not use Glade, but any view can be used as a slave) that happens to be attached to another view.

To implement this, Kiwi uses GTK+'s really cool ``reparenting'', a property which allows a widget to be removed from one place in the widget hierarchy and placed in the other (in Kiwi terms, a View's widget is replaced by another View).

A view that will embed one or more slaves is called a Parent view (though there is no class with that name; read on). There are two important requirements for a Parent view:

  1. The parent View must be a BaseView. This allows us to easily find a named widget and change it for a slave. No, there is no ParentView class: it would be a useless, as there is nothing a ParentView needs to do that a BaseView doesn't do already.
  2. The parent View must contain a "placeholder" widget identified by a name, inside an gtk.EventBox, name being the Name: property defined in the Widget tab in Glade. The part about EventBox may seem a bit awkward, but it allows us to guarantee that the child packing of the substituted widget will not change. If we were to swap simply a widget for another, the first widget's container would loose the child packing information (i.e., gtk.Box and gtk.Table's child attributes: expand, fill, padding and span).

I'll demonstrate by showing how a parent view is built in glade. Look at examples/Browser/news_shell.glade:

\includegraphics[scale=0.905]{images/shell1.eps}

In news.glade I put together a simple dialog, and in the place where I want the slave to be embedded, I have added an EventBox (with any name) and a label (named my_placeholder). We now write a small SlaveView called NewsView:

from kiwi.ui.views import SlaveView
from kiwi.ui.objectlist import ObjectList, Column

class Item:
   def __init__(self, news, author):
       self.news, self.author = news, author

class NewsView(SlaveView):
    def __init__(self):
        news = ObjectList(Column("news", "News",),
                          Column("author", "Author"))
        for item in newsitems:
            news.append(item)
        SlaveView.__init__(self, toplevel=news)

# Some news articles borrowed from Pigdog Journal for our example
newsitems = [
  NewsItem("Smallpox Vaccinations for EVERYONE",
           "JRoyale"),
  NewsItem("Is that uranium in your pocket or are you just happy to see me?",
           "Baron Earl"),
]

Now of course, this view can't display itself; it's a SlaveView, and it lacks a toplevel window. But that doesn't matter; we want to attach it as a slave to a main View. We create news, an instance of NewsView, and a BaseView instance called shell, which will be our parent View. We then call the method attach_slave(), which does the actual reparenting; it takes as arguments the name of the widget to be replaced and the view to be attached. At this point, we run the parent view as we would normally:

from kiwi.ui.views import BaseView
news = NewsView()
shell = BaseView("news_shell", delete_handler=mainquit)
shell.attach_slave("my_placeholder", news)

news.show_all()
news.focus_toplevel() # explained next section, don't worry
shell.show_and_loop()

In summary, I gather some random news, create a NewsView instance, a BaseView instance (that will be the parent), call the special function attach_slave(), and that's it. (There is a minor detail with widget focus that is discussed in the next section; basically, the slave needs to be attached to the parent before trying to focus its widgets). The final result is shown below:

\includegraphics[scale=0.905]{images/shell2.eps}

Note that all the examples up to now have been very simple and static: there is no user interaction going on beyond closing the window (for instance, the buttons in the previous example do nothing!). To be able to handle events, we need to understand how the Controller classes (and their derivates, the Delegates) work. The next section explains them in detail.