Beginner's guide to creating a GNOME applet with Python (Part II)


It's been a while since I wrote the first [1] part of this guide. I'm happy to introduce you the second part of the tutorial. This part continues the story about applet-only techniques. Even if you're skilled in PyGTK, you're adviced to read this part.

Part II: GNOME applets and PyGTK
Before we begin...
If you have read part I [1] of the tutorial, and have a test applet running in both "panel" and "debug" mode, you would have no problems running all code described below. All terms described in part I, keep their meanings here so, if you have not read the article, you can do it now.


1. Launching an applet
Generally, a GNOME applet could be treated as an application "attached" to the panel through Bonobo component model system [2]. We can launch the sample applet in a standard GNOME window with the -d key [1]. Still, for example creating and attaching a menu to an applet is not the same task of creating a menu for a windowed application. Or, an applet will always be "vertically-oriented" in debug-mode. So, this time the applet will be added to a panel during all test-runs.

Note: If the sample applet doesn't appear on a panel, it means, that something went wrong (a possible bug in a script). Try launching the applet in debug mode to see the output and catch possible bugs.


2. Popup Context Menu
Every applet has a basic context popup-menu with three (at least in GNOME v2.26) items:



According to GNOME documentation library [3], the only way to extend this menu is to call
1
2
3
4
void panel_applet_setup_menu  (PanelApplet *applet,
                               const gchar *xml,
                               const BonoboUIVerb *verb_list,
                               gpointer user_data);

function (the code above is in C programming language). The corresponding method in Python is Applet object's setup_menu(xml, verbs, user_data) method.

The required xml string has a very easy-to-understand format:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<popup name="button3">
<menuitem name="ItemPreferences" 
          verb="Preferences" 
          label="_Preferences" 
          pixtype="stock" 
          pixname="gtk-preferences"/>
<separator/>
<submenu name="Submenu" _label="Su_bmenu">
<menuitem name="ItemAbout" 
          verb="About" 
          label="_About" 
          pixtype="stock" 
          pixname="gtk-about"/>
</submenu>
</popup>

The example above creates a menu of two items, a separator between them and a single sub-item:



As you can see, the Preferences and About icons are set from the current environment's theme.
So, pixtype="stock" and pixname="gtk-about" attributes mean that an icon from GTK stock collection named "gtk-about" [4] will appear in a menu item.
The name and label attributes define menuitem object's name and label properties. The underscore ("_") character defines a shortcut to a menu.

The verb attribute is crucial for each menuitem node. The name of each verb links a menuitem and a callback function.
1
verbs = [('About', show_about), ('Preferences', show_preferences)]

To link menus and verbs list to an applet, a setup_menu method is called:
1
applet.setup_menu(xml, verbs, None)

Where None is a "user data" (could be any python data object or None)

Finally, all snippets described above, plus callback functions will look like:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def create_menu(applet):
   xml="""<popup name="button3">
<menuitem name="ItemPreferences" 
          verb="Preferences" 
          label="_Preferences" 
          pixtype="stock" 
          pixname="gtk-preferences"/>
<separator/>
<submenu name="Submenu" _label="Su_bmenu">
<menuitem name="ItemAbout" 
          verb="About" 
          label="_About" 
          pixtype="stock" 
          pixname="gtk-about"/>
</submenu>
</popup>"""

   verbs = [('About', show_about), ('Preferences', show_preferences)]
   applet.setup_menu(xml, verbs, None)


def show_about(*arguments):
   print(arguments)

   
def show_preferences(*arguments):
   print(arguments)

   
def applet_factory(gnome_applet, iid):   
   create_menu(gnome_applet)
   ...
   ...

Now, launch the applet in debug mode, and print out the *arguments list in show_about(...) and show_preferences(...) functions.

The result should be similar to:
1
(<bonobo.ui.Component object at 0x963f5cc (BonoboUIComponent at 0x969dac0)>, 'About')

Also try changing the None argument in setup_menu(...) method to e.g. 'Hello!' string and then printing out *arguments list one more time. This time, the result should look like:

1
(<bonobo.ui.Component object at 0x963f5cc (BonoboUIComponent at 0x969dac0)>, 'About', 'Hello!')

Now, you can change the callbacks' definitions
1
def show_about(*arguments):
to
1
def show_about(obj, label, *data):
which slightly clarifies the code.


3.1 Applet's orientation
An applet could be set up both on a horizontal or vertical panel. This should be taken into account before creating the visual elements of an applet. Also an applet's orientation could be changed by user (dragging an applet from a horizontal panel to vertical panel), in this case a callback for the change-orient signal should be created: [5]

1
2
3
4
5
6
7
def change_orientation(applet, orient, user_data):
   pass

def applet_factory(applet, iid):   
   applet.connect('change-orient', change_orientation)
   ...
   ...

The code below demonstrates how change-orient signal could be used:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def applet_factory(applet, iid):   
   # initialize an orientation-dependent label
   orientation = applet.get_orient() 
   if orientation == gnomeapplet.ORIENT_UP or orientation == gnomeapplet.ORIENT_DOWN:
      label = gtk.Label("Vertical")
   else:
      label = gtk.Label("Horizontal")

   applet.connect('change-orient', change_orientation, label) # label is the user data
   ...
   ...


def change_orientation(applet, orient, user_data):
   orientation = applet.get_orient()
   label = user_data                 # get the label

   if orientation == gnomeapplet.ORIENT_UP or orientation == gnomeapplet.ORIENT_DOWN:
      label.set_label("Vertical")    # set label's property 
   else:
      label.set_label("Horizontal")

The applet was added to a vertical panel:



Then, dragged to a auto-hidden horizontal one:




3.2 Applet's background
The GNOME panel has three background options:



And an applet can detect these options' changes via change-background signal. [6]

But in practical situations, an applet doesn't respond to the panel's background changes. What really matters is an applet's background transparency. This is where the power of GNU helps: the sources of GNOME trash applet contain a "hack" line of code that makes an applet transparent. In Python it is:
1
2
3
4
def applet_factory(applet, iid):   
   applet.set_background_widget(applet) # /* enable transparency hack */
   ...
   ...

Now, the applet should look like this:




That's it! This is the end of the second part of the tutorial. There is not much left to say about the applet techniques. But there is much to learn about PyGTK (creating dialogs, different widgets, signals, etc.) . A great tutorial is available at the official site http://www.pygtk.org.

References
1. Beginner's guide to creating a GNOME applet with Python (Part I)
2. Bonobo component model
3. The PanelApplet object
4. GTK stock items
5. www.pygtk.org - GNOME applet with python
6. The "change-background" signal
Comments

Frederik B
WEDNESDAY, 10 MARCH 2010, 04:30
I was stuck at the menu in my own applet and found your guide. So thank you! :)

P.S: Some of your source-code is XML and thus has to be escaped, to be read properly in the second box ;)

Zaur
THURSDAY, 11 MARCH 2010, 02:06
Hi, Frederik,

Glad this article helped you.

And thank you for mentioning the problem with formatting. I've been recently updating the engine so there might be few more formatting bugs :)

exquisitor
SUNDAY, 16 MAY 2010, 12:23
There is no XML after "The required xml string has a very easy-to-understand format:". I can see only numbers from 1 till 15.

exquisitor
SUNDAY, 16 MAY 2010, 12:48
Nice tutorial! Thank you for your work.

It helps to understand basics of programming applets with PyGTK, but you should describe a complete working example and some PyGTK techniques, IMHO.

An ideal tutorial example: a checker applet. It monitors some source of information: for example USD/EUR one time a minute. Than it displays information in some way: red background when it is too low, green when it is too high, etc... User can set this "too hight" and "too low" limits from preferences window, made with glade and available from context menu.

If you write such a tutorial it will be in google top for "python applet" $)

Zaur
MONDAY, 17 MAY 2010, 03:43
Hi, exquisitor!

Thank you very much for reporting the bug with XML. Surprisingly Firefox and Chrome (say Gecko and Webkit based browsers) don't display the CDATA blocks as Opera does :)

I thought about writing a tutorial on PyGTK, but the point of this tutorial is to learn about applet techniques. There is no big difference between "native" PyGTK application or an applet :)

exquisitor
WEDNESDAY, 19 MAY 2010, 08:55
Good evening, Zaur!

I understand your position. Let me explain my point of view.

I want to write a simple applet. It obtains balance from my Internet provider page once a ten minutes and shows it on panel. I have written python programs before, but without GUI. So this applet will be my first PyGTK program. :)

With the help of your tutorial I managed to write an applet that checks and shows my balance and now, when xml bug is fixed, I hope I'll get a menu. The next step is to write preferences window and find out, how to store passwords with Gnome. These problems are very common for applets and it will be great to have a complex tutorial, which shows development of whole working application.

Here is a beautiful example: http://www.pygtk.org/articles/applets_arturogf/. Unfortunately, it is outdated and too brief for starting developer.

P.S.
Why doesn't your blog engine allow to send replies on comments to e-mail? It's convenient.

Zaur
THURSDAY, 20 MAY 2010, 12:41
Well, there is a special e-mail at the about page.
But I never intended to make the engine complex (It was written during my Django self-studies :)

Ivan Dmitriev
TUESDAY, 06 JULY 2010, 09:29
thanks for the transparency hack, helped me brush up my word clock applet

PS: small worldweb, huh?

Eric V
MONDAY, 19 JULY 2010, 10:15
Thanks for this guide - this was very useful as a beginner to programming a GUI.


Leave a comment

Introduce yourself:
Comment: BB-codes
Captcha:
captcha