Nous allons revenir aujourd'hui sur une notion fondamentale de GTK+, que je n'ai fait que présenter dans le premier article de cette série. Il s'agit des signaux et des fonctions de rappel.
Les Signaux de X11
Toutes les bonnes applications X11 qui se respectent sont construites de la même façon : une initialisation suivie d'une boucle d'attente d'événements. Ces événements sont en fait des signaux que les fenêtres de l'application génèrent à la moindre sollicitation. Ces signaux sont extrêmement variés puisqu'ils permettent à une fenêtre de dire que :
- un bouton de la souris a été pressé (ou relâché) alors que le pointeur est dans cette fenêtre,
- la fenêtre est passée au-dessus ou au-dessous d'une autre,
- la fenêtre a changé de palette de couleur, de taille,
- la fenêtre vient d'être créée, détruite
- le pointeur de la souris est entré ou sorti de cette fenêtre,
- la fenêtre a besoin d'être redessinée,
- la fenêtre vient d'acquérir ou de perdre le focus du clavier,
- la fenêtre contenant la fenêtre a changé de taille,
- la disposition des touches du clavier a changé (!),
- une touche du clavier a été pressée ou relâchée, alors que la fenêtre possède le focus du clavier,
- la fenêtre est affichée à (ou effacée de) l'écran,
- le pointeur de la souris a bougé dans la fenêtre,
- les propriétés de la fenêtre ont changé,
- la fenêtre n'est plus contenue dans la même fenêtre qu'auparavant.
Et cette liste n'est même pas complètement exhaustive ! J'ai volontairement omis les signaux les plus exotiques. Vous imaginez donc que, dès que vous utilisez une application X-window, des dizaines de signaux sont générés sans arrêt. Heureusement, il est possible de choisir, pour chaque fenêtre, les signaux qui doivent être générés.
Les Signaux de GTK+
Comme les widgets de GTK+ sont la plupart du temps associés à une fenêtre, GTK+ reprend bien sûr tous ces signaux et en ajoute d'autres plus spécifiques aux widgets. Par exemple, tous les widgets créés avec GTK+ peuvent émettre les signaux suivants :
event, button_press_event, button_release_event, mo tion_not ify_event,
delete_event, destroy_event, expose_event, key_press_
ev ent,
key_release_event, enter_notify_event, leave_noti fy_event,
configure_event, focus_in_event, focus_out_event, map_event, un map_event,
property_notify_event, selection_clear_event, selection_request_event,
selection_notify_event, proximity_in_event, proximity_out_event,
visibility_notify_event, client_event, et no_expo se_event
pour ce qui est des signaux habituels de X11 ;
selection_get et selection_received
pour les signaux en rapport avec le copier-coller de X11 ;
drag_begin, drag_end, drag_data_get, drag_data_delete, drag_leave,
drag_motion, drag_drop, drag_data_received
pour le drag'n'drop de GTK+.
Ce qui fait pas mal, convenez-en ! Rassurez-vous, GTK+ gère déjà la plupart de ces signaux par défaut. Par exemple, GTK+ gère les signaux 'enter_notify_event' et 'leave_notify_event' d'à peu près tous les widgets, ce qui lui permet de les mettre en surbrillance lorsque le pointeur de la souris passe au-dessus ('entre') du widget et de le restaurer dans son apparence normale lorsque le pointeur 'sort' du widget. De la même façon, GTK+ redessine les widgets en utilisant le signal 'expose_event' de ces widgets.
La majeure partie du développement d'une application GTK+ consiste en fait à utiliser ces signaux pour réagir de la bonne façon aux événements qui surviennent à nos widgets.
Pour réagir correctement à chacun des signaux, GTK+ utilise et met à notre disposition un système de connexion entre un objet (un widget), un signal et une fonction de traitement, appelée fonction de rappel.
Il existe une bonne dizaine de fonctions permettant une telle connexion. La plus utilisée est la suivante :
guint gtk_signal_connect(GtkObject *Widget,
const gchar *NomDuSignal,
GtkSignalFunc FonctionDeRappel,
gpointer Donnees);
qui connecte le signal 'NomDuSignal' pouvant être reçu par le widget 'Widget' à la fonction 'FonctionDeRappel'. 'Donnees' est un pointeur sur des données qui sera passé tel quel à la fonction de rappel. Les types des variables peuvent vous paraître un peu étrangers. En fait, guint n'est rien d'autre qu'un bon vieux 'unsigned int' ; gchar est un 'char' ; GtkSignalFunc est le type 'pointeur sur une fonction quelconque' (void (*)()) et gpointer un 'void *'. Ces types ont été définis pour standardiser GTK+ quelle que soit la plate-forme de développement. Vous pouvez donc indifféremment utiliser les styles propres à GTK+ ou ceux du C standard ; mais à l'usage, ceux de GTK+ sont vraiment très pratiques.
La valeur de retour de la fonction 'gtk_signal_connect' est un numéro de signal unique pour le widget 'Widget', qui nous permettra par la suite de déconnecter le signal de notre fonction pour ce widget.
En fait, par un appel à cette fonction, on dit clairement à GTK+ que, lorsque le 'Widget' reçoit le signal 'NomDuSignal', la fonction 'FonctionDeRappel' doit être appelée avec comme premier paramètre 'Widget' et comme deuxième ou troisième (selon le signal) paramètre 'Donnees'. Notez que cette fonction de rappel est appelée AVANT la fonction de rappel par défaut de GTK+. Si vous désirez que votre fonction de rappel soit appelée APRÈS la fonction par défaut, vous pouvez utiliser :
guint gtk_signal_connect_after(GtkObject *Widget,
const gchar *NomDuSignal,
GtkSignalFunc FonctionDeRappel,
gpointer Donnees);
où les paramètres et la valeur de retour ont exactement le même rôle que pour 'gtk_signal_connect'.
Si, par la suite, vous désirez que votre fonction ne soit plus appelée lorsque le widget 'Widget' reçoit ce même signal, vous pouvez utiliser la fonction :
void gtk_signal_disconnect(GtkObject *Widget, guint NumeroDeSignal);
où 'NumeroDeSignal' est le numéro de signal renvoyé par une fonction gtk_signal_connect*.
Les fonctions de rappel
Même si GtkSignalFunc désigne un pointeur sur une fonction quelconque, les fonctions de rappel de GTK+ doivent avoir un prototype particulier. Ce prototype dépend du type du signal. Pour les signaux qui correspondent exactement à un signal équivalent de X11, le prototype de la fonction de rappel doit être le suivant :
gint FonctionDeRappel(GtkWidget *Widget, GdkEvent *event, gpointer Donnees);
et pour tous les autres, le prototype doit être le suivant :
gint FonctionDeRappel(GtkWidget *Widget, gpointer Donnees);
'Widget' est bien sûr le widget concerné par le signal. 'Donnees' est le pointeur que vous avez précisé lorsque vous avez connecté le signal et 'event' contient une structure décrivant complètement le signal. Par exemple, si le signal correspond à un clic d'un bouton de la souris, on pourra connaître la position de la souris dans le widget par ((GdkEventButton *)event)->x, et ((GdkEventButton *)event)->y. Et 'Donnees' est le pointeur que l'on a fourni lors de la connexion du signal. Les plus curieux d'entre vous pourront connaître exactement quels sont les champs disponibles suivant le signal reçu en examinant de près le fichier gdk/gdktypes.h. Mais rassurez-vous, nous reviendrons sûrement plus en détail sur les GdkEvent* dans les prochains articles de cette série.
La valeur de retour indique à GTK s'il doit continuer de propager le signal ou pas. De plus, cette valeur n'est pas toujours prise en compte par GTK+ donc, si vous ne voulez pas vous compliquer la vie, renvoyez toujours la valeur FALSE. Ce qui indique à GTK+ qu'il peut continuer à traiter le signal. Sinon, c'est-à-dire si vous voulez vraiment comprendre en détail comment GTK+ propage ses signaux, voici comment cela se passe (d'après le didacticiel fourni avec GTK+) :
Cela commence avec le widget où le signal est apparu.
GTK+ émet le signal "event" (signal au sens X11).
Si la dernière des fonctions attachées de rappel connectées à ce signal retourne la valeur TRUE, GTK+ arrête le traitement de ce signal.
Sinon, il émet le signal GTK+ spécifique. Par exemple, si le signal correspond à une pression sur un bouton de la souris, GTK+ émet le signal "button_press_event".
Si la dernière des fonctions de rappel attachées à ce signal renvoie la valeur TRUE, GTK+ arrête le traitement de ce signal.
Sinon, GTK+ reproduit le schéma précédent avec le widget parent et continue ainsi jusqu'à ce qu'un widget top level (une fenêtre gérée par le window manager) soit atteint ou jusqu'à ce qu'un groupe de fonctions de rappel renvoie la valeur TRUE.
Il arrive souvent qu'une fonction de rappel utilise uniquement un seul paramètre, qui est un objet (ou un widget) de GTK+. Pour cela, GTK+ met à notre disposition des fonctions de connexion de signaux légèrement différentes :
guint gtk_signal_connect_object(GtkObject *Widget,
const gchar *NomDuSignal,
GtkSignalFunc FonctionDeRappel,
GtkObject *Objet);
et
guint gtk_signal_connect_object_after(GtkObject *Widget,
const gchar *NomDuSignal,
GtkSignalFunc FonctionDeRappel,
GtkObject *Objet);
où 'Objet' est justement l'unique paramètre que recevra la fonction de rappel et qui a donc le prototype suivant :
gint FonctionDeRappel(GtkObject *Objet);
ou
gint FonctionDeRappel(void);
Ceci est justement le prototype de beaucoup de fonctions internes de GTK+ comme, par exemple, la fonction void gtk_exit(void); qui permet de finir un programme GTK+ ou gtk_widget_destroy(GtkWidget *Widget); qui permet de détruire un widget. Il est en effet souvent pratique de détruire une fenêtre de dialogue lorsque l'on clique sur un bouton de cette fenêtre. Pour cela, on peut utiliser :
gtk_signal_connect_object(GTK_OBJECT(Bouton), "clicked",
(GtkSignalFunc)gtk_widget_destroy,
GTK_OBJECT(Fenetre));
et non pas :
gtk_signal_connect(GTK_OBJECT(Bouton), "clicked",
(GtkSignalFunc)gtk_widget_destroy,
GTK_OBJECT(Fenetre));
qui détruit Bouton et non pas Fenêtre ! Essayez de bien comprendre pourquoi.
Bon, rien ne vaut un bon exemple pour éclaircir les idées.
Exemple :
/* signaux.c */
/* Exemple de gestion de signaux avec GTK+ */
#include <stdio.h>
#include <gtk/gtk.h>
/* Fonction de Rappel associée au bouton 1, lorsqu'il reçoit le signal 'clicked' */
gint ClickBouton1(GtkWidget *Bouton, char *param)
{
printf("On a cliqué sur le bouton 1\n");
printf("Le paramètre passé à cette fonction est : %s\n", param);
return FALSE; /* On indique à GTK+ qu'il doit continuer à gérer le signal */
}
/* Fonction de Rappel associée au bouton 2 lorsqu'il reçoit le signal 'event' */
gint SignalBouton2(GtkWidget *Bouton, GdkEvent *event, char *param)
{
/* On permet à GTK de traiter lui-même le signal "expose_event" */
if (event->type == GDK_EXPOSE)
return FALSE;
/* Mais on lui fait croire que l'on a traité tous les autres.
En particulier, on lui fait croire que l'on traite nous-même les signaux
'enter_notify_event' et 'leave_notify_event' et donc, le bouton n'est plus mis automatiquement en surbrillance quand le poin teur de la souris
passe dessus...*/
return TRUE;
}
int main(int argc, char *argv[])
{
GtkWidget *Dialogue;
GtkWidget *HBox;
GtkWidget *Bouton;
GtkWidget *BoutonQuitter;
/* Initialisation de la bibliothèque GTK+ */
gtk_init(&argc, &argv);
/* Création de la fenêtre principale de l'application */
Dialogue = gtk_dialog_new();
gtk_widget_show(Dialogue);
/* Création du bouton permettant de Quitter l'application */
BoutonQuitter = gtk_button_new_with_label("Quitter");
gtk_signal_connect_object(GTK_OBJECT(BoutonQuitter), "clicked",
(GtkSignalFunc)gtk_exit, NULL);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(Dialogue)->action_area), BoutonQuitter,
TRUE, TRUE, 0);
gtk_widget_show(BoutonQuitter);
/* Création de la boîte horizontale qui contiendra nos deux boutons */
HBox = gtk_hbox_new(TRUE, 5);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(Dialogue)->vbox), HBox, TRUE, TRUE, 0);
gtk_widget_show(HBox);
/* Création du premier bouton */
Bouton = gtk_button_new_with_label("Bouton 1");
gtk_signal_connect(GTK_OBJECT(Bouton), "clicked",
(GtkSignalFunc)ClickBouton1, "salut");
gtk_box_pack_start(GTK_BOX(HBox), Bouton, TRUE, TRUE, 0);
gtk_widget_show(Bouton);
/* Création du second bouton */
Bouton = gtk_button_new_with_label("Bouton 2");
gtk_signal_connect(GTK_OBJECT(Bouton), "event",
(GtkSignalFunc)SignalBouton2, "bonjour");
gtk_box_pack_start(GTK_BOX(HBox), Bouton, TRUE, TRUE, 0);
gtk_widget_show(Bouton);
/* La boucle principale */
gtk_main();
return 0;
}
Comme toujours, compilez ce programme avec :
gcc signaux.c -o signaux `gtk-config --cflags --libs`
A propos, faites bien attention au sens des apostrophes, il s'agit de ` (AltGr+7 sur un clavier français classique) et pas de '.
Comme d'habitude, amusez-vous à changer le maximum de paramètres afin de bien comprendre comment tout cela fonctionne.
La prochaine fois
La prochaine fois, nous commencerons l'étude systématique des widgets de GTK+, de leurs signaux, de leurs particularités et des fonctions qui les gouvernent.
D'ici là, n'hésitez pas à me harceler de questions et à me faire part de vos créations.