if (state & GDK_BUTTON1_MASK && pixmap != NULL)
 draw_brush (widget, x, y);
 return TRUE;
20.3 Le widget DrawingArea et le dessin
Revenons au processus de dessin sur l'écran. Le widget que l'on utilise pour ceci est le widget DrawingArea. Un tel widget est essentiellement une fenêtre X et rien de plus. Il s'agit d'une toile vide sur laquelle nous pouvons dessiner ce que nous voulons.
Une zone de dessin est créée avec l'appel :
GtkWidget* gtk_drawing_area_new (void);
Une taille par défaut peut être donnée au widget par l'appel :
void gtk_drawing_area_size (GtkDrawingArea *darea,
 gint width,
 gint height);
Cette taille par défaut peu être surchargée en appelant gtk_widget_set_usize() et celle-ci, à son tour, peut être surchargée si l'utilisateur modifie manuellement la taille de la fenêtre contenant la zone de dessin.
Il faut noter que lorsque l'on crée un widget DrawingArea, nous sommes complètement responsable du dessin du contenu. Si notre fenêtre est cachée puis redécouverte, nous recevrons un événement d'exposition et devrons redessiner ce qui a été caché auparavant.
Devoir se rappeler tout ce qui a été dessiné à l'écran pour pouvoir correctement le redessiner peut s'avérer, c'est le moins que l'on puisse dire, pénible. De plus, cela peut être visible si des portions de la fenêtre sont effacées puis redessinées étape par étape. La solution à ce problème est d'utiliser un pixmap d'arrière-plan qui n'est pas sur l'écran. Au lieu de dessiner directement à l'écran, on dessine sur une image stockée dans la mémoire du serveur et qui n'est pas affichée, puis, lorsque l'image change ou lorsque de nouvelles portions de l'image sont affichées, on copie les parties adéquates sur l'écran.
Pour créer un pixmap mémoire, on appelle la fonction :
GdkPixmap* gdk_pixmap_new (GdkWindow *window,
 gint width,
 gint height,
 gint depth);
Le paramètre windows indique une fenêtre GTK de laquelle ce pixmap tire certaines de ses propriétés. width et height précisent la taille du pixmap. depth précise la profondeur de couleur, c'est-à-dire le nombre de bits par pixel, de la nouvelle fenêtre. Si cette profondeur vaut -1, elle correspondra à celle de window.
Nous créons le pixmap dans notre gestionnaire "configure_event". Cet événement est généré à chaque fois que la fenêtre change de taille, y compris lorsqu'elle initialement créée.
/* Pixmap d'arrière-plan pour la zone de dessin */
static GdkPixmap *pixmap = NULL;
/* Création d'un nouveau pixmap d'arrière-plan de la taille voulue */
static gint
configure_event (GtkWidget *widget, GdkEventConfigure *event)
 if (pixmap)
 gdk_pixmap_destroy(pixmap);
 pixmap = gdk_pixmap_new(widget->window,
 widget->allocation.width,
 widget->allocation.height,
 -1);
 gdk_draw_rectangle (pixmap,
 widget->style->white_gc,
 TRUE,
 0, 0,
 widget->allocation.width,
 widget->allocation.height);
 return TRUE;
L'appel à gdk_draw_rectangle() remet le pixmap à blanc. Nous en dirons un peu plus dans un moment.
Notre gestionnaire d'événement d'exposition copie alors simplement la partie concernées du pixmap sur l'écran (on détermine la zone qu'il faut redessiner en utilisant le champ event->area de l'événement d'exposition)&nbsp:
/* Remplit l'écran à partir du pixmap d'arrière-plan */
static gint
expose_event (GtkWidget *widget, GdkEventExpose *event)
 gdk_draw_pixmap(widget->window,
 widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
 pixmap,
 event->area.x, event->area.y,
 event->area.x, event->area.y,
 event->area.width, event->area.height);
 return FALSE;
Nous avons vu comment garder l'écran à jour avec notre pixmap, mais comment dessine-t-on réellement ce que l'on veut dans le pixmap ? Il existe un grand nombre d'appels dans la bibliothèque GDK de GTK pour dessiner sur des dessinables. Un dessinable est simplement quelque chose sur lequel on peut dessiner. Cela peut être une fenêtre, un pixmap, ou un bitmap (une image en noir et blanc). Nous avons déjà vu plus haut deux de ces appels, gdk_draw_rectangle() et gdk_draw_pixmap(). La liste complète est :
gdk_draw_line ()
gdk_draw_rectangle ()
gdk_draw_arc ()
gdk_draw_polygon ()
gdk_draw_string ()
gdk_draw_text ()
gdk_draw_pixmap ()
gdk_draw_bitmap ()
gdk_draw_image ()
gdk_draw_points ()
gdk_draw_segments ()
Consultez la documentation de référence ou le fichier en-tête <gdkgdk.h>/ pour plus de détails sur ces fonctions. Celles-ci partagent toutes les mêmes deux paramètres. Le premier est le dessinable sur lequel dessiner, le second est un contexte graphique (GC).
Un contexte graphique encapsule l'information sur des choses comme les couleurs de premier et d'arrière plan et la largeur de ligne. GDK possède un ensemble complet de fonctions pour créer et manipuler les contextes graphiques, mais, pour simplifier, nous n'utiliserons que les contextes graphiques prédéfinis. Chaque widget a un style associé (qui peut être modifié dans un fichier gtkrc, voir la section sur les fichiers rc de GTK). Celui-ci, entre autres choses, stocke plusieurs contextes graphiques. Quelques exemples d'accès à ces contextes sont :
widget->style->white_gc
widget->style->black_gc
widget->style->fg_gc[GTK_STATE_NORMAL]
widget->style->bg_gc[GTK_WIDGET_STATE(widget)]
Les champs fg_gc, bg_gc, dark_gc et light_gc sont indexés par un paramètre de type GtkStateType qui peut prendre les valeurs :
GTK_STATE_NORMAL,
GTK_STATE_ACTIVE,
GTK_STATE_PRELIGHT,
GTK_STATE_SELECTED,
GTK_STATE_INSENSITIVE
Par exemple, pour GTK_STATE_SELECTED, la couleur de premier plan par défaut est blanc et la couleur d'arrière plan par défaut est bleu foncé.
Notre fonction draw_brush(), qui réalise le dessin à l'écran est alors :
/* Dessine un rectangle à l'écran */
static void
draw_brush (GtkWidget *widget, gdouble x, gdouble y)
 GdkRectangle update_rect;
 update_rect.x = x - 5;
 update_rect.y = y - 5;
 update_rect.width = 10;
 update_rect.height = 10;
 gdk_draw_rectangle (pixmap,
 widget->style->black_gc,
 TRUE,
 update_rect.x, update_rect.y,
 update_rect.width, update_rect.height);
 gtk_widget_draw (widget, &update_rect);
Après avoir dessiné le rectangle représentant la brosse sur le pixmap, nous appelons la fonction :
void gtk_widget_draw (GtkWidget *widget,
 GdkRectangle *area);
qui indique à X que la zone donnée par le paramètre area a besoin d'être mise à jour. X génèrera éventuellement un événement d'exposition (en combinant peut-être les zones passés dans plusieurs appels à gtk_widget_draw()) ce qui forcera notre gestionnaire d'événement d'exposition à copier les parties adéquates à l'écran.
Nous avons maintenant couvert entièrement le programme de dessin, sauf quelques détails banals comme la création de la fenêtre principale. Le code source complet est disponible à l'endroit où vous avez obtenu ce didacticiel.
20.4 Ajouter le support XInput
Il est maintenant possible d'acheter des périphériques bon marché, comme les tablettes graphiques qui permettent d'exprimer beaucoup plus facilement son talent qu'avec une souris. La façon la plus simple pour utiliser de tels périphériques est simplement de le faire comme un remplacement de la souris, mais cela ne tire pas partie des nombreux avantages de ces périphériques, comme :
 * Sensibilité à la pression ;
 * rapport d'inclinaison ;
 * positionnement au dessous du pixel ;
 * entrées multiples (par exemple, un stylet avec pointe et gomme).
Pour des informations sur l'extension XInput, voir XInput-HOWTO.
Si l'on examine la définition complète de, par exemple, la structure GdkEventMotion, on voit qu'elle possède des champs pour supporter des informations étendues sur les périphériques.
struct _GdkEventMotion
 GdkEventType type;
 GdkWindow *window;
 guint32 time;
 gdouble x;
 gdouble y;
 gdouble pressure;
 gdouble xtilt;
 gdouble ytilt;
 guint state;
 gint16 is_hint;
 GdkInputSource source;
 guint32 deviceid;
pressure indique la pression comme un nombre réel compris entre 0 et 1. xtilt et ytilt peuvent prendre des valeurs entre -1 et 1, correspondant au degré d'inclinaison dans chaque direction, source et deviceid précisent de deux façons différentes le périphérique pour lequel l'événement est survenus. source donne une simple information sur le type du périphérique. Il peut prendre l'une des valeurs suivantes :
GDK_SOURCE_MOUSE
GDK_SOURCE_PEN
GDK_SOURCE_ERASER
GDK_SOURCE_CURSOR
deviceid précise un ID numérique unique pour le périphérique. Il peut être utilisé pour trouver des informations supplémentaires sur le périphérique en utilisant l'appel gdk_input_list_devices() (voir ci-dessous). La valeur spéciale GDK_CORE_POINTER sert à désigner le périphérique de pointage principal (habituellement la souris).
Valider l'information supplémentaire sur un périphérique
Pour indiquer à GTK que l'on désire obtenir des informations supplémentaires sur le périphérique, on a simplement besoin d'ajouter une ligne à nos programmes.
gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);
En donnant la valeur GDK_EXTENSION_EVENTS_CURSOR, on indique que nous désirons les événements d'extension, mais seulement si l'on ne doit pas dessiner notre propre curseur. Voir la section Sophistications supplémentaires ci-dessous pour des plus de détails sur le dessin du curseur. Nous pourrions aussi donner les valeurs GDK_EXTENSION_EVENTS_ALL si nous voulons dessiner notre propre curseur, ou GDK_EXTENSION_EVENTS_NONE pour revenir à la situation par défaut.
Toutefois, nous ne sommes pas complètement à la fin de l'histoire. Par défaut, aucun périphérique d'extension n'est autorisé. Nous avons besoin d'un mécanisme pour que les utilisateurs puissent autoriser et configurer leur extensions. GTK dispose du widget InputDialog pour automatiser cette tâche. La procédure suivante gère un widget InputDialog. Elle crée le dialogue s'il n'est pas présent et le place au premier plan sinon.
void
input_dialog_destroy (GtkWidget *w, gpointer data)
 *((GtkWidget **)data) = NULL;
void
create_input_dialog ()
 static GtkWidget *inputd = NULL;
 if (!inputd)
 inputd = gtk_input_dialog_new();
 gtk_signal_connect (GTK_OBJECT(inputd), "destroy",
 (GtkSignalFunc)input_dialog_destroy, &inputd);
 gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button),
 "clicked",
 (GtkSignalFunc)gtk_widget_hide,
 GTK_OBJECT(inputd));
 gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button);
 gtk_widget_show (inputd);
 else
 if (!GTK_WIDGET_MAPPED(inputd))
 gtk_widget_show(inputd);
 else
 gdk_window_raise(inputd->window);
(vous pouvez remarquer la façon dont nous gérons ce dialogue. En le connectant au signal "destroy", nous nous assurons que nous ne garderons pas un pointeur sur le dialogue après l'avoir détruit -- cela pourrait provoquer une erreur de segmentation).
InputDialog a deux boutons "Close" et "Save", qui n'ont pas d'actions qui leur sont assignées par défaut. Dans la fonction ci-dessus, nous associons à "Close" le masquage du dialogue et nous cachons le bouton "Save" car nous n'implantons pas la sauvegarde des options XInput dans ce programme.
Utiliser l'information supplémentaire d'un périphérique
Lorsque l'on a validé le périphérique, on peut simplement utiliser l'information supplémentaire des champs des structures d'événement. En fait, il est toujours prident d'utiliser ces informations car ces champs auront des valeurs par défaut judicieuses même lorsque les événements supplémentaires ne sont pas autorisés.
La seule modification consiste à appeler gdk_input_window_get_pointer() au lieu de gdk_window_get_pointer. Cela est nécessaire car gdk_window_get_pointer ne retourne pas l'information supplémentaire.
void gdk_input_window_get_pointer (GdkWindow *window,
 guint32 deviceid,
 gdouble *x,
 gdouble *y,
 gdouble *pressure,
 gdouble *xtilt,
 gdouble *ytilt,
 GdkModifierType *mask);
Lorsque l'on appelle cette fonction, on doit préciser l'ID du périphérique ainsi que la fenêtre. Habituellement, on aura obtenu cet ID par le champ deviceid d'une structure d'événement. Cette fonction retournera une valeur cohérente lorsque les événements ne sont pas autorisés (dans ce cas, event->deviceid aura la valeur GDK_CORE_POINTER).
La structure de base des gestionnaires d'événements de déplacement et de bouton pressé ne change donc pas trop -- il faut juste ajouter le code permettant de traiter l'information supplémentaire.
static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
 print_button_press (event->deviceid);
 if (event->button == 1 && pixmap != NULL)
 draw_brush (widget, event->source, event->x, event->y, event->pressure);
 return TRUE;
static gint
motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
 gdouble x, y;
 gdouble pressure;
 GdkModifierType state;
 if (event->is_hint)
 gdk_input_window_get_pointer (event->window, event->deviceid,
 &x, &y, &pressure, NULL, NULL, &state);
 else
 x = event->x;
 y = event->y;
 pressure = event->pressure;
 state = event->state;