Ce mois-ci, nous continuons notre tour d'horizon des widgets de GTK+ avec, notamment, une présentation des GtkContainers et de leurs dérivés.
Qu'est-ce qu'un container ?
Un container est simplement un widget qui peut en contenir (d'où le nom) un ou plusieurs autres. Et l'une des grandes forces de GTK+ est que, justement, la plupart des widgets GTK+ sont des containers. Par exemple, les boutons ne sont pas simplement un rectangle avec un texte à l'intérieur mais bien un widget container avec peu importe quoi dedans. Effectivement, la plupart du temps, c'est un GtkLabel qui est contenu, mais cela n'est pas obligatoire. On peut vraiment placer ce que l'on veut dans un bouton. Cela va du label au pixmap en passant par les tables ou les frames et autres. Comme les containers représentent une grande partie des widgets, toutes les fonctions présentées ici pourront être utilisées sur pléthore de types de widgets différents, à l'aide d'un simple transtypage. La macro permettant de transtyper un widget en GtkContainer est évidemment GTK_CONTAINER(). De plus, n'oubliez pas que les GtkContainers sont aussi des Widgets. Les fonctions présentées dans l'article précédent s'appliquent donc aussi ici.
Les fonctions relatives aux GtkContainers
Tout d'abord, le rôle principal des containers, c'est de contenir d'autres widgets. Il faut donc une fonction pour ajouter un widget à un container. La voici :
void gtk_container_add(GtkContainer *container,
GtkWidget *widget);
On pouvait difficilement faire plus simple, non ? Mais je vois déjà des lecteurs fidèles qui me disent : « Oui, bon, mais dans l'article sur les boîtes et les tables, on nous avait dit que pour ajouter des widgets dans des boîtes, il fallait utiliser gtk_box_pack_start(), alors je n'y comprends rien ! » Et là je réponds : «Quel est le problème ? »
En effet, les GtkContainers sont des widgets génériques et la méthode gtk_container_add() est souvent surchargée par des fonctions introduites par des widgets dérivés. En fait, on utilisera bien entendu des fonctions du genre 'gtk_box_pack_...' pour les boîtes parce que cela est plus adapté. Et on gardera gtk_container_add() pour des widgets qui n'ont pas de fonctions mieux adaptées.
Pour être tout à fait exact, rien ne nous empêche d'écrire pour une boîte :
gtk_container_add(GTK_CONTAINER(boite), widget);
Cela fonctionne même très bien (puisque GtkBox est un descendant de GtkContainer) et c'est strictement équivalent, dans ce cas-là, à :
gtk_box_pack_start_defaults(GTK_BOX(boite), widget);
A vous de choisir lequel des deux est le plus lisible.
Maintenant, on peut aussi avoir besoin de retirer un widget d'un container. La fonction :
void gtk_container_remove(GtkContainer *container,
GtkWidget *widget);
sert exactement à cela.
Pour l'esthétique, on veut souvent laisser quelques pixels entre le container et le contenu. Cela peut être réalisé avec :
void gtk_container_set_border_width(GtkContainer *container,
guint largeur_du_bord);
Faites attention avec cette fonction, cependant. En effet, elle ne réagit pas toujours comme on l'imagine. Par exemple, si le container est un GtkFrame (un cadre en relief avec un titre), l'espace est ajouté à l'extérieur du cadre et pas à l'intérieur. Si l'on veut séparer les widgets du bord intérieur du cadre, il faut les inclure dans un autre container et modifier le bord de celui-ci. Cela ne devrait pas poser de gros problèmes car les frames contiennent en général plusieurs widgets qui sont dans un container, comme une table ou une boîte.
Les GtkBins
Avant d'aller plus loin, il faut savoir qu'il existe deux grandes catégories de containers : les GtkBins et les autres. Les GtkBins ne peuvent contenir qu'un seul autre widget. Et donc, des appels successifs à gtk_container_add() ne rajoutent pas de nouveaux widgets, mais en fait, seul le dernier widget ajouté est vraiment contenu dans le GtkBin. Les autres deviennent orphelins. Malgré ce comportement étrange, les GtkBins sont extrêmement fréquents. Les GTkFrames, les GtkWindows, les GtkButtons, les GtkMenuItems et leurs dérivés sont tous des GTkBins. Cela signifie que l'on peut ajouter un widget, et un seul, à l'intérieur de chacun de ces types.
Voyons cela dans un exemple :
/* Bins.c */
#include <gtk/gtk.h>
int main(int argc, char *argv[])
{
GtkWidget *Fenetre;
GtkWidget *Bouton;
GtkWidget *Frame;
GtkWidget *EventBox;
GtkWidget *Label;
gtk_init(&argc, &argv);
/* La fenêtre */
Fenetre = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_container_set_border_width(GTK_CONTAINER(Fenetre), 10);
/* Le bouton */
Bouton = gtk_button_new();
gtk_container_add(GTK_CONTAINER(Fenetre), Bouton);
gtk_container_set_border_width(GTK_CONTAINER(Bouton), 5);
/* La frame */
Frame = gtk_frame_new("coucou");
gtk_container_add(GTK_CONTAINER(Bouton), Frame);
/* L'event box */
EventBox = gtk_event_box_new();
gtk_container_add(GTK_CONTAINER(Frame), EventBox);
gtk_container_set_border_width(GTK_CONTAINER(EventBox), 20);
/* Le label */
Label = gtk_label_new("Hello, monde");
gtk_container_add(GTK_CONTAINER(EventBox), Label);
/* On montre le tout */
gtk_widget_show_all(Fenetre);
/* Et on laisse GTK faire la suite du boulot */
gtk_main();
return 0;
}
à compiler avec :
gcc Bins.c -o Bins `gtk-config --cflags --libs`
ou
gcc Bins.c -o Bins $(gtk-config --cflags --libs)
L'exemple précédent montre une fenêtre contenant un bouton contenant une frame contenant un event box contenant un label ne contenant rien puisque les labels ne sont pas des containers. On voit sur cet exemple (fig. 1) que les GtkBins sont empilables comme des poupées russes et qu'il est donc un peu impropre de dire que les GtkBins ne peuvent pas contenir plus d'un seul widget. En fait, ils ne peuvent pas contenir plus d'un seul widget au niveau juste en dessous.
fig.1
Les Containers qui contiennent plus d'un widget
Les containers servant à ranger les widgets comme les boîtes, les tables, les packers ou les paneds contiennent en général plusieurs widgets qu'ils doivent placer côte à côte et non pas les uns dans les autres. Autrement dit, ils contiennent plusieurs enfants directs.
La liste des enfants d'un tel container peut être obtenue grâce à la fonction suivante :
GList *gtk_container_children(GtkContainer *container);
La valeur renvoyée est un pointeur sur le premier élément d'une liste doublement chaînée. Mais, en général, lorsque l'on a besoin de la liste des enfants d'un container, c'est que l'on veut leur appliquer un traitement quelconque. Pour cela, on utilisera plutôt la fonction suivante :
void gtk_container_foreach(GtkContainer *Container,
GtkCallback FonctionRappel,
gpointer DonneeSup);
où Container est notre container, FonctionRappel est une fonction qui sera appelée pour tous les widgets enfants et DonneeSup est ce qui sera transmis comme deuxième paramètre à la fonction FonctionRappel (le premier étant le widget bien évidemment). Ainsi l'appel suivant :
gtk_container_foreach(GTK_CONTAINER(Boite),
(GtkCallback)MaFonction,"coucou");
est équivalent à :
{
GList *List, *TmpList;
List = gtk_container_children(GTK_CONTAINER(boite));
for (TmpList = List ; TmpList ; TmpList = TmpList- >next)
MaFonction(GTK_WIDGET(TmpList->data), "coucou");
}
mais est nettement plus court...
L'exemple suivant présente une utilisation possible de cette fonction ainsi qu'une manière de placer un pixmap dans un bouton avant le label. L'astuce consiste à créer un bouton vide, dans lequel on place une boîte horizontale. C'est la boîte qui contient le pixmap et le label :
/* PixmapBouton.c */
#include <stdio.h>
#include <gtk/gtk.h>
static char *Icon[] = {
/* width height num_colors chars_per_pixel */
" 10 9 2 1",
/* colors */
". c None",
"y c #ff1f00",
/* pixels */
"..........",
"........yy",
".......yy.",
"......yy..",
".yyy.yy...",
"..yyyy....",
"...yy.....",
"..........",
"..........",
};
void Affiche(GtkWidget *widget)
{
printf("Un %s a été trouvé dans la boîte\n",
gtk_widget_get_name(widget));
}
int main(int argc, char *argv[])
{
GtkWidget *Fenetre;
GtkWidget *Bouton;
GtkWidget *HBox;
GtkWidget *Pixmap;
GtkWidget *Label;
GdkPixmap *PixmapIcon;
GdkBitmap *MaskIcon;
GdkColor transparent;
gtk_init(&argc, &argv);
/* La fenêtre */
Fenetre = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_container_set_border_width(GTK_CONTAINER(Fene tre), 10);
gtk_widget_realize(Fenetre);
/* Le bouton */
Bouton = gtk_button_new();
gtk_container_add(GTK_CONTAINER(Fenetre), Bouton);
/* On place une hbox dans le bouton */
HBox = gtk_hbox_new(FALSE, 3);
gtk_container_add(GTK_CONTAINER(Bouton), HBox);
gtk_container_set_border_width(GTK_CONTAINER(HBox), 5);
/* Le pixmap */
PixmapIcon = gdk_pixmap_create_from_xpm_d(Fenetre->window,
&MaskIcon,
&transparent,
Icon);
Pixmap = gtk_pixmap_new(PixmapIcon, MaskIcon);
gdk_pixmap_unref(PixmapIcon);
gtk_box_pack_start_defaults(GTK_BOX(HBox), Pixmap);
/* Le label */
Label = gtk_label_new("Click");
gtk_box_pack_start_defaults(GTK_BOX(HBox), Label);
/* On montre le tout */
gtk_widget_show_all(Fenetre);
/* On affiche le contenu de la boite */
gtk_container_foreach(GTK_CONTAINER(HBox),
(GtkCallback)Affiche, NULL);
/* Et on laisse GTK faire la suite du boulot */
gtk_main();
return 0;
}
à compiler comme d'habitude avec :
gcc PixmapBouton.c -o PixmapBouton `gtk-config --cflags --libs`
ou
gcc PixmapBouton.c -o PixmapBouton $(gtk-config --cflags --libs)
Notez bien que l'on peut déréférencer le GdkPixmap dès que la création du GtkPixmap, car celui-ci en génère une copie en interne. Vous pouvez facilement reprendre le bout de code de création des pixmaps pour vos propres applications. En effet, le tableau Icon est simplement un fichier .xpm à peine modifié. Je suis sûr que vos machines regorgent de tels fichiers. Vous devriez donc arriver à trouver comment obtenir des boutons contenant les images de votre choix.
La prochaine fois
Eh oui, c'est tout pour aujourd'hui. Je continuerais bien un peu, mais je risquerais de dépasser la place qui m'est impartie. La prochaine fois, dans notre tour d'horizon des widgets, j'aimerais bien vous parler des menus. Comment on les fait en GTK+ et tout ça. D'ici là, faites-moi parvenir vos oeuvres et prenez de l'avance sur les autres lecteurs en me posant des questions. (Je réponds d'autant plus vite que la question est précise...)
David.Odin@bigfoot.com