This tutorial is covered by the FDL. Programs listed here are covered by the LGPL.
Because the changelog is getting huge, the rest is in a separated page now. You can read it here.
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 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:
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.
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.
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.
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.
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
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.
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?).
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.
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.
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.
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.
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.
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.
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.
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.
#!/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.
#-- 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.
#!/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
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.
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.
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
.
The custom widget have the follow properties:
Time to generate the code. tepache generates the following code.
#!/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!
#!/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 }
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.
#!/usr/bin/env python import gtk import project2 gui = project2.Window1() gui.browser.load_url("http://www.pygtk.org") gtk.main()
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.