Programmation GTK/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&nbsp:

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&nbsp:

 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.