Last modification Mon Jul 4 21:52:07 2005
Sandino "tigrux" Flores <tigrux_at_ximian_dot_com>

Writing PyGTK applications in a visual way

License

This tutorial is covered by the FDL. Programs listed here are covered by the LGPL.

Changelog

Because the changelog is getting huge, the rest is in a separated page now. You can read it here.

Files

Index

Preamble

I'm fan of programming with python and gnome libs. To make developing with pygtk easier, I have developed:

I'm supposing you have some experience using python. There are excellent tutorials of python. Diveintopython is the best tutorial I've ever read, and you should take a look on it. Even if you think you are and expert. glade is very easy to use, and I won't show you how to use glade in detail, I'm only going to show you the basic features.

GTK and its event model

Gtk is the Gimp ToolKit, a set of libraries and programs intented to make applications with graphic user interfaces (GUI). At the moment of writing this tutorial, GTK has reached its version 2.4.13. GTK is a multiplatform library, and it is available in most of the current operating systems and most of the common hardware you can found. GTK lies on other libraries like:

GLIB
Gimp Libray, it is a library which provides algorithms, data structures, threads, process management, and other common things.
ATK
The Accesibility Toolkit. Programs and computers to be used by handicapped people usually require modifications, like braille keyboards, text to speech support, screen magnifiers, high contranst displays and so on. This library allows you to create software for handicapped.
GDK
Gimp Drawing kit. Drawing rectangles, lines, points, polygons, loading images, and other graphical operations is done by GDK. GTK uses GDK to draw whatever you can do with GTK.
Pango
Fonts rendering. You should know free software is used in all the world; and each language has different rules for writing and reading. Pango makes the hard work of rendering such charsets. Pango uses unicode so behaved applications should use utf charsets.

GTK provides many widgets. Look at your screen and you'll see many widgets. Buttons, toolbars, menus, radiobuttons and all widgets. Each widget is internally represented as a class. Widgets can contain other widgets, like buttons contain label or images. Widgets use signals. A signals are a kind of abstractions for low level events. So, instead of looking for the current position of the pointer when a button was clicked on a button, you only have to connect a piece of code to the signal "clicked" of a widget of class Button.

Our first example

In our first example, your goal is learning the basics of glade, widgets, signals and signal handlers. We will design a silly example with a couple of buttons, a label and an entry. The functionality of this example is copying the text in the entry to the label when a button is clicked.

In order to start the project, run glade. If you can't find it in your menu of applications, try running it using the command line. Its binary is usually named glade-2 to differentiate it from the old version based on gtk+-1.2.

01-glade-main.png

01-glade-main.png

You are ready to start a new project now. Click in New and you'll be asked to choose either a GTK or Gnome project. The difference is gnome projects can use more stock icons and more widgets. But, using gnome projects is not a good idea when portability is in your mind. Only plain gtk projects can be used in other operating systems rather than linux or bsd.

02-glade-new-project.png

02-glade-new-project.png

From the main window of glade, turn visible the dialogs for properties and palette. The palette has many widgets separated in categories. You are free to use Gtk+ Basic and Gtk+ Additional; but, you should not use widgets in Deprecated since those are legacy widgets remaining only for compatibility with old applications and they could be removed in future versions of GTK. Please, spend a few minutes identifying widget icons and its names.

03-glade-palette.png

03-glade-palette.png

Once the palette looks familiar, make a click in the icon for Window. You'll notice an empty window is shown and you are ready to add some widgets to it, but, wait a few seconds and keep reading

04-glade-new-window1.png

04-glade-new-window1.png

Our fist step with this new window is take a look at its properties. Select the window, then see the dialog for properties. You can practice modifying properties like title and border width, and shown in the next image.

05-glade-window1-properties.png

05-glade-window1-properties.png

Now, it's time to take a look at its signals. Click in signals tab, in the properties dialog, and click in the button with the ellipsis [...] A list with signals will be shown for the current widget. Also you'll see the list of signals is divided into sections. Each section corresponds to a parent class. Because the class GtkWindow derives from the class GtkWidget, Windows inherits behavior and functionality of Widgets, and so on for class GtkContainer and GtkBin.The parent of the class hierarchy is always GObject. GObject has basic functionality, like instrospection (what can you do?), properties (what can you be set or get?) and signal handling (what can you notify?).

06-glade-window1-list-signals.png

06-glade-window1-list-signals.png

A signal handler is called when a signal is emitted; so, you can attach or connect code to signals. Our first signal will be destroy. This signal is raised when a widget is being destroyed. When the user closes the window, our applications should finish. There are some common signal handlers we can use. One of them is gtk_main_quit. This handler shuts down gtk, and it is exactly what we want.

07-glade-window1-signals.png

07-glade-window1-signals.png

Windows in GTK are containers which can only contain one widget. So, how can we add buttons, entries and other widgets? Making use of other containers, like Boxes and Tables. So, click in the icon for VBox, then, click inside the window, and specify 3 when a dialog asks you for a number of rows. A new vertical box with 3 rows will be added to the window. Play with its properties so it looks like the next image.

08-glade-window1-vbox1.png

08-glade-window1-vbox1.png

Our application will have two buttons. You would think the next step would be use an horizontal box. Right, you can use an horizontal box. But because dealing with buttons is a very common task, gtk and glade provide a widget to make things easier. That class of widget is GtkButtonBox and it has vertical and horizontal variants. Click in the icon for GtkHButtonBox then click in the lowest placeholder of the box. Say 2 when you be asked for the number of child buttons. Then, change properties of the buttonbox and the buttons to obtain a pleasing design. Go to the properties for each button, and set stock icons for them. One will use an Apply stock icon and the other one will use Quit. Notice the button box is filling much space. Go to its properties then go to its packing. Expand means recalculate the starting position of the widget and Fill means fill all the available space. Turn both of them to false since we don't want to have an applications with a huge button box. Finally, click inside the window, press secondary button of the mouse and select Redisplay for the window.

09-glade-window1-hbuttonbox1-packing.png

09-glade-window1-hbuttonbox1-packing.png

Now, add an entry and a label to the window. Labels are interesting because they can render a markup language, similar to html which is part a pango's features. Set the text for the label as shown in the next screenshot.

10-glade-window1-label1-properties.png

10-glade-window1-label1-properties.png

We are almost done with the graphical design. It's time to connect signals for the buttons. Connect the Quit button to gtk_main_quit and the other one to the default signal handler assigned by glade. It is shown is the screenshot.

11-glade-window1-button1-signals.png

11-glade-window1-button1-signals.png

You have finished yout first glade design. If you followed the tutorial until this point, your design will look very similar to the next image.

12-glade-window1-final-design.png

12-glade-window1-final-design.png

Now, save your project. The first time you save, a dialog will ask you for some options. If you have applied my glade patch, then you can select Tepache, Don't worry if your glade does not have that option, you can generate the code manually using the command line.

13-glade-project-options.png

13-glade-project-options.png

OK, here the dirty work comes. Open a terminal, go to the directory where you saved the project. I'll supose you named your project project1.glade. So, run the next command:

[tigrux@garritas example1]$ tepache project1.glade
written file project1.py
[tigrux@garritas example1]$ ls project1.py project1.py.orig
project1.py  project1.py.orig

As you can see, two files are generated, project1.py and project1.py.orig. You are not allowed to modify or delete project1.py.orig, this file is very important to keep the code you write. You should edit project1.py instead. Open this file, and you'll notice it has weird comments related to context. Those comments and the file project1.py.orig allow keep the code you write. Once you edit project1.py with your own code, If you delete project1.py.orig or modify comments related to context then tepache would not be able to make any difference between your code and the autogenerated code, and you would lose you own code. So, keep this advice in mind. Next is the listing of the autogenerated code.

project1.glade

project1.py.orig

#!/usr/bin/env python
# -*- coding: UTF8 -*-

# Python module project1.py
# Autogenerated from project1.glade
# Generated on Sat Jul  2 21:27:36 2005

# Warning: Do not modify any context comment such as #--
# They are required to keep user's code

import os

import gtk

from SimpleGladeApp import SimpleGladeApp
from SimpleGladeApp import bindtextdomain

app_name = "project1"
app_version = "0.0.1"

glade_dir = ""
locale_dir = ""

bindtextdomain(app_name, locale_dir)


class Window1(SimpleGladeApp):

    def __init__(self, path="project1.glade",
                 root="window1",
                 domain=app_name, **kwargs):
        path = os.path.join(glade_dir, path)
        SimpleGladeApp.__init__(self, path, root, domain, **kwargs)

    #-- Window1.new {
    def new(self):
        print "A new %s has been created" % self.__class__.__name__
    #-- Window1.new }

    #-- Window1 custom methods {
    #   Write your own methods here
    #-- Window1 custom methods }

    #-- Window1.on_button1_clicked {
    def on_button1_clicked(self, widget, *args):
        print "on_button1_clicked called with self.%s" % widget.get_name()
    #-- Window1.on_button1_clicked }


#-- main {

def main():
    window1 = Window1()

    window1.run()

if __name__ == "__main__":
    main()

#-- main }

Lets go to test the autogenerated code. Using the terminal, run the code, click in the Apply button. Press the Quit button or close the window. You'll notice a couple of messages are printed in the terminal. That means the code works.

[tigrux@garritas example1]$ ./project1.py
A new Window1 has been created
self.button1 received signal clicked
[tigrux@garritas example1]

We are ready to modify project1.py to add our own code. Gtk is very intuitive and the common widgets are well documented. Also, the code generated by tepache is clean so you'll find easyly the part of the code you want to modify.

We only need to modify two functions: Window1.new and Window1.on_button1_click
The changes we have to do are shown in the next listing.

project1.user_changes.py

#-- Window1.new {
def new(self):
    pass  #pass means no code so we do nothing when the window is created
#-- Window1.new }

#-- Window1.on_button1_clicked {
def on_button1_clicked(self, widget, *args):
    text = self.entry1.get_text() #get the text from entry1
    self.label1.set_text(text)    #set the text to label1
#-- Window1.on_button1_clicked }

As you can see, using widgets in classes derivated from SimpleGladeApp is easy and intuitive. So, a widget created with glade you named bogus_widget will be used in your code as self.bogus_widget.

Our code is finally done. And the code is shown as well as a screenshot.

14-glade-project-running.png

14-glade-project-running.png

project1.py

#!/usr/bin/env python
# -*- coding: UTF8 -*-

# Python module project1.py
# Autogenerated from project1.glade
# Generated on Sat Jul  2 21:27:36 2005

# Warning: Do not modify any context comment such as #--
# They are required to keep user's code

import os

import gtk

from SimpleGladeApp import SimpleGladeApp
from SimpleGladeApp import bindtextdomain

app_name = "project1"
app_version = "0.0.1"

glade_dir = ""
locale_dir = ""

bindtextdomain(app_name, locale_dir)


class Window1(SimpleGladeApp):

    def __init__(self, path="project1.glade",
                 root="window1",
                 domain=app_name, **kwargs):
        path = os.path.join(glade_dir, path)
        SimpleGladeApp.__init__(self, path, root, domain, **kwargs)

    #-- Window1.new {
    def new(self):
        pass  #pass means no code so we do nothing when the window is created
    #-- Window1.new }

    #-- Window1 custom methods {
    #   Write your own methods here
    #-- Window1 custom methods }

    #-- Window1.on_button1_clicked {
    def on_button1_clicked(self, widget, *args):
        text = self.entry1.get_text() #get the text from entry1
        self.label1.set_text(text)    #set the text to label1
    #-- Window1.on_button1_clicked }


#-- main {

def main():
    window1 = Window1()

    window1.run()

if __name__ == "__main__":
    main()

#-- main }

Do you have any complains? I have one. It will be better if the label changes when pressing the key Return in the entry. How to do that? Hopefully, you won't have to modify the code, just the glade file.

Using glade, open our project. Select the entry, go to signals for the widget entry. Select the signal activate and write on_button1_clicked as signal handler. In this way, the same callback for button1's clicked and entry1's activate will be used. Wasn't it easy? :-D

Understanding the trick behind magic

You'll notice that running tepache again does not touch the code you have written. Also, this program automagically creates skeletons. I'm going to explain how this is done. Open a terminal, and run the next commands:

[tigrux@garritas example1]$ diff -U1 project1.py.orig project1.py
--- project1.py.orig
+++ project1.py
@@ -35,3 +35,3 @@
     def new(self):
-        print "A new %s has been created" % self.__class__.__name__
+        pass  #pass means no code so we do nothing when the window is created
     #-- Window1.new }
@@ -44,3 +44,4 @@
     def on_button1_clicked(self, widget, *args):
-        print "on_button1_clicked called with self.%s" % widget.get_name()
+        text = self.entry1.get_text() #get the text from entry1
+        self.label1.set_text(text)    #set the text to label1
     #-- Window1.on_button1_clicked }
     
[tigrux@garritas example1]$ cat project1.glade | grep signal
  <signal name="destroy" handler="gtk_main_quit"/>
              <signal name="clicked" handler="on_button1_clicked"/>
              <signal name="clicked" handler="gtk_main_quit"/>

Now you can see there is no magic. I only use simple commands like diff and patch to preserve your changes. The other thing, generating the code from glade, is made with a library named SAX which has a standard implementation in python.

Custom widgets

You can use custom widgets in the same way you used signal handlers. Custom widgets require a creation function, this functions should return a widget. It is commonly used when you want to use widgets not shipped with standard gtk, or when you want to have to use widgets you have made extending the current ones.

Do you Want to see an example? Of course you do. You are reading this because you want to learn more. In the next example, we will use the gtkmozmbed module, it is shipped with gnome-python-extras.

Because you already know how to use glade, I'll show you only the new things you could not know at the moment. First, where are the custom widgets? In the palette of glade, of course.

01-glade-palette-custom.png

01-glade-palette-custom.png

Next, using glade, make a design as shown in the screenshot. Set stock icons for the buttons, and use mnemotecnical signals. In this example, I'll use on_back, on_reload, on_forward and on_load.

02-glade-mozembed-design.png

02-glade-mozembed-design.png

The custom widget have the follow properties:

03-glade-mozembed-properties.png

03-glade-mozembed-properties.png

Time to generate the code. tepache generates the following code.

project2.glade

project2.py.orig

#!/usr/bin/env python
# -*- coding: UTF8 -*-

# Python module project2.py
# Autogenerated from project2.glade
# Generated on Sat Jul  2 21:27:16 2005

# Warning: Do not modify any context comment such as #--
# They are required to keep user's code

import os

import gtk

from SimpleGladeApp import SimpleGladeApp
from SimpleGladeApp import bindtextdomain

app_name = "project2"
app_version = "0.0.1"

glade_dir = ""
locale_dir = ""

bindtextdomain(app_name, locale_dir)


class Window1(SimpleGladeApp):

    def __init__(self, path="project2.glade",
                 root="window1",
                 domain=app_name, **kwargs):
        path = os.path.join(glade_dir, path)
        SimpleGladeApp.__init__(self, path, root, domain, **kwargs)

    #-- Window1.new {
    def new(self):
        print "A new %s has been created" % self.__class__.__name__
    #-- Window1.new }

    #-- Window1 custom methods {
    #   Write your own methods here
    #-- Window1 custom methods }

    #-- Window1.on_back {
    def on_back(self, widget, *args):
        print "on_back called with self.%s" % widget.get_name()
    #-- Window1.on_back }

    #-- Window1.on_reload {
    def on_reload(self, widget, *args):
        print "on_reload called with self.%s" % widget.get_name()
    #-- Window1.on_reload }

    #-- Window1.on_forward {
    def on_forward(self, widget, *args):
        print "on_forward called with self.%s" % widget.get_name()
    #-- Window1.on_forward }

    #-- Window1.on_load {
    def on_load(self, widget, *args):
        print "on_load called with self.%s" % widget.get_name()
    #-- Window1.on_load }

    #-- Window1.make_browser {
    def make_browser(self, str1, str2, int1, int2):
        widget = gtk.Label("make_browser")
        widget.show_all()
        return widget
    #-- Window1.make_browser }


#-- main {

def main():
    window1 = Window1()

    window1.run()

if __name__ == "__main__":
    main()

#-- main }

Then, writing code for handlers of the buttons, you'll have a complete internet browser in less than 100 lines!

project2.py

#!/usr/bin/env python
# -*- coding: UTF8 -*-

# Python module project2.py
# Autogenerated from project2.glade
# Generated on Sat Jul  2 21:27:16 2005

# Warning: Do not modify any context comment such as #--
# They are required to keep user's code

import os

import gtk
import gtkmozembed

from SimpleGladeApp import SimpleGladeApp
from SimpleGladeApp import bindtextdomain

app_name = "project2"
app_version = "0.0.1"

glade_dir = ""
locale_dir = ""

bindtextdomain(app_name, locale_dir)


class Window1(SimpleGladeApp):

    def __init__(self, path="project2.glade",
                 root="window1",
                 domain=app_name, **kwargs):
        path = os.path.join(glade_dir, path)
        SimpleGladeApp.__init__(self, path, root, domain, **kwargs)

    #-- Window1.new {
    def new(self):
        pass
    #-- Window1.new }

    #-- Window1 custom methods {
    #   Write your own methods here
    def on_browser_location(self, widget):
        self.entry1.set_text( self.browser.get_location() )
    #-- Window1 custom methods }

    #-- Window1.on_back {
    def on_back(self, widget, *args):
        self.browser.go_back()
    #-- Window1.on_back }

    #-- Window1.on_reload {
    def on_reload(self, widget, *args):
        self.browser.reload(gtkmozembed.FLAG_RELOADNORMAL)
    #-- Window1.on_reload }

    #-- Window1.on_forward {
    def on_forward(self, widget, *args):
        self.browser.go_forward()
    #-- Window1.on_forward }

    #-- Window1.on_load {
    def on_load(self, widget, *args):
        self.browser.load_url( self.entry1.get_text() )
    #-- Window1.on_load }

    #-- Window1.make_browser {
    def make_browser(self, str1, str2, int1, int2):
        widget = gtkmozembed.MozEmbed()
        widget.connect("location", self.on_browser_location)
        widget.load_url(str1)
        widget.show_all()
        return widget
    #-- Window1.make_browser }


#-- main {

def main():
    window1 = Window1()

    window1.run()

if __name__ == "__main__":
    main()

#-- main }
04-glade-mozembed-running.png

04-glade-mozembed-running.png

Using generated code as modules

You have noticed tepache generates python modules with names very predictible. How so predictible? Python modules should be named containing only a limited charset. For example, test-foo.py is an illegal name for a module. But test_foo.py is a legal one. Guess what? The code sketcher replaces all non valid characters in the name with underscores _ So, using my-example$1.glade, my_example_1.py will be generated. Then, you can use the generated module as any regular python module in your program. The next example reuses the example developed above.

using_project2.py

#!/usr/bin/env python

import gtk
import project2

gui = project2.Window1()
gui.browser.load_url("http://www.pygtk.org")

gtk.main()

Acknowledgments

I want to thank Alexandro "JZA" Colorado for helping me with the style sheet I used in this tutorial.

Thanks to Marco "MaoP" Ocampo, he has been very patient being the main beta tester of this program.

And finally, but more important, thanks to James Henstridge, Damon Chaplin, Guido Van Rossum and Federico Mena for writing much of the software I have used while writing this tutorial.

References