Didacticiel: Bien débuter

Page suivante Page précédente Table des matières

## 2. Bien débuter

La première chose à faire est, bien sûr, de récupérer les sources de GTK et de les installer. Vous pouvez en obtenir la dernière version sur `ftp.gimp.org` dans le répertoire `/pub/gtk`. D'autres sources d'informations se trouvent sur `http://www.gimp.org/gtk`. GTK utilise autoconf de GNU pour se configurer. Lorsque vous l'aurez détarré, tapez ./configure --help pour consulter la liste des options.

Pour commencer notre introduction à GTK, nous débuterons avec le programme le plus simple qui soit. Celui-ci créera une fenêtre de 200x200 pixels et ne pourra se terminer qu'en le tuant à partir du shell.

> `

>     #include <gtk/gtk.h>

>     int main (int argc, char *argv[])
>     {
>         GtkWidget *window;

>         gtk_init (&argc, &argv);

>         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
>         gtk_widget_show (window);

>         gtk_main ();

>         return 0;
>     }

> `

Tous les programmes inclueront évidemment le fichier `gtk/gtk.h` qui déclare les variables, fonctions, structures, etc. qui seront utilisées par votre application GTK.

La ligne :

> `

>     gtk_init (&argc, &argv);

> `

appelle la fonction gtk_init(gint *argc, gchar ***argv) qui sera appelée dans toutes les applications GTK. Cette fonction configure certaines choses pour nous, comme l'aspect visuel et les couleurs par défaut, puis appelle gdk_init(gint *argc, gchar ***argv). Cette dernière initialise la bibliothèque pour qu'elle puisse être utilisée, configure les gestionnaires de signaux par défaut et vérifie les paramètres passés à notre application via la ligne de commande en recherchant l'un des suivants :

- `--display`
- `--debug-level`
- `--no-xshm`
- `--sync`
- `--show-events`
- `--no-show-events`

Elle les supprime alors de la liste des paramètres, en laissant tout ce qu'elle ne reconnaît pas pour que notre application l'analyse ou l'ignore. Ceci crée un ensemble de paramètres standards acceptés par toutes les applications GTK.

Les deux lignes de code suivantes créent et affichent une fenêtre.

> `

>       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
>       gtk_widget_show (window);

> `

Le paramètre `GTK_WINDOW_TOPLEVEL` précise que l'on veut que la fenêtre créée suive l'aspect et le placement définis par le gestionnaire de fenêtres. Plutôt que de créer une fenêtre de 0x0, une fenêtre sans fenêtre fille est de 200x200 par défaut : on peut ainsi la manipuler facilement.

La fonction gtk_widget_show() informe GTK que l'on a configuré le widget et qu'il peut l'afficher.

La ligne suivante lance la boucle principale de traitement de GTK.

> `

>     gtk_main ();

> `

gtk_main() est un autre appel que vous verrez dans toute application GTK. Lorsque le contrôle atteind ce point, GTK se met en attente d'événements X (click sur un bouton, ou appui d'une touche, par exemple), de timeouts ou d'entrées-sorties fichier. Dans notre exemple simple, cependant, les événements sont ignorés.

## 2.1 « Bonjour tout le monde » en GTK

OK, écrivons un programme avec un widget (bouton). C'est le classique « Bonjour tout le monde » à la sauce GTK.

> `

>     #include <gtk/gtk.h>

>         /* fonction de rappel. Dans cet exemple, les paramètres sont ignorés...
>          *  Les fonctions de rappel sont détaillées plus loin. */

>     void hello (GtkWidget *widget, gpointer data)
>     {
>         g_print ("Bonjour tout le monde.\n");
>     }

>     gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
>     {
>         g_print ("le signal delete_event est survenu.\n");

>         /* Si l'on renvoit TRUE dans le gestionnaire du signal "delete_event",
>          * GTK émettra le signal "destroy". Retourner FALSE signifie que l'on
>          * ne veut pas que la fenêtre soit détruite.
>          * Utilisé pour faire apparaître des boîtes de dialogue du type
>          * « Êtes-vous sûr de vouloir quitter ? » */

>         /* Remplacez FALSE par TRUE et la fenêtre principale sera détruite par
>          * un signal « delete_event ». */

>         return (FALSE);
>     }

>     /* Autre fonction de rappel */

>     void destroy (GtkWidget *widget, gpointer data)
>     {
>         gtk_main_quit ();
>     }

>     int main (int argc, char *argv[])
>     {
>         /* GtkWidget est le type pour déclarer les widgets. */

>         GtkWidget *window;
>         GtkWidget *button;

>         /* Cette fonction est appelée dans toutes les applications GTK.
>          * Les paramètres passés en ligne de commande sont analysés et
>          * retournés à l'application. */

>         gtk_init (&argc, &argv);

>         /* Création d'une nouvelle fenêtre. */

>         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

>         /* Lorsque la fenêtre reçoit le signal "delete_event"
>          * (envoyé par le gestionnaire de fenêtres en utilisant l'option
>          * « close » ou la barre de titre), on lui demande d'appeler la
>          * fonction delete_event() définie plus haut. La donnée passée en
>          * paramètre à la fonction de rappel est NULL et est ignoré dans le
>          * rappel. */

>         gtk_signal_connect (GTK_OBJECT (window), "delete_event",
>                             GTK_SIGNAL_FUNC (delete_event), NULL);

>         /* Ici, on connecte l'évenement "destroy" à un gestionnaire de signal.
>          * Cet événement arrive lorsqu'on appelle gtk_widget_destroy() sur la
>          * fenêtre, ou si l'on retourne TRUE dans le rappel "delete_event". */

>         gtk_signal_connect (GTK_OBJECT (window), "destroy",
>                             GTK_SIGNAL_FUNC (destroy), NULL);

>         /* Configuration de la largeur du contour de la fenêtre. */

>         gtk_container_border_width (GTK_CONTAINER (window), 10);

>         /* Création d'un nouveau bouton portant le label
>          * "Bonjour tout le monde". */

>         button = gtk_button_new_with_label ("Bonjour tout le monde");

>         /* Quand le bouton recevra le signal "clicked", il appellera la
>          * fonction hello() définie plus haut en lui passant NULL en paramètre. */

>         gtk_signal_connect (GTK_OBJECT (button), "clicked",
>                             GTK_SIGNAL_FUNC (hello), NULL);

>         /* Ceci provoquera la destruction de la fenêtre par appel de la
>          * fonction gtk_widget_destroy(window) lors du signal "clicked".
>          * Le signal de destruction pourrait venir de là, ou du
>          * gestionnaire de fenêtres. */

>         gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
>                                    GTK_SIGNAL_FUNC (gtk_widget_destroy),
>                                    GTK_OBJECT (window));

>         /* Insertion du bouton dans la fenêtre (container gtk). */

>         gtk_container_add (GTK_CONTAINER (window), button);

>         /* L'étape finale consiste à afficher ce nouveau widget... */

>         gtk_widget_show (button);

>         /* ... et la fenêtre. */

>         gtk_widget_show (window);

>         /* Toutes les applications GTK doivent avoir un gtk_main().
>          * Le déroulement du programme se termine là et attend qu'un
>          * événement survienne (touche pressée ou événement souris). */

>         gtk_main ();

>         return 0;
>     }

> `

## 2.2 Compilation de « Bonjour tout le monde »

Supposons que vous avez sauvegardé le code précédent dans un fichier nommé bonjour.c, pour le compiler tapez la commande suivante :

> `

>     gcc -Wall -g bonjour.c -o bonjour_monde -L/usr/X11R6/lib \
>         -lgtk -lgdk -lglib -lXext -lX11 -lm

> `

Les bibliothèques invoquées ci-dessus doivent toutes être dans vos chemins de recherche par défaut, sinon, ajoutez `-L<library directory>` pour que gcc recherche dans ces répertoires les bibliothèques nécessaires. Sur mon système Debian GNU/Linux, par exemple, je dois ajouter `-L/usr/X11R6/lib` pour qu'il trouve les bibliothèques X11 (NdT : et c'est pareil sur mon système Red Hat Linux...).

L'ordre des bibliothèques est important. L'éditeur de liens doit connaître les fonctions d'une bibliothèque dont il a besoin avant de les traiter.

Si vous compilez en utilisant des bibliothèques statiques, l'ordre dans lequel vous listez les bibliothèques devient très important. L'exemple donné ci-dessus devrait fonctionner dans tous les cas.

Les bibliothèques que l'on utilise sont :

- La bibliothèque glib (`-lglib`), qui contient diverses fonctions. Seule g_print() est utilisée dans cet exemple. GTK est construit au dessus de glib et vous aurez donc toujours besoin de celle-ci. Voir la section concernant glib pour plus de détails.
- La bibliothèque GDK (`-lgdk`), l'enveloppe de Xlib.
- La bibliothèque GTK (`-lgtk`), la bibliothèque des widgets, construite au dessus de GDK.
- La bibliothèque Xlib (`-lX11` utilisée par GDK.
- La bibliothèque Xext (`-lXext`). Cette dernière contient le code pour les pixmaps en mémoire partagée et les autres extensions X.
- La bibliothèque mathématique (`-lm`). Elle est utilisée pour différentes raisons par GTK.

## 2.3 Théorie des signaux et des rappels

Avant de voir en détail le programme « Bonjour tout le monde », nous parlerons d'abord des événements et des fonctions de rappel. GTK est dirigé par les événements, ce qui signifie qu'il restera inactif dans gtk_main jusqu'à ce qu'un événement survienne et que le contrôle soit passé à la fonction appropriée.

Ce passage du contrôle est réalisé en utilisant le concept de « signal ». Lorsqu'un événement survient, comme l'appui sur un bouton, le signal approprié sera « émis » par le widget qui a été pressé. C'est de cette façon que GTK réalise la plupart de son travail. Pour qu'un bouton réalise une action, on configure un gestionnaire de signal pour capturer ces signaux et appeler la fonction adéquate. Ceci est fait en utilisant une fonction comme :

> `

>     gint gtk_signal_connect (GtkObject *object,
>                              gchar *name,
>                              GtkSignalFunc func,
>                              gpointer func_data);

> `

Où le premier paramètre est le widget qui émettra le signal, et le deuxième est le nom du signal que l'on souhaite intercepter. Le troisième paramètre est la fonction que l'on veut appeler quand le signal est capturé, et le quatrième sont les données que l'on souhaite passer à cette fonction.

La fonction spécifiée par le troisième paramètre s'appelle une « fonction de rappel » et doit être de la forme :

> `

>     void callback_func(GtkWidget *widget, gpointer *callback_data);

> `

Où le premier paramètre sera un pointeur vers le widget qui a émis le signal, et le second un pointeur vers les données passées par le dernier paramètre de la fonction gtk_signal_connect() décrite plus haut.

Un autre appel utilisé dans l'exemple « Bonjour tout le monde » est :

> `

>     gint gtk_signal_connect_object (GtkObject *object,
>                                     gchar  *name,
>                                     GtkSignalFunc func,
>                                     GtkObject *slot_object);

> `

gtk_signal_connect_object() est la même chose que gtk_signal_connect() sauf que la fonction de rappel utilise un seul paramètre : un pointeur vers un objet GTK. Lorsqu'on utilise cette fonction pour connecter des signaux, le rappel doit être de cette forme :

> `

>     void callback_func (GtkObject *object);

> `

Où l'objet est d'ordinaire un widget. En général, on ne configure pas de rappels pour gtk_signal_connect_object. D'habitude, ceux-ci sont utilisés pour appeler une fonction GTK acceptant un simple widget ou objet comme paramètre, comme dans notre exemple.

La raison pour laquelle il y a deux fonctions pour connecter les signaux est simplement de permettre aux fonctions de rappel d'avoir un nombre différent de paramètres. De nombreuses fonctions de la bibliothèque GTK n'acceptent qu'un simple pointeur vers un GtkWidget comme paramètre et vous pouvez donc utiliser gtk_signal_connect_object() pour celles-ci, tandis que pour vos fonctions vous pouvez avoir besoin d'avoir de fournir plus de données aux fonctions de rappel.

## 2.4 « Bonjour tout le monde » pas à pas

Maintenant que nous connaissons la théorie, clarifions un peu en progressant à travers le programme « Bonjour tout le monde ».

Voici la fonction de rappel appelée lorsque le bouton est « clicked ». Dans notre exemple, on ignore le widget et les données mais il n'est pas difficile de faire quelque chose avec. Le prochain exemple utilisera le paramètre des données pour nous dire quel bouton a été pressé.

> `

>     void hello (GtkWidget *widget, gpointer *data)
>     {
>         g_print ("Bonjour tout le monde\n");
>     }

> `

Cette fonction de rappel est un peu spéciale. L'événement "delete_event" survient lorsque le gestionnaire de fenêtres l'envoie à l'application. On doit choisir ce qu'il faut faire de ces événements. On peut les ignorer, leur donner une réponse, ou simplement quitter l'application.

La valeur que l'on retourne dans cette fonction de rappel permet à GTK de savoir ce qu'il a à faire. En retournant FALSE, on l'informe que l'on ne veut pas que le signal "destroy" soit émis, afin de laisser notre application tourner. En retournant TRUE, on lui demande d'émettre "destroy" qui appellera à son tour notre gestionnaire du signal "destroy".

> `

>     gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
>     {
>         g_print ("le signal delete_event est survenu.\n");

>         return (FALSE);
>     }

> `

Voici une autre fonction de rappel qui ne fait que quitter l'application en appelant gtk_main_quit(). Il n'y a pas grand chose de plus à dire car elle est plutôt triviale :

> `

>     void destroy (GtkWidget *widget, gpointer *data)
>     {
>         gtk_main_quit ();
>     }

> `

Je suppose que vous connaissez la fonction main()... oui, comme les autres programmes C, toutes les applications GTK en ont une.

> `

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

> `

La partie qui suit déclare deux pointeurs sur des structures de type GtkWidget. Ceux-ci sont utilisés plus loin pour créer une fenêtre et un bouton.

> `

>         GtkWidget *window;
>         GtkWidget *button;

> `

Et revoici notre gtk_init. Comme précédemment, il initialise le toolkit et analyse les paramètres de la ligne de commande. Il supprime chaque paramètre reconnu de la liste et modifie argc et argv pour faire comme si ces paramètres n'avaient jamais existé, laissant notre application analyser les paramètres restants.

> `

>         gtk_init (&argc, &argv);

> `

Création d'une nouvelle fenêtre. C'est plutôt classique. La mémoire est allouée pour une structure GtkWidget et window pointe donc sur celle-ci. Cela configure une nouvelle fenêtre, mais celle-ci ne sera pas affichée tant que l'on n'a pas appelé gtk_widget_show(window) vers la fin de notre programme.

> `

>         window = gtk_window_new (GTK_WINDOW_TOPLEVEL);

> `

Voici maintenant un exemple de connexion d'un gestionnaire de signal à un objet : la fenêtre. Le signal "destroy" est capturé. Il est émis lorsqu'on utilise le gestionnaire de fenêtres pour tuer la fenêtre (et que l'on retourne TRUE dans le gestionnaire "delete_event"), ou lorsqu'on utilise l'appel gtk_widget_destroy() en lui passant le widget window comme objet à détruire. Ici, on appelle juste la fonction destroy() définie ci-dessus avec le paramètre NULL, ce qui quitte GTK pour nous.

`GTK_OBJECT` et `GTK_SIGNAL_FUNC` sont des macros qui réalisent les conversions et les vérifications de types pour nous. Elles rendent aussi le code plus lisible.

> `

>         gtk_signal_connect (GTK_OBJECT (window), "destroy",
>                             GTK_SIGNAL_FUNC (destroy), NULL);

> `

La fonction suivante sert à configurer un attribut d'un objet container. Elle configure simplement la fenêtre pour qu'elle ait une zone vide autour d'elle de 10 pixels de large où aucun widget ne pourra se trouver. Il existe d'autres fonctions similaires que nous verrons dans la section sur la Configuration des attributs des widgets

À nouveau, `GTK_CONTAINER` est une macro réalisant la conversion de type.

> `

>         gtk_container_border_width (GTK_CONTAINER (window), 10);

> `

Cet appel crée un nouveau bouton. Il alloue l'espace mémoire pour une nouvelle structure GtkWidget, l'initialise et fait pointer button vers elle. Ce bouton portera le label « Bonjour tout le monde » lorsqu'il sera affiché.

> `

>         button = gtk_button_new_with_label ("Bonjour tout le monde");

> `

Maintenant, prenons ce bouton et faisons lui faire quelque chose d'utile. On lui attache un gestionnaire de signal pour que, lorsqu'il émettra le signal "clicked", notre fonction hello() soit appelée. On ignore les paramètres et on ne passe donc que la valeur NULL à la fonction de rappel hello(). Évidemment, le signal "clicked" est émis lorsqu'on clique sur le bouton avec la souris.

> `

>         gtk_signal_connect (GTK_OBJECT (button), "clicked",
>                             GTK_SIGNAL_FUNC (hello), NULL);

> `

On utilisera aussi ce bouton pour quitter notre programme, ce qui permettra d'illustrer la façon dont le signal "destroy" peut venir soit du gestionnaire de fenêtres, soit de notre programme. Quand le bouton est "clicked" comme cela est décrit plus haut, il appelle d'abord la fonction de rappel hello() puis celle-ci dans l'ordre dans lequel elles sont configurées. On peut avoir autant de fonctions de rappel que l'on désire, elles seront exécutées selon leur ordre de connexion. Puisque la fonction gtk_widget_destroy() n'accepte que GtkWidget *widget comme paramètre, on utilise ici la fonction gtk_signal_connect_object() à la place de gtk_signal_connect().

> `

>         gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
>                                    GTK_SIGNAL_FUNC (gtk_widget_destroy),
>                                    GTK_OBJECT (window));

> `

Voici un appel de placement, qui sera expliqué en détail plus tard, mais qui est plutôt facile à comprendre. Il indique simplement à GTK que le bouton doit être placé dans la fenêtre où il s'affichera.

> `

>      gtk_container_add (GTK_CONTAINER (window), button);

> `

Maintenant, nous avons tout configuré comme on le souhaitait : les gestionnaires de signaux sont en place et le bouton est mis dans la fenêtre où il doit se trouver. On demande alors à GTK de « montrer » les widgets à l'écran. Le widget window est affiché en dernier afin que la fenêtre entière surgisse d'un coup plutôt que voir d'abord la fenêtre s'afficher puis ensuite le bouton apparaître à l'intérieur. Il faut dire qu'avec des exemples simples comme celui-ci, vous ne ferez pas la différence.

> `

>         gtk_widget_show(button);

>         gtk_widget_show (window);

> `

Bien sûr, on appelle gtk_main() qui attendra les événements venant du serveur X et demandera aux widgets d'émettre les signaux lorsque ces événements surviendront.

> `

>         gtk_main ();

> `

Enfin, le return final. Il est exécuté lorsque gtk_quit() est appelé.

> `

>         return 0;

> `

Lorsque l'on clique sur un bouton GTK, le widget émet un signal "clicked". Afin de pouvoir utiliser cette information, notre programme configure un gestionnaire pour capturer ce signal. Ce gestionnaire appelle la fonction de notre choix. Dans notre exemple, lorsque le bouton que l'on a créé est "clicked", la fonction hello() est appelée avec le paramètre NULL, puis le gestionnaire suivant de ce signal est à son tour appelé. Il appelle la fonction gtk_widget_destroy() en lui passant le widget window comme paramètre, ce qui provoque la destruction de celui-ci. Ceci force la fenêtre à envoyer un signal "destroy", qui est capturé à son tour et appelle notre fonction de rappel destroy() qui ferme simplement GTK.

Une autre façon de procéder consiste à utiliser le gestionnaire de fenêtres pour détruire la fenêtre. Cela provoquera l'émission du signal "delete_event" qui sera pris en charge par notre gestionnaire delete_event(). S'il retourne FALSE, la fenêtre restera telle quelle et rien ne se passera. Retourner TRUE forcera GTK à émettre le signal "destroy" qui, bien sûr, appelera la fonction de rappel destroy() provoquant la sortie du GTK.

On remarquera que ces signaux ne sont pas les mêmes que les signaux systèmes Unix et ne sont pas implantés en utilisant ceux-ci, bien que la terminologie employée soit presque identique.

Page suivante Page précédente Table des matières