Building GNOME Applications with Python

Malcolm Tredinnick

CommSecure

Who Am I?

Where Are We Heading?

A Possible Development Process

(Assume the purpose of the application is already known)

  1. User interface
  2. User assistance (Help)
  3. Be nice to non-English speakers (Internationalisation)
  4. Be nice to all users (Accessibility)

Of course, building an application is much more complicated than assuming you have an idea and then adding the four points on this slide. But these are the areas that are most relevant from a desktop point of view. What your application actually does is usually independent of the toolkit you use to build it. But how you implement the external face of your application is our concern in this talk.

User Interfaces — Some GTK Theory

GTK Signals

The last point just emphasises the fact that we can use just about anything that is "callable" when we are creating a callback, so the developer can feel free to use classes as much or as little as they like.

When connecting handlers to signals, as many "data" arguments as you like can be passed in. So w.connect(name, handler, foo, bar, baz) is perfectly fine. The handler obviously has to accept the right number of parameters, too. This extra data (and no data is required, by the way) is useful to provide some context in the callback so that callback functions can be shared between multiple signals.

Because the widget raising the signal is passed into each handler, we can easily share handlers between widgets. It is very common to have a single function for hiding dialog boxes when they are closed (rather than destroying the widget), for example.

The "generally" qualification on the signal handler prototype is because some handlers take extra paramters all the time. For example, the 'response' signal on a GtkDialog box will always have a response identifier as the second argument (before the user data). The only way to be certain about a specific prototype is to look at the reference documentation.

Events

Because event handlers receive so much context in the call (the widget and the event), you could right a single "mother of all event handlers" that does everything. But try to resist — your code will not be very maintainable.

However, a certain amount of collapsing of common functionality is helped by this type of handler signature. For example, you could decide to handlie all occurrences of keypresses (outside of entry widgets) in a single place.

Events Can Be Tricky

Event / Signal Propogation

What Signals Are Available?

Practical Matters

Practical Matters

Rough Design

This is a simulation of a "back of the envelope" design and how I picture the application's windows being arranged. The main window is displayed at startup, the other three are brought up under the control of Main as required.

Turning Scribble Into Code

Glade

The initial appearance of Glade, before loading of starting any project.

  • Top-level windows appear in the project box in the top-left.
  • Widgets that can be added to the project are in the palette in the lower-left.
  • The property box on the right is where all the widget attributes are edited.

Adding Widgets

A closer view of the widget palette, showing the widgets that are only available in a "GNOME" (as opposed to "GTK") project. The top-left widget is the main GNOME application window (menubar, toolbar, status bar, progress bar and main application area).

The GNOME Application Window

A detail view of the "GNOME Application Window" widget.

Editing Static Properties

Here we can see how the AboutBox being created on the right can have various attributes set through the Glade Properties box on the left.

What Glade Produces

This is an extract of the XML output from the earlier screenshots. Only the about box description is shown here. The point is that the save format of Glade is easily readable by humans and machine readable for any XML-capable tools.

A Full Editing Session

A typical Glade editing session can result in a lot of windows being open at once. This can be a nuisance on smaller (e.g. laptop) screens.

Time To Write Code...

The pygtk.require('2.0') line is so that a system with parallel installs of gtk-1.2 and gtk-2 uses the correct version of pygtk.

Failure to import gnome.ui will result in no stock icons displaying in menu items or the toolbar.

Managing Top-Level Windows

class About:
    def __init__(self, gladeFile, name, version):
        tree = gtk.glade.XML(gladeFile)
        self.aboutBox = tree.get_widget('about')
        self.aboutBox.set_property('name', name)
        self.aboutBox.set_property('version', version)

        # Hook up individual signals and events.
        self.aboutBox.connect('response', dialogResponse)
        self.aboutBox.connect('close', dialogClose)
        self.aboutBox.connect('delete_event', dialogClose)

    def show(self, *args):
        self.aboutBox.show()
[any material that should appear in print but not on the slide]

Generic Close/Hide Functions

def dialogClose(dialog):
    self.dialog.hide()
    return True

def dialogResponse(dialog, response, *args):
    if response < 0:
        dialog.hide()
        dialog.emit_stop_by_name('response')

Hooking Up Menus

Hooking Up Menus (2)

Hooking Up Menus — The Code

# The 'windows' parameter to __init__ is a mapping of top-level window
# names to their widgets.

class Main:
    def __init__(self, gladeFile, windows):
        tree = gtk.glade.XML(gladeFile, 'main')
        main = tree.get_widget('main')

        # Quit the program when the main window is closed.
        main.connect('delete_event', self.quit)

        # Callbacks for all the menu items.
        callbacks = {
            'on_quit_activate': self.quit,
            'on_about_activate': windows['about'].show,
        }
        tree.signal_autoconnect(callbacks)

Through judicious use of the signal_autoconnect() method, hooking up multiple callbacks is easy. The "judicious" portion here is because we only constructed the XMl tree from the 'main' widget downwards — not for the entire file. This prevents the autoconnection from running wild amongst handlers we do not want to connect in this class.

Making Help Available

  • Write help documentation (in multiple languages, if required)
  • Tell libgnome where to find the help files
  • Insert calls to spawn the help browser at the right places

Help (In Code)

  • Initialise the library correctly by changing the gnome.init() call to read
    appProperties = {'app-datadir': _currentDir()}
    gnome.init(name, version, properties = appProperties)
    
  • Help code resides under gnome/help/<LANG>/ under this prefix.
  • Warning: The app-datadir path must be an absolute path.
  • In the Main class, we have (after adding menu item and handler callback)
        def showHelp(self, *args):
            gnome.help_display('debugger-help.xml')
    

Help In Action

Internationalisation

  • Much support comes for free: anything from a standard GNOME library is already translated!
  • To get basic support:
    import gettext, locale
    ...
    def main(name, version):
        gettextName = 'debuggerDemo'
        localeDir = '%s/locale' % _currentDir()
        gettext.bindtextdomain(gettextName, localeDir)
        gettext.textdomain(gettextName)
        gettext.install(gettextName, localeDir, unicode = 1)
    

I18n For Free (Somewhat)

Internationalising The Remainder

  • Need to mark all strings for translation.
  • Strings can be in .py files, .glade files, ...
  • Want to be nice to translators and have everything in one file
  • So ...?

Internationalising The Remainder

  • Need to mark all strings for translation.
  • Strings can be in .py files, .glade files, ...
  • Want to be nice to translators and have everything in one file.
  • Intltool is your friend!

The I18n Process

  • Create a directory for storing translation files (*.po). Usually po/ is used.
  • Create a file po/POTFILES.in containing the name of each file to scan for translatable strings.
    • touch po/POTFILES.in
      intltool-update -m
  • Generate the initial translation template:
    • intltool-update -p -g debuggerDemo

Keeping Translations Up To Date

  • Translators will create files with names like cy.po and zh_TW.po.
  • Periodically, intltool-update will be run to update existing PO files.
  • Installation process needs to generate MO files from PO files and store them in the correct locale directories before translations are useful.
    • This can be done at installation time, or checked at runtime.

Spit And Polish

  • Read the GNOME Human Interface Guidelines (the HIG).
  • Read it again.
  • Apply as many of the recommendations as you can to your application.
    • Window titles and menu strings
    • Dialog box layout
    • Configuration handling and "doing the right thing"
  • Although you may not agree with everything, all of the points are tested in production and work well. Consistency is also an important side-effect here.
    • Users feel comfortable when things behave consistently.
    • Comfortable users are happy users.

Be Available (Applications Menu)

  • Create a .desktop file for your application so that it will appear in the panel's Applications menu.
  • Specification available from http://www.freedesktop.org/
  • Comments and descriptive names are translatable, so you really create foo.desktop.in and mark translatable strings.
  • [Desktop Entry]
    Encoding=UTF-8
    _Name=Project Management
    _Comment=Planner Project Management
    Exec=planner %F
    Icon=gnome-planner.png
    Terminal=false
    Type=Application
    Categories=Application;Office;ProjectManagement;
    StartupNotify=true
    MimeType=application/x-planner;
    

Other Areas To Explore

  • Using GConf to store persistent configuration information or synchronise config info between two instances of the application.
  • Being a drop target (the destination of a "drag-and-drop" action).
  • Writing a panel applet.
  • Printing.
  • The many, many GTK widgets that are available.

And Now ...?

  • Start playing! Interactive PyGTK shell at http://www.pygtk.org/pygtk2tutorial/examples/pygtkconsole.py
  • Ask questions on the mailing list. A very helpful group. Responsive to well thought-out questions.
  • Play with gtk-demo.py that comes with the pygtk2 distribution. Shows what is possible in GTK
  • Read the GTK reference manual (the C version). The introduction to each section describes the widgets' purposes very nicely (this is somewhat missing in the Python documentation).

Slides Available

Useful Resources