This document is released under the terms of the FDL
| Revision History | |
|---|---|
| Revision 0.1.1 | 29-10-2003 |
|
Added gobject.SIGNAL_ACTION for key bindings stuff | |
| Revision 0.1.0 | 05-10-2003 |
| |
| Revision First Draft | 29-09-2003 |
Table of Contents
Abstract
This document tries to explain the process of creating subclasses of GObject, the base class of the GNOME framework, in Python.
GNOME (GNU Network Object Model Environment) is a project whose main goals are to create a complete, free and easy-to-use desktop environment for users as well as a powerful application development framework for software developers. As a result of this second goal GNOME is based on a set of libraries which are very easy to access from a large amount of programming languages.
The library most modules depend on is GLib which provides a lot of useful functionality embedded and used in the GNOME framework; this document in particular discussed the Object system defined in Glib that allow us to use Object Orientation throughout the GNOME framework. The Glib library, as most of the GNOME core libraries, is programmed in the C programming language, which is not an Object Oriented language in the sense that it does not contains the syntax and built-in types that are conventionally used when programming in an Object Oriented way in other languages like C++, Java or Python. But this does not mean that you can not program in an Object Oriented way with C, it's only that you have to do some extra work. And it's your lucky day, because GLib does most of this work for you. GObject is commonly known as the part of GLib that provides the Object Oriented features that C lacks.
One of the nice things that GObject provides is a class mechanism with the kind of things you are used to work within an Object Oriented language like inheritance, polymorph-ism, interfaces and virtual methods. But it also gives you a very powerful feature that allows you to connect different objects in an asynchronous and independent way. Yes I'm talking about signals. If all this is not enough for you, then let me tell you that GObject also support introspection allowing some programs (like GUI builders or debuggers) to know the properties of an object.
But what if you are used to another programming language like Python and you create GNOME applications using some of the GNOME bindings (like PyGTK) but you still want to use some of those nice features of GObject? Well, in that case, this article is for you.
You may wonder why not use Python's own Object Oriented features to implement Object Oriented programs. And in most of the situations you are probably right. But, as said before, there are a few features of GObject, that are really useful when working with the GNOME framework. These are:
Signals. This is a mechanism to communicate your program with its environment (the user input and the Window Manager actions). But what is really useful is that you can use signals to communicate between different parts of your programs in a very modular manner. Have you ever heard of the Model View Controller philosophy? Well, let me tell you that signals make this possible in a very simple way. We will talk about this later.
Property change notifications. As you can guess, having callbacks that listen for property changes is a nice feature that can make your design a little bit cleaner.
Property introspection. Sure, you can have the same thing with dir(MyClass) but what if you just want to get the properties, not the methods?
Type checking. If you have a boolean property and you want to avoid things like self.my_boolean_prop = 'foo', the GObject property system can help you.
One more good thing about GObject subclassing is that you can always use the normal Python Object Oriented features at the same time you use the GObject features and things will just work smoothly.
So let's get some action and create our first example. We are going to create a Car class with a fuel property that indicates the amount of fuel that our car has. This is the code
| These are the standard imports you need to use when using PyGTK |
| The __gproperties__ dictionary is a class property where you define the properties of your cars. In our example we just have one property, the fuel. The format to define a property is the following:
|
| Next thing you need to do is call the __init__ method of gobject.GObject. This call is used to register your properties and signals. You also need to create the appropriate Python instance members to hold your properties. In this case we just need a fuel instance variable. |
| When using GObject properties you need to define a method called do_get_property which will be called for you each time somebody tries to access one of your properties. All you need to do is return the appropriate property. |
| You also need to define the do_set_property method, that will be called when somebody tries to set one of your property's value. GObject will make sure that the type of the new value matches the type of your property and that the value is in the appropriate range if this is needed (for char, int, long, float and double types) before calling this method. |
| Last thing you have to do is a call to gobject.type_register with your class as the argument. This call registers your class as an official GType and it is absolutely necessary. Some people say that this is not a very elegant solution but so far that's the way to do it. The main problem is that this is a class initialization process and not an instance initialization issue. That's why it can not be implemented in the __init__() method. Maybe using Python metaclasses can solve this in a nice manner. |
You can use your brand new custom properties as with any other regular property like the ones you find using GTK+ Widgets. For those of you that don't know what I'm talking about, here is a small example:
>>> from car1 import Car
>>> aCar = Car()
>>> print "The car has %f of fuel at the beginning" % aCar.get_property('fuel')
The car has 50.000000 of fuel at the beginning
>>> aCar.set_property('fuel', 20)
>>> print "Now the car has %f of fuel" % aCar.get_property('fuel')
Now the car has 20.000000 of fuel |
What I think is really useful is connecting a callback to the notify signal of a property. In the next example we will create a function that will check if we are running out of fuel using the notify mechanism of GObject:
1 import pygtk
2 pygtk.require('2.0')
3 import gobject
4
5 from car1 import Car
6
7 def myCallback(obj, property):
8 if property.name == 'fuel':
9 if obj.get_property('fuel') < 10:
10 print 'we are running out of fuel!!'
11
12 def test():
13 aCar = Car()
14 aCar.connect('notify', myCallback)
15 aCar.set_property('fuel', 5.0)
16
17 if __name__ == '__main__':
18 test()
19 |
In this example, the myCallback function is called for any change in any property of aCar. Suppose we have 4 different properties in a Car, that's why we need the if clause at the beginning of the callback.
Now we will see in another way of doing the same as in the previous example but using a nice feature of GObject signal names:
When naming a property with more than one word you have to be careful with the character used to separate the words. GObject translates all the underscore characters to hyphen characters so if you have a property called background_color, its internal and valid name will be background-color. The places where you have to be careful about this are the do_get_property() and do_set_property() methods and the signal connection calls. Let's see an example:
Note that you can use long names like backgroundColor (this means capitalizing the first letter of every word but the first one) to avoid this problem.
One very common thing you probably want to do when using GTK+ is subclassing a widget (or any other GObject subclass) and you should know one issue about this: If you define custom properties or signals in your new class you can't call the superclass __init__ method in your __init__ method or it will overwrite your property definitions. You should call the __gobject_init__ method instead.
This leads us to the next question: what if I really need to call the superclass's __init__ method? Well, then there is a design bug either in your class, or in GTK+. Recently I created a subclass of gtk.ListStore to make my custom gtk.TreeView model. In C you can create an empty GtkListStore and then you can add some columns to it with another function call. But in Python there is no wrapper for this function so you need to set the column types in the constructor for gtk.ListStore. This means you can't define custom properties in your own subclasses of gtk.ListStore. Don't worry about this, there is already a bug report in the PyGTK bugzilla and usually their maintainers fix the bugs pretty fast.
The other thing you probably want to use when subclassing GObject is define custom signals. You can create your own signals that can be emitted so users of your class can connect to them.
When a signal is emitted a set of closures will be executed. A closure is an abstraction of the callback concept. A closure is the callback itself (a function pointer), the user data (it will be the last parameter to the callback) and another function for cleanup issues, which will not be discussed in this document.
For the sake of this article you don't really need to know the difference between a callback and a closure so both terms will be used. But be advised that this is not totally correct.
As we said before, when a signal is emitted, a set of closures will be executed. One of them is the same one for all the instances of this class and hence its name: the class closure, and the other ones are custom user callbacks. Note that not all the signals need to have a class closure because it is optional.
The GObject signal system is a very flexible (and complex) one so you can change the order in which your callback is executed very easily. Let's see the states in which a signal goes so you can understand what you can do:
RUN_FIRST. If there is a class closure for this signal and it was created with the SIGNAL_RUN_FIRST flag it is executed now.
HANDLER_RUN_FIRST. All the non blocked closures connected with the GObject.connect family of functions are executed now.
RUN_LAST. If there is a class closure for this signal and it was created with the SIGNAL_RUN_LAST flag it is executed now.
HANDLER_RUN_LAST. All the non blocked closures connected with the GObject.connect_after family of functions are executed now.
There are other states like EMISSION_HOOK and RUN_CLEANUP which you can't use from Python so they won't be explained here.
I think this is enough theory for now so let's jump into some real code. We can use the signal concept to improve our car class. We can have an engine-started signal that will be emitted when the car's engine is started so other parts of the car can connect to this signal and do something useful.
Now that we have our own signal we can connect callbacks to it to make it a little bit more useful:
Now that you know what the class closure is, you may want to override it. Suppose you want to subclass the Car class with a LuxuryCar class and you want to do something more when the engine is started. In this particular case when you subclass a Python class with another Python class there is nothing special with this. Let's see an example:
1 import pygtk
2 pygtk.require('2.0')
3 import gobject
4
5 from car4 import Car
6
7 class LuxuryCar(Car):
8 def do_engine_started(self, remaining_fuel):
9 Car.do_engine_started(self, remaining_fuel)
10 print 'Welcome to the Luxury Car'
11
12
13 if __name__ == '__main__':
14 luxuryCar = LuxuryCar()
15 luxuryCar.start() |
Note that since you are not defining new properties or signals you don't have to call gobject.type_register(LuxuryCar)
But, what if you want to override the class closure of a GTK+ Widget? The GTK+ library is written in C and most of the functions that implement the class closure for the signals of the widgets are not public. This among other consequences means that they are not accessible to the PyGTK bindings.
Now suppose you want to create a special kind of gtk.Entry that will not allow the user to paste anything into it. In the regular gtk.Entry the class closure of the 'paste_clipboard' signal has the function that handles the paste behavior. So our class need to override this class closure. This is the code you need to write:
Note that in this particular case you could have done the same thing in another way: connect a callback to the 'paste_clipboard' signal and call stop_emit_by_name in this callback. But you can do it only because the 'paste_clipboard' signal is defined as a RUN_LAST signal allowing the user callback code to be executed before the class closure. What if the signal is defined as RUN_FIRST? Then, there is no way to modify the default behavior unless you override the class closure.
I know it's hard to find a good use of overriding the class closure but you can find several ones of it looking in the DiaCanvas library. This library is build in top of GnomeCanvas and it's very useful if you want to draw diagrams. There is a class called PlacementTool which is used when the user clicks on the canvas and he/she is creating new items. You can override the class closure of the 'button-press' and 'button-release' signals if you want to do special things when creating items (as I needed to do some time ago :)