Programmation GTK/Gestion d'évènements
= Scribble, un programme simple de dessin =
Présentation.
Dans cette section, nous construirons un programme simple de dessin. Ce faisant, nous examinerons comment gérer les événements souris, comment dessiner dans une fenêtre, et comment mieux dessiner en utilisant un pixmap en arrière plan. Après avoir créé ce programme, nous l'étendrons en lui ajoutant le support des périphériques Xinput, comme les tables de tracé. GTK dispose de routines de support qui facilitent beaucoup l'obtention des informations étendues (comme la pression et l'inclinaison du stylet) à partir de tels périphériques.
Gestion d'événement.
Les signaux GTK que nous avons déjà vus concernent les actions de haut niveau, comme la sélection d'un choix d'un menu. Cependant, il est quelques fois utile de connaître les cas de bas niveau, comme le déplacement de la souris, ou la pression d'une touche. Il existe aussi des signaux GTK correspondant à ces événements bas niveau. Les gestionnaires de ces signaux ont un paramètre supplémentaire qui est un pointeur vers une structure contenant des informations sur l'événement. Par exemple, les gestionnaires des événements de déplacement recoivent un paramètre vers une structure GdkEventMotion qui ressemble (en partie) à ceci :
struct _GdkEventMotion
 GdkEventType type;
 GdkWindow *window;
 guint32 time;
 gdouble x;
 gdouble y;
 guint state;
};
type sera initialisé avec le type de l'événement, ici GDK_MOTION_NOTIFY, window est la fenêtre dans laquelle l'événement est survenu. x et y donnent les coordonnées de l'événement et state spécifie l'état du modificateur lorsque l'événement s'est produit (c'est-à-dire quelles sont les touches de modification et les boutons souris qui ont été pressés). Il s'agit d'un OU bit à bit de l'une des valeurs suivantes :
GDK_SHIFT_MASK
GDK_LOCK_MASK
GDK_CONTROL_MASK
GDK_MOD1_MASK
GDK_MOD2_MASK
GDK_MOD3_MASK
GDK_MOD4_MASK
GDK_MOD5_MASK
GDK_BUTTON1_MASK
GDK_BUTTON2_MASK
GDK_BUTTON3_MASK
GDK_BUTTON4_MASK
GDK_BUTTON5_MASK
Comme pour les autres signaux, on appelle gtk_signal_connect() pour déterminer ce qui se passe lorsqu'un événement survient. Mais nous devons aussi faire en sorte que GTK sache de quels événements nous voulons être avertis. Pour ce faire, on appelle la fonction :
void gtk_widget_set_events (GtkWidget *widget,
 gint events);
Le deuxième champ spécifie les événements qui nous intéressent. Il s'agit d'un OU bit à bit de constantes qui indiquent différent types d'événements. Pour référence ultérieure, les types d'événements sont :
GDK_EXPOSURE_MASK
GDK_POINTER_MOTION_MASK
GDK_POINTER_MOTION_HINT_MASK
GDK_BUTTON_MOTION_MASK 
GDK_BUTTON1_MOTION_MASK 
GDK_BUTTON2_MOTION_MASK 
GDK_BUTTON3_MOTION_MASK 
GDK_BUTTON_PRESS_MASK 
GDK_BUTTON_RELEASE_MASK 
GDK_KEY_PRESS_MASK 
GDK_KEY_RELEASE_MASK 
GDK_ENTER_NOTIFY_MASK 
GDK_LEAVE_NOTIFY_MASK 
GDK_FOCUS_CHANGE_MASK 
GDK_STRUCTURE_MASK 
GDK_PROPERTY_CHANGE_MASK 
GDK_PROXIMITY_IN_MASK 
GDK_PROXIMITY_OUT_MASK 
Il y a quelques points subtils qui doivent être observés lorsqu'on appelle gtk_widget_set_events(). D'abord, elle doit être appelée avant que la fenêtre X d'un widget GTK soit créée. En pratique, cela signifie que l'on doit l'appeler immédiatement après avoir créé le widget. Ensuite, le widget doit avoir une fenêtre X associée. Pour des raisons d'efficacité, de nombreux types de widgets n'ont pas de fenêtre propre, mais se dessinent dans la fenêtre de leur parent. Ces widgets sont :
GtkAlignment
GtkArrow
GtkBin
GtkBox
GtkImage
GtkItem
GtkLabel
GtkPaned
GtkPixmap
GtkScrolledWindow
GtkSeparator
GtkTable
GtkViewport
GtkAspectFrame
GtkFrame
GtkVPaned
GtkHPaned
GtkVBox
GtkHBox
GtkVSeparator
GtkHSeparator
Pour capturer les événements pour ces widgets, on doit utiliser un widget EventBox. Voir la section sur Le widget EventBox pour plus de détails.
Pour notre programme de dessin, on veut savoir quand le bouton de la souris est pressé et quand la souris est déplacée, nous indiquons donc GDK_POINTER_MOTION_MASK et GDK_BUTTON_PRESS_MASK. On veut aussi savoir quand il est nécessaire de redessiner notre fenêtre, on indique donc GDK_EXPOSURE_MASK. Bien que nous voulions être avertis via un événement Configure lorsque la taille de notre fenêtre change, on n'a pas besoin de préciser le flag GDK_STRUCTURE_MASK correspondant car il est automatiquement spécifié pour chaque fenêtre.
Il arrive cependant qu'il puisse y avoir un problème en indiquant seulement GDK_POINTER_MOTION_MASK. Cela fera que le serveur ajoutera un nouvel événement de déplacement à la file des événements à chaque fois que l'utilisateur déplace la souris. Si cela nous prend 0,1 seconde pour gérer un événement de déplacement, si le serveur X n'ajoute un nouvel événement de déplacement dans la queue que toutes les 0,05 secondes, nous serons vite à la traîne de l'utilisateur. Si l'utilisateur dessine pendant 5 secondes, cela nous prendra 5 secondes de plus pour le traiter après qu'il ait relâché le bouton de la souris ! Ce que l'on voudrait, c'est ne récupérer qu'un événement de déplacement pour chaque événement que l'on traite. Pour cela, il faut préciser GDK_POINTER_MOTION_HINT_MASK.
Avec GDK_POINTER_MOTION_HINT_MASK, le serveur nous envoie un événement de déplacement la première fois que la pointeur se déplace après être entré dans la fenêtre, ou après un événement d'appui ou de relâchement d'un bouton. Les événements de déplacement suivants seront supprimés jusqu'à ce que l'on demande explicitement la position du pointeur en utilisant la fonction :
GdkWindow* gdk_window_get_pointer (GdkWindow *window,
 gint *x,
 gint *y,
 GdkModifierType *mask);
(Il existe une autre fonction, gtk_widget_get_pointer() qui possède une interface simple, mais n'est pas très utile car elle ne fait que récupérer la position de la souris et ne se préoccupe pas de savoir si les boutons sont pressés).
Le code pour configurer les événements pour notre fenêtre ressemble alors à :
 gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
 (GtkSignalFunc) expose_event, NULL);
 gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
 (GtkSignalFunc) configure_event, NULL);
 gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
 (GtkSignalFunc) motion_notify_event, NULL);
 gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
 (GtkSignalFunc) button_press_event, NULL);
 gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
 | GDK_LEAVE_NOTIFY_MASK
 | GDK_BUTTON_PRESS_MASK
 | GDK_POINTER_MOTION_MASK
 | GDK_POINTER_MOTION_HINT_MASK);
Nous garderons les gestionnaires de "expose_event" et "configure_event" pour plus tard. Les gestionnaires de "motion_notify_event" et "button_press_event" sont très simples :
static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
 if (event->button == 1 && pixmap != NULL)
 draw_brush (widget, event->x, event->y);
 return TRUE;
static gint
motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
 int x, y;
 GdkModifierType state;
 if (event->is_hint)
 gdk_window_get_pointer (event->window, &x, &y, &state);
 else
 x = event->x;
 y = event->y;
 state = event->state;