Glade3 et Gtk+

Dumas Gérald

Ce chapitre aborde la construction d'une interface grâce à Glade3 et son utilisation dans un programme Gtk+ en C. ♪

- Titre : Glade3 et Gtk+

- Auteur : Dumas Gérald

- Parution : 1 février 2012

- Mise à jour : 22 février 2012

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2012 gerald3d. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- I-A. Glade, c'est quoi ?

- I-B. Intérêt

- I-C. De quoi ai-je besoin ?

- II. Ma première fenêtre

- II-A. Création d'une fenêtre principale

- II-B. Ajout de Widgets

- III. Le code source en C

- III-A. Le squelette

- III-B. Un peu d'explication

- IV. Gérer les CallBacks

- IV-A. Comment affecter un Callback à un signal ?

- IV-B. Activer les Callbacks dans le code source

- IV-C. Exemple d'utilisation de la donnée utilisateur

- IV-D. Plusieurs fenêtres

- V. Code source complet et Makefile

- VI. Remerciements

I - Introduction

I-A - Glade, c'est quoi ?

Glade est une application qui permet la construction d'une interface graphique à la souris sans écrire aucune ligne de code. On peut faire une analogie avec un traitement de texte WYSIWYG tel que LibreOffice ou Word. Vous avez une représentation visuelle immédiate du résultat au contraire d'un texte écrit en LaTex (ou avec Lyx). Le résultat pour ce dernier ne pourra être vu qu'après le traitement du fichier source par diverses procédures d'analyses et de conversions.
Glade sauve l'interface construite dans un fichier texte au format XML. Il peut donc être visualisé et même modifié à la volée.

I-B - Intérêt

Si vous devez créer une interface simple, avec peu de widgets, la question peut se poser de passer par Glade. Mais si votre projet prend de l'importance, son utilisation se justifie pleinement. Imaginez un instant que vous deviez concevoir l'interface du prochain logiciel de PAO qui va tout révolutionner. Le code source, rien que pour l'interface, va vous prendre des milliers de lignes de code. Avec Glade, le temps passé à écrire ces lignes de code sera utilisé à la conception.
Autre possibilité, et non des moindres, est la modification de votre interface en cours de projet sans changer la moindre ligne de code. Alors, toujours indécis ?

I-C - De quoi ai-je besoin ?

De Glade3 bien entendu. Si vous êtes sous Linux l'installation est simple. Utilisez votre système de paquetage préféré pour son installation (yum, apt...). Sinon un petit tour sur le site officiel vous permettra de télécharger la version adéquate.

II - Ma première fenêtre

II-A - Création d'une fenêtre principale

Lançons Glade. Une belle fenêtre s'ouvre. Pour l'instant il nous propose une feuille blanche dans un onglet "1 non enregistré".
Toute application graphique dispose d'une fenêtre principale. Commençons par créer cette fenêtre. Cliquez sur l'icône "Fenêtre". Une fenêtre apparaît au centre de Glade.

Nous disposons à présent d'un widget de type GtkWindow* (oui il s'agit bien de cela). La liste d'onglets en bas à droite s'est remplie. C'est ici que l'on va pouvoir accéder aux différentes propriétés du widget sélectionné au centre de l'interface.

Pour notre fenêtre on a déjà la possibilité de modifier son nom. Par défaut Glade nomme les widgets par leur type avec un numéro d'ordre. Dans notre exemple c'est la première fenêtre de type window. Son nom est donc par défaut "Window1". Changeons-le. La fenêtre s'appellera "MainWindow" (choix arbitraire).
Le nom de chaque widget est important. C'est par lui que nous pourrons y accéder depuis notre programme C. Alors pensez bien à nommer ceux auxquels vous voudrez avoir accès.

La casse est importante. "MainWindow" n'est pas "mainwindow".

II-B - Ajout de Widgets

Maintenant que vous avez compris le principe on va un peut agrémenter notre fenêtre. Ajoutons un GtkBox en cliquant sur "Boite" puis sur la partie grisée de MainWindow. Une fenêtre de dialogue (en bas de l'image ci-contre) nous demande combien de parties doit contenir notre boite. Laissons 3 par défaut et validons. On n'aura aucun besoin d'accéder directement à cette boite dans notre programme. Par conséquent nous laisserons son nom tel quel. Dans la partie haute de la boite plaçons une "Barre de menus" et dans la partie basse une "Barre d'état". Pour la partie du milieu on va d'abord placer une "Fenêtre de défilement" puis à l'intérieur de celle-ci une "Vue texte".
Renommons les widgets dont nous aurons besoin dans notre application :
- la "Barre d'état" :"StatusBar" ;
- la "Vue texte" : "TextView".

Ceci fait il faut modifier quelques propriétés de certains widgets. La "Vue texte" doit prendre toute la place possible dans la fenêtre. Ce n'est pas le cas pour le moment. La "Fenêtre de défilement" contrôle son comportement. Sélectionnons-la puis l'onglet "Regroupement". Le contrôle "Étendre" est pour l'instant à "Non". Un simple clic sur "Non" le bascule à "Oui".
Pour la "Barre d'état" il faut s'assurer dans le même onglet que les paramètres "Remplir" et "Étendre" sont à "Non". Si vous avez réussi à faire toutes ces manipulations bravo, vous avez compris l'utilisation générale de Glade.

Les copies d'écran correspondent à Glade v3.10.2. Si vous travaillez avec Glade2 certaines icônes telles que la boite ne sont pas sous cette forme. Vous pouvez avoir deux icônes ; une pour la boite horizontale et l'autre pour la verticale. Pour notre exemple choisissez la boite verticale.

Il est maintenant temps de sauver notre première interface. Donnons-lui le nom de "test.glade" (arbitraire). L'extension n'a aucune importance.

III - Le code source en C

III-A - Le squelette

Le premier code présenté permet :

- de charger le fichier « test.glade » précédemment traité,

- d'affecter la fonction gtk_main_quit(); à la croix de la fenêtre,

- d'afficher le tout.

Il se veut le plus court possible.

1. #include <gtk/gtk.h>

3. int

4. main(int argc, char *argv [])

5. {

6. GtkWidget *fenetre_principale = NULL;

7. GtkBuilder *builder = NULL;

8. GError *error = NULL;

9. gchar *filename = NULL;

10. /* Initialisation de la librairie Gtk. */

11. gtk_init(&argc, &argv);

13. /* Ouverture du fichier Glade de la fenêtre principale */

14. builder = gtk_builder_new();

16. /* Création du chemin complet pour accéder au fichier test.glade. */

17. /* g_build_filename(); construit le chemin complet en fonction du système */

18. /* d'exploitation. ( / pour Linux et \ pour Windows) */

19. filename =  g_build_filename ("test.glade", NULL);

21. /* Chargement du fichier test.glade. */

22. gtk_builder_add_from_file (builder, filename, &error);

23. g_free (filename);

24. if (error)

25. {

26. gint code = error->code;

27. g_printerr("%s\n", error->message);

28. g_error_free (error);

29. return code;

30. }

32. /* Récupération du pointeur de la fenêtre principale */

33. fenetre_principale = GTK_WIDGET(gtk_builder_get_object (builder, "MainWindow"));

35. /* Affectation du signal "destroy" à la fonction gtk_main_quit(); pour la */

36. /* fermeture de la fenêtre. */

37. g_signal_connect (G_OBJECT (fenetre_principale), "destroy", (GCallback)gtk_main_quit, NULL);

39. /* Affichage de la fenêtre principale. */

40. gtk_widget_show_all (fenetre_principale);

42. gtk_main();

44. return 0;

45. }

la fonction gtk_main_quit(); attachée au signal "destroy" ne respecte pas le prototype associé. Cependant cet exemple reste fonctionnel.

Le respect des prototypes doit être la règle lors de vos développements.

III-B - Un peu d'explication

L'utilisation des fichiers générés par Glade avec Gtk+ passe par la manipulation des GtkBuilder. Pour que les choses soient bien claires, je ne détaillerai pas dans ce tutoriel toutes les fonctions associées à ce type de pointeur. Je me contenterai des essentielles. Pour la liste complète n'hésitez pas à aller faire un tour sur la documentation officielle.

La première chose à faire est de déclarer un pointeur de type GtkBuilder (ligne 7). Ce pointeur doit être initialisé avec la fonction gtk_builder_new(); (ligne 14). La documentation officielle ne nous spécifie rien de particulier quant à la réussite ou non de cette initialisation. Aucune vérification n'est donc à effectuer.
La phase suivante est la plus cruciale à mes yeux. Le fichier XML créé par Glade va être chargé et analysé pour configurer convenablement notre pointeur fraîchement initialisé. La fonction gtk_builder_add_from_file(); (ligne 22) est là pour ça. Elle prend trois arguments :

- le pointeur GtkBuilder initialisé,

- le chemin complet du fichier XML généré par Glade et

- un pointeur GError en cas de défaillance de l'opération.

Cette fonction retourne 0 si l'opération s'est correctement déroulée. Pour cet exemple je n'ai pas testé la valeur de retour mais plutôt la valeur du pointeur GError. C'est un choix personnel. Libre à vous de faire le contraire.

Lors du test de la valeur de retour de error j'ai poussé la rigueur de programmation au maximum. Si error est différent de NULL la fonction main(); quitte ce qui provoque bien entendu l'arrêt de l'application. Un pointeur GError doit être libéré s'il a été initialisé. Mais comme l'application s'arrête à quoi bon? (rigueur rigueur quand tu nous tiens...)
Ce petit code exemple n'est là que pour vous rappeler que tout pointeur initialisé dans le tas DOIT ÊTRE libéré.

Revenons à nos moutons. Le fichier "test.glade" est chargé. Le plus gros du travail est fait. Si si, je vous assure:). Il va maintenant être possible d'accéder à tous les pointeurs de tous les widgets contenus dans notre interface. Comment ? En utilisant tout simplement la fonction gtk_builder_get_object(); (ligne 33). Cette fonction prend deux arguments et elle renvoie un Gobject que l'on pourra transtyper dans le type désiré. Les deux arguments sont :

- le pointeur GtkBuilder qui contient l'interface,

- le nom du widget que l'on désire récupérer.

il existe une fonction qui a un nom très approchant : gtk_builder_get_objects();. Seul le 's' de fin les distingue. Cette fonction permet de récupérer tous les widgets de l'interface dans une GSList.

Une fois le pointeur de la fenêtre principale récupéré le signal "destroy" du widget de la fenêtre principale est affecté à la fonction gtk_main_quit(); pour quitter l'application lors du clic sur la croix de l'interface. Nous verrons au chapitre suivant comment utiliser Glade pour ne pas être obligé de faire cette déclaration dans notre code source.

Résumé : le code source est réduit à sa plus simple expression. Il devient plus facile à lire, on peut se consacrer pleinement au cœur du programme et l'interface est modifiable à la volée sans avoir besoin de recompiler.

La taille de la fenêtre n'a pas été définie. Lors de l'exécution de ce programme la fenêtre sera réduite à son minimum. La copie d'écran ci-dessus est le résultat de l'agrandissement via la souris.

Il est possible de définir sa taille dans Glade dans l'onglet "Commun". Les deux premiers arguments sont : "Largeur demandée" et "Hauteur demandée". À vous de les fixer si vous en ressentez le besoin.

IV - Gérer les CallBacks

IV-A - Comment affecter un Callback à un signal ?

En reprenant notre code exemple le signal "destroy" est attaché à la fonction gtk_main_quit(); qui va faire office de Callback.

g_signal_connect (G_OBJECT (fenetre_principale), "destroy", (GCallback)gtk_main_quit, NULL);

Cette technique est la technique d'affectation des signaux de nos chers widgets. Mais il est aussi possible d'utiliser Glade pour ça. Ouvrons à nouveau notre fichier "test.glade" dans Glade. Parmi les quatre onglets en bas à droite de l'interface il y a l'onglet "Signaux".
Sélectionnons le widget MainWindow puis l'onglet "Signaux" en bas à droite. Différentes branches sont à votre disposition. La branche dont les propriétés sont directement liées au widget est ouverte, les autres restant fermées.

Refermons la branche GtkWindow et ouvrons la branche GtkWidget. En se déplaçant dans cette branche on trouve le signal "destroy". Double-cliquons sur l'entrée texte dans la colonne "Gestionnaire" en face de ce signal et saisissons "gtk_main_quit". Nous venons d'affecter cette fonction au signal "destroy" du widget "MainWindow".

La colonne "Données utilisateur" permet d'indiquer quelle donnée vous désirez propager au Callback que vous venez d'associer. La dernière colonne, "Intervertir", permet d'intervertir les données utilisateurs et le pointeur du widget concerné dans la fonction Callback. Cela revient à utiliser la fonction g_signal_connect_swapped(); en lieu et place de g_signal_connect();.
Lorsqu'on saisit une fonction dans la colonne gestionnaire il faut appuyer sur la touche "entrée" pour valider cette saisie. Cette action effectuée une nouvelle ligne apparaît en dessous de celle-ci. Cette nouvelle ligne permet d'ajouter une fonction Callback pour le même signal. Si vous n'avez jamais fait ce genre de programmation il est temps de commencer ;). Sauvons notre modification et passons à l'étape suivante.

IV-B - Activer les Callbacks dans le code source

Tout d'abord il est nécessaire d'ajouter une option lors de l'assemblage de notre programme C.

`pkg-config --libs gmodule-2.0`

Votre Makefile ou votre IDE ainsi configuré il est maintenant possible de relier dans notre code source les Callbacks inscrits dans le fichier Glade.

Il existe deux fonctions qui permettent de relier les fonctions callback avec les déclarations faites dans le fichier glade.

1. gtk_builder_connect_signals();. Si vous pouvez compiler avec gmodule-2.0 ce sera la fonction à utiliser.

2. gtk_builder_connect_signals_full();. Si pour une raison ou une autre la compilation avec gmodule-2.0 est impossible, cette fonction est faite pour vous.

gtk_builder_connect_signals(); ne demande que deux arguments. Le pointeur GtkBuilder que nous avons déjà initialisé et une donnée utilisateur quelconque. Cette dernière donnée est très importante lorsqu'on utilise Glade avec la déclaration des Callbacks dans le fichier XML.
En effet, la fonction g_signal_connect(); (ou ses dérivées) n'est plus utilisée. Il ne va pas être possible d'affecter des données utilisateur différentes en fonction des signaux affectés. Cette contrainte force la structuration de vos données internes et en fin de compte on va se retrouver avec un programme plus lisible ;).

gtk_builder_connect_signals_full(); demande un argument de plus. Le pointeur GtkBuilderConnectFunc supplémentaire est une fonction Callback qui va être appelée pour toutes les déclarations de Callback qui ont été faites avec Glade. Dans cette fonction il serait possible d'affecter une donnée personnelle différente pour chaque signal affecté. Pour ma part je ne l'utilise jamais. Je préfère, et de loin, la version simplifiée. Je vous donne tout de même un exemple de code pour la mettre en œuvre :

#include <gtk/gtk.h>

/* Cette fonction est appelée pour tous les signaux dont la fonction callback */
/* a été remplie dans Glade. */
/* Elle permet de connecter explicitement chaque Callback. */
void
callback_connect_event (GtkBuilder *builder,  GObject *object,
const gchar *signal_name,
const gchar *handler_name,
GObject *connect_object,
GConnectFlags flags,
gpointer user_data)
{
switch (flags)
{
case G_CONNECT_AFTER:
g_signal_connect_after (object, signal_name, (GCallback)handler_name, user_data);
break;
case G_CONNECT_SWAPPED:
g_signal_connect_swapped (object, signal_name, (GCallback)handler_name, user_data);
break;
default:
g_signal_connect (object, signal_name, (GCallback)handler_name, user_data);
}
}

int
main (int argc, char *argv[])
{
...

/* Connexion des signaux avec leur callback respectif */
gtk_builder_connect_signals_full (builder, (GtkBuilderConnectFunc) callback_connect_event, NULL);

...
}

Considérons que nous pouvons compiler avec l'option `pkg-config --libs gmodule-2.0`.
La modification dans le code source est très simple. On ajoute "gtk_builder_connect_signals (builder, NULL); pour affecter les signaux déclarés dans le fichier Glade. On supprime la ligne 39 précédente qui affecte la fonction gtk_main_quit(); au signal "destroy" devenue inutile. Le code source devient :

#include <gtk/gtk.h>

int
main(int argc, char *argv [])
{
GtkWidget *fenetre_principale = NULL;
GtkBuilder *builder = NULL;
gchar *filename = NULL;
GError *error = NULL;
/* Initialisation de la librairie Gtk. */
gtk_init(&argc, &argv);

/* Ouverture du fichier Glade de la fenêtre principale */
builder = gtk_builder_new();

/* Création du chemin complet pour accéder au fichier test.glade. */
/* g_build_filename(); construit le chemin complet en fonction du système */
/* d'exploitation. ( / pour Linux et \ pour Windows) */
filename =  g_build_filename ("test.glade", NULL);

/* Chargement du fichier test.glade. */
gtk_builder_add_from_file (builder, filename, &error);
g_free (filename);
if (error)
{
gint code = error->code;
g_printerr("%s\n", error->message);
g_error_free (error);
return code;
}

/* Affectation des signaux de l'interface aux différents CallBacks. */
gtk_builder_connect_signals (builder, NULL);

/* Récupération du pointeur de la fenêtre principale */
fenetre_principale = GTK_WIDGET(gtk_builder_get_object (builder, "MainWindow"));

/* Affichage de la fenêtre principale. */
gtk_widget_show_all (fenetre_principale);

gtk_main();

return 0;
}

Ce code est finalement peu différent du précédent. Mais imaginez un peu que vous ayez à gérer une vingtaine de signaux. Le code va être beaucoup plus chargé. Alors qu'à présent, peu importe le nombre, rien ne change :).
J'ai volontairement mis la donnée utilisateur à NULL. Toutes les fonctions Callbacks recevront donc la valeur NULL sur le pointeur "gpointer user_data" pour celles qui en possèdent un. La section suivante est dédiée à l'utilisation de ce paramètre.

IV-C - Exemple d'utilisation de la donnée utilisateur

Nous allons voir une manière d'utiliser le dernier paramètre de la fonction gtk_builder_connect_signals();, c'est-à-dire la donnée utilisateur. Je tiens à préciser que cette façon de procéder n'est pas la seule, mais c'est la mienne :).
Pour rappel, un gpointer n'est rien d'autre qu'un pointeur non typé void. En partant de là on voit tout de suite que si l'on a plus d'une information à transmettre ça va se compliquer. Un des moyens de contourner le problème est d'utiliser un pointeur sur une structure.

Ce postulat posé, voilà une structure de base qui pourra être étoffée selon vos besoins.

typedef struct
{
GtkBuilder *builder;
gpointer user_data;
} SGlobalData;

Le pointeur builder est celui que nous avons initialisé auparavant. Il est parfois (voir souvent) nécessaire d'accéder à d'autres widgets que celui qui a émis le signal. Le propager est donc nécessaire.

Le pointeur user_data est là pour l'exemple. À vous d'ajouter le nombre de variables que vous voulez à la place en fonction des besoins de votre interface. Pour ma part, j'ai tendance à créer plusieurs sous-structures pour les différents organes de mon programme. Chaque variable déclarée du type de ces sous-structures est ajoutée à la structure principale que je propage. Ainsi il est possible de déclarer des variables locales à la fonction main(); sans être obligé de faire des allocations dynamiques. Lorsque la fonction main(); se termine les variables locales sont automatiquement libérées. Ainsi pas d'oubli de libération.

Pour être plus clair reprenons notre exemple et modifions-le pour intégrer cette fameuse structure. Si vous en êtes à lire ces lignes je suppose que vous avez compris comment utiliser Glade. Modifiez le fichier "test.glade". Ajoutez le nom "callback_about" au signal "activate" du menu "À propos". Bon, OK. Je vois que ça pose quelques petits problèmes à certains. Je vous explique rapidement comment faire :).

Faites un clic droit sur la barre de menus dans l'interface. Dans le menu qui apparaît sélectionnez "Éditer". Une fenêtre s'ouvre avec deux onglets. Choisissez l'onglet "Hiérarchie". Sur la gauche de la fenêtre il y a toutes les entrées principales du menu : "_Fichier", "É_dition", "_Affichage" et "Aid_e" (le "_" indique la lettre de raccourci utilisable pour accéder au menu via le clavier).
Déroulez l'entrée "Aid_e". Le sous-menu "gtk-about" apparaît. Sélectionnez-le. Dans la partie inférieure de la fenêtre vous retrouvez la liste des différents signaux. Il suffit alors d'affecter la fonction callback_about au signal "activate".

Il nous faut maintenant créer la fonction callback_about(); dans notre code source.
Cette fonction crée une fenêtre de dialogue pour afficher un "À propos...". Pour agrémenter un peu l'exemple, la fonction rend modale la fenêtre de dialogue par rapport à la fenêtre principale (la fenêtre principale ne pourra pas être utilisable tant que la fenêtre de dialogue restera ouverte). Ce qui vous montre comment interagir avec un pointeur quelconque de l'interface principale.

#include <gtk/gtk.h>

typedef struct
{
GtkBuilder *builder;
gpointer user_data;
} SGlobalData;

void callback_about (GtkMenuItem *menuitem, gpointer user_data);

int
main(int argc, char *argv [])
{
SGlobalData data;  /* variable propagée à tous les callbacks. */
gchar *filename = NULL;
GError *error = NULL;

/* Initialisation de la librairie Gtk. */
gtk_init(&argc, &argv);

/* Ouverture du fichier Glade de la fenêtre principale */
data.builder = gtk_builder_new();

/* Création du chemin complet pour accéder au fichier test.glade. */
/* g_build_filename(); construit le chemin complet en fonction du système */
/* d'exploitation. ( / pour Linux et \ pour Windows) */
filename =  g_build_filename ("test.glade", NULL);

/* Chargement du fichier test.glade. */
gtk_builder_add_from_file (data.builder, filename, &error);
g_free (filename);
if (error)
{
gint code = error->code;
g_printerr("%s\n", error->message);
g_error_free (error);
return code;
}

/* Affectation des signaux de l'interface aux différents CallBacks. */
gtk_builder_connect_signals (data.builder, &data);

/* Récupération du pointeur de la fenêtre principale */
fenetre_principale = GTK_WIDGET(gtk_builder_get_object (data.builder, "MainWindow"));

/* Affichage de la fenêtre principale. */
gtk_widget_show_all (fenetre_principale);

gtk_main();

return 0;
}

void
callback_about (GtkMenuItem *menuitem, gpointer user_data)
{
/* Transtypage du pointeur user_data pour récupérer nos données. */
SGlobalData *data = (SGlobalData*) user_data;
GtkWidget *dialog = NULL;

dialog =  gtk_about_dialog_new ();

/* Pour l'exemple on va rendre la fenêtre "À propos" modale par rapport à la */
/* fenêtre principale. */
gtk_window_set_transient_for (GTK_WINDOW(dialog), GTK_WINDOW(gtk_builder_get_object (data->builder, "MainWindow")));

gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
}

La prochaine section va nous permettre de pousser encore plus loin l'intégration du code source en utilisant une autre possibilité de Glade : la création de plusieurs fenêtres dans le même fichier.

IV-D - Plusieurs fenêtres

Jusqu'à présent notre fichier Glade ne contient que la fenêtre principale. Reprenons-le. Cliquons sur l'icône "Boite de dialogue de message". Une nouvelle fenêtre apparaît au-dessous de notre fenêtre principale. Renommons-la "AboutWindow". Il est possible, toujours dans l'onglet "Général", de remplir tous les champs nécessaires à la fenêtre "À propos...".

Parmi ces champs il y a en un : "Fenêtre parente transitoire". Cliquons sur le bouton avec trois petits points en face de cette option. Une fenêtre de dialogue s'ouvre et nous propose la liste des fenêtres que nous pouvons choisir. Sélectionnons "MainWindow" et sauvons le tout. La fenêtre dialogue "À propos..." restera maintenant au-dessus de la fenêtre principale.

Reprenons notre code source. Le fait de charger le fichier créé par Glade va initialiser tous les widgets inscrits. Cette information est importante pour la suite des opérations.
La fonction callback_about(); va avoir quelques modifications. Il nous suffit maintenant de récupérer le pointeur de la fenêtre "AboutWindow" plutôt que de la créer. Cependant cette fenêtre ne doit pas être détruite lors de sa fermeture. Si vous la détruisez, le pointeur récupéré depuis le GtkBuilder ne pointera plus sur rien ! La bonne méthode est de la cacher grâce à la fonction gtk_widget_hide();.

void
callback_about (GtkMenuItem *menuitem, gpointer user_data)
{
/* Transtypage du pointeur user_data pour récupérer nos données. */
SGlobalData *data = (SGlobalData*) user_data;
GtkWidget *dialog = NULL;

/* Récupération de la fenêtre "AboutWindow". */
dialog =  GTK_WIDGET (gtk_builder_get_object (data->builder, "AboutWindow"));
gtk_dialog_run (GTK_DIALOG (dialog));

/* On cache la fenêtre de dialogue. Si on la détruisait le prochain appel */
/* à ce callback provoquerait un segdefault ! */
gtk_widget_hide (dialog);
}

Comme vous pouvez le voir on réduit encore le code source.

V - Code source complet et Makefile

Ce tutoriel touche à sa fin. Vous trouverez à la suite le code source complet, un Makefile simple pour essayer et le contenu du fichier "test.glade".

main.c

#include <gtk/gtk.h>

typedef struct
{
GtkBuilder *builder;
gpointer user_data;
} SGlobalData;

void callback_about (GtkMenuItem *menuitem, gpointer user_data);

int
main(int argc, char *argv [])
{
GtkWidget *fenetre_principale = NULL;
SGlobalData data;
GError *error = NULL;
gchar *filename = NULL;
/* Initialisation de la bibliothèque Gtk. */
gtk_init(&argc, &argv);

/* Ouverture du fichier Glade de la fenêtre principale */
data.builder = gtk_builder_new();

/* Création du chemin complet pour accéder au fichier test.glade. */
/* g_build_filename(); construit le chemin complet en fonction du système */
/* d'exploitation. ( / pour Linux et \ pour Windows) */
filename =  g_build_filename ("test.glade", NULL);

/* Chargement du fichier test.glade. */
gtk_builder_add_from_file (data.builder, filename, &error);
g_free (filename);
if (error)
{
gint code = error->code;
g_printerr("%s\n", error->message);
g_error_free (error);
return code;
}

/* Affectation des signaux de l'interface aux différents CallBacks. */
gtk_builder_connect_signals (data.builder, &data);

/* Récupération du pointeur de la fenêtre principale */
fenetre_principale = GTK_WIDGET(gtk_builder_get_object (data.builder, "MainWindow"));

/* Affichage de la fenêtre principale. */
gtk_widget_show_all (fenetre_principale);

gtk_main();

return 0;
}

void
callback_about (GtkMenuItem *menuitem, gpointer user_data)
{
/* Transtypage du pointeur user_data pour récupérer nos données. */
SGlobalData *data = (SGlobalData*) user_data;
GtkWidget *dialog = NULL;

/* Récupération de la fenêtre "AboutWindow". */
dialog =  GTK_WIDGET (gtk_builder_get_object (data->builder, "AboutWindow"));
gtk_dialog_run (GTK_DIALOG (dialog));

/* On cache la fenêtre de dialogue. Si on la détruisait le prochain appel */
/* à ce callback provoquerait un segdefault! */
gtk_widget_hide (dialog);
}

Makefile

CC=gcc

CFLAGS   = -Wunused -Wall -fPIC -I. -c -O3 -D REENTRANT

CFLAGS  += -DGTK_DISABLE_DEPRECATED=1 -DGDK_DISABLE_DEPRECATED
CFLAGS  += -DGDK_PIXBUF_DISABLE_DEPRECATED -DG_DISABLE_DEPRECATED
CFLAGS  += -DGTK_MULTIHEAD_SAFE=1 -DGTK_MULTIDEVICE_SAFE=1

CFLAGS  += `pkg-config gtk+-3.0 --cflags`

LDFLAGS  = `pkg-config gtk+-3.0 --libs`
LDFLAGS += `pkg-config gmodule-2.0 --libs`

FILES = main.o

.c.o:
$(CC) -c $< $(CFLAGS)

all:$(FILES)
$(CC) -o test $(FILES) $(LDFLAGS)

clean:
rm -f *.o test *core*

test.glade

<?xml version="1.0" encoding="UTF-8"?>
<interface>
<!-- interface-requires gtk+ 3.0 -->
<object class="GtkWindow" id="MainWindow">
<property name="can_focus">False</property>
<signal name="destroy" handler="gtk_main_quit" swapped="no"/>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkMenuBar" id="menubar1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="menuitem1">
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Fichier</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="menu1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem1">
<property name="label">gtk-new</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem2">
<property name="label">gtk-open</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem3">
<property name="label">gtk-save</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem4">
<property name="label">gtk-save-as</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem1">
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem5">
<property name="label">gtk-quit</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem2">
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">É_dition</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="menu2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem6">
<property name="label">gtk-cut</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem7">
<property name="label">gtk-copy</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem8">
<property name="label">gtk-paste</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem9">
<property name="label">gtk-delete</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem3">
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Affichage</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem4">
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Aid_e</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="menu3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem10">
<property name="label">gtk-about</property>
<property name="use_action_appearance">False</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<signal name="activate" handler="callback_about" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="TextView">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkStatusbar" id="StatusBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkAboutDialog" id="AboutWindow">
<property name="can_focus">False</property>
<property name="border_width">5</property>
<property name="title" translatable="yes">test : A propos</property>
<property name="window_position">center-on-parent</property>
<property name="type_hint">dialog</property>
<property name="transient_for">MainWindow</property>
<property name="program_name">test</property>
<property name="version">0.1</property>
<property name="website">www.developpez.com</property>
<property name="website_label" translatable="yes">www.developpez.com</property>
<property name="authors">gerald3d</property>
<property name="wrap_license">True</property>
<property name="license_type">lgpl-2-1</property>
<child internal-child="vbox">
<object class="GtkBox" id="aboutdialog-vbox1">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="aboutdialog-action_area1">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
</interface>

VI - Remerciements

Je tiens tout particulièrement à remercier Troumad pour les nombreux conseils, remarques et relectures techniques qu'il m'a prodigués.

Je remercie aussi Claude LELOUP pour la relecture orthographique de cet article.