Le langage Python
Cinquième partie

Cet article est le cinquième d'une série destinée à vous faire découvrir le langage Python. Le mois dernier nous avons exploré les différentes fonctionnalités fournies par les modules Python les plus classiques ainsi que quelques astuces courantes de programmation permises par le langage. Ce mois-ci, nous allons voir comment utiliser Python pour écrire rapidement des applications Gtk+ ou GNOME. Cet article suppose que le lecteur connait déjà les bases de la programmation Gtk+/GNOME.

Avant d'aller plus loin, vous allez devoir vous assurer que les interfaces ("les bindings") Gtk+ et GNOME pour Python sont bien installées sur votre machine. Si vous avez la chance d'utiliser la distribution Debian GNU/Linux sur votre machine, il vous suffit de taper la commande 'apt-get install python-gtk python-gnome' pour vérifier que ces deux paquetages sont bien installés ou de les installer à la volée le cas échéant. Si vous utilisez une autre distribution, consultez dans les références de cet article la liste des URLs de logiciels à télécharger.
Pour vérifier que les modules Python correspondant fonctionnent correctement, il vous suffit de lancer l'interpréteur et de taper l'instruction :
>>> import gtk, gnome
>>>
Si l'interpréteur vous rend le prompt sans afficher aucun message, c'est tout bon. Bravo!
Si le message
"No display information -- gtk_init not called"
est affiché, cela signifie tout simplement que vous avez oublié de positionner correctement votre variable d'environnement $DISPLAY (la valeur ":0" convient dans la plupart des cas, sauf si vous utilisez un interpréteur Python situé sur une machine distante).
Enfin, dans le cas où vous obtiendriez un message se terminant par :
"RuntimeError: cannot open display",
vérifiez que l'interpréteur a bien le droit d'ouvrir une connexion vers votre serveur X. A ce moment, la commande "xhost +" tapée à partir d'un terminal ayant les droits d'accès au serveur X permettra de régler ce problème.
Si par contre vous obtenez un autre message d'erreur, c'est qu'il y a un problème d'installation.

    Bases de la programmation d'interfaces graphiques

À la différence de la programmation "traditionnelle", la programmation d'interfaces graphiques avec des toolkits évolués (dont Gtk+ fait bien sûr partie) repose sur un modèle de programmation événementiel. C'est-à-dire que lorsque le programme a fini de mettre en place tous les éléments de l'interface graphique, il appelle une fonction qui va se charger d'intercepter tous les événements (mouvements de la souris, clics, entrées aux clavier, etc) reçus par le programme et de les dispatcher vers les fonctions qui doivent les traiter. Ce mécanisme utilise les callbacks, ou fonctions de rappel : Lorsque l'on défini un élément de l'interface graphique (une widget), on lui associe un ou plusieurs callbacks qui seront appelés lorsque des événements déterminés s'appliqueront à cette widget.
Voyons cela de plus près avec un premier exemple :
 
 
hellogtk.py
01 : #!/usr/bin/python
02 :
03 : from gtk import *
04 : 
05 : def hello(*args):
06 :    print "Hello World"
07 :    window.destroy()
08 :
09 : def destroyfunc(*args):
10 :     window.hide()
11 :     mainquit()
12 : 
13 : window = GtkWindow(WINDOW_TOPLEVEL)
14 : window.connect("destroy", destroyfunc)
15 : window.set_border_width(10)
16 :
17 : button = GtkButton("Hello World")
18 : button.connect("clicked", hello)
19 : window.add(button)
20 : button.show()
21 : window.show()
22 :
23 : mainloop()

 À la ligne 3, on importe le module Gtk+ qui va permettre d'utiliser des primitives graphiques. On défini ensuite (lignes 5 à 11) deux fonctions de callback qui seront utilisés quelques lignes plus loin.
La mise en place des éléments de l'interface commence réellement à la ligne 13 : On commence par créer une fenêtre, que l'on déclare de type 'WINDOW_TOPLEVEL', c'est-à-dire qu'elle sera prise en charge directement par le gestionnaire de fenêtres. Ensuite, on connecte l'événement 'destroy' de la fenêtre 'window' à la fonction destroyfunc(). Cette fonction sera appelée dans le cas où l'on ferme directement la fenêtre à partir du gestionnaire de fenêtres (typiquement en cliquant sur la croix en haut à droite de la fenêtre).
Ensuite, on utilise la méthode set_border_width() pour indiquer que l'on désire que cette fenêtre ait une bordure de 10 pixels de large sur ses quatre bords. Ce n'est absolument pas indispensable, mais ça fait beaucoup plus joli ;-)
L'unique fenêtre de notre application étant définie, passons maintenant à son contenu. Nous allons pour le moment nous limiter à afficher un seul bouton qui permettra simplement de quitter l'application. Celui-ci est créé à la ligne 17. La ligne suivante utilise le même mécanisme que la ligne 14 pour lui assigner un callback : lorsque l'on cliquera sur ce bouton, la fonction 'hello' sera appelée. Celle-ci se contente d'afficher un message sur la sortie standard, puis envoie elle-même l'événement destroy à la fenêtre. Maintenant, il ne reste plus qu'à indiquer que l'on désire que le bouton soit placé à l'intérieur de notre belle fenêtre (ligne 19), puis à demander successivement l'affichage de nos deux éléments (lignes 20 et 21). Deux choses importantes à noter à ce sujet :

- D'une part les widgets créés ne sont jamais affichés automatiquement. Il faut systématiquement leur appliquer la méthode show(), pour qu'ils apparaissent.
- D'autre part, et bien que le problème ne se pose pas sur les programmes aussi simple que celui que nous venons de voir, il est d'usage de demander en premier l'affichage des widgets les plus internes. En effet, il est visuellement plus agréable de voir apparaître d'un coup une fenêtre contenant un bouton plutôt que de voir apparaître une fenêtre vide dans laquelle viendra de placer un bouton une fraction de seconde plus tard.

Enfin, pour terminer notre application, nous devons exécuter la fonction mainloop. Celle-ci ne se termine jamais (a moins bien sur que le programme ne soit interrompu par un ctrl-C) et a pour rôle d'intercepter les événements en provenance du serveur X, et de les analyser pour pouvoir déclencher les callbacks appropriés.

Comme vous l'avez déjà constaté par vous même si vous avez bien suivi les articles précédents, l'interface Gtk+ fournie par Python est entièrement orientée objet. Cela apporte non seulement une grande convivialité, mais permet de développer une application graphique en très peu de temps. Il existe actuellement plusieurs "grosses" applications graphiques développées entièrement grâce au couple Python/Gtk+, par exemple le jeu de solitaire PySol ou le programme de dessin vectoriel Sketch.
Pour être tout à fait honnête, Python/Gtk+ comporte cependant un petit inconvénient : L'API est très mal documentée. Ce problème est largement atténué du fait que l'API reste extrêmement proche de l'API C (à l'orientation objet près), d'autre part le code source du module contient un exemple extrêmement complet (testgtk.py) qui utilise toutes les widgets Gtk+, auquel on peut se référer en cas de doute.

Maintenant, passons à une application GNOME. Par rapport à Gtk+, GNOME fourni un grand nombre de widgets, et en général de fonctionnalités qui simplifient encore plus la vie du programmeur.
 
 
hellognome.py
01 : #!/usr/bin/python
02 :
03 : from gtk import * 
04 : from gnome.ui import * 
05 :
06 : def about(widget): 
07 :   about = GnomeAbout('Test de GNOME-Python', 'v3.14159', 
08 :          'Copyright (c) 2000 LMag France', 
09 :         ['Vincent <vincent@debian.org>', 'Auteur 2', 'Auteur 3'], 
10 :         "Encore un programme d'exemple.") 
11 :   about.show() 
12 :
13 : def menus(): 
14 :    file_menu = [UIINFO_ITEM_STOCK('Nouveau',None,None,STOCK_MENU_NEW), 
15 :        UIINFO_SEPARATOR, 
16 :        UIINFO_ITEM_STOCK('Quitter',None,mainquit,STOCK_MENU_QUIT)] 
17 :    help_menu = [UIINFO_ITEM_STOCK('A Propos',None,about,STOCK_MENU_ABOUT)] 
18 :    menubar = [UIINFO_SUBTREE('Fichier',file_menu),
19 :        UIINFO_SUBTREE('Help',help_menu)] 
20 :   return menubar 
21 :
22 : def toolbar(): 
23 :    toolbar_info = [UIINFO_ITEM_STOCK('Nouveau', None, None, STOCK_PIXMAP_NEW), 
24 :        UIINFO_SEPARATOR, 
25 :        UIINFO_ITEM_STOCK('Ouvrir', None, None, STOCK_PIXMAP_OPEN), 
26 :        UIINFO_ITEM_STOCK('Enregistrer', None, None, STOCK_PIXMAP_SAVE)] 
27 :    return toolbar_info 
28 :
29 : def main(): 
30 :    window = GnomeApp('hellognome.py', 'Test de GNOME-Python') 
31 :    window.create_menus(menus()) 
32 :    window.create_toolbar(toolbar())
33 :    text = GtkText(None, None) 
34 :    text.set_editable(TRUE) 
35 :    window.set_contents(text) 
36 :    window.connect('destroy', mainquit) 
37 :    window.show() 
38 :    mainloop() 
39 :
40 : if __name__ == '__main__':
41 :    main()

Cette application hellognome.py est à peu de choses près l'application GNOME la plus simple possible en tenant compte du fait qu'elle comporte quand même les éléments classiques que l'on retrouve dans toutes les applications, comme une barre de menu, une boîte d'informations sur le programme 'A propos', la partie principale de la fenêtre de l'application comportant une widget GtkText. À ce titre, c'est un bon point de départ pour construire des applications plus complexes.
Examinons le code source de plus près :
Pour commencer, en plus d'importer l'interface de la bibliothèque Gtk+, on importe également l'interface des widgets graphiques définies par GNOME (gnome.ui).
Des lignes 6 à 11, on défini la fonction about(), qui affiche la fenêtre 'A propos...' lorsque l'entrée correspondante du menu principal est sélectionnée. Cette fonction prend comme paramétres une liste de chaînes : le nom du logiciel, sa version, son copyright, la liste de ses auteurs et une présentation sommaire du logiciel.
Viennent ensuite les définitions des menus et de la barre d'outils. Celles-ci reprennent d'assez près la syntaxe utilisée avec l'interface GNOME pour le langage C.
Enfin, la fonction main() est définie et c'est elle qui va s'occuper de mettre en place tous les éléments de l'interface : Tout d'abord, on crée une nouvelle application GNOME grâce à la fonction GnomeApp(). Ensuite, on appelle successivement les méthodes create_menus() et create_toolbar() de l'application pour la création effective des menus et de la barre d'outils. Les lignes 33 à 35 quant à elles créent la widget GtkText qui est utilisée dans la partie principale de la fenêtre de l'application. Maintenant, il ne reste plus qu'à rendre les éléments graphiques visibles et lancer la boucle principale de gestion des événements.

Terminons maintenant avec un exemple plus élaboré qui vous permettra d'épater vos ami(e)s ;-)
Il consiste à créer une applet pour le tableau de bord GNOME. Contrairement à ce que l'on pourrait croire, créer des applets n'est pas plus compliqué de de programmer une application GNOME standard. En effet, bien que les applets et le tableau de bord communiquent entre eux via le protocole CORBA, tout ce mécanisme de communication inter-processus est transparent pour le programmeur (sauf bien sûr dans l'éventualité où celui-ci désire réellement "mettres les mains dans le cambouis", mais c'est rarement le cas).
 
 
horloge.py
01 : #!/usr/bin/python
02 :
03 : from gnome.applet import *
04 : from gtk import *
05 : from time import *
06 :
07 : def update_label():
08 :        str = strftime("%a %d %b\n%T", localtime(time()))
09 :        label.set_text(str)
10 :        return TRUE
11 :
12 : app = AppletWidget("horloge-python")
13 :
14 : frame = GtkFrame()
15 : label = GtkLabel()
16 : label.set_padding(4,4)
17 : update_label()
18 :
19 : frame.add(label)
20 : label.show()
21 :
22 : frame.show()
23 : app.add(frame)
24 : app.set_tooltip("Très belle horloge en Python")
25 : app.show()
26 :
27 : timeout_add(1000, update_label)
28 :
29 : mainloop()

Ceci étant dit, regardons le code source de plus près :
- lignes 1 à 5 : rien de spécial, si ce n'est que l'on importe également le module 'gnome.applet' qui est indispensable pour les programmes de type applet.
- lignes 7 à 10 : définition de la fonction update_label() qui a pour rôle de remettre à jour toutes les secondes le label qui indique la date et l'heure.
- ligne 12 : c'est là que commencent les choses sérieuses : la fonction AppletWidget crée l'applet elle-même.
- lignes 14 à 25 : définition et configuration des widgets qui composent l'application. Celle-ci se compose simplement d'une frame contenant un label texte. Notez au passage la ligne 24 qui permet de déclarer la chaîne de caractères à utiliser dans la bulle d'aide correspondant à l'applet.
- ligne 27 : on arme un timer qui aura comme rôle d'appeler à intervalles réguliers la fonction update_label(), pour tenir l'affichage à jour. L'intervalle entre deux appels est donné en millièmes de secondes.
- ligne 29 : enfin, pour pour tous les programmes Gtk, on appelle la fonction mainloop().

Mine de rien, le couple Python/GNOME permet de créer une application graphique simple, bien qu'utile et fonctionnelle en moins de 30 lignes de code! Qui dit mieux?

Voila, grâce à ce cinquième article, vous devriez maintenant être capable d'impressioner vos ami(e)s en créant des applications graphiques complexes en quelques heures ;-)
 
 
News du monde Python
Python 1.6b1 :
    Après les versions alpha, voici la première bêta de Python 1.6. Parmi les nouveauté, on note :
        - Le support de l'Unicode a été terminé.
        - Le module de gestion des expressions régulières a été entièrement réécrit.
        - Beaucoup de bugs ont été corrigés.

 
Ressources
Site web : 
    http://www.python.org/
Listes de diffusion Python : 
 - En Français : 
        http://www.aful.org/mailman/listinfo/python
 - En Anglais : 
    http://www.python.org/psa/MailingLists.html
 - Listes des développeurs (pour ceux d'entre vous qui sont intéressés par des discussions d'assez haut niveau technique; attention, ce n'est pas une liste de diffusion destinée au support) 
    http://www.python.org/sigs/
Groupes de discussion : 
    comp.lang.python
    comp.lang.python.announce
GNOME :
    http://www.gnome.org/
GNOME-Python :
    ftp://ftp.daa.com.au/pub/james/python/
Python-Gtk :
    ftp://ftp.gtk.org/pub/gtk/python/
Gtk+
    http://www.gtk.org/
Tutorial Gtk+ :
    http://www.gtk.org/tutorial/
    (Une version en Français de ce tutorial, traduite par Éric Jacoboni, est disponible dans les sources de Gtk+)
PySol :
    http://wildsau.idv.uni-linz.ac.at/mfx/pysol/
Sketch :
    http://sketch.sourceforge.net/
Documentation Python en Français (traduite par Olivier Berger, Bruno Liénard et Daniel Calvelo Aros) : 
        http://perso.club-internet.fr/olberger/python/python.html
Livres: 
"Learning python" par Mark Lutz & David Ascher, éditions O'Reilly
"Programming python" par Mark Lutz, éditions O'Reilly
Utilisateur de GNU/Linux depuis 1993, Vincent Renardias a commencé a s'impliquer activement dans le développement à partir de 1996 : Développeur de la distribution Debian, auteur de la traduction Française de The GIMP et de l'environnement GNOME, créateur du groupe d'utilisateurs Linux de Marseille (PLUG), ... Actuellement responsable technique de la société Echo, il continue à contribuer activement au système GNU/Linux. 
Vincent Renardias <vincent@echo.fr>