Ce mois-ci, nous terminons notre tour d'horizon des principes de base de la programmation sous Gnome. Organisation de projets, internationalisation, configuration et gestion de sessions, voici la liste des sujets qui clôtureront cette première partie de la programmation sous Gnome.
Voici le dernier article générique de notre dossier sur la programmation sous Gnome. Par la suite, nous aborderons des sujets un peu plus pointus tels que la programmation CORBA, l'utilisation de XML, la spécification DOM, Bonobo, l'impression ou encore l'écriture d'items de canevas spécialisés.
Pour l'instant, nous allons voir comment organiser efficacement le code de vos projets. Nous verrons ensuite comment internationaliser vos programmes. C'est là un sujet des plus importants : une bonne application doit toujours être multilingue. Nous apprendrons enfin comment gérer la configuration de vos applications et comment implémenter la gestion de sessions.
Automake - Automake
Nous avons maintenant vu l'essentiel des bases de la programmation sous Gnome, suffisamment en tout cas pour que vous puissiez commencer à écrire vos propres applications. Il est donc temps que nous voyions comment utiliser Automake. Cela pourra vous paraître un peu compliqué au départ mais très rapidement, vous le constaterez, écrire une application évoluée sans utiliser Automake vous semblera impensable. Il ne s'agit cependant pas d'un didacticiel complet sur Automake, mais plutôt d'une présentation minimale, suffisante malgré tout, pour écrire une application Gnome complète.
Automake est un ensemble de scripts de gestion de projets. Ils vous permettent d'écrire très facilement les fichiers de configuration de vos applications ainsi que les Makefiles de votre arborescence.
L'organisation standard d'une arborescence Gnome est la suivante :
- La racine contient les fichiers de configuration (configure.in, acconfig.h), le Makefile.am principal et le script autogen.sh.
- Le répertoire po contient les fichiers de traduction (internationalisation) dont nous verrons l'utilisation par la suite.
- Le répertoire macros contient des macros aclocal que l'on copie généralement directement de l'arborescence d'un autre projet Gnome. (La dernière version est cependant toujours disponible sur le serveur CVS de Gnome dans le module macros).
- Le répertoire src contient généralement les sources de l'application et le Makefile.am indique les sources à compiler.
Le plus simple est, comme toujours, de partir d'une arborescence déjà construite par un autre et de l'adapter à votre application. Une arborescence complète est disponible sur le site Web contenant les exemples de ce dossier.
Nous allons décrire les fichiers principaux de l'arborescence. Nous étudierons le répertoire po dans la section suivante.
Makefile.am :
# Sous-répertoires dans lesquels exécuter make
SUBDIRS = po macros intl src
Nous avons juste besoin de décrire dans quels répertoires exécuter make. Peu de projets nécessitent de rajouter autre chose à ce fichier.
configure.in :
# Initialiser autoconf. Pour que le script puisse vérifier que
# le fichier configure.in se trouve au bon endroit, il faut lui
# indiquer le nom d'un fichier qui se trouve dans le répertoire du
# projet. Ici nous indiquons le fichier "README.squelette" qu'il ne
# faut donc pas oublier de créer à la racine du projet.
AC_INIT(README.squelette)
# Créer le header de configuration.
AM_CONFIG_HEADER(config.h)
# Initialiser Automake. Le projet s'appelle "Squelette", il en
# est à la version 0.1.
AM_INIT_AUTOMAKE(squelette, 0.1)
# Initialiser libtool
AM_PROG_LIBTOOL
# Autoriser le mode "Maintainer" qui gère des règles de # dépendance
# facilitant la maintenance de l'arborescence d'un projet.
AM_MAINTAINER_MODE
# Permet de déterminer sur quel système va s'effectuer la # compilation.
AC_CANONICAL_HOST
# Inclut les macros présentes dans le répertoire "macros".
AM_ACLOCAL_INCLUDE(macros)
# Vérifie que les librairies Gnome sont bien disponibles sur le
# système et initialise de nombreuses variables utilisées dans
# "src/Makefile.am".
GNOME_INIT
# Vérifications et options "classiques" :
# Quel est le compilateur
# Sur les systèmes Posix, définir les options correctes
# Détecter si le système possède des headers au format C ansi
# Autoriser le changement de nom de programme via configure
# Vérifier que Libtool est présent
AC_PROG_CC
AC_ISC_POSIX
AC_HEADER_STDC
AC_ARG_PROGRAM
AM_PROG_LIBTOOL
# Ceci doit toujours se trouver après AC_PROG_CC
# Avertissements maximum à la compilation
GNOME_COMPILE_WARNINGS
# Vérifier que diverses librairies liées à X Window sont présentes
# (xpm et gestion de session notamment)
GNOME_X_CHECKS
# Définir les langues en lesquelles le projet est traduit. Dans notre
# cas, il n'y a que le français. En général, vous rajoutez des # langues
# au fur et à mesure, lorsque les traducteurs de tous pays se
# mettent spontanément au travail.
ALL_LINGUAS="fr"
# Valider le support multilingue Gnome
AM_GNOME_GETTEXT
# Opération obligatoire, définir les trois variables suivantes, très
# utilisées dans "src/Makefile.am (mais parfois de façon implicite)"
AC_SUBST(CFLAGS) AC_SUBST(CPPFLAGS) AC_SUBST(LDFLAGS)
# Créer les fichiers suivants.
AC_OUTPUT([
Makefile
macros/Makefile
src/Makefile
intl/Makefile
po/Makefile.in
])
Les commentaires expliquent l'utilité de chacune des macros utilisées. Ne pas oublier de créer le fichier README.squelette ou la configuration ne s'exécutera pas complètement.
autogen.sh:
#!/bin/sh
# Exécuter ce fichier pour lancer la configuration
srcdir=`dirname $0`
test -z "$srcdir" && srcdir=.
PKG_NAME="squelette"
.$srcdir/macros/autogen.sh
Ce script est le seul que vous devrez exécuter. N'oubliez pas de lui inclure les droits d'exécution :
chmod 755 autogen.sh
src/Makefile.am:
# fichier à inclure et flags à passer au
# compilateur.
INCLUDES = \
-DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
$(GNOME_INCLUDEDIR)
-I$(includedir)
# Nom de l'exécutable
bin_PROGRAMS = LFM15
# Libraires à utiliser.
LDADD = \
$(GNOME_LIBDIR) \
$(GNOMEUI_LIBS) \
$(INTLLIBS)
# Sources à utiliser pour construire l'exécutable LFM15
LFM15_SOURCES = \
LFM15.c
Les variables INCLUDES et LDADD sont pratiquement toujours les mêmes. A moins que vous n'ayez besoin d'utiliser des librairies autre que glib, gtk et gnome-libs, vous n'aurez pas à les modifier. Si d'aventure vous deviez les personnaliser, référez-vous aux sources de Gnumeric qui contiennent de nombreux exemples.
Avec bin_PROGRAMS, vous précisez quels sont les noms des exécutables que vous désirez construire. Vous pourrez spécifier des sources différentes avec les variables pour chaque exécutable.
nom_exécutable_SOURCES
Pour un usage classique, ces fichiers sont les seuls que vous devrez modifier. D'autres fichiers sont nécessaires aux fonctionnement d'automake (par exemple acconfig.h). C'est pourquoi, afin de ne pas perdre de temps, vous devriez toujours partir d'une arborescence existante et la modifier selon vos besoins. L'arborescence créée pour ce dossier est un bon point de départ.
Pour configurer votre projet, vous n'aurez qu'à taper
./autogen.sh
Ensuite, la commande make compilera votre projet comme n'importe quel autre projet Gnome. Référez-vous à l'article du mois dernier pour plus de détails sur la compilation et l'installation d'un projet Gnome.
La commande make accepte des options dont celle que l'on appelle la cible, indiquant l'action à effectuer :
make all (ou make) : compilation et édition de liens
make install : installation du projet (à faire suivre en général d'un ldconfig)
make clean : effacer les fichiers résultant de la compilation (fichiers objets et exécutables)
make distclean : comme clean, mais supprime aussi les fichiers générés par la configuration. Vous devrez relancer autogen.sh
make dist : Crée une distribution du projet sous forme .tar.gz prête à être distribuée sous la même forme que n'importe quel projet GNU.
make distcheck : comme dist, mais lance la compilation de la distribution pour voir si elle s'effectue correctement.
Internationalisation
Nous allons créer un petit fichier LFM15.c à ajouter dans le répertoire src. Ce fichier servira simplement à apprendre comment utiliser l'internationalisation sous Gnome.
LFM15.c:
#include "config.h"
#include <gnome.h>
int
main (int argc, char *argv[])
{
/* vous trouverez ces deux instructions dans tous les */
/* projets Gnome internationalisés, elles indiquent */
/* où trouver les fichiers de traduction */
bindtextdomain(PACKAGE, GNOMELOCALEDIR);
textdomain(PACKAGE);
/* Entourer les chaînes qui devront être traduites */
/* de _() */
printf (_("Hi!\n"));
printf (_("This is an internationalization test!\n"));
printf (_("This is a third sentence!\n"));
return 1;
}
Les macros PACKAGE et GNOMELOCALEDIR sont définies dans le fichier config.h, généré par automake. La ligne
#include "config.h"
est donc fondamentale. Essayez toujours de l'inclure en premier dans vos sources.
L'usage de la macro _() est très classique. Relisez l'article d'Eric Jacobini dans Linux Mag numéro 5 pour plus de détails sur son utilisation.
Maintenant, voici comment écrire les traductions. Tout se passe dans le répertoire po.
Vous devez écrire un fichier POTFILES.in, dans lequel vous indiquez quels sont les fichiers sources que vous désirez voir traduits. Dans notre cas :
po/POTFILES.in:
src/LFM15.c
Ensuite, vous devez créer un fichier fr.po qui est destiné à recevoir les traductions :
touch po/fr.po
Relancez ensuite make (et éventuellement autogen.sh) pour être sûr que les modifications ont été prises en compte.
Pour l'instant, le fichier fr.po ne contient rien. Dans le répertoire po, exécutez :
make update-po
Editez le fichier fr.po. Il doit maintenant contenir des lignes du genre :
#: src/LFM15.c:12
msgid "Hi!\n"
msgstr ""
La ligne msgid représente la chaîne originale et la ligne msgstr la chaîne traduite. Remplacez "" par "Salut\n" puis exécutez dans le répertoire racine :
make install
Choisissez ensuite la langue française comme langue d'affichage :
export LC_ALL="fr_FR"
Et le tour est joué.
La gestion de configuration
La gestion de configuration est le procédé qui permet de sauvegarder les options de configuration d'une application. L'utilisateur retrouve à chaque démarrage de l'application les options telles qu'il les avait choisies à la session précédente.
Les options (à moins que vous ne le spécifiiez autrement) sont sauvegardées dans un fichier situé dans le répertoire :
$(HOME)/.gnome/
Chaque application possède normalement un fichier de configuration séparé.
Voici un exemple très simple illustrant les possibilités de la gestion de configuration sous Gnome.
#include "config.h"
#include <gnome.h>
int
main (int argc, char *argv[])
{
gchar *last_entered_string;
gchar new_entered_string[1024];
gnome_init ("LFM16", "1.0", argc, argv);
/* nom du fichier de configuration */
gnome_config_push_prefix ("LFM16/");
/* récuperer la dernière chaîne entrée */
last_entered_string = gnome_config_get_string
("test_section/name");
if (last_entered_string) {
printf ("Dernière chaîne entrée = %s\n",last_ent-
ered_string);
g_free (last_entered_string);
}
scanf ("%s", new_entered_string);
printf ("Nouvelle chaine: %s\n", new_entered_string);
gnome_config_set_string ("test_section/name",
new_entered_string);
gnome_config_pop_prefix ();
/* sauvegarder les modifications dans le fichier de
configuration */
gnome_config_sync ();
return 1;
}
La commande
gnome_config_get_string (chemin)
signifie : aller chercher la valeur de la clef spécifiée par le chemin. Une clef se présente sous la forme :
"fichier/section/nom_de_clef"
fichier est le nom du fichier sous $(HOME)/.gnome/.
section est le nom de la section dans laquelle se situe la clef.
nom_de_clef indique (brillamment) le nom de la clef.
fichier correspond généralement au nom du programme. Les sections servent à regrouper les clefs par thèmes. En effet, les fichiers de configuration sont des fichiers texte lisibles par l'utilisateur qui doivent donc rester compréhensibles. Veillez alors, dans vos programmes, a être le plus explicite possible dans vos choix de chemins.
Les fonctions
gnome_config_push_prefix ()
gnome_config_pop_prefix ()
permettent d'éviter d'avoir à taper la totalité du chemin dans chaque instruction gnome_config_get ou gnome_config_set.
Dans l'exemple précédent, on aurait pu taper :
gnome_config_push_prefix ("LFM16/test_section/");
puis
last_entered_string = gnome_config_get_string ("name");
La commande gnome_config_sync sauve les modifications dans le fichier de configuration. Si vous ne l'appelez pas, les données ne seront pas sauvegardées.
Pour sauvegarder une valeur entière, vous utiliserez la routine
gnome_config_set_int().
Pour connaître la liste des commandes gnome_config, visitez la page
http://developer.gnome.org/doc/API/libgnome/gnome-gnome-config.html
Gestion de sessions
Nous allons maintenant voir comment ajouter la gestion de sessions à vos applications. Nous avons vu le mois dernier comment le faire avec des applets. Le principe pour des applications génériques est à peu près le même. Au moment où l'utilisateur quitte la session, l'application reçoit un signal lui indiquant de sauver son état actuel. Cela se fait via la gestion de configuration, mais avec un peu d'aide de la part du gestionnaire de sessions de Gnome. Gnome implémente le protocole de gestion de sessions de X window, qui est utilisé par CDE, et que KDE prévoit d'utiliser d'ici peu. Gnome utilise par ailleurs un système de runlevels assurant le démarrage des applications dans un ordre donné. Cette dernière extension ne change rien en terme de programmation.
Ceci étant dit, voici un exemple très simple :
#include "config.h"
#include <gnome.h>
static GtkWidget *entry;
static gint
load_session ()
{
gchar *file;
gchar *entry_text;
/* récuperer l'unique prefixw associé à l'application par le */
/* gestionnaire de session. */
file = gnome_client_get_config_prefix (gnome_master_client());
gnome_config_push_prefix (file);
entry_text = gnome_config_get_string ("test_session/entry_text");
gnome_config_pop_prefix ();
if (entry_text)
gtk_entry_set_text (GTK_ENTRY (entry), entry_text);
return TRUE;
}
/* sauver les informations permettant de redémarrer l'application */
/* en l'état à la prochaine session. */
static gint
save_session (GnomeClient *client, gint phase,
GnomeSaveStyle save_style,
gint is_shutdown, GnomeInteractStyle interact_style,
gint is_fast, gpointer client_data)
{
gchar *file;
gchar *entry_text;
gchar* args[4] = { "rm", "-r", NULL, NULL };
entry_text = gtk_entry_get_text (GTK_ENTRY (entry));
if (entry_text) {
file = gnome_client_get_config_prefix (gnome_master_client());
gnome_config_push_prefix (file);
gnome_config_set_string ("test_session/entry_text", entry_text);
gnome_config_pop_prefix ();
gnome_config_sync ();
/* comment supprimer la configuration */
args[2] = gnome_config_get_real_path (gnome_client_get_config_prefix (client));
gnome_client_set_discard_command (client, 3, args);
}
return TRUE;
}
/* au moment où le gestionnaire de session veut fermer l'application */
/* ce callback est appelé */
static gint
session_die (GnomeClient* client, gpointer client_data)
{
gtk_main_quit ();
return TRUE;
}
/* Callback appele lorsque l'application recoit */
/* un signal indiquant qu'elle va etre fermee. */
static void
quit_cb (GtkWidget *widget, void *data)
{
gtk_main_quit ();
return;
}
int
main(int argc, char *argv[])
{
GtkWidget *application;
GnomeClient *client;
bindtextdomain(PACKAGE, GNOMELOCALEDIR);
textdomain(PACKAGE);
/* initialiser la librairie Gnome */
gnome_init ("LFM17", "1.0", argc, argv);
/* déterminer l'objet "client"
client = gnome_master_client ();
/* avant d'être fermee l'application a une chance de sauvegarder */
/* son état actuel. */
gtk_signal_connect (GTK_OBJECT (client), "save_yourself",
GTK_SIGNAL_FUNC (save_session), argv[0]);
gtk_signal_connect (GTK_OBJECT (client), "die",
GTK_SIGNAL_FUNC (session_die), NULL);
/* creer l'objet Gnome de Haut niveau */
application = gnome_app_new \
("Linux France Magazine 9", "Gestion de session")
gtk_window_set_default_size ( GTK_WINDOW (application), 300, 200);
/* lorsque la fenetre recoit l'ordre de se fermer, appeler
le callback "quit_cb" */
gtk_signal_connect (GTK_OBJECT (application), "delete_event",
GTK_SIGNAL_FUNC (quit_cb),
NULL);
/* creer l'objet graphique principal */
entry = gtk_entry_new ();
/* ajouter le bouton a la fenetre principale de l'application */
gnome_app_set_contents (GNOME_APP (application), entry);
/* dire a tout le monde de se montrer */
gtk_widget_show (entry);
gtk_widget_show (application);
load_session ();
gtk_main ();
return 0;
}
La majorité du code mis en oeuvre est en fait du code de sauvegarde de configuration. La commande magique, ici, se situe dans la ligne
file = gnome_client_get_config_prefix (gnome_master_client());
Cette ligne assure l'unicité du chemin de configuration que vous utiliserez. Le gestionnaire de sessions vous fournira de nouveau ce chemin lorsqu'il demandera à votre application de redémarrer.
Enfin, il faut indiquer au gestionnaire de session comment libérer (les éventuelles) ressources utilisées pour stocker la configuration une fois que l'application aura démarré. Dans notre cas, puisque nous utilisons un fichier de configuration, il s'agira de l'effacer, purement et simplement. C'est le rôle de la ligne
gnome_client_set_discard_command (client, 3, args);
où nous avons indiqué (dans args) comment effacer le fichier de configuration.
Vous voilà maintenant muni des outils de base pour implémenter la gestion de sessions dans vos propres applications. Une remarque cependant : ne sauvegardez pas la géométrie de vos fenêtres, c'est là le rôle des gestionnaires de fenêtres.
Bertrand.Guiheneuf@aful.org