Didacticiel Ian Main, slow@intergate.bc.ca January 24, 1998. 11.. IInnttrroodduuccttiioonn GTK (GIMP Toolkit) a t d'abord dvelopp pour tre une bote outils pour GIMP (General Image Manipulation Program). GTK est construit sur GDK (GIMP Drawing Kit) qui est, avant tout, une encapsulation des fonctions Xlib. On l'appelle GIMP toolkit car il fut cr pour dvelopper GIMP, mais il est dsormais utilis dans plusieurs projets de logiciels libres. Les auteurs sont : +o Peter Mattis petm@xcf.berkeley.edu +o Spencer Kimball spencer@xcf.berkeley.edu +o Josh MacDonald jmacd@xcf.berkeley.edu GTK est essentiellement une interface de programmation (API) oriente objet. Bien qu'il soit entirement crit en C, il est implant en utilisant la notion de classes et de fonctions de rappel (pointeurs de fonctions). Un troisime composant, appel glib, remplace certains appels standard et comporte quelques fonctions supplmentaires pour grer les listes chanes, etc. Les fonctions de remplacement sont utilises pour accrotre la portabilit de GTK car certaines de ces fonctions, comme g_strerror(), ne sont pas disponibles ou ne sont pas standard sur d'autres Unix. D'autres comportent des amliorations par rapport aux versions de la libc : g_malloc(), par exemple, facilite le dbuggage. Ce didacticiel tente de dcrire du mieux possible GTK, mais il n'est pas exhaustif. Il suppose une bonne connaissance du langage C, et de la faon de crer des programmes C. Il serait trs prcieux au lecteur d'avoir dj une exprience de la programmation X, mais cela n'est pas ncessaire. Si l'apprentissage de GTK marque vos dbuts dans l'approche des widgets, n'hsitez pas faire des commentaires sur ce didacticiel et sur les problmes qu'il vous a pos. Il y a aussi une API C++ pour GTK (GTK--), si vous prfrez utiliser ce langage, consultez plutt la documentation qui la concerne. Une encapsulation en Objective C et des liaisons Guile sont galement disponibles, mais ne seront pas abordes ici. J'apprcierais beaucoup avoir un cho des problmes que vous avez rencontr pour apprendre GTK partir de ce document. De plus, toute suggestion sur son amlioration est la bienvenue. 22.. BBiieenn ddbbuutteerr La premire chose faire est, bien sr, de rcuprer les sources de GTK et de les installer. Vous pouvez en obtenir la dernire version sur ftp.gimp.org dans le rpertoire /pub/gtk. D'autres sources d'informations se trouvent sur http://www.gimp.org/gtk. GTK utilise _a_u_t_o_c_o_n_f de GNU pour se configurer. Lorsque vous l'aurez dtarr, tapez _._/_c_o_n_f_i_g_u_r_e _-_-_h_e_l_p pour consulter la liste des options. Pour commencer notre introduction GTK, nous dbuterons avec le programme le plus simple qui soit. Celui-ci crera une fentre de 200x200 pixels et ne pourra se terminer qu'en le tuant partir du shell. #include int main (int argc, char *argv[]) { GtkWidget *window; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_show (window); gtk_main (); return 0; } Tous les programmes inclueront videmment le fichier gtk/gtk.h qui dclare les variables, fonctions, structures, etc. qui seront utilises par votre application GTK. La ligne : gtk_init (&argc, &argv); appelle la fonction _g_t_k___i_n_i_t_(_g_i_n_t _*_a_r_g_c_, _g_c_h_a_r _*_*_*_a_r_g_v_) qui sera appele dans toutes les applications GTK. Cette fonction configure certaines choses pour nous, comme l'aspect visuel et les couleurs par dfaut, puis appelle _g_d_k___i_n_i_t_(_g_i_n_t _*_a_r_g_c_, _g_c_h_a_r _*_*_*_a_r_g_v_). Cette dernire initialise la bibliothque pour qu'elle puisse tre utilise, configure les gestionnaires de signaux par dfaut et vrifie les paramtres passs notre application via la ligne de commande en recherchant l'un des suivants : +o --display +o --debug-level +o --no-xshm +o --sync +o --show-events +o --no-show-events Elle les supprime alors de la liste des paramtres, en laissant tout ce qu'elle ne reconnat pas pour que notre application l'analyse ou l'ignore. Ceci cre un ensemble de paramtres standards accepts par toutes les applications GTK. Les deux lignes de code suivantes crent et affichent une fentre. window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_show (window); Le paramtre GTK_WINDOW_TOPLEVEL prcise que l'on veut que la fentre cre suive l'aspect et le placement dfinis par le gestionnaire de fentres. Plutt que de crer une fentre de 0x0, une fentre sans fentre fille est de 200x200 par dfaut : on peut ainsi la manipuler facilement. La fonction _g_t_k___w_i_d_g_e_t___s_h_o_w_(_) informe GTK que l'on a configur le widget et qu'il peut l'afficher. La ligne suivante lance la boucle principale de traitement de GTK. gtk_main (); _g_t_k___m_a_i_n_(_) est un autre appel que vous verrez dans toute application GTK. Lorsque le contrle atteind ce point, GTK se met en attente d'vnements X (click sur un bouton, ou appui d'une touche, par exemple), de timeouts ou d'entres-sorties fichier. Dans notre exemple simple, cependant, les vnements sont ignors. 22..11.. BBoonnjjoouurr ttoouutt llee mmoonnddee eenn GGTTKK OK, crivons un programme avec un widget (bouton). C'est le classique Bonjour tout le monde la sauce GTK. #include /* fonction de rappel. Dans cet exemple, les paramtres sont ignors... * Les fonctions de rappel sont dtailles plus loin. */ void hello (GtkWidget *widget, gpointer data) { g_print ("Bonjour tout le monde.\n"); } gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { g_print ("le signal delete_event est survenu.\n"); /* Si l'on renvoit TRUE dans le gestionnaire du signal "delete_event", * GTK mettra le signal "destroy". Retourner FALSE signifie que l'on * ne veut pas que la fentre soit dtruite. * Utilis pour faire apparatre des botes de dialogue du type * tes-vous sr de vouloir quitter ? */ /* Remplacez FALSE par TRUE et la fentre principale sera dtruite par * un signal delete_event . */ return (FALSE); } /* Autre fonction de rappel */ void destroy (GtkWidget *widget, gpointer data) { gtk_main_quit (); } int main (int argc, char *argv[]) { /* GtkWidget est le type pour dclarer les widgets. */ GtkWidget *window; GtkWidget *button; /* Cette fonction est appele dans toutes les applications GTK. * Les paramtres passs en ligne de commande sont analyss et * retourns l'application. */ gtk_init (&argc, &argv); /* Cration d'une nouvelle fentre. */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* Lorsque la fentre reoit le signal "delete_event" * (envoy par le gestionnaire de fentres en utilisant l'option * close ou la barre de titre), on lui demande d'appeler la * fonction delete_event() dfinie plus haut. La donne passe en * paramtre la fonction de rappel est NULL et est ignor dans le * rappel. */ gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (delete_event), NULL); /* Ici, on connecte l'venement "destroy" un gestionnaire de signal. * Cet vnement arrive lorsqu'on appelle gtk_widget_destroy() sur la * fentre, ou si l'on retourne TRUE dans le rappel "delete_event". */ gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (destroy), NULL); /* Configuration de la largeur du contour de la fentre. */ gtk_container_border_width (GTK_CONTAINER (window), 10); /* Cration d'un nouveau bouton portant le label * "Bonjour tout le monde". */ button = gtk_button_new_with_label ("Bonjour tout le monde"); /* Quand le bouton recevra le signal "clicked", il appellera la * fonction hello() dfinie plus haut en lui passant NULL en paramtre. */ gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (hello), NULL); /* Ceci provoquera la destruction de la fentre par appel de la * fonction gtk_widget_destroy(window) lors du signal "clicked". * Le signal de destruction pourrait venir de l, ou du * gestionnaire de fentres. */ gtk_signal_connect_object (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (window)); /* Insertion du bouton dans la fentre (container gtk). */ gtk_container_add (GTK_CONTAINER (window), button); /* L'tape finale consiste afficher ce nouveau widget... */ gtk_widget_show (button); /* ... et la fentre. */ gtk_widget_show (window); /* Toutes les applications GTK doivent avoir un gtk_main(). * Le droulement du programme se termine l et attend qu'un * vnement survienne (touche presse ou vnement souris). */ gtk_main (); return 0; } 22..22.. CCoommppiillaattiioonn ddee BBoonnjjoouurr ttoouutt llee mmoonnddee Supposons que vous avez sauvegard le code prcdent dans un fichier nomm _b_o_n_j_o_u_r_._c, pour le compiler tapez la commande suivante : gcc -Wall -g bonjour.c -o bonjour_monde -L/usr/X11R6/lib \ -lgtk -lgdk -lglib -lXext -lX11 -lm Les bibliothques invoques ci-dessus doivent toutes tre dans vos chemins de recherche par dfaut, sinon, ajoutez -L pour que _g_c_c recherche dans ces rpertoires les bibliothques ncessaires. Sur mon systme Debian GNU/Linux, par exemple, je dois ajouter -L/usr/X11R6/lib pour qu'il trouve les bibliothques X11 (NdT : et c'est pareil sur mon systme Red Hat Linux...). L'ordre des bibliothques est important. L'diteur de liens doit connatre les fonctions d'une bibliothque dont il a besoin avant de les traiter. Si vous compilez en utilisant des bibliothques statiques, l'ordre dans lequel vous listez les bibliothques devient trs important. L'exemple donn ci-dessus devrait fonctionner dans tous les cas. Les bibliothques que l'on utilise sont : +o La bibliothque glib (-lglib), qui contient diverses fonctions. Seule _g___p_r_i_n_t_(_) est utilise dans cet exemple. GTK est construit au dessus de _g_l_i_b et vous aurez donc toujours besoin de celle-ci. Voir la section concernant ``glib'' pour plus de dtails. +o La bibliothque GDK (-lgdk), l'enveloppe de Xlib. +o La bibliothque GTK (-lgtk), la bibliothque des widgets, construite au dessus de GDK. +o La bibliothque Xlib (-lX11 utilise par GDK. +o La bibliothque Xext (-lXext). Cette dernire contient le code pour les pixmaps en mmoire partage et les autres extensions X. +o La bibliothque mathmatique (-lm). Elle est utilise pour diffrentes raisons par GTK. 22..33.. TThhoorriiee ddeess ssiiggnnaauuxx eett ddeess rraappppeellss Avant de voir en dtail le programme Bonjour tout le monde , nous parlerons d'abord des vnements et des fonctions de rappel. GTK est dirig par les vnements, ce qui signifie qu'il restera inactif dans _g_t_k___m_a_i_n jusqu' ce qu'un vnement survienne et que le contrle soit pass la fonction approprie. Ce passage du contrle est ralis en utilisant le concept de signal . Lorsqu'un vnement survient, comme l'appui sur un bouton, le signal appropri sera mis par le widget qui a t press. C'est de cette faon que GTK ralise la plupart de son travail. Pour qu'un bouton ralise une action, on configure un gestionnaire de signal pour capturer ces signaux et appeler la fonction adquate. Ceci est fait en utilisant une fonction comme : gint gtk_signal_connect (GtkObject *object, gchar *name, GtkSignalFunc func, gpointer func_data); O le premier paramtre est le widget qui mettra le signal, et le deuxime est le nom du signal que l'on souhaite intercepter. Le troisime paramtre est la fonction que l'on veut appeler quand le signal est captur, et le quatrime sont les donnes que l'on souhaite passer cette fonction. La fonction spcifie par le troisime paramtre s'appelle une fonction de rappel et doit tre de la forme : void callback_func(GtkWidget *widget, gpointer *callback_data); O le premier paramtre sera un pointeur vers le widget qui a mis le signal, et le second un pointeur vers les donnes passes par le dernier paramtre de la fonction _g_t_k___s_i_g_n_a_l___c_o_n_n_e_c_t_(_) dcrite plus haut. Un autre appel utilis dans l'exemple Bonjour tout le monde est : gint gtk_signal_connect_object (GtkObject *object, gchar *name, GtkSignalFunc func, GtkObject *slot_object); _g_t_k___s_i_g_n_a_l___c_o_n_n_e_c_t___o_b_j_e_c_t_(_) est la mme chose que _g_t_k___s_i_g_n_a_l___c_o_n_n_e_c_t_(_) sauf que la fonction de rappel utilise un seul paramtre : un pointeur vers un objet GTK. Lorsqu'on utilise cette fonction pour connecter des signaux, le rappel doit tre de cette forme : void callback_func (GtkObject *object); O l'objet est d'ordinaire un widget. En gnral, on ne configure pas de rappels pour _g_t_k___s_i_g_n_a_l___c_o_n_n_e_c_t___o_b_j_e_c_t. D'habitude, ceux-ci sont utiliss pour appeler une fonction GTK acceptant un simple widget ou objet comme paramtre, comme dans notre exemple. La raison pour laquelle il y a deux fonctions pour connecter les signaux est simplement de permettre aux fonctions de rappel d'avoir un nombre diffrent de paramtres. De nombreuses fonctions de la bibliothque GTK n'acceptent qu'un simple pointeur vers un _G_t_k_W_i_d_g_e_t comme paramtre et vous pouvez donc utiliser _g_t_k___s_i_g_n_a_l___c_o_n_n_e_c_t___o_b_j_e_c_t_(_) pour celles-ci, tandis que pour vos fonctions vous pouvez avoir besoin d'avoir de fournir plus de donnes aux fonctions de rappel. 22..44.. BBoonnjjoouurr ttoouutt llee mmoonnddee ppaass ppaass Maintenant que nous connaissons la thorie, clarifions un peu en progressant travers le programme Bonjour tout le monde . Voici la fonction de rappel appele lorsque le bouton est clicked . Dans notre exemple, on ignore le widget et les donnes mais il n'est pas difficile de faire quelque chose avec. Le prochain exemple utilisera le paramtre des donnes pour nous dire quel bouton a t press. void hello (GtkWidget *widget, gpointer *data) { g_print ("Bonjour tout le monde\n"); } Cette fonction de rappel est un peu spciale. L'vnement "delete_event" survient lorsque le gestionnaire de fentres l'envoie l'application. On doit choisir ce qu'il faut faire de ces vnements. On peut les ignorer, leur donner une rponse, ou simplement quitter l'application. La valeur que l'on retourne dans cette fonction de rappel permet GTK de savoir ce qu'il a faire. En retournant FALSE, on l'informe que l'on ne veut pas que le signal "destroy" soit mis, afin de laisser notre application tourner. En retournant TRUE, on lui demande d'mettre "destroy" qui appellera son tour notre gestionnaire du signal "destroy". gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data) { g_print ("le signal delete_event est survenu.\n"); return (FALSE); } Voici une autre fonction de rappel qui ne fait que quitter l'application en appelant _g_t_k___m_a_i_n___q_u_i_t_(_). Il n'y a pas grand chose de plus dire car elle est plutt triviale : void destroy (GtkWidget *widget, gpointer *data) { gtk_main_quit (); } Je suppose que vous connaissez la fonction _m_a_i_n_(_)... oui, comme les autres programmes C, toutes les applications GTK en ont une. int main (int argc, char *argv[]) { La partie qui suit dclare deux pointeurs sur des structures de type _G_t_k_W_i_d_g_e_t. Ceux-ci sont utiliss plus loin pour crer une fentre et un bouton. GtkWidget *window; GtkWidget *button; Et revoici notre _g_t_k___i_n_i_t. Comme prcdemment, il initialise le toolkit et analyse les paramtres de la ligne de commande. Il supprime chaque paramtre reconnu de la liste et modifie _a_r_g_c et _a_r_g_v pour faire comme si ces paramtres n'avaient jamais exist, laissant notre application analyser les paramtres restants. gtk_init (&argc, &argv); Cration d'une nouvelle fentre. C'est plutt classique. La mmoire est alloue pour une structure _G_t_k_W_i_d_g_e_t et _w_i_n_d_o_w pointe donc sur celle- ci. Cela configure une nouvelle fentre, mais celle-ci ne sera pas affiche tant que l'on n'a pas appel _g_t_k___w_i_d_g_e_t___s_h_o_w_(_w_i_n_d_o_w_) vers la fin de notre programme. window = gtk_window_new (GTK_WINDOW_TOPLEVEL); Voici maintenant un exemple de connexion d'un gestionnaire de signal un objet : la fentre. Le signal "destroy" est captur. Il est mis lorsqu'on utilise le gestionnaire de fentres pour tuer la fentre (et que l'on retourne TRUE dans le gestionnaire "delete_event"), ou lorsqu'on utilise l'appel _g_t_k___w_i_d_g_e_t___d_e_s_t_r_o_y_(_) en lui passant le widget _w_i_n_d_o_w comme objet dtruire. Ici, on appelle juste la fonction _d_e_s_t_r_o_y_(_) dfinie ci-dessus avec le paramtre NULL, ce qui quitte GTK pour nous. GTK_OBJECT et GTK_SIGNAL_FUNC sont des macros qui ralisent les conversions et les vrifications de types pour nous. Elles rendent aussi le code plus lisible. gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (destroy), NULL); La fonction suivante sert configurer un attribut d'un objet container. Elle configure simplement la fentre pour qu'elle ait une zone vide autour d'elle de 10 pixels de large o aucun widget ne pourra se trouver. Il existe d'autres fonctions similaires que nous verrons dans la section sur la ``Configuration des attributs des widgets'' nouveau, GTK_CONTAINER est une macro ralisant la conversion de type. gtk_container_border_width (GTK_CONTAINER (window), 10); Cet appel cre un nouveau bouton. Il alloue l'espace mmoire pour une nouvelle structure GtkWidget, l'initialise et fait pointer _b_u_t_t_o_n vers elle. Ce bouton portera le label Bonjour tout le monde lorsqu'il sera affich. button = gtk_button_new_with_label ("Bonjour tout le monde"); Maintenant, prenons ce bouton et faisons lui faire quelque chose d'utile. On lui attache un gestionnaire de signal pour que, lorsqu'il mettra le signal "clicked", notre fonction _h_e_l_l_o_(_) soit appele. On ignore les paramtres et on ne passe donc que la valeur NULL la fonction de rappel _h_e_l_l_o_(_). videmment, le signal "clicked" est mis lorsqu'on clique sur le bouton avec la souris. gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (hello), NULL); On utilisera aussi ce bouton pour quitter notre programme, ce qui permettra d'illustrer la faon dont le signal "destroy" peut venir soit du gestionnaire de fentres, soit de notre programme. Quand le bouton est "clicked" comme cela est dcrit plus haut, il appelle d'abord la fonction de rappel _h_e_l_l_o_(_) puis celle-ci dans l'ordre dans lequel elles sont configures. On peut avoir autant de fonctions de rappel que l'on dsire, elles seront excutes selon leur ordre de connexion. Puisque la fonction _g_t_k___w_i_d_g_e_t___d_e_s_t_r_o_y_(_) n'accepte que _G_t_k_W_i_d_g_e_t _*_w_i_d_g_e_t comme paramtre, on utilise ici la fonction _g_t_k___s_i_g_n_a_l___c_o_n_n_e_c_t___o_b_j_e_c_t_(_) la place de _g_t_k___s_i_g_n_a_l___c_o_n_n_e_c_t_(_). gtk_signal_connect_object (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (window)); Voici un appel de placement, qui sera expliqu en dtail plus tard, mais qui est plutt facile comprendre. Il indique simplement GTK que le bouton doit tre plac dans la fentre o il s'affichera. gtk_container_add (GTK_CONTAINER (window), button); Maintenant, nous avons tout configur comme on le souhaitait : les gestionnaires de signaux sont en place et le bouton est mis dans la fentre o il doit se trouver. On demande alors GTK de montrer les widgets l'cran. Le widget _w_i_n_d_o_w est affich en dernier afin que la fentre entire surgisse d'un coup plutt que voir d'abord la fentre s'afficher puis ensuite le bouton apparatre l'intrieur. Il faut dire qu'avec des exemples simples comme celui-ci, vous ne ferez pas la diffrence. gtk_widget_show(button); gtk_widget_show (window); Bien sr, on appelle _g_t_k___m_a_i_n_(_) qui attendra les vnements venant du serveur X et demandera aux widgets d'mettre les signaux lorsque ces vnements surviendront. gtk_main (); Enfin, le _r_e_t_u_r_n final. Il est excut lorsque _g_t_k___q_u_i_t_(_) est appel. return 0; Lorsque l'on clique sur un bouton GTK, le widget met un signal "clicked". Afin de pouvoir utiliser cette information, notre programme configure un gestionnaire pour capturer ce signal. Ce gestionnaire appelle la fonction de notre choix. Dans notre exemple, lorsque le bouton que l'on a cr est "clicked", la fonction _h_e_l_l_o_(_) est appele avec le paramtre NULL, puis le gestionnaire suivant de ce signal est son tour appel. Il appelle la fonction _g_t_k___w_i_d_g_e_t___d_e_s_t_r_o_y_(_) en lui passant le widget _w_i_n_d_o_w comme paramtre, ce qui provoque la destruction de celui-ci. Ceci force la fentre envoyer un signal "destroy", qui est captur son tour et appelle notre fonction de rappel _d_e_s_t_r_o_y_(_) qui ferme simplement GTK. Une autre faon de procder consiste utiliser le gestionnaire de fentres pour dtruire la fentre. Cela provoquera l'mission du signal "delete_event" qui sera pris en charge par notre gestionnaire _d_e_l_e_t_e___e_v_e_n_t_(_). S'il retourne FALSE, la fentre restera telle quelle et rien ne se passera. Retourner TRUE forcera GTK mettre le signal "destroy" qui, bien sr, appelera la fonction de rappel _d_e_s_t_r_o_y_(_) provoquant la sortie du GTK. On remarquera que ces signaux ne sont pas les mmes que les signaux systmes Unix et ne sont pas implants en utilisant ceux-ci, bien que la terminologie employe soit presque identique. 33.. CCoonnttiinnuuoonnss 33..11.. TTyyppeess ddee ddoonnnneess Vous avez probablement not certaines choses qui ncessitent des explications dans les exemples prcdents. les _g_i_n_t, _g_c_h_a_r, etc. que vous avez pu voir sont des redfinitions de _i_n_t et _c_h_a_r, respectivement. Leur raison d'tre est de s'affranchir des dpendances ennuyeuses concernant la taille des types de donnes simples lorsqu'on ralise des calculs. Un bon exemple est _g_i_n_t_3_2 qui dsignera un entier cod sur 32 bits pour toutes les plateformes, que ce soit une station Alpha 64 bits ou un PC i386 32 bits. Les redfinitions de type sont trs simples et intuitives. Elles sont toutes dcrites dans le fichier _g_l_i_b_/_g_l_i_b_._h (qui est inclus par _g_t_k_._h). On notera aussi la possibilit d'utiliser un _G_t_k_W_i_d_g_e_t lorsque la fonction attend un _G_t_k_O_b_j_e_c_t. GTK possde une architecture oriente objet, et un widget est un objet. 33..22.. CCoommppllmmeennttss ssuurr lleess ggeessttiioonnnnaaiirreess ddee ssiiggnnaauuxx Regardons nouveau la dclaration de _g_t_k___s_i_g_n_a_l___c_o_n_n_e_c_t. gint gtk_signal_connect (GtkObject *object, gchar *name, GtkSignalFunc func, gpointer func_data); Vous avez remarqu que le valeur de retour est de type _g_i_n_t ? Il s'agit d'un marqueur qui identifie votre fonction de rappel. Comme on le disait plus haut, on peut avoir autant de fonctions de rappel que l'on a besoin, par signal et par objet, et chacune sera excute son tour, dans l'ordre dans lequel elle a t attache. Ce marqueur vous permet d'ter ce rappel de la liste en faisant &;: void gtk_signal_disconnect (GtkObject *object, gint id); Ainsi, en passant le widget dont on veut supprimer le gestionnaire et le marqueur ou identificateur retourn par l'une des fonctions _s_i_g_n_a_l___c_o_n_n_e_c_t, on peut dconnecter un gestionnaire de signal. Une autre fonction permettant de supprimer tous les gestionnaires de signaux pour un objet est : gtk_signal_handlers_destroy (GtkObject *object); Cet appel n'a pas trop besoin d'explications. Il te simplement tous les gestionnaires de signaux de l'objet pass en paramtre. 33..33.. UUnn BBoonnjjoouurr ttoouutt llee mmoonnddee aammlliioorr tudions une version lgrement amliore avec de meilleurs exemples de fonctions de rappel. Ceci permettra aussi d'introduire le sujet suivant : le placement des wigdets. #include /* Notre nouveau rappel amlior. La donne passe cette fonction est * imprime sur stdout. */ void rappel (GtkWidget *widget, gpointer *data) { g_print ("Re-Bonjour - %s a t press\n", (char *) data); } /* Un autre rappel */ void delete_event (GtkWidget *widget, GdkEvent *event, gpointer *data) { gtk_main_quit (); } int main (int argc, char *argv[]) { /* GtkWidget est le type pour dclarer les widgets */ GtkWidget *window; GtkWidget *button; GtkWidget *box1; /* Cette fonction est appele dans toutes les applications GTK. * Les paramtre passs en ligne de commande sont analyss et * retourns l'application. */ gtk_init (&argc, &argv); /* Cration d'une nouvelle fentre. */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* Nouvel appel qui intitule notre nouvelle fentre * "Salut les boutons !" */ gtk_window_set_title (GTK_WINDOW (window), "Salut les boutons !"); /* Configuration d'un gestionnaire pour "delete_event" afin de * quitter immdiatement GTK. */ gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (delete_event), NULL); /* Configuration de la largeur du contour de la fentre. */ gtk_container_border_width (GTK_CONTAINER (window), 10); /* Cration d'une bote pour y placer les widgets. * Ceci est dcrit en dtails plus loin dans la section * placement . La bote n'est pas matrialise, elle est juste * utilise comme moyen d'arranger les widgets. */ box1 = gtk_hbox_new(FALSE, 0); /* On met la bote dans la fentre principale. */ gtk_container_add (GTK_CONTAINER (window), box1); /* On cre un nouveau bouton portant le label Bouton 1 . */ button = gtk_button_new_with_label ("Bouton 1"); /* Lorsque le bouton est cliqu, on appelle la fonction rappel * avec un pointeur sur la chane Bouton 1 comme paramtre. */ gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (rappel), (gpointer) "Bouton 1"); /* Au lieu d'utiliser gtk_container_add, on place ce bouton dans * la bote invisible qui a t place dans la fentre. */ gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0); /* N'oubliez jamais cette tape qui indique GTK que la configuration * de ce bouton est termine et qu'il peut tre affich. */ gtk_widget_show(button); /* On fait la mme chose pour crer un deuxime bouton. */ button = gtk_button_new_with_label ("Bouton 2"); /* On appelle la mme fonction de rappel avec un paramtre diffrent, * un pointeur sur la chane Bouton 2 . */ gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (rappel), (gpointer) "Bouton 2"); gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0); /* L'ordre dans lequel on affiche les boutons n'est pas vraiment * important, mais il est prfrable d'afficher la fentre en dernier * pour qu'elle surgisse d'un coup. */ gtk_widget_show(button); gtk_widget_show(box1); gtk_widget_show (window); /* Le reste est dans gtk_main et on attend que la fte commence ! */ gtk_main (); return 0; } Compilez ce programme en utilisant les mmes paramtres que pour l'exemple prcdent. Vous remarquerez que, maintenant, il est plus difficile de quitter le programme : vous devez utiliser le gestionnaire de fentres ou une commande shell pour le dtruire. Un bon exercice pour le lecteur serait d'insrer un troisime bouton Quitter qui permettrait de sortir du programme. Vous pouvez aussi jouer avec les options de _g_t_k___b_o_x___p_a_c_k___s_t_a_r_t_(_) en lisant la section suivante. Essayez de redimensionner la fentre, et observez son comportement. Juste une remarque : il existe une autre constante utilisable avec _g_t_k___w_i_n_d_o_w___n_e_w_(_) - GTK_WINDOW_DIALOG. Ceci permet d'interagir de faon un peu diffrente avec le gestionnaire de fentres et doit tre utilis pour les fentres temporaires comme les botes de dialogue, par exemple. 44.. PPllaacceemmeenntt ddeess wwiiddggeettss Lorsqu'on cre une application, on veut mettre plus qu'un simple bouton dans une fentre. Notre premier exemple Bonjour le monde n'utilisait qu'un seul widget et on pouvait donc simplement faire un appel _g_t_k___c_o_n_t_a_i_n_e_r___a_d_d pour placer le widget dans la fentre. Mais si l'on dsire en mettre plus, comment peut-on contrler l'endroit o le widget sera positionn ? C'est ici que le placement entre en jeu. 44..11.. TThhoorriiee ddeess bbootteess ddee ppllaacceemmeenntt La majeure partie du placement est faites en crant des botes comme dans l'exemple ci-dessus. Ce sont des widgets containers invisibles o l'on peut placer nos widgets. Elles existent sous deux formes : botes horizontales et botes verticales. Lorsque l'on place des widgets dans une bote horizontale, les objets sont insrs horizontalement de gauche droite ou de droite gauche selon l'appel utilis. Dans une bote verticale, les widgets sont placs de haut en bas ou vice versa. On peut utiliser n'importe quelle combinaison de botes l'intrieur ou ct d'autres botes pour crer l'effet dsir. Pour crer une nouvelle bote horizontale, on appelle _g_t_k___h_b_o_x___n_e_w_(_), et pour les botes verticales, _g_t_k___v_b_o_x___n_e_w_(_). Les fonctions _g_t_k___b_o_x___p_a_c_k___s_t_a_r_t_(_) et _g_t_k___b_o_x___p_a_c_k___e_n_d_(_) servent placer les objets l'intrieur de ces containers. La fonction _g_t_k___b_o_x___p_a_c_k___s_t_a_r_t_(_) placera de haut en bas dans une bote verticale et de gauche droite dans une bote horizontale. _g_t_k___b_o_x___p_a_c_k___e_n_d_(_) fera le contraire en plaant de bas en haut et de droite gauche. En utilisant ces fonctions, on peut aligner droite ou gauche nos widgets et mme les mlanger de n'importe quelle faon pour obtenir l'effet dsir. Dans la plupart de nos exemples, on utilisera _g_t_k___b_o_x___p_a_c_k___s_t_a_r_t_(_). Un objet peut tre un autre container ou un widget. En fait, de nombreux widgets (dont les boutons) sont eux-mmes des containers, mais on utilise gnralement seulement un label dans un bouton. En utilisant ces appels, GTK sait o vous voulez placer vos widgets et il peut donc les dimensionner automatiquement et faire d'autres choses bien pratiques. Il existe aussi plusieurs options permettant de prciser comment les widgets doivent tre placs. Comme vous pouvez l'imaginer, cette mthode nous donne pas mal de libert pour placer et crer les widgets. 44..22.. DDttaaiillss ssuurr lleess bbootteess cause de cette libert, le placement des botes avec GTK peut paratre droutant au premier abord. Il existe beaucoup d'options et il n'est pas tout de suite vident de comprendre comment elles s'accordent toutes ensemble. En fait, il y a 5 styles de base diffrents. Box Packing Example Image Chaque ligne contient une bote horizontale (_h_b_o_x) contenant plusieurs boutons. L'appel _g_t_k___b_o_x___p_a_c_k indique la faon dont sont placs tous les boutons dans la hbox. Chaque bouton est plac dans la hbox de la mme faon (mmes paramtres que la fonction _g_t_k___b_o_x___p_a_c_k___s_t_a_r_t_(_)). Voici la dclaration de la fonction _g_t_k___b_o_x___p_a_c_k___s_t_a_r_t. void gtk_box_pack_start (GtkBox *box, GtkWidget *child, gint expand, gint fill, gint padding); Le premier paramtre est la bote dans laquelle on place l'objet, le second est cet objet. Tous les objets sont tous des boutons jusqu' maintenant, on place donc des boutons dans des botes. Le paramtre _e_x_p_a_n_d de _g_t_k___b_o_x___p_a_c_k___s_t_a_r_t_(_) ou _g_t_k___b_o_x___p_a_c_k___e_n_d_(_) contrle la faon dont le widget est plac dans la bote. S'il vaut TRUE, les widgets sont disposs dans la bote de faon en occuper tout l'espace. S'il vaut FALSE, la bote est rtrcie pour correspondre la taille du widget. Mettre _e_x_p_a_n_d FALSE vous permettra d'aligner droite et gauche vos widgets. Sinon, ils s'largiront pour occuper toute la bote. Le mme effet pourrait tre obtenu en utilisant uniquement une des deux fonctions _g_t_k___b_o_x___p_a_c_k___s_t_a_r_t ou _p_a_c_k___e_n_d. Le paramtre _f_i_l_l des fonctions _g_t_k___b_o_x___p_a_c_k contrle si de l'espace supplmentaire doit tre allou aux objets eux-mmes (TRUE), ou si on doit rajouter de l'espace (_p_a_d_d_i_n_g) dans la bote autour des objets (FALSE). Il n'a de sens que si le paramtre _e_x_p_a_n_d vaut TRUE. Lorsque l'on cre une nouvelle bote, on utilise une fonction comme : GtkWidget * gtk_hbox_new (gint homogeneous, gint spacing); Le paramtre _h_o_m_o_g_e_n_e_o_u_s de _g_t_k___h_b_o_x___n_e_w (et c'est la mme chose pour _g_t_k___v_b_o_x___n_e_w) vrifie que chaque objet de la bote ait la mme taille (i.e. la mme largeur dans une hbox, la mme hauteur dans une vbox). S'il vaut TRUE, le paramtre _e_x_p_a_n_d des fonctions _g_t_k___b_o_x___p_a_c_k sera toujours mis TRUE. Quelle est alors la diffrence entre les paramtres _s_p_a_c_i_n_g (configur lorsque la bote est cre) et _p_a_d_d_i_n_g (configur lorque les lments sont placs) ? _s_p_a_c_i_n_g ajoute de l'espace entre les objets, et _p_a_d_d_i_n_g en ajoute de chaque ct d'un objet. La figure suivante devrait clairer tout cela : Box Packing Example Image Voici le code utilis pour crer les images ci-dessus. J'y ai mis beaucoup de commentaires en esprant que vous n'aurez pas de problme pour le relire. Compilez-le et jouez avec les diffrents paramtres. 44..33.. PPrrooggrraammmmee ddee ddmmoonnssttrraattiioonn ddeess ppllaacceemmeennttss #include "gtk/gtk.h" void delete_event (GtkWidget *widget, GdkEvent *event, gpointer *data) { gtk_main_quit (); } /* Construction d'une nouvelle hbox remplie de boutons. Les paramtres qui * nous intressent sont passs cette fonction. * On n'affiche pas la bote, mais tout ce qu'elle contient. */ GtkWidget *make_box (gint homogeneous, gint spacing, gint expand, gint fill, gint padding) { GtkWidget *box; GtkWidget *button; char padstr[80]; /* Cration d'une hbox avec les paramtres homogeneous et spacing * voulus. */ box = gtk_hbox_new (homogeneous, spacing); /* Cration d'une srie de boutons configurs de faon approprie */ button = gtk_button_new_with_label ("gtk_box_pack"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); button = gtk_button_new_with_label ("(box,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); button = gtk_button_new_with_label ("button,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); /* Cration d'un bouton portant un label dpendant de la valeur * du paramtre expand. */ if (expand == TRUE) button = gtk_button_new_with_label ("TRUE,"); else button = gtk_button_new_with_label ("FALSE,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); /* Mme chose que ci-dessus mais sous forme abrge. */ button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,"); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); /* Rcupration du paramtre padding sous forme de chane. */ sprintf (padstr, "%d);", padding); button = gtk_button_new_with_label (padstr); gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding); gtk_widget_show (button); return box; } int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *button; GtkWidget *box1; GtkWidget *box2; GtkWidget *separator; GtkWidget *label; GtkWidget *quitbox; int which; /* Initialisation, ne jamais oublier ! :) */ gtk_init (&argc, &argv); if (argc != 2) { fprintf (stderr, "usage : %s num, o num vaut 1, 2, ou 3.\n", *argv); /* Nettoyage dans GTK et sortie avec un code d'erreur de 1 */ gtk_exit (1); } which = atoi (argv[1]); /* Cration de notre fentre. */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); /* Il ne faut jamais oublier de connecter le signal "destroy" la * fentre principale. C'est trs important pour disposer d'un * comportement intuitif adquat. */ gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (delete_event), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10); /* Cration d'une bote verticale (vbox) pour y placer les botes * horizontales. * Ceci permet de placer les botes horizontales contenant les boutons * les unes au dessus des autres dans cette vbox. */ box1 = gtk_vbox_new (FALSE, 0); /* L'exemple afficher. Ils correspondent aux images ci-dessus. */ switch (which) { case 1: /* Cration d'un label. */ label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); /* Alignement du label gauche. On prcisera cette fonction ainsi * que les autres dans la section sur les attributs des widgets. */ gtk_misc_set_alignment (GTK_MISC (label), 0, 0); /* Placement du label dans la bote verticale (vbox box1). Il ne * faut pas oublier que les widgets qui s'ajoutent une vbox sont * placs les uns au dessus des autres. */ gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); /* Affichage du label */ gtk_widget_show (label); /* On appelle notre fonction de construction de bote : * homogeneous = FALSE, spacing = 0, * expand = FALSE, fill = FALSE, padding = 0 */ box2 = make_box (FALSE, 0, FALSE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* On appelle notre fonction de construction de bote : * homogeneous = FALSE, spacing = 0, * expand = FALSE, fill = FALSE, padding = 0 */ box2 = make_box (FALSE, 0, TRUE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Paramtres : homogeneous = FALSE, spacing = 0, * expand = TRUE, fill = TRUE, padding = 0 */ box2 = make_box (FALSE, 0, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Cration d'un sparateur, on verra cela plus tard, mais ils sont * simples utiliser. */ separator = gtk_hseparator_new (); /* Placement du sparateur dans la vbox. Ne pas oublier que tous les * widgets sont placs dans une vbox et qu'il seront placs * verticalement. */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); /* Cration d'un nouveau label et affichage de celui-ci. */ label = gtk_label_new ("gtk_hbox_new (TRUE, 0);"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0); gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); gtk_widget_show (label); /* Paramtres : homogeneous = TRUE, spacing = 0, * expand = TRUE, fill = FALSE, padding = 0 */ box2 = make_box (TRUE, 0, TRUE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Paramtres : homogeneous = TRUE, spacing = 0, * expand = TRUE, fill = TRUE, padding = 0 */ box2 = make_box (TRUE, 0, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Un autre sparateur */ separator = gtk_hseparator_new (); /* Les 3 derniers paramtres de gtk_box_pack_start sont : * expand = FALSE, fill = TRUE, padding = 5. */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); break; case 2: /* Cration d'un label, box1 est une vbox identique * celle cre au dbut de main() */ label = gtk_label_new ("gtk_hbox_new (FALSE, 10);"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0); gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); gtk_widget_show (label); /* Paramtres : homogeneous = FALSE, spacing = 10, * expand = TRUE, fill = FALSE, padding = 0 */ box2 = make_box (FALSE, 10, TRUE, FALSE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Paramtres : homogeneous = FALSE, spacing = 10, * expand = TRUE, fill = TRUE, padding = 0 */ box2 = make_box (FALSE, 10, TRUE, TRUE, 0); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); separator = gtk_hseparator_new (); /* Les 3 derniers paramtres de gtk_box_pack_start sont : * expand = FALSE, fill = TRUE, padding = 5. */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); label = gtk_label_new ("gtk_hbox_new (FALSE, 0);"); gtk_misc_set_alignment (GTK_MISC (label), 0, 0); gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 0); gtk_widget_show (label); /* Paramtres : homogeneous = FALSE, spacing = 0, * expand = TRUE, fill = FALSE, padding = 10 */ box2 = make_box (FALSE, 0, TRUE, FALSE, 10); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Paramtres : homogeneous = FALSE, spacing = 0, * expand = TRUE, fill = TRUE, padding = 10 */ box2 = make_box (FALSE, 0, TRUE, TRUE, 10); gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); separator = gtk_hseparator_new (); /* Les 3 derniers paramtres de gtk_box_pack_start sont : * expand = FALSE, fill = TRUE, padding = 5. */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); break; case 3: /* Ceci est une dmonstration de la possibilit d'utiliser * gtk_box_pack_end() pour aligner les widgets droite. * On cre d'abord une nouvelle bote comme d'habitude. */ box2 = make_box (FALSE, 0, FALSE, FALSE, 0); /* On cre le label qui sera mis la fin. */ label = gtk_label_new ("end"); /* On le place en utilisant gtk_box_pack_end(), il est ainsi * mis droite de la hbox cre par l'appel make_box(). */ gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0); /* Affichage du label. */ gtk_widget_show (label); /* Placement de box2 dans box1 (la vbox, vous vous rappelez ? :) */ gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0); gtk_widget_show (box2); /* Sparateur pour le bas. */ separator = gtk_hseparator_new (); /* Configuration du sparateur en 400x5 pixels. * La hbox que l'on a cre aura donc 400 pixels de large, * et le label "end" sera spar des autres de la hbox. * Sinon, tous les widgets de la hbox seraient placs les plus * prs possible les uns des autres. */ gtk_widget_set_usize (separator, 400, 5); /* Placement du sparateur dans la vbox (box1) * cre au debut de main(). */ gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5); gtk_widget_show (separator); } /* Cration d'une nouvelle hbox.. vous pouvez en utiliser autant que * que vous en avez besoin ! */ quitbox = gtk_hbox_new (FALSE, 0); /* Notre bouton pour quitter. */ button = gtk_button_new_with_label ("Quit"); /* Configuration du signal pour dtruire la fentre. Ceci enverra le * signal "destroy" la fentre. Ce signal sera son tour captur * par notre gestionnaire de signal dfini plus haut. */ gtk_signal_connect_object (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (gtk_widget_destroy), GTK_OBJECT (window)); /* Placement du bouton dans la quitbox . * Les 3 derniers paramtres de gtk_box_pack_start sont : * expand = TRUE, fill = FALSE, padding = 0. */ gtk_box_pack_start (GTK_BOX (quitbox), button, TRUE, FALSE, 0); /* Placement de la quitbox dans la vbox (box1) */ gtk_box_pack_start (GTK_BOX (box1), quitbox, FALSE, FALSE, 0); /* Placement de la vbox (box1), qui contient maintenant tous nos * widgets, dans la fentre principale. */ gtk_container_add (GTK_CONTAINER (window), box1); /* Affichage */ gtk_widget_show (button); gtk_widget_show (quitbox); gtk_widget_show (box1); /* Affichage de la fentre en dernier */ gtk_widget_show (window); /* Ne pas oublier notre fonction principale. */ gtk_main (); /* Le contrle revient ici lorsque gtk_main_quit() est appele, * jusqu' ce que gtk_exit() soitutilise. */ return 0; } 44..44.. PPllaacceemmeenntt aavveecc lleess ttaabblleess tudions une autre mthode de placement : les tables. Elles peuvent s'avrer trs utiles dans certaines situations. En utilisant des tables, on cre une grille dans laquelle on peut placer les widgets. Ceux-ci peuvent occuper tous les endroits que l'on dsire. La premire chose faire est, bien sr, d'tudier la fonction _g_t_k___t_a_b_l_e___n_e_w : GtkWidget* gtk_table_new (gint rows, gint columns, gint homogeneous); Le premier paramtre est le nombre de lignes de la table et le deuxime, le nombre de colonnes. Le paramtre _h_o_m_o_g_e_n_e_o_u_s s'occupe de la faon dont les cases de la table seront dimensionnes. Si homogeneous vaut TRUE, les cases prennent la taille du plus grand widget de la table. S'il vaut FALSE, la taille des cases dpend du widget le plus haut de la ligne et du plus large de cette colonne. Le nombre de lignes et colonnes va de 0 n, o n est le nombre spcifi dans l'appel _g_t_k___t_a_b_l_e___n_e_w. Ainsi, avec _r_o_w_s = 2 et _c_o_l_u_m_n_s = 2, la table ressemblera ceci : 0 1 2 0+----------+----------+ | | | 1+----------+----------+ | | | 2+----------+----------+ On notera que le systme de coordonnes part du coin en haut gauche. Pour placer un widget dans une case, ou utilise la fonction suivante : void gtk_table_attach (GtkTable *table, GtkWidget *child, gint left_attach, gint right_attach, gint top_attach, gint bottom_attach, gint xoptions, gint yoptions, gint xpadding, gint ypadding); O le premier paramtre (_t_a_b_l_e) est la table que l'on a cre et le second (_c_h_i_l_d) est le widget que l'on veut placer dans la table. Les paramtres _l_e_f_t___a_t_t_a_c_h et _r_i_g_h_t___a_t_t_a_c_h spcifient l'emplacement du widget et le nombre de cases utiliser. Par exemple, si on veut placer un bouton dans le coin infrieur droit de la table dcrite plus haut et que l'on dsire ne remplir QUE cette case, _l_e_f_t___a_t_t_a_c_h vaudra 1, _r_i_g_h_t___a_t_t_a_c_h vaudra 2; _t_o_p___a_t_t_a_c_h vaudra 1 et _b_o_t_t_o_m___a_t_t_a_c_h vaudra 2. Si on veut un widget occupant toute la ligne suprieure de notre table, on utilisera les valeurs 0, 2, 0, 1. Les paramtres _x_o_p_t_i_o_n_s et _y_o_p_t_i_o_n_s servent prciser les options de placement et peuvent tre combines par un OU logique pour permettre des options multiples. Ces options sont : +o GTK_FILL - Si la case de la table est plus large que le widget, et que GTK_FILL est spcifi, le widget s'largira pour occuper toute la place disponible. +o GTK_SHRINK - Si la table a moins de place qu'il ne lui en faut (gnralement, cause d'un redimensionnement de la fentre par l'utilisateur), les widgets sont alors simplement pousss vers le bas de la fentre et disparaissent. Si GTK_SHRINK est spcifi, les widgets se rduiront en mme temps que la table. +o GTK_EXPAND - Cette option provoque l'extension de la table pour qu'elle utilise tout l'espace restant dans la fentre. Le paramtres de _p_a_d_d_i_n_g jouent le mme rle que pour les botes, il crent une zone libre, spcifie en pixels, autour du widget. gtk_table_attach() a BEAUCOUP d'options. Voici donc une fonction-raccourci : void gtk_table_attach_defaults (GtkTable *table, GtkWidget *widget, gint left_attach, gint right_attach, gint top_attach, gint bottom_attach); _x_o_p_t_i_o_n_s et _o_p_t_i_o_n_s valent par dfaut GTK_FILL | GTK_EXPAND, et _x_p_a_d_d_i_n_g et _y_p_a_d_d_i_n_g valent 0. Les autres paramtres sont les mmes que ceux de la fonction prcdente. Il existe aussi les fonctions _g_t_k___t_a_b_l_e___s_e_t___r_o_w___s_p_a_c_i_n_g_(_) et _g_t_k___t_a_b_l_e___s_e_t___c_o_l___s_p_a_c_i_n_g_(_). Elles permettent de placer des espaces aprs une ligne ou une colonne. void gtk_table_set_row_spacing (GtkTable *table, gint row, gint spacing); et void gtk_table_set_col_spacing (GtkTable *table, gint column, gint spacing); Pour les colonnes, l'espace est ajout droite de la colonne et pour les lignes, il est ajout en dessous. On peut aussi configurer un espacement pour toutes les lignes et/ou colonnes avec : void gtk_table_set_row_spacings (GtkTable *table, gint spacing); Et, void gtk_table_set_col_spacings (GtkTable *table, gint spacing); Avec ces appels, la dernire ligne et la dernire colonne n'ont pas d'espace supplmentaire. 44..55.. EExxeemmppllee ddee ppllaacceemmeenntt aavveecc ttaabbllee Pour le moment, tudiez l'exemple sur les tables (testgtk.c) distribu avec les sources de GTK. 55.. VVuuee dd''eennsseemmbbllee ddeess wwiiddggeettss Les tapes pour crer un widget en GTK sont : 1. _g_t_k___*___n_e_w_(_) - une des fonctions disponibles pour crer un nouveau widget. Ces fonctions sont dcrites dans cette section. 2. Connexion de tous les signaux que l'on souhaite utiliser avec les gestionnaires adquats. 3. Configuration des attributs du widget. 4. Placement du widget dans un container en utilisant un appel appropri comme _g_t_k___c_o_n_t_a_i_n_e_r___a_d_d_(_) ou _g_t_k___b_o_x___p_a_c_k___s_t_a_r_t_(_). 5. Affichage du widget grce _g_t_k___w_i_d_g_e_t___s_h_o_w_(_). _g_t_k___w_i_d_g_e_t___s_h_o_w_(_) permet GTK de savoir que l'on a fini de configurer les attributs du widget et qu'il est prt tre affich. On peut aussi utiliser _g_t_k___w_i_d_g_e_t___h_i_d_e_(_) pour le faire disparatre. L'ordre dans lequel on affiche les widgets n'est pas important, mais il est prfrable d'afficher la fentre en dernier pour qu'elle surgisse d'un seul coup plutt que de voir les diffrents widgets apparatre l'cran au fur et mesure. Les fils d'un widget (une fentre est aussi un widget) ne seront pas affichs tant que la fentre elle-mme n'est pas affiche par la fonction _g_t_k___w_i_d_g_e_t___s_h_o_w_(_). 55..11.. CCoonnvveerrssiioonnss ddee ttyyppeess Vous remarquerez, au fur et mesure que vous progressez, que GTK utilise un systme de coercition de type. Celle-ci est toujours ralise en utilisant des macros qui vrifient si l'objet donn peut tre converti et qui ralisent cette coercition. Les macros que vous rencontrerez le plus sont : +o GTK_WIDGET(widget) +o GTK_OBJECT(object) +o GTK_SIGNAL_FUNC(function) +o GTK_CONTAINER(container) +o GTK_WINDOW(window) +o GTK_BOX(box) Elles sont toutes utilises pour convertir les paramtres des fonctions. Vous les verrez dans les exemples et, en rgle gnrale, vous saurez les utiliser simplement en regardant la dclaration d'une fonction. Comme vous pouvez le voir dans la hirarchie de classes ci-dessous, tous les _G_t_k_W_i_d_g_e_t_s drivent d'une classe de base _G_t_k_O_b_j_e_c_t. Ceci signifie que vous pouvez utiliser un widget chaque fois qu'une fonction requiert un objet - il suffit d'utiliser la macro GTK_OBJECT(). Par exemple : gtk_signal_connect(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(fonction_rappel), donnee_de_rappel); Cet appel convertit le bouton en objet et fournit une conversion pour le pointeur de fonction vers la fonction de rappel. De nombreux widgets sont aussi des containers. Si vous regardez la hirarchie de classe ci-dessous, vous remarquerez que beaucoup de widgets viennent de la classe _G_t_k_C_o_n_t_a_i_n_e_r. N'importe lequel de ces widgets peut tre utilis avec la macro GTK_CONTAINER pour tre pass en paramtre une fonction qui attend un container. Malheureusement, ces macros ne peuvent tre couvertes en dtail dans ce didacticiel, Je vous recommande donc de jeter un coup d'oeil sur les fichier en-ttes GTK : ils peuvent s'avrer trs instructifs. En fait, il n'est pas difficile de comprendre comment fonctionne un widget, il suffit d'tudier les dclarations des fonctions. 55..22.. LLaa hhiirraarrcchhiiee ddeess wwiiddggeettss Voici l'arbre de la hirarchie de classes utilises pour implanter les widgets. GtkObject +-- GtkData | \-- GtkAdjustment | \-- GtkWidget +-- GtkContainer | +-- GtkBin | | +-- GtkAlignment | | +-- GtkFrame | | | *-- GtkAspectFrame | | | | | +-- GtkItem | | | +-- GtkListItem | | | +-- GtkMenuItem | | | | +-- GtkCheckMenuItem | | | | *-- GtkRadioMenuItem | | | | | | | *-- GtkTreeItem | | | | | +-- GtkViewport | | \-- GtkWindow | | +-- GtkDialog | | \-- GtkFileSelection | | | +-- GtkBox | | +-- GtkHBox | | \-- GtkVBox | | +-- GtkColorSelection | | \-- GtkCurve | | | +-- GtkButton | | +-- GtkOptionMenu | | \-- GtkToggleButton | | \-- GtkCheckButton | | \-- GtkRadioButton | | | +-- GtkList | +-- GtkMenuShell | | +-- GtkMenu | | \-- GtkMenuBar | | | +-- GtkNotebook | +-- GtkScrolledWindow | +-- GtkTable | \-- GtkTree | +-- GtkDrawingArea +-- GtkEntry +-- GtkMisc | +-- GtkArrow | +-- GtkImage | +-- GtkLabel | \-- GtkPixmap | +-- GtkPreview +-- GtkProgressBar +-- GtkRange | +-- GtkScale | | +-- GtkHScale | | \-- GtkVScale | | | \-- GtkScrollbar | +-- GtkHScrollbar | \-- GtkVScrollbar | +-- GtkRuler | +-- GtkHRuler | \-- GtkVRuler | \-- GtkSeparator +-- GtkHSeparator \-- GtkVSeparator 55..33.. WWiiddggeettss ssaannss ffeennttrree Les widgets suivants n'ont pas de fentre associe. Si vous voulez capturer des vnements, vous devez utiliser _G_t_k_E_v_e_n_t_B_o_x. Reportez-vous la section sur ``Le widget EventBox'' GtkAlignment GtkArrow GtkBin GtkBox GtkImage GtkItem GtkLabel GtkPaned GtkPixmap GtkScrolledWindow GtkSeparator GtkTable GtkViewport GtkAspectFrame GtkFrame GtkVPaned GtkHPaned GtkVBox GtkHBox GtkVSeparator GtkHSeparator Nous continuerons notre exploration de GTK en examinant chaque widget tour tour, crant quelques fonctions simples pour les afficher. Une autre source intressante est le programme _t_e_s_t_g_t_k_._c livr avec GTK. Il se trouve dans le rpertoire _g_t_k_/ 66.. WWiiddggeettss bboouuttoonnss 66..11.. BBoouuttoonnss nnoorrmmaauuxx On a dj presque vu tout ce qu'il y avait voir sur le widget bouton. Il est trs simple. Cependant, il y a deux faons de crer un bouton. On peut utiliser _g_t_k___b_u_t_t_o_n___n_e_w___w_i_t_h___l_a_b_e_l_(_) pour crer un bouton avec un label, ou _g_t_k___b_u_t_t_o_n___n_e_w_(_) pour crer un bouton vide. Dans ce dernier cas, c'est vous de placer un label ou un pixmap sur celui-ci. Pour ce faire, crez une bote, puis placez vos objets dans celle-ci en utilisant la fonction habituelle _g_t_k___b_o_x___p_a_c_k___s_t_a_r_t, utilisez alors _g_t_k___c_o_n_t_a_i_n_e_r___a_d_d pour placer la bote dans le bouton. Voici un exemple d'utilisation de _g_t_k___b_u_t_t_o_n___n_e_w_(_) pour crer un bouton contenant une image et un label. J'ai spar du reste le code qui cre une bote pour que vous puissiez l'utiliser dans vos programmes. #include /* Cration d'une hbox avec une image et un label. Cette fonction * retourne la bote... */ GtkWidget *xpm_label_box (GtkWidget *parent, gchar *xpm_filename, gchar *label_text) { GtkWidget *box1; GtkWidget *label; GtkWidget *pixmapwid; GdkPixmap *pixmap; GdkBitmap *mask; GtkStyle *style; /* Cration de la boite pour un xpm et un label */ box1 = gtk_hbox_new (FALSE, 0); gtk_container_border_width (GTK_CONTAINER (box1), 2); /* Choix d'un style de bouton... Je suppose que c'est pour obtenir * la couleur du fond. Si quelqu'un connat la vraie raison, qu'il * m'claire sur ce point. */ style = gtk_widget_get_style(parent); /* Chargement de xpm pour crer une image */ pixmap = gdk_pixmap_create_from_xpm (parent->window, &mask, &style->bg[GTK_STATE_NORMAL], xpm_filename); pixmapwid = gtk_pixmap_new (pixmap, mask); /* Cration d'un label */ label = gtk_label_new (label_text); /* placement de l'image et du label dans la bote */ gtk_box_pack_start (GTK_BOX (box1), pixmapwid, FALSE, FALSE, 3); gtk_box_pack_start (GTK_BOX (box1), label, FALSE, FALSE, 3); gtk_widget_show(pixmapwid); gtk_widget_show(label); return (box1); } /* Notre fonction de rappel habituelle */ void callback (GtkWidget *widget, gpointer *data) { g_print ("Bonjour - %s a t press\n", (char *) data); } int main (int argc, char *argv[]) { /* GtkWidget est le type utilis pour dclarer les widgets */ GtkWidget *window; GtkWidget *button; GtkWidget *box1; gtk_init (&argc, &argv); /* Cration d'une fentre */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Pixmap'd Buttons!"); /* Il est prfrable de faire cela pour toutes les fentres */ gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_exit), NULL); /* Configuration du bord de la fentre */ gtk_container_border_width (GTK_CONTAINER (window), 10); /* Cration d'un bouton */ button = gtk_button_new (); /* Vous devriez tre habitu voir ces fonctions maintenant */ gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (callback), (gpointer) "cool button"); /* Appel de notre fonction de cration de bote */ box1 = xpm_label_box(window, "info.xpm", "cool button"); /* Placement et affichage de tous nos widgets */ gtk_widget_show(box1); gtk_container_add (GTK_CONTAINER (button), box1); gtk_widget_show(button); gtk_container_add (GTK_CONTAINER (window), button); gtk_widget_show (window); /* Le reste est dans gtk_main */ gtk_main (); return 0; } La fonction _x_p_m___l_a_b_e_l___b_o_x_(_) peut tre utilise pour placer des xpms et des labels sur tout widget qui peut tre container. 66..22.. BBoouuttoonnss ccoommmmuuttaatteeuurrss Les boutons commutateurs ressemblent beaucoup aux boutons normaux, sauf qu'ils seront toujours alternativement dans un tat ou dans un autre. Le changement d'tat s'effectue par un click. Ils peuvent tre enfoncs et, lorsqu'on clique dessus, ils se relvent. Re-cliquez, et ils se renfoncent. Les boutons commutateurs sont la base des cases cocher ou des boutons radio, donc la plupart des appels utiliss pour les boutons commutateurs sont hrits par les cases cocher et les boutons radio. J'insisterai l dessus quand nous les aborderons. Cration d'un bouton commutateur : GtkWidget* gtk_toggle_button_new (void); GtkWidget* gtk_toggle_button_new_with_label (gchar *label); Comme vous pouvez l'imaginer, elles fonctionnent comme celles des boutons normaux. La premire cre un bouton commutateur vide et la deuxime un bouton commutateur contenant dj un label. Pour rcuprer l'tat d'un commutateur et cela comprend aussi les cases cocher et les boutons radio, on utilise une macro comme nous le montrons dans l'exemple qui suit et qui teste l'tat du commutateur dans une fonction de rappel. Le signal qui nous intresse et qui est mis par les boutons commutateurs (ce qui comprend aussi les cases cocher et les boutons radio), est le signal "toggled". Pour vrifier l'tat de ces boutons, on configure un gestionnaire de signal qui capture "toggled" et utilise la macro pour dterminer l'tat. La fonction de rappel ressemblera ceci : void rappel_bouton_commutateur (GtkWidget *widget, gpointer data) { if (GTK_TOGGLE_BUTTON(widget)->active) { /* Si l'on est ici, c'est que le bouton est relch. */ } else { /* le bouton est enfonc */ } } L'appel qui suit peut tre utilis pour configurer l'tat d'un bouton commutateur et de ses descendants, les cases cocher et les boutons radio. On lui passe notre bouton en premier paramtre et TRUE ou FALSE pour spcifier s'il doit tre relch ou enfonc. Par dfaut, il est relch (FALSE). void gtk_toggle_button_set_state (GtkToggleButton *toggle_button, gint state); On notera que lorsqu'on utilise cette fonction, et que l'tat est modifi, cela force le bouton mettre un signal "clicked". void gtk_toggle_button_toggled (GtkToggleButton *toggle_button); Cet appel ne fait que commuter le bouton et mettre le signal "toggled". 66..33.. CCaasseess ccoocchheerr Les cases cocher hritent de nombreuses proprits et fonctions des boutons commutateurs, mais ont un aspect diffrent. Au lieu d'tre des boutons contenant du texte, ce sont de petits carrs avec un texte sur leur droite. Il sont souvent utiliss pour valider ou non des options dans les applications. Les deux fonctions de cration sont identiques celles des boutons normaux. GtkWidget* gtk_check_button_new (void); GtkWidget* gtk_check_button_new_with_label (gchar *label); La fonction _n_e_w___w_i_t_h___l_a_b_e_l cre une case cocher avec un texte cot d'elle. La vrification de l'tat d'une case cocher est identique celle des boutons commutateurs. 66..44.. BBoouuttoonnss rraaddiioo Les boutons radio ressemblent aux cases cocher sauf qu'ils sont groups de faon ce qu'un seul d'entre-eux puisse tre slectionn un moment donn. Ils sont utiliss par les applications lorsqu'il s'agit d'effectuer un choix dans une liste d'options. La cration d'un bouton radio s'effectue grce l'un des appels suivants : GtkWidget* gtk_radio_button_new (GSList *group); GtkWidget* gtk_radio_button_new_with_label (GSList *group, gchar *label); On notera le paramtre supplmentaire de ces fonctions. Elles ncessitent un groupe pour raliser correctement leur tche. Le premier appel doit passer NULL au premier paramtre puis on peut crer un groupe en utilisant : GSList* gtk_radio_button_group (GtkRadioButton *radio_button); On passe alors ce groupe en premier paramtre des appels suivants aux fonctions de cration. Il est prfrable, aussi, de prciser quel bouton doit tre choisi par dfaut avec la fonction : void gtk_toggle_button_set_state (GtkToggleButton *toggle_button, gint state); Celle-ci est dcrite dans la section sur les boutons commutateurs et fonctionne exactement de la mme faon. [Mettre ici un exemple d'utilisation de tout cela car je crois que cela ferait beaucoup de bien...] 77.. WWiiddggeettss ddiivveerrss 77..11.. LLaabbeellss Les labels sont trs utiliss dans GTK et sont relativement simples. Ils n'mettent pas de signaux car ils n'ont pas de fentre X qui leur est associe. Si vous avez besoin de capturer des signaux ou de faire des coupures ( clippings ), utilisez un widget EventBox. Pour crer un label, on utilise : GtkWidget* gtk_label_new (char *str); O l'unique paramtre est la chane de caractres que l'on veut que le label affiche. Pour changer le texte d'un label aprs sa cration, on utilise la fonction : void gtk_label_set (GtkLabel *label, char *str); o le premier paramtre est le label que l'on veut modifier, que l'on convertit en utilisant la macro GTK_LABEL(), et le second est la nouvelle chane. L'espace ncessaire la nouvelle chane sera automatiquement ajust si ncessaire. Pour rcuprer la chane courante, on utilise la fonction : void gtk_label_get (GtkLabel *label, char **str); o le premier paramtre est le label dont on veut rcuprer la chane et le second sert retourner cette chane. 77..22.. LLee wwiiddggeett bbuullllee dd''aaiiddee Ce sont les petits textes qui surgissent lorsque vous laissez votre pointeur sur un bouton ou un autre widget pendant quelques secondes. Ils sont faciles utiliser, on ne donnera donc pas d'exemple. Si vous voulez voir du code, consultez le programme _t_e_s_t_g_t_k_._c distribu avec GTK. Certains widgets (comme les labels) ne fonctionnent pas avec les bulles d'aide. Le premier appel que vous utiliserez sera pour crer une nouvelle bulle d'aide. Vous n'avez besoin que de le faire une fois dans une fonction donne. Le _G_t_k_T_o_o_l_t_i_p que cette fonction retourne peut tre utilis pour crer plusieurs bulles d'aide. GtkTooltips *gtk_tooltips_new (void); Lorsque vous avez cr une nouvelle bulle d'aide et le widget sur lequel vous voulez l'utiliser, vous n'avez qu' faire cet appel pour la configurer : void gtk_tooltips_set_tips (GtkTooltips *tooltips, GtkWidget *widget, gchar *tips_text); Les paramtres sont la bulle d'aide dj cre, suivi du widget pour lequel vous voulez voir apparatre cette bulle et le texte que vous voulez qu'elle contienne. Voici un petit exemple : GtkTooltips *tooltips; GtkWidget *button; ... tooltips = gtk_tooltips_new (); button = gtk_button_new_with_label ("bouton 1"); ... gtk_tooltips_set_tips (tooltips, button, "C'est le bouton 1"); D'autres fonctions peuvent tre utilises avec les bulles d'aide. Je ne ferais que les numrer et les dcrire brivement. void gtk_tooltips_destroy (GtkTooltips *tooltips); Destruction de bulles d'aide. void gtk_tooltips_enable (GtkTooltips *tooltips); Activation d'un ensemble de bulles d'aide dsactives. void gtk_tooltips_disable (GtkTooltips *tooltips); Dsactivation d'un ensemble de bulles d'aide actives. void gtk_tooltips_set_delay (GtkTooltips *tooltips, gint delay); Configure le nombre de millisecondes pendant lequel le pointeur soit se trouver sur le widget avant que la bulle d'aide n'apparaisse. Par dfaut, ce dlai est de 1000 millisecondes, soit 1 seconde. void gtk_tooltips_set_tips (GtkTooltips *tooltips, GtkWidget *widget, gchar *tips_text); Change le texte d'une bulle d'aide dj cre. void gtk_tooltips_set_colors (GtkTooltips *tooltips, GdkColor *background, GdkColor *foreground); Configure les couleurs de fond et de premier plan des bulles d'aides. Je ne sais toujours pas comment spcifier les couleurs... Et c'est tout concernant les fonctions associes aux bulles d'aide. C'est plus que vous ne vouliez srement en savoir :) 77..33.. BBaarrrreess ddee pprrooggrreessssiioonn Les barres de progression sont utilises pour afficher la progression d'une opration. Elles sont trs simple utiliser comme vous pourrez le constater en tudiant le code ci-dessous. Commenons d'abord par l'appel permettant de crer une nouvelle barre. GtkWidget *gtk_progress_bar_new (void); Maintenant que la barre est cre, nous pouvons l'utiliser. void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage); Le premier paramtre est la barre de progression sur laquelle on veut agir, et le second est le pourcentage effectu , signifiant le remplissage de la barres de 0 100 % (rel compris entre 0 et 1). Les barres de progression sont gnralement utilises avec les dlais d'expiration ou autres fonctions identiques (voir la section sur ``Expirations, fonctions d'E/S et d'attente'') pour donner l'illusion du multi-tches. Toutes emploient la fonction _g_t_k___p_r_o_g_r_e_s_s___b_a_r___u_p_d_a_t_e de la mme faon. Voici un exemple de barre de progression mise jour par des expirations. Ce code montre aussi comment rinitialiser une barre. #include static int ptimer = 0; int pstat = TRUE; /* Cette fonction incrmente et met jour la barre de progression, * elle la rinitialise si pstat vaut FALSE */ gint progress (gpointer data) { gfloat pvalue; /* rcupration de la valeur courante de la barre */ pvalue = GTK_PROGRESS_BAR (data)->percentage; if ((pvalue >= 1.0) || (pstat == FALSE)) { pvalue = 0.0; pstat = TRUE; } pvalue += 0.01; gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue); return TRUE; } /* Cette fonction signale une rinitialisation de la barre */ void progress_r (void) { pstat = FALSE; } void destroy (GtkWidget *widget, gpointer *data) { gtk_main_quit (); } int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *button; GtkWidget *label; GtkWidget *table; GtkWidget *pbar; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (destroy), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10); table = gtk_table_new(3,2,TRUE); gtk_container_add (GTK_CONTAINER (window), table); label = gtk_label_new ("Exemple de barre de progression"); gtk_table_attach_defaults(GTK_TABLE(table), label, 0,2,0,1); gtk_widget_show(label); /* Cre une barre, la place dans la table et l'affiche */ pbar = gtk_progress_bar_new (); gtk_table_attach_defaults(GTK_TABLE(table), pbar, 0,2,1,2); gtk_widget_show (pbar); /* Configure le dlai d'expiration pour grer automatiquement la * mise jour de la barre */ ptimer = gtk_timeout_add (100, progress, pbar); /* Ce bouton indique la barre qu'elle doit se rinitialiser */ button = gtk_button_new_with_label ("Reset"); gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (progress_r), NULL); gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,2,3); gtk_widget_show(button); button = gtk_button_new_with_label ("Annuler"); gtk_signal_connect (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (destroy), NULL); gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,2,3); gtk_widget_show (button); gtk_widget_show(table); gtk_widget_show(window); gtk_main (); return 0; } Dans ce petit programme, il y a quatre parties concernant le fonctionnement gnral des barres de progression, nous les tudierons dans l'ordre de leurs appels. pbar = gtk_progress_bar_new (); Cet appel cre une nouvelle barre, nomme _p_b_a_r. ptimer = gtk_timeout_add (100, progress, pbar); Cet appel utilise des dlais d'expiration pour permettre un intervalle de temps constant. ces dlais ne sont pas ncessaires l'utilisation des barres de progression. pvalue = GTK_PROGRESS_BAR (data)->percentage; Ce code assigne _p_v_a_l_u_e la valeur du pourcentage de la barre. gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue); Finalement, ce code met jour la barre avec la valeur de _p_v_a_l_u_e. Et c'est tout ce qu'il y a savoir sur les barres de progression. Amusez-vous bien. 77..44.. BBootteess ddee ddiiaalloogguuee Les widgets botes de dialogue sont trs simples : ce sont simplement des fentres avec plusieurs choses dj places dedans. La structure d'une bote de dialogue est : struct GtkDialog { GtkWindow window; GtkWidget *vbox; GtkWidget *action_area; }; Comme vous le voyez, cela cre simplement une fentre et la place dans une vbox suivie d'un sparateur et d'une hbox pour la zone d'action . Le widget bote de dialogue peut servir produire des messages pour l'utilisateur ainsi qu' d'autres tches. Il est vraiment rudimentaire et il n'y a qu'une seule fonction pour les botes de dialogue : GtkWidget* gtk_dialog_new (void); Ainsi, pour crer un nouveau dialogue, on utilise : GtkWidget window; window = gtk_dialog_new (); Ceci crera la bote de dialogue et c'est maintenant vous de l'utiliser. Vous pouvez, par exemple, placer un bouton dans la zone d'action en faisant quelque chose comme : button = ... gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0); gtk_widget_show (button); Et vous pouvez aussi ajouter un label la zone de la vboxb : label = gtk_label_new ("Les botes de dialogues sont pratiques"); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), label, TRUE, TRUE, 0); gtk_widget_show (label); Comme exemple d'utilisation d'une bote de dialogue, vous pourriez mettre deux boutons dans la zone d'action (un bouton Annuler et un bouton Ok ) et un label dans la zone de la vbox posant une question l'utilisateur ou signalant une erreur, etc. Vous pouvez alors attacher un signal diffrent chacun des boutons et raliser l'opration que l'utilisateur a choisie. 77..55.. PPiixxmmaappss Les pixmaps sont des structures de donnes contenant des images. Celles-ci peuvent tre utilises diffrents endroits, mais le plus souvent comme icnes dans le bureau X Window. Un bitmap est un pixmap de 2 couleurs. Pour utiliser des pixmaps avec GTK, on doit d'abord construire une structure _G_d_k_P_i_x_m_a_p en utilisant les fonctions de la couche GDK. Les pixmaps peuvent soit tre crs partir de donnes en memoire, ou partir de donnes lues dans un fichier. Nous utiliserons chacun des appels pour crer un pixmap. GdkPixmap *gdk_bitmap_create_from_data( GdkWindow *window, gchar *data, gint width, gint height ); Cette fonction sert crer un pixmap mono-plan (2 couleurs) partir de donnes en mmoire. Chaque bit de la donne _d_a_t_a. _w_i_d_t_h et _h_e_i_g_h_t sont exprims en pixels. Le pointeur vers un _G_d_k_W_i_n_d_o_w pointe sur la fentre courante car les ressources d'un pixmap n'ont de signification que dans le contexte de l'cran o il doit s'afficher. GdkPixmap* gdk_pixmap_create_from_data( GdkWindow *window, gchar *data, gint width, gint height, gint depth, GdkColor *fg, GdkColor *bg ); Cette fonction est utilise pour crer un pixmap d'une profondeur donne (nombre de couleurs) partir de la donne spcifie pas _d_a_t_a. _f_g et _b_g sont les couleurs utiliser pour l'avant et l'arrire-plan. GdkPixmap* gdk_pixmap_create_from_xpm( GdkWindow *window, GdkBitmap **mask, GdkColor *transparent_color, const gchar *filename ); Le format XPM est une reprsentation des pixmaps reconnue par le systme X Window. Il est largement utilis et de nombreux utilitaires pour crer des fichiers d'images ce format sont disponibles. Le fichier _f_i_l_e_n_a_m_e doit contenir une image dans ce format qui sera charge dans la structure pixmap. Le masque _m_a_s_k indique quels sont les bits opaques du pixmap. Tous les autres bits sont coloriss en utilisant la couleur spcifie par _t_r_a_n_s_p_a_r_e_n_t___c_o_l_o_r. Un exemple d'utilisation est prsent ci- dessous. GdkPixmap* gdk_pixmap_create_from_xpm_d (GdkWindow *window, GdkBitmap **mask, GdkColor *transparent_color, gchar **data); De petites images peuvent tre intgres dans un programme sous la forme de donnes _d_a_t_a au format XPM. Un pixmap est cr en utilisant ces donnes au lieu de les lire dans un fichier. Un exemple de telles donnes est : /* XPM */ static const char * xpm_data[] = { "16 16 3 1", " c None", ". c #000000000000", "X c #FFFFFFFFFFFF", " ", " ...... ", " .XXX.X. ", " .XXX.XX. ", " .XXX.XXX. ", " .XXX..... ", " .XXXXXXX. ", " .XXXXXXX. ", " .XXXXXXX. ", " .XXXXXXX. ", " .XXXXXXX. ", " .XXXXXXX. ", " .XXXXXXX. ", " ......... ", " ", " "}; void gdk_pixmap_destroy( GdkPixmap *pixmap ); Lorsqu'on a utilis un pixmap et que l'on en a plus besoin tout de suite, il est prfrable de librer la ressource en utilisant un appel _g_d_k___p_i_x_m_a_p___d_e_s_t_r_o_y. Les pixmaps doivent tre considres comme une ressource prcieuse. Quand le pixmap est cr, on peut l'afficher comme un widget GTK. On doit crer un widget pixmap qui contiendra le pixmap GDK. Ceci est ralis de la faon suivante : GtkWidget* gtk_pixmap_new( GdkPixmap *pixmap, GdkBitmap *mask ); Les autres fonctions pour les widgets pixmap sont : guint gtk_pixmap_get_type( void ); void gtk_pixmap_set( GtkPixmap *pixmap, GdkPixmap *val, GdkBitmap *mask); void gtk_pixmap_get( GtkPixmap *pixmap, GdkPixmap **val, GdkBitmap **mask); _g_t_k___p_i_x_m_a_p___s_e_t sert changer le pixmap pris en charge par le widget. _v_a_l est le pixmap cr par le GDK. Voici un exemple illustrant l'utilisation d'un pixmap dans un bouton : #include /* donnes XPM d'une icne "Ouvrir fichier" */ static const char * xpm_data[] = { "16 16 3 1", " c None", ". c #000000000000", "X c #FFFFFFFFFFFF", " ", " ...... ", " .XXX.X. ", " .XXX.XX. ", " .XXX.XXX. ", " .XXX..... ", " .XXXXXXX. ", " .XXXXXXX. ", " .XXXXXXX. ", " .XXXXXXX. ", " .XXXXXXX. ", " .XXXXXXX. ", " .XXXXXXX. ", " ......... ", " ", " "}; /* Termine l'application lorsqu'elle est appele * via le signal "delete_event" */ void close_application( GtkWidget *widget, GdkEvent *event, gpointer *data ) { gtk_main_quit(); } /* Invoque lorsque le bouton est cliqu. Affiche simplement * un message. */ void button_clicked( GtkWidget *widget, gpointer *data ) { printf( "bouton cliqu\n" ); } int main( int argc, char *argv[] ) { /* GtkWidget est le type pour dclarer les widgets */ GtkWidget *window, *pixmapwid, *button; GdkPixmap *pixmap; GdkBitmap *mask; GtkStyle *style; /* Cre la fentre principale et attache le signal "delete_event" pour * terminer l'application */ gtk_init( &argc, &argv ); window = gtk_window_new( GTK_WINDOW_TOPLEVEL ); gtk_signal_connect( GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (close_application), NULL ); gtk_container_border_width( GTK_CONTAINER (window), 10 ); gtk_widget_show( window ); /* Utilisation de GDK pour crer le pixmap */ style = gtk_widget_get_style( window ); pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, &style->bg[GTK_STATE_NORMAL], (gchar **)xpm_data ); /* Cration d'un widget pixmap GTK pour contenir le pixmap GDK */ pixmapwid = gtk_pixmap_new( pixmap, mask ); gtk_widget_show( pixmapwid ); /* Cration d'un bouton pour contenir le widget pixmap */ button = gtk_button_new(); gtk_container_add( GTK_CONTAINER(button), pixmapwid ); gtk_container_add( GTK_CONTAINER(window), button ); gtk_widget_show( button ); gtk_signal_connect( GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(button_clicked), NULL ); /* Affichage de la fentre */ gtk_main (); return 0; } Pour charger un fichier partir d'un fichier XPM appel _i_c_o_n_0_._x_p_m se trouvant dans le rpertoire courant, on aurait cr le pixmap ainsi : /* Charge un pixmap partir d'un fichier */ pixmap = gdk_pixmap_create_from_xpm( window->window, &mask, &style->bg[GTK_STATE_NORMAL], "./icon0.xpm" ); pixmapwid = gtk_pixmap_new( pixmap, mask ); gtk_widget_show( pixmapwid ); gtk_container_add( GTK_CONTAINER(window), pixmapwid ); Utilisation des formes Un dsavantage de l'utilisation des pixmaps est que l'objet affich est toujours rectangulaire, quelle que soit l'image. On voudrait pouvoir crer des bureaux et des applications avec des icnes ayant des formes plus naturelles. Par exemple, pour une interface de jeu, on aimerait avoir des boutons ronds pousser. Pour faire cela, on doit utiliser des fentres avec des formes. Une fentre avec forme est simplement un pixmap dont les pixels du fond sont transparents. Ainsi, lorsque l'image d'arrire-plan est multicolore, on ne la cache pas avec le bord de notre icne. L'exemple suivant affiche une image de brouette sur le bureau. #include /* XPM */ static char * WheelbarrowFull_xpm[] = { "48 48 64 1", " c None", ". c #DF7DCF3CC71B", "X c #965875D669A6", "o c #71C671C671C6", "O c #A699A289A699", "+ c #965892489658", "@ c #8E38410330C2", "# c #D75C7DF769A6", "$ c #F7DECF3CC71B", "% c #96588A288E38", "& c #A69992489E79", "* c #8E3886178E38", "= c #104008200820", "- c #596510401040", "; c #C71B30C230C2", ": c #C71B9A699658", "> c #618561856185", ", c #20811C712081", "< c #104000000000", "1 c #861720812081", "2 c #DF7D4D344103", "3 c #79E769A671C6", "4 c #861782078617", "5 c #41033CF34103", "6 c #000000000000", "7 c #49241C711040", "8 c #492445144924", "9 c #082008200820", "0 c #69A618611861", "q c #B6DA71C65144", "w c #410330C238E3", "e c #CF3CBAEAB6DA", "r c #71C6451430C2", "t c #EFBEDB6CD75C", "y c #28A208200820", "u c #186110401040", "i c #596528A21861", "p c #71C661855965", "a c #A69996589658", "s c #30C228A230C2", "d c #BEFBA289AEBA", "f c #596545145144", "g c #30C230C230C2", "h c #8E3882078617", "j c #208118612081", "k c #38E30C300820", "l c #30C2208128A2", "z c #38E328A238E3", "x c #514438E34924", "c c #618555555965", "v c #30C2208130C2", "b c #38E328A230C2", "n c #28A228A228A2", "m c #41032CB228A2", "M c #104010401040", "N c #492438E34103", "B c #28A2208128A2", "V c #A699596538E3", "C c #30C21C711040", "Z c #30C218611040", "A c #965865955965", "S c #618534D32081", "D c #38E31C711040", "F c #082000000820", " ", " .XoO ", " +@#$%o& ", " *=-;#::o+ ", " >,<12#:34 ", " 45671#:X3 ", " +89<02qwo ", "e* >,67;ro ", "ty> 459@>+&& ", "$2u+ > ", "Oh$;ya *3d.a8j,Xe.d3g8+ ", " Oh$;ka *3d$a8lz,,xxc:.e3g54 ", " Oh$;kO *pd$%svbzz,sxxxxfX..&wn> ", " Oh$@mO *3dthwlsslszjzxxxxxxx3:td8M4 ", " Oh$@g& *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B* ", " Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5& ", " Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM* ", " OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ", " 2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ", " :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo", " +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g", " *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&en", " p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>", " OA1666 >=01-kuu666> ", " ,6ky& &46-10ul,66, ", " Ou0<> o66y66By7=xu664 ", " <> +66uv,zN666* ", " 566,xxj669 ", " 4666FF666> ", " >966666M ", " oM6668+ ", " *4 ", " ", " "}; /* Termine l'application lorsqu'elle est appele * (via le signal "delete_event"). */ void close_application( GtkWidget *widget, GdkEvent *event, gpointer *data ) { gtk_main_quit(); } int main (int argc, char *argv[]) { GtkWidget *window, *pixmap, *fixed; GdkPixmap *gdk_pixmap; GdkBitmap *mask; GtkStyle *style; GdkGC *gc; /* cre la fentre principale et attache le signal "delete_event" * pour terminer l'application. On notera que la fentre principale * n'a pas de barre de titre car nous la faisons surgir. */ gtk_init (&argc, &argv); window = gtk_window_new( GTK_WINDOW_POPUP ); gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (close_application), NULL); gtk_widget_show (window); /* Cration du pixmap et du widget pixmap */ style = gtk_widget_get_default_style(); gc = style->black_gc; gdk_pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask, &style->bg[GTK_STATE_NORMAL], WheelbarrowFull_xpm ); pixmap = gtk_pixmap_new( gdk_pixmap, mask ); gtk_widget_show( pixmap ); /* Pour afficher le pixmap, on utilise un widget fixe pour placer * le pixmap */ fixed = gtk_fixed_new(); gtk_widget_set_usize( fixed, 200, 200 ); gtk_fixed_put( GTK_FIXED(fixed), pixmap, 0, 0 ); gtk_container_add( GTK_CONTAINER(window), fixed ); gtk_widget_show( fixed ); /* On masque tout sauf l'image elle-mme */ gtk_widget_shape_combine_mask( window, mask, 0, 0 ); /* Affichage de la fentre */ gtk_widget_set_uposition( window, 20, 400 ); gtk_widget_show( window ); gtk_main (); return 0; } Pour rendre l'image de la brouette sensible aux clics, on peut lui attacher le signal "button_press_event" pour lui faire faire quelque chose. Les quelques lignes suivantes font que l'image sera sensible un clic souris qui provoquera l'arrt de l'application. gtk_widget_set_events( window, gtk_widget_get_events( window ) | GDK_BUTTON_PRESS_MASK ); gtk_signal_connect( GTK_OBJECT(window), "button_press_event", GTK_SIGNAL_FUNC(close_application), NULL ); 88.. WWiiddggeettss ccoonnttaaiinneerrss 88..11.. BBlloocc--nnootteess Le widget bloc-notes est un ensemble de pages qui se chevauchent. Chaque page contient des informations diffrentes. Rcemment, ce widget est devenu plus commun dans la programmation des interfaces graphiques et c'est un bon moyen de montrer des blocs d'information similaires qui justifient une sparation de leur affichage. Le premier appel de fonction que l'on doit connatre est, vous l'aviez devin, celui qui cre un widget bloc-notes. GtkWidget* gtk_notebook_new (void); Lorsque le bloc-notes a t cr, il y a 12 fonctions permettant de travailler sur les blocs-notes. tudions-les sparment. La premire permet de positionner les indicateurs de pages. Ceux-ci (dsigns par le mot tab (signet)), peuvent se trouver en haut, en bas, gauche ou droite des pages. void gtk_notebook_set_tab_pos (GtkNotebook *notebook, GtkPositionType pos); _G_t_k_P_o_s_i_t_i_o_n_T_y_p_e peut prendre les valeurs suivantes qu'il n'est pas ncessaire d'expliquer : +o GTK_POS_LEFT +o GTK_POS_RIGHT +o GTK_POS_TOP +o GTK_POS_BOTTOM GTK_POS_TOP est la valeur par dfaut. La fonction suivante permet d'ajouter des pages un bloc-notes. Il y a trois faons d'ajouter des pages. Regardons les deux premires qui sont trs semblables. void gtk_notebook_append_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label); void gtk_notebook_prepend_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label); Ces fonctions ajoutent des pages au bloc-notes_*_n_o_t_e_b_o_o_k en les insrant la fin (_a_p_p_e_n_d) ou au dbut (_p_r_e_p_e_n_d). _*_c_h_i_l_d est le widget qui est plac dans la page du bloc-notes, et _*_t_a_b___l_a_b_e_l est le label de la page qui est ajoute. La troisime fonction ajoutant une page un bloc-notes conserve toutes les proprits des deux prcdentes, mais elle nous permet en plus de spcifier la position o l'on dsire insrer cette page. void gtk_notebook_insert_page (GtkNotebook *notebook, GtkWidget *child, GtkWidget *tab_label, gint position); Les paramtres sont les mmes que ___a_p_p_e_n_d__ et ___p_r_e_p_e_n_d__ sauf qu'il y en a un de plus : _p_o_s_i_t_i_o_n. Celui-ci sert spcifier l'endroit o cette page sera insre. Maintenant que nous savons insrer une page, voyons comment en supprimer une. void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num); Cette fonction te la page spcifie par _p_a_g_e___n_u_m du widget _*_n_o_t_e_b_o_o_k. Pour connatre la page courante d'un bloc-notes, on dispose de la fonction : gint gtk_notebook_current_page (GtkNotebook *notebook); Les deux fonctions suivantes permettent de passer la page suivante ou prcdente d'un bloc-notes. Il suffit de faire l'appel de la fonction adquate avec le widget sur lequel on veut oprer. Remarque : lorsqu'on est sur la dernire page du bloc-notes et que l'on appelle _g_t_k___n_o_t_e_b_o_o_k___n_e_x_t___p_a_g_e, on revient la premire page. De mme, si l'on est sur la premire page et que l'onappelle _g_t_k___n_o_t_e_b_o_o_k___p_r_e_v___p_a_g_e, on se retrouve sur sa dernire page. void gtk_notebook_next_page (GtkNoteBook *notebook); void gtk_notebook_prev_page (GtkNoteBook *notebook); La fonction qui suit permet de choisir la page active . Si vous voulez ouvrir le bloc-notes la page 5, par exemple, vous utiliserez cette fonction. Sans elle, le bloc-notes s'ouvre sur sa premire page par dfaut. void gtk_notebook_set_page (GtkNotebook *notebook, gint page_num); Les deux fonctions suivantes ajoutent ou tent les indicateurs de page et le contour du bloc-notes, respectivement. void gtk_notebook_set_show_tabs (GtkNotebook *notebook, gint show_tabs); void gtk_notebook_set_show_border (GtkNotebook *notebook, gint show_border); _s_h_o_w___t_a_b_s et _s_h_o_w___b_o_r_d_e_r peuvent valoir TRUE ou FALSE (0 ou 1). Voyons maintenant un exemple, il est tir du code de _t_e_s_t_g_t_k_._c de la distribution GTK et montre l'utilisation des 13 fonctions. Ce petit programme cre une fentre contenant un bloc-notes et six boutons. Le bloc-notes contient 11 pages, ajoutes par trois moyens diffrents : la fin, au milieu et au dbut. Les boutons permettent de faire tourner les indicateurs de page, ajouter/ter les indicateurs et le contour, ter une page, passer la page suivante et prcdente, et sortir du programme. #include /* Rotation des indicateurs de page */ void rotate_book (GtkButton *button, GtkNotebook *notebook) { gtk_notebook_set_tab_pos (notebook, (notebook->tab_pos +1) %4); } /* Ajout/Suppression des indicateurs de pages et des contours */ void tabsborder_book (GtkButton *button, GtkNotebook *notebook) { gint tval = FALSE; gint bval = FALSE; if (notebook->show_tabs == 0) tval = TRUE; if (notebook->show_border == 0) bval = TRUE; gtk_notebook_set_show_tabs (notebook, tval); gtk_notebook_set_show_border (notebook, bval); } /* Suppression d'une page */ void remove_book (GtkButton *button, GtkNotebook *notebook) { gint page; page = gtk_notebook_current_page(notebook); gtk_notebook_remove_page (notebook, page); /* Il faut rafrachir le widget -- * ce qui force le widget se redessiner. */ gtk_widget_draw(GTK_WIDGET(notebook), NULL); } void delete (GtkWidget *widget, GdkEvent *event, gpointer *data) { gtk_main_quit (); } int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *button; GtkWidget *table; GtkWidget *notebook; GtkWidget *frame; GtkWidget *label; GtkWidget *checkbutton; int i; char bufferf[32]; char bufferl[32]; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_signal_connect (GTK_OBJECT (window), "delete_event", GTK_SIGNAL_FUNC (delete), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10); table = gtk_table_new(2,6,TRUE); gtk_container_add (GTK_CONTAINER (window), table); /* Cration d'un bloc-notes, placement des indicateurs de page. */ notebook = gtk_notebook_new (); gtk_notebook_set_tab_pos (GTK_NOTEBOOK (notebook), GTK_POS_TOP); gtk_table_attach_defaults(GTK_TABLE(table), notebook, 0,6,0,1); gtk_widget_show(notebook); /* Ajoute un groupe de pages la fin du bloc-notes. */ for (i=0; i < 5; i++) { sprintf(bufferf, "Append Frame %d", i+1); sprintf(bufferl, "Page %d", i+1); frame = gtk_frame_new (bufferf); gtk_container_border_width (GTK_CONTAINER (frame), 10); gtk_widget_set_usize (frame, 100, 75); gtk_widget_show (frame); label = gtk_label_new (bufferf); gtk_container_add (GTK_CONTAINER (frame), label); gtk_widget_show (label); label = gtk_label_new (bufferl); gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label); } /* Ajoute une page un endroit prcis. */ checkbutton = gtk_check_button_new_with_label ("Cochez moi !"); gtk_widget_set_usize(checkbutton, 100, 75); gtk_widget_show (checkbutton); label = gtk_label_new ("Emplacement de la nouvelle page"); gtk_container_add (GTK_CONTAINER (checkbutton), label); gtk_widget_show (label); label = gtk_label_new ("Ajout de page"); gtk_notebook_insert_page (GTK_NOTEBOOK (notebook), checkbutton, label, 2); /* Ajout de pages au dbut du bloc-notes */ for (i=0; i < 5; i++) { sprintf(bufferf, "Prepend Frame %d", i+1); sprintf(bufferl, "Page %d", i+1); frame = gtk_frame_new (bufferf); gtk_container_border_width (GTK_CONTAINER (frame), 10); gtk_widget_set_usize (frame, 100, 75); gtk_widget_show (frame); label = gtk_label_new (bufferf); gtk_container_add (GTK_CONTAINER (frame), label); gtk_widget_show (label); label = gtk_label_new (bufferl); gtk_notebook_prepend_page (GTK_NOTEBOOK(notebook), frame, label); } /* Configuration de la page de dpart (page 4) */ gtk_notebook_set_page (GTK_NOTEBOOK(notebook), 3); /* Cration des boutons */ button = gtk_button_new_with_label ("Fermer"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", GTK_SIGNAL_FUNC (delete), NULL); gtk_table_attach_defaults(GTK_TABLE(table), button, 0,1,1,2); gtk_widget_show(button); button = gtk_button_new_with_label ("Page suivante"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) gtk_notebook_next_page, GTK_OBJECT (notebook)); gtk_table_attach_defaults(GTK_TABLE(table), button, 1,2,1,2); gtk_widget_show(button); button = gtk_button_new_with_label ("Page prcdente"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) gtk_notebook_prev_page, GTK_OBJECT (notebook)); gtk_table_attach_defaults(GTK_TABLE(table), button, 2,3,1,2); gtk_widget_show(button); button = gtk_button_new_with_label ("Position des indicateurs"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) rotate_book, GTK_OBJECT(notebook)); gtk_table_attach_defaults(GTK_TABLE(table), button, 3,4,1,2); gtk_widget_show(button); button = gtk_button_new_with_label ("Indicateurs/Contours oui/non"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) tabsborder_book, GTK_OBJECT (notebook)); gtk_table_attach_defaults(GTK_TABLE(table), button, 4,5,1,2); gtk_widget_show(button); button = gtk_button_new_with_label ("Oter page"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) remove_book, GTK_OBJECT(notebook)); gtk_table_attach_defaults(GTK_TABLE(table), button, 5,6,1,2); gtk_widget_show(button); gtk_widget_show(table); gtk_widget_show(window); gtk_main (); return 0; } En esprant que ceci vous aide crer des blocs-notes pour vos applications GTK. 88..22.. FFeennttrreess aavveecc bbaarrrreess ddee ddffiilleemmeenntt Les fentres avec barres de dfilement servent crer des zones dfilantes l'intrieur d'une vraie fentre. On peut insrer n'importe quel widget dans ces fentres, ils seront accessibles quelle que soit leur taille en utilisant les barres de dfilement. La fonction suivante sert crer une fentre avec barre de dfilement : GtkWidget* gtk_scrolled_window_new (GtkAdjustment *hadjustment, GtkAdjustment *vadjustment); Le premier paramtre est l'ajustement horizontal, et le second l'ajustement vertical. Ils sont presque toujours positionns NULL. void gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window, GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy); Cela permet de configurer le fonctionnement des barres de dfilement. Le premier paramtre est la fentre dfilement que l'on veut modifier, le second configure le fonctionnement de la barre horizontale et le troisime celui de la barre verticale. Ce fonctionnement peut tre GTK_POLICY AUTOMATIC ou GTK_POLICY_ALWAYS. GTK_POLICY_AUTOMATIC dcidera automatiquement de votre besoin en barres de dfilement, alors que GTK_POLICY_ALWAYS mettra toujours celles-ci. Voici un exemple simple qui place 100 boutons commutateurs dans une fentre dfilement. Je n'ai comment que les parties qui sont nouvelles pour vous. #include void destroy(GtkWidget *widget, gpointer *data) { gtk_main_quit(); } int main (int argc, char *argv[]) { static GtkWidget *window; GtkWidget *scrolled_window; GtkWidget *table; GtkWidget *button; char buffer[32]; int i, j; gtk_init (&argc, &argv); /* Cration d'une bote de dialogue pour y placer la fentre dfilement. * Une bote de dialogue est une fentre comme les autres sauf qu'elle contient * une vbox et un sparateur horizontal. Ce n'est qu'un raccourci pour crer des * zones de dialogue. */ window = gtk_dialog_new (); gtk_signal_connect (GTK_OBJECT (window), "destroy", (GtkSignalFunc) destroy, NULL); gtk_window_set_title (GTK_WINDOW (window), "dialog"); gtk_container_border_width (GTK_CONTAINER (window), 0); /* Cration d'une fentre dfilement. */ scrolled_window = gtk_scrolled_window_new (NULL, NULL); gtk_container_border_width (GTK_CONTAINER (scrolled_window), 10); /* La gestion des barres est soit GTK_POLICY AUTOMATIC, soit GTK_POLICY_ALWAYS. * GTK_POLICY_AUTOMATIC dcide automatiquement s'il faut ou non des barres, * GTK_POLICY_ALWAYS met toujours des barres * Le premier paramtre correspond la barre horizontale, * le second la barre verticale. */ gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); /* Cration d'une bote de dialogue */ gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window, TRUE, TRUE, 0); gtk_widget_show (scrolled_window); /* Cration d'une table de 10x10 cases. */ table = gtk_table_new (10, 10, FALSE); /* Configure l'espace des lignes et des colonnes de 10 pixels */ gtk_table_set_row_spacings (GTK_TABLE (table), 10); gtk_table_set_col_spacings (GTK_TABLE (table), 10); /* Place la table fans la fentre dfilement */ gtk_container_add (GTK_CONTAINER (scrolled_window), table); gtk_widget_show (table); /* Cre une grille de boutons commutateurs dans la table */ for (i = 0; i < 10; i++) for (j = 0; j < 10; j++) { sprintf (buffer, "bouton (%d,%d)\n", i, j); button = gtk_toggle_button_new_with_label (buffer); gtk_table_attach_defaults (GTK_TABLE (table), button, i, i+1, j, j+1); gtk_widget_show (button); } /* Ajoute un bouton Fermer en bas de la bote de dialogue */ button = gtk_button_new_with_label ("Fermer"); gtk_signal_connect_object (GTK_OBJECT (button), "clicked", (GtkSignalFunc) gtk_widget_destroy, GTK_OBJECT (window)); /* On met ce bouton en bouton par dfaut . */ GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0); /* Rcupre le bouton par dfaut. Le fait de presser la touche Entre * activera le bouton. */ gtk_widget_grab_default (button); gtk_widget_show (button); gtk_widget_show (window); gtk_main(); return(0); } Essayez de changer la taille de la fentre et faites attention aux ractions des barres de dfilement. On peut aussi utiliser la fonction _g_t_k___w_i_d_g_e_t___s_e_t___u_s_i_z_e_(_) pour configurer la taille par dfaut de la fentre et des autres widgets. 99.. WWiiddggeettss lliisstteess Le widget _G_t_k_L_i_s_t sert de container vertical pour des widgets _G_t_k_L_i_s_t_I_t_e_m. Un widget _G_t_k_L_i_s_t possde sa propre fentre pour recevoir les vnements et sa propre couleur de fond qui est habituellement blanche. Comme il est directement driv de _G_t_k_C_o_n_t_a_i_n_e_r, il peut tre trait comme tel en utilisant la macro GTK_CONTAINER(List) : voir le widget _G_t_k_C_o_n_t_a_i_n_e_r pour en savoir plus. On doit d'abord connatre l'utilisation des _G_L_i_s_t et des fonctions _g___l_i_s_t___*_(_) qui leur sont lies pour pouvoir utiliser pleinement le widget _G_t_k_L_i_s_t. Un champ de la structure d'un widget _G_t_k_L_i_s_t nous intresse particulirement : struct _GtkList { ... GList *selection; guint selection_mode; ... }; Le champ _s_e_l_e_c_t_i_o_n d'un _G_t_k_L_i_s_t pointe sur une liste chane de tous les items qui sont slectionns, ou vaut NULL si aucune slection n'est faite. Ainsi, pour connatre la slection courante, on consulte le champ _G_T_K___L_I_S_T_(_)_-_>_s_e_l_e_c_t_i_o_n mais on ne doit pas le modifier car ses champs internes sont grs par les fonctions _g_t_k___l_i_s_t___*_(_). Le champ _s_e_l_e_c_t_i_o_n___m_o_d_e dtermine les options de slection d'un _G_t_k_L_i_s_t et donc le contenu du champ du _G_T_K___L_I_S_T_(_)_-_>_s_e_l_e_c_t_i_o_n : _s_e_l_e_c_t_i_o_n___m_o_d_e peut avoir l'une des valeurs suivantes : +o GTK_SELECTION_SINGLE - _s_e_l_e_c_t_i_o_n vaut NULL ou contient un pointeur vers un seul item slectionn. +o GTK_SELECTION_BROWSE - _s_e_l_e_c_t_i_o_n vaut NULL si la liste ne contient aucun widget ou seulement des widgets non sensitifs. Sinon, ce champ contient un pointeur vers une seule structure Glist, et donc vers exactement un item. +o GTK_SELECTION_MULTIPLE - _s_e_l_e_c_t_i_o_n vaut NULL si aucun item n'est slectionn ou pointe vers le premier item slectionn. Ce dernier point son tour vers le second item, etc. +o GTK_SELECTION_EXTENDED - _s_e_l_e_c_t_i_o_n vaut toujours NULL. La valeur par dfaut est GTK_SELECTION_MULTIPLE. 99..11.. SSiiggnnaauuxx void GtkList::selection_changed (GtkList *LIST) Ce signal sera invoqu chaque fois que le champ slection d'un GtkList a chang. Cela arrive lorsqu'un fils d'un GtkList a t slectionn ou dslectionn. void GtkList::select_child (GtkList *LIST, GtkWidget *CHILD) Ce signal est invoqu lorsqu'un fils du GtkList va tre slectionn. Ceci arrive principalement lors d'appels _g_t_k___l_i_s_t___s_e_l_e_c_t___i_t_e_m_(_)_, _g_t_k___l_i_s_t___s_e_l_e_c_t___c_h_i_l_d_(_) et lors d'appuis de boutons. Quelques fois, il est indirectement dclench lorsque des fils sont ajouts ou supprims du GtkList. void GtkList::unselect_child (GtkList *LIST, GtkWidget *CHILD) Ce signal est invoqu lorsqu'un fils du GtkList va tre dslectionn. Cela arrive principalement lors d'appels _g_t_k___l_i_s_t___u_n_s_e_l_e_c_t___i_t_e_m_(_)_, _g_t_k___l_i_s_t___u_n_s_e_l_e_c_t___c_h_i_l_d_(_), et lors d'appuis de boutons. Quelques fois, il est indirectement dclench lorsque des fils sont ajouts ou supprims du GtkList. 99..22.. FFoonnccttiioonnss guint gtk_list_get_type (void) Retourne l'identificateur de type GtkList . GtkWidget* gtk_list_new (void) Cre un nouvel objet GtkList . Le nouveau widget est retourn sous la forme d'un pointeur vers un objet GtkWidget . NULL est retourn en cas d'erreur. void gtk_list_insert_items (GtkList *LIST, GList *ITEMS, gint POSITION) Insre des items dans _L_I_S_T, partir de _P_O_S_I_T_I_O_N. _I_T_E_M_S est une liste doublement chane o chaque noeud doit pointer vers un nouveau _G_t_k_L_i_s_t_I_t_e_m. Les noeuds _G_L_i_s_t de _I_T_E_M_S sont pris en charge par _L_I_S_T. void gtk_list_append_items (GtkList *LIST, GList *ITEMS) Insre des items la fin de _L_I_S_T selon le mme principe que _g_t_k___l_i_s_t___i_n_s_e_r_t___i_t_e_m_s. Les noeuds _G_L_i_s_t de _I_T_E_M_S sont pris en charge par _L_I_S_T. void gtk_list_prepend_items (GtkList *LIST, GList *ITEMS) Insre des items au dbut de _L_I_S_T selon le mme principe que _g_t_k___l_i_s_t___i_n_s_e_r_t___i_t_e_m_s. Les noeuds _G_L_i_s_t de _I_T_E_M_S sont pris en charge par _L_I_S_T. void gtk_list_remove_items (GtkList *LIST, GList *ITEMS) te des items de _L_I_S_T. _I_T_E_M_S est une liste doublement chane dont chaque noeud pointe vers un fils direct de _L_I_S_T. Il est de la responsabilit de l'appelant de faire un appel _g___l_i_s_t___f_r_e_e_(_I_T_E_M_S_) aprs cela. L'appelant doit aussi dtruire lui-mme les items. void gtk_list_clear_items (GtkList *LIST, gint START, gint END) te et dtruit des items de _L_I_S_T. Un widget est concern si sa position courante dans _L_I_S_T est dans l'intervalle spcifi par _S_T_A_R_T et _E_N_D. void gtk_list_select_item (GtkList *LIST, gint ITEM) Invoque le signal _G_t_k_L_i_s_t_:_:_s_e_l_e_c_t___c_h_i_l_d pour un item spcifi par sa position courante dans _L_I_S_T. void gtk_list_unselect_item (GtkList *LIST, gint ITEM) Invoque le signal _G_t_k_L_i_s_t_:_:_u_n_s_e_l_e_c_t___c_h_i_l_d pour un item spcifi par sa position courante dans _L_I_S_T. void gtk_list_select_child (GtkList *LIST, GtkWidget *CHILD) Invoque le signal _G_t_k_L_i_s_t_:_:_s_e_l_e_c_t___c_h_i_l_d pour le fils _C_H_I_L_D spcifi. void gtk_list_unselect_child (GtkList *LIST, GtkWidget *CHILD) Invoque le signal _G_t_k_L_i_s_t_:_:_u_n_s_e_l_e_c_t___c_h_i_l_d pour le fils _C_H_I_L_D spcifi. gint gtk_list_child_position (GtkList *LIST, GtkWidget *CHILD) Retourne la position de _C_H_I_L_D dans _L_I_S_T. Retourne -1 en cas d'erreur. void gtk_list_set_selection_mode (GtkList *LIST, GtkSelectionMode MODE) Configure _L_I_S_T dans le mode de slection _M_O_D_E qui peut tre GTK_SELECTION_SINGLE, GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE ou GTK_SELECTION_EXTENDED. GtkList* GTK_LIST (gpointer OBJ) Convertit un pointeur gnrique en <\em GtkList*\ . Voir _S_t_a_n_d_a_r_d _M_a_c_r_o_s_:_:, pour plus d'informations. GtkListClass* GTK_LIST_CLASS (gpointer CLASS) Convertit un pointeur gnrique en GtkListClass* . Voir _S_t_a_n_d_a_r_d _M_a_c_r_o_s_:_:, pour plus d'informations. gint GTK_IS_LIST (gpointer OBJ) Dtermine si un pointeur gnrique rfrence un objet GtkList . Voir _S_t_a_n_d_a_r_d _M_a_c_r_o_s_:_:, pour plus d'informations. 99..33.. EExxeemmppllee Voici un programme affichant les changements de slection dans une _G_t_k_L_i_s_t et permettant d' emprisonner des items en les slectionnant avec le bouton droit de la souris. /* Compilez ce programme avec : * $ gcc -L/usr/X11R6/lib/ -I/usr/local/include/ -lgtk -lgdk -lglib -lX11 -lm -Wall main.c */ #include #include /* Chane pour stocker les donnes dans les items de la liste. */ const gchar *list_item_data_key="list_item_data"; /* prototypes des gestionnaires de signaux que l'on connectera au widget GtkList. */ static void sigh_print_selection (GtkWidget *gtklist, gpointer func_data); static void sigh_button_event (GtkWidget *gtklist, GdkEventButton *event, GtkWidget *frame); /* fonction principale pour configurer l'interface utilisateur */ gint main (int argc, gchar *argv[]) { GtkWidget *separator; GtkWidget *window; GtkWidget *vbox; GtkWidget *scrolled_window; GtkWidget *frame; GtkWidget *gtklist; GtkWidget *button; GtkWidget *list_item; GList *dlist; guint i; gchar buffer[64]; /* initialise gtk (et donc gdk) */ gtk_init(&argc, &argv); /* Cration d'une fentre pour placer tous les widgets. * Connexion de gtk_main_quit() l'vnement "destroy" de * la fentre afin de prendre en charge les vnements fermeture d'une * fentre du gestionnaire de fentre. */ window=gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(window), "Exemple de widget GtkList"); gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(gtk_main_quit), NULL); /* l'intrieur de la fentre, on a besoin d'une bote pour placer * verticalement les widgets. */ vbox=gtk_vbox_new(FALSE, 5); gtk_container_border_width(GTK_CONTAINER(vbox), 5); gtk_container_add(GTK_CONTAINER(window), vbox); gtk_widget_show(vbox); /* Fentre dfilement pour placer le widget GtkList l'intrieur. */ scrolled_window=gtk_scrolled_window_new(NULL, NULL); gtk_widget_set_usize(scrolled_window, 250, 150); gtk_container_add(GTK_CONTAINER(vbox), scrolled_window); gtk_widget_show(scrolled_window); /* Cration du widget GtkList * Connexion du gestionnaire de signal sigh_print_selection() au signal * "selection_changed" du GtkList pour afficher les items slectionns * chaque fois que la slection change. */ gtklist=gtk_list_new(); gtk_container_add(GTK_CONTAINER(scrolled_window), gtklist); gtk_widget_show(gtklist); gtk_signal_connect(GTK_OBJECT(gtklist), "selection_changed", GTK_SIGNAL_FUNC(sigh_print_selection), NULL); /* Cration d'une Prison pour y mettre un item. */ frame=gtk_frame_new("Prison"); gtk_widget_set_usize(frame, 200, 50); gtk_container_border_width(GTK_CONTAINER(frame), 5); gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT); gtk_container_add(GTK_CONTAINER(vbox), frame); gtk_widget_show(frame); /* Connexion du gestionnaire de signal sigh_button_event() au signal * mise au arrts des items du GtkList. */ gtk_signal_connect(GTK_OBJECT(gtklist), "button_release_event", GTK_SIGNAL_FUNC(sigh_button_event), frame); /* Cration d'un sparateur. */ separator=gtk_hseparator_new(); gtk_container_add(GTK_CONTAINER(vbox), separator); gtk_widget_show(separator); /* Cration d'un bouton et connexion de son signal "clicked" la * destruction de la fentre. */ button=gtk_button_new_with_label("Fermeture"); gtk_container_add(GTK_CONTAINER(vbox), button); gtk_widget_show(button); gtk_signal_connect_object(GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(window)); /* Cration de 5 items, chacun ayant son propre label. * Ajout de ceux-ci au GtkList en utilisant gtk_container_add(). * On interroge le texte du label et on l'associe avec * list_item_data_key chaque item. */ for (i=0; i<5; i++) { GtkWidget *label; gchar *string; sprintf(buffer, "ListItemContainer avec Label #%d", i); label=gtk_label_new(buffer); list_item=gtk_list_item_new(); gtk_container_add(GTK_CONTAINER(list_item), label); gtk_widget_show(label); gtk_container_add(GTK_CONTAINER(gtklist), list_item); gtk_widget_show(list_item); gtk_label_get(GTK_LABEL(label), &string); gtk_object_set_data(GTK_OBJECT(list_item), list_item_data_key, string); } /* Cration de 5 autres labels. Cette fois-ci, on utilise * gtk_list_item_new_with_label(). On ne peut interroger la chane * des labels car on n'a pas les pointeurs de labels et on associe * donc simplement le list_item_data_key de chaque item ayant la mme * chane de texte pour l'ajouter au items que l'on place dans une liste * doublement chane (GList). On les ajoute alors par un simple appel * gtk_list_append_items(). * Comme on utilise g_list_prepend() pour mettre les items dans la liste * doublement chane, leur ordre sera dcroissant (au lieu d'tre croissant si * on utilisait g_list_append()). */ dlist=NULL; for (; i<10; i++) { sprintf(buffer, "Item avec le label %d", i); list_item=gtk_list_item_new_with_label(buffer); dlist=g_list_prepend(dlist, list_item); gtk_widget_show(list_item); gtk_object_set_data(GTK_OBJECT(list_item), list_item_data_key, "Item avec label intgr"); } gtk_list_append_items(GTK_LIST(gtklist), dlist); /* Enfin, on veut voir la fentre... */ gtk_widget_show(window); /* Lancement de la boucle principale de gtk */ gtk_main(); /* On arrive ici aprs que gtk_main_quit() ait t appele lorsque * la fentre principale a t dtruite. */ } /* Gestionnaire de signal connect aux vnements boutons presser/relcher * du GtkList. */ void sigh_button_event (GtkWidget *gtklist, GdkEventButton *event, GtkWidget *frame) { /* On ne fait quelque chose que si le troisime bouton (celui de droite) a t * relch. */ if (event->type==GDK_BUTTON_RELEASE && event->button==3) { GList *dlist, *free_list; GtkWidget *new_prisoner; /* On recherche l'item slectionn ce moment prcis. * Ce sera notre prisonnier ! */ dlist=GTK_LIST(gtklist)->selection; if (dlist) new_prisoner=GTK_WIDGET(dlist->data); else new_prisoner=NULL; /* On recherche les items dj prisonniers et on les * remet dans la liste. * Il ne faut pas oublier de librer la liste doublement * chane que gtk_container_children() retourne. */ dlist=gtk_container_children(GTK_CONTAINER(frame)); free_list=dlist; while (dlist) { GtkWidget *list_item; list_item=dlist->data; gtk_widget_reparent(list_item, gtklist); dlist=dlist->next; } g_list_free(free_list); /* Si l'on a un nouveau prisonnier, on l'te du GtkList et on le place * dans le cadre Prison . On doit dslectionner l'item avant. if (new_prisoner) { GList static_dlist; static_dlist.data=new_prisoner; static_dlist.next=NULL; static_dlist.prev=NULL; gtk_list_unselect_child(GTK_LIST(gtklist), new_prisoner); gtk_widget_reparent(new_prisoner, frame); } } } /* Gestionnaire de signal appel lorsque le GtkList * met le signal "selection_changed". */ void sigh_print_selection (GtkWidget *gtklist, gpointer func_data) { GList *dlist; /* Recherche dans la liste doublement chane des items slectionns * du GtkList, faire en lecture seulement ! */ dlist=GTK_LIST(gtklist)->selection; /* S'il n'y a pas d'items slectionn, il n'y a rien d'autre faire que * de le dire l'utilisateur. */ if (!dlist) { g_print("Slection nettoye\n"); return; } /* Ok, on a une slection et on l'affiche. */ g_print("La slection est "); /* On rcupre l'item dans la liste doublement chane * puis on interroge la donne associe par list_item_data_key * et on l'affiche. */ while (dlist) { GtkObject *list_item; gchar *item_data_string; list_item=GTK_OBJECT(dlist->data); item_data_string=gtk_object_get_data(list_item, list_item_data_key); g_print("%s ", item_data_string); dlist=dlist->next; } g_print("\n"); } 99..44.. WWiiddggeett iitteemm ddee lliissttee Le widget _G_t_k_L_i_s_t_I_t_e_m sert de container pour contenir un fils, lui fournissant des fonctions de slection/dsselection exactement comme le widget GtkList les demande pour ses fils. Un _G_t_k_L_i_s_t_I_t_e_m a sa propre fentre pour recevoir les vnements et a sa propre couleur de fond, habituellement blanche. Comme il est directement driv d'un _G_t_k_I_t_e_m, il peut tre trait comme tel en utilisant la macro GTK_ITEM(ListItem), reportez-vous la section sur le widget GtkItem pour plus de dtail sur celui-ci. Habituellement, un _G_t_k_L_i_s_t_I_t_e_m contient juste un label pour identifier, par exemple, un nom de fichier dans un _G_t_k_L_i_s_t -- la fonction approprie _g_t_k___l_i_s_t___i_t_e_m___n_e_w___w_i_t_h___l_a_b_e_l_(_) est donc fournie. Le mme effet peut tre obtenu en crant un _G_t_k_L_a_b_e_l part, en configurant son alignement avec _x_a_l_i_g_n=0 et _y_a_l_i_g_n=0.5 suivi d'un ajout ultrieur au _G_t_k_L_i_s_t_I_t_e_m. Tout comme on n'est pas forc d'ajouter un _G_t_k_L_a_b_e_l un _G_t_k_L_i_s_t_I_t_e_m, on peut aussi ajouter un _G_t_k_V_B_o_x ou un _G_t_k_A_r_r_o_w etc. un _G_t_k_L_i_s_t_I_t_e_m. 99..55.. SSiiggnnaauuxx Un _G_t_k_L_i_s_t_I_t_e_m ne cre pas de nouveaux signaux par lui-mme, mais hrite de ceux d'un _G_t_k_I_t_e_m. Voir _G_t_k_I_t_e_m_:_:, pour plus d'informations. 99..66.. FFoonnccttiioonnss guint gtk_list_item_get_type (void) Retourne l'identificateur du type GtkListItem . GtkWidget* gtk_list_item_new (void) Cration d'un objet _G_t_k_L_i_s_t_I_t_e_m. Le nouveau widget est retourn sous la forme d'un pointeur vers un objet _G_t_k_W_i_d_g_e_t. NULL est retourn en cas d'erreur. GtkWidget* gtk_list_item_new_with_label (gchar *LABEL) Cration d'un objet _G_t_k_L_i_s_t_I_t_e_m ayant un simple _G_t_k_L_a_b_e_l comme seul fils. Le nouveau widget est retourn sous la forme d'un pointeur vers un objet _G_t_k_W_i_d_g_e_t. NULL est retourn en cas d'erreur. void gtk_list_item_select (GtkListItem *LIST_ITEM) Cette fonction est surtout un emballage de _g_t_k___i_t_e_m___s_e_l_e_c_t _(_G_T_K___I_T_E_M _(_l_i_s_t___i_t_e_m_)_) qui mettra le signal _G_t_k_I_t_e_m_:_:_s_e_l_e_c_t. Voir _G_t_k_I_t_e_m_:_:, pour plus d'informations. void gtk_list_item_deselect (GtkListItem *LIST_ITEM) Cette fonction est surtout un emballage de _g_t_k___i_t_e_m___d_e_s_e_l_e_c_t _(_G_T_K___I_T_E_M _(_l_i_s_t___i_t_e_m_)_) qui mettra le signal _G_t_k_I_t_e_m_:_:_d_e_s_e_l_e_c_t. Voir _G_t_k_I_t_e_m_:_:, pour plus d'informations. GtkListItem* GTK_LIST_ITEM (gpointer OBJ) Convertit un pointeur gnrique en _G_t_k_L_i_s_t_I_t_e_m_*. Voir _S_t_a_n_d_a_r_d _M_a_c_r_o_s_:_: pour plus d'informations. GtkListItemClass* GTK_LIST_ITEM_CLASS (gpointer CLASS) Convertit un pointeur gnrique en _G_t_k_L_i_s_t_I_t_e_m_C_l_a_s_s_*. Voir _S_t_a_n_d_a_r_d _M_a_c_r_o_s_:_: pour plus d'informations. gint GTK_IS_LIST_ITEM (gpointer OBJ) Dtermine si un pointeur gnrique se rfre un objet _G_t_k_L_i_s_t_I_t_e_m. Voir _S_t_a_n_d_a_r_d _M_a_c_r_o_s_:_: pour plus d'informations. 99..77.. EExxeemmppllee L'exemple des GtkList couvre aussi l'utilisation des GtkListItem. tudiez-le attentivement. 1100.. WWiiddggeettss sslleeccttiioonnss ddee ffiicchhiieerrss Le widget slection de fichier est un moyen simple et rapide pour afficher un fichier dans une bote de dialogue. Il est complet, avec des boutons Ok, Annuler et Aide. C'est un bon moyen de raccourcir les temps de programmation. Pour crer une bote de slection de fichier, on utilise : GtkWidget* gtk_file_selection_new (gchar *title); Pour configurer le nom de fichier, par exemple pour aller dans un rpertoire prcis ou donner un nom de fichier par dfaut, on utilise la fonction : void gtk_file_selection_set_filename (GtkFileSelection *filesel, gchar *filename); Pour rcuprer le texte que l'utilisateur a entr, ou sur lequel il a cliqu, on utilisera la fonction : gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel); Des pointeurs permettent d'accder aux widgets contenus dans la widget de slection de fichiers. Ce sont : +o dir_list +o file_list +o selection_entry +o selection_text +o main_vbox +o ok_button +o cancel_button +o help_button Le plus souvent, on utilise les pointeurs _o_k___b_u_t_t_o_n_, _c_a_n_c_e_l___b_u_t_t_o_n, et _h_e_l_p___b_u_t_t_o_n pour prciser leurs utilisations. Voici un exemple emprunt _t_e_s_t_g_t_k_._c et modifi pour fonctionner tout seul. Comme vous le verrez, il n'y a pas grand chose faire pour crer un wigdget de slection de fichier. Cependant, dans cet exemple, si le bouton Aide apparat l'cran, il ne fait rien car aucun signal ne lui est attach. #include /* Rcupre le nom de fichier slectionn et l'affiche sur la console. */ void file_ok_sel (GtkWidget *w, GtkFileSelection *fs) { g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs))); } void destroy (GtkWidget *widget, gpointer *data) { gtk_main_quit (); } int main (int argc, char *argv[]) { GtkWidget *filew; gtk_init (&argc, &argv); /* Cration d'un widget de slection de fichier. */ filew = gtk_file_selection_new ("File selection"); gtk_signal_connect (GTK_OBJECT (filew), "destroy", (GtkSignalFunc) destroy, &filew); /* Connexion de ok_button la fonction file_ok_sel() */ gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button), "clicked", (GtkSignalFunc) file_ok_sel, filew ); /* Connexion de cancel_button pour dtruire le widget */ gtk_signal_connect_object (GTK_OBJECT (GTK_FILE_SELECTION (filew)->cancel_button), "clicked", (GtkSignalFunc) gtk_widget_destroy, GTK_OBJECT (filew)); /* Configuration du nom de fichier, comme s'il s'agissait d'un dialogue de * sauvegarde et que nous donnions un nom de fichier par dfaut. */ gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew), "penguin.png"); gtk_widget_show(filew); gtk_main (); return 0; } 1111.. WWiiddggeettss MMeennuu Il y a deux faons de crer des menus, la facile et la complique. Les deux ont leur utilit, mais on peut gnralement utiliser l'_u_s_i_n_e _m_e_n_u_s (c'est la mthode facile...). La mthode complique consiste crer tous les menus en utilisant directement les appels. La mthode facile consiste utiliser les appels _g_t_k___m_e_n_u___f_a_c_t_o_r_y. C'est beaucoup plus simple, mais chaque approche a ses avantages et inconvnients. L'usine menus est beaucoup plus facile utiliser, elle facilite aussi l'ajout d'autres menus. Par contre, crire quelques fonctions permettant de crer des menus en utilisant la mthode manuelle peut tre le dbut d'un long chemin avant une quelconque utilisation. Avec l'usine menus, il n'est pas possible d'ajouter des images ou des / aux menus. 1111..11.. CCrraattiioonn mmaannuueellllee ddee mmeennuuss Selon la tradition pdagogique, nous commencerons par le plus compliqu :) Regardons les fonctions utilises pour crer les menus. La premire sert crer un nouveau menu. GtkWidget *gtk_menu_bar_new() Cette fonction cre une nouvelle barre de menu. On utilise la fonction _g_t_k___c_o_n_t_a_i_n_e_r___a_d_d pour la placer dans une fentre, ou les fonctions _b_o_x___p_a_c_k pour la placer dans une bote - la mme que pour les boutons. GtkWidget *gtk_menu_new(); Cette fonction retourne un pointeur vers un nouveau menu, il n'est jamais montr (avec _g_t_k___w_i_d_g_e_t___s_h_o_w), il ne fait que contenir les items du menu. Ceci deviendra plus clair lorsque nous tudierons l'exemple ci-dessous. Les deux appels suivants servent crer des items de menu qui seront placs dans le menu. GtkWidget *gtk_menu_item_new() et GtkWidget *gtk_menu_item_new_with_label(const char *label) Ces appels servent crer les menus qui doivent tre affichs. On doit bien faire la diffrence entre un menu qui est cr avec _g_t_k___m_e_n_u___n_e_w_(_) et un item de menu cr avec les fonctions _g_t_k___m_e_n_u___i_t_e_m___n_e_w_(_). L'item de menu sera un vritable bouton avec une action associe alors qu'un menu sera un container contenant les items. gtk_menu_item_append() gtk_menu_item_set_submenu() Les fonctions _g_t_k___m_e_n_u___n_e_w___w_i_t_h___l_a_b_e_l_(_) et _g_t_k___m_e_n_u___n_e_w_(_) sont telles que vous les attendiez aprs avoir tudi les boutons. L'une cre un nouvel item de menu contenant dj un label, et l'autre ne fait que crer un item de menu vide. Voici les tapes pour crer une barre de menu avec des menus attachs : +o Crer un nouveau menu avec _g_t_k___m_e_n_u___n_e_w_(_) +o Crer un item de menu avec _g_t_k___m_e_n_u___i_t_e_m___n_e_w_(_). Ce sera la racine du menu, le texte apparaissant ici sera aussi sur la barre de menu. +o Utiliser plusieurs appels _g_t_k___m_e_n_u___i_t_e_m___n_e_w_(_) pour chaque item que l'on dsire dans le menu. Utiliser _g_t_k___m_e_n_u___i_t_e_m___a_p_p_e_n_d_(_) pour placer chacun de ces items les uns aprs les autres. Cela cre une liste d'items de menu. +o Utiliser _g_t_k___m_e_n_u___i_t_e_m___s_e_t___s_u_b_m_e_n_u_(_) pour attacher les items de menus nouvellement crs l'item de menu racine (celui cr la seconde tape). +o Crer une nouvelle barre de menu avec _g_t_k___m_e_n_u___b_a_r___n_e_w_(_). Cette tape ne doit tre faite qu'une fois lorsque l'on cre une srie de menu sur une seule barre de menus. +o Utiliser _g_t_k___m_e_n_u___b_a_r___a_p_p_e_n_d_(_) pour placer le menu racine dans la barre de menu. La cration d'un menu surgissant est presque identique. La diffrence est que le menu n'est pas post automatiquement par une barre de menu, mais explicitement en appelant la fonction _g_t_k___m_e_n_u___p_o_p_u_p_(_) par un vnement bouton press . Suivez ces tapes +o Crer une fonction de gestion d'vnement. Elle doit avoir le prototype static gint handler(GtkWidget *widget, GdkEvent *event); et elle utilisera l'vnement _e_v_e_n_t pour savoir o faire surgir le menu. +o Ce gestionnaire, si l'vnement est un appui sur un bouton souris, traite _e_v_e_n_t comme un vnement bouton (ce qu'il est) et l'utilise, de la faon indique dans le code d'exemple, pour passer l'information _g_t_k___m_e_n_u___p_o_p_u_p_(_). +o Lier ce gestionnaire un widget avec : gtk_signal_connect_object(GTK_OBJECT(widget), "event", GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu)); o _w_i_d_g_e_t est le widget auquel vous le liez, _h_a_n_d_l_e_r est le gestion- naire, et _m_e_n_u est un menu cr avec _g_t_k___m_e_n_u___n_e_w_(_). Cela peut tre un menu qui est aussi post par une barre de menu, comme le montre l'exem- ple. 1111..22.. EExxeemmppllee ddee mmeennuu mmaannuueell #include static gint button_press (GtkWidget *, GdkEvent *); static void menuitem_response (GtkWidget *, gchar *); int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *menu; GtkWidget *menu_bar; GtkWidget *root_menu; GtkWidget *menu_items; GtkWidget *vbox; GtkWidget *button; char buf[128]; int i; gtk_init (&argc, &argv); /* Cration d'un fentre */ window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW (window), "Test de Menu GTK"); gtk_signal_connect(GTK_OBJECT (window), "delete_event", (GtkSignalFunc) gtk_exit, NULL); /* Initialise le widget menu -- Attention : n'appelez jamais * gtk_show_widget() pour le widget menu !!! * C'est le menu qui contient les items de menu, celui qui surgira * lorsque vous cliquez sur le menu racine de l'application. */ menu = gtk_menu_new(); /* Voici le menu racine dont le label sera le nom du menu affich sur la barre * de menu. Il n'a pas de gestionnaire de signal attach car il ne fait * qu'afficher le reste du menu lorsqu'il est press. */ root_menu = gtk_menu_item_new_with_label("Menu racine"); gtk_widget_show(root_menu); /* Puis, on cre une petite boucle crant trois entres pour menu test * Notez l'appel gtk_menu_append(). Ici, on ajoute une liste d'items * notre menu. Normalement, on devrait aussi capturer le signal "clicked" * pour chacun des items et configurer une fonction de rappel pour lui, * mais on l'a omis ici pour gagner de la place. */ for(i = 0; i < 3; i++) { /* Copie des noms dans buf. */ sprintf(buf, "Sous-menu Test - %d", i); /* Cration d'un nouveau item de menu avec un nom... */ menu_items = gtk_menu_item_new_with_label(buf); /* ...et ajout de celui-ci dans le menu. */ gtk_menu_append(GTK_MENU (menu), menu_items); /* On fait quelque chose d'intressant lorsque l'item est * slectionn. */ gtk_signal_connect (GTK_OBJECT(menu_items), "activate", GTK_SIGNAL_FUNC(menuitem_response), (gpointer) g_strdup(buf)); /* Affichage du widget. */ gtk_widget_show(menu_items); } /* Maintenant, on spcifi que nous voulons que notre nouveau menu * soit le menu du menu racine . */ gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu); /* Cration d'une vbox pour y mettre un menu et un bouton. */ vbox = gtk_vbox_new(FALSE, 0); gtk_container_add(GTK_CONTAINER(window), vbox); gtk_widget_show(vbox); /* Cration d'une barre de menus pour contenir les menus. Puis, on * l'ajoute notre fentre principale. */ menu_bar = gtk_menu_bar_new(); gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2); gtk_widget_show(menu_bar); /* Cration d'un bouton pour y attacher le menu. */ button = gtk_button_new_with_label("Pressez moi"); gtk_signal_connect_object(GTK_OBJECT(button), "event", GTK_SIGNAL_FUNC (button_press), GTK_OBJECT(menu)); gtk_box_pack_end(GTK_BOX(vbox), button, TRUE, TRUE, 2); gtk_widget_show(button); /* Finalement, on ajoute l'item de menu la barre de menu -- * c'est l'item de menu racine sur lequel je me suis dchan ;-) */ gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu); /* Affichage de la fentre. */ gtk_widget_show(window); gtk_main (); return 0; } /* On rpond un appui sur le bouton en postant un nouveau menu pass comme * un widget. * * On remarque que le paramtre "widget" est le menu poster, PAS le bouton * qui a t press. */ static gint button_press (GtkWidget *widget, GdkEvent *event) { if (event->type == GDK_BUTTON_PRESS) { GdkEventButton *bevent = (GdkEventButton *) event; gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL, bevent->button, bevent->time); /* On indique l'appelant que l'on a gr cet vnement. */ return TRUE; } /* On indique l'appelant que l'on n'a pas gr cet vnement. */ return FALSE; } /* Affiche une chane lorsqu'un item de menu est choisi. */ static void menuitem_response (GtkWidget *widget, gchar *string) { printf("%s\n", string); } Vous pouvez aussi configurer un item de menu pour qu'il ne soit pas slectionnable et, en utilisant une table de raccourcis clavier, lier des touches aux fonctions du menu. 1111..33.. UUttiilliissaattiioonn ddee GGttkkMMeennuuFFaaccttoorryy Maintenant que nous avons explor la voie difficile, nous allons voir l'utilisation des appels _g_t_k___m_e_n_u___f_a_c_t_o_r_y_. 1111..44.. EExxeemmppllee dd''uussiinnee mmeennuu Voici un exemple utilisant l'usine menu de GTK. Le premier fichier est _m_e_n_u_s_._h. Nous sparerons _m_e_n_u_s_._c et _m_a_i_n_._c cause des variables globales utilises dans le fichier _m_e_n_u_s_._c. #ifndef __MENUS_H__ #define __MENUS_H__ #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ void get_main_menu (GtkWidget **menubar, GtkAcceleratorTable **table); void menus_create(GtkMenuEntry *entries, int nmenu_entries); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __MENUS_H__ */ Voici le fichier _m_e_n_u_s_._c : #include #include #include "main.h" static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path); static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path); void menus_init(void); void menus_create(GtkMenuEntry * entries, int nmenu_entries); /* Structure GtkMenuEntry utilise pour crer les menus. Le premier champ * est la chane de dfinition du menu. Le second, la touche de raccourci * utilise pour accder cette fonction du menu avec le clavier. * Le troisime est la fonction de rappel utiliser lorsque l'item de menu * est choisi (par la touche de raccourci ou avec la souris). Le dernier * lment est la donne passer la fonction de rappel. */ static GtkMenuEntry menu_items[] = { {"
/Fichier/Nouveau", "N", NULL, NULL}, {"
/Fichier/Ouvrir", "O", NULL, NULL}, {"
/Fichier/Sauver", "S", NULL, NULL}, {"
/Fichier/Sauver sous", NULL, NULL, NULL}, {"
/Fichier/", NULL, NULL, NULL}, {"
/Fichier/Quitter", "Q", file_quit_cmd_callback, "OK, c'est fini"}, {"
/Options/Test", NULL, NULL, NULL} }; /* Calcul du nombre d'lments de menu_item */ static int nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]); static int initialize = TRUE; static GtkMenuFactory *factory = NULL; static GtkMenuFactory *subfactory[1]; static GHashTable *entry_ht = NULL; void get_main_menu(GtkWidget ** menubar, GtkAcceleratorTable ** table) { if (initialize) menus_init(); if (menubar) *menubar = subfactory[0]->widget; if (table) *table = subfactory[0]->table; } void menus_init(void) { if (initialize) { initialize = FALSE; factory = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR); subfactory[0] = gtk_menu_factory_new(GTK_MENU_FACTORY_MENU_BAR); gtk_menu_factory_add_subfactory(factory, subfactory[0], "
"); menus_create(menu_items, nmenu_items); } } void menus_create(GtkMenuEntry * entries, int nmenu_entries) { char *accelerator; int i; if (initialize) menus_init(); if (entry_ht) for (i = 0; i < nmenu_entries; i++) { accelerator = g_hash_table_lookup(entry_ht, entries[i].path); if (accelerator) { if (accelerator[0] == '\0') entries[i].accelerator = NULL; else entries[i].accelerator = accelerator; } } gtk_menu_factory_add_entries(factory, entries, nmenu_entries); for (i = 0; i < nmenu_entries; i++) if (entries[i].widget) { gtk_signal_connect(GTK_OBJECT(entries[i].widget), "install_accelerator", (GtkSignalFunc) menus_install_accel, entries[i].path); gtk_signal_connect(GTK_OBJECT(entries[i].widget), "remove_accelerator", (GtkSignalFunc) menus_remove_accel, entries[i].path); } } static gint menus_install_accel(GtkWidget * widget, gchar * signal_name, gchar key, gchar modifiers, gchar * path) { char accel[64]; char *t1, t2[2]; accel[0] = '\0'; if (modifiers & GDK_CONTROL_MASK) strcat(accel, ""); if (modifiers & GDK_SHIFT_MASK) strcat(accel, ""); if (modifiers & GDK_MOD1_MASK) strcat(accel, ""); t2[0] = key; t2[1] = '\0'; strcat(accel, t2); if (entry_ht) { t1 = g_hash_table_lookup(entry_ht, path); g_free(t1); } else entry_ht = g_hash_table_new(g_string_hash, g_string_equal); g_hash_table_insert(entry_ht, path, g_strdup(accel)); return TRUE; } static void menus_remove_accel(GtkWidget * widget, gchar * signal_name, gchar * path) { char *t; if (entry_ht) { t = g_hash_table_lookup(entry_ht, path); g_free(t); g_hash_table_insert(entry_ht, path, g_strdup("")); } } void menus_set_sensitive(char *path, int sensitive) { GtkMenuPath *menu_path; if (initialize) menus_init(); menu_path = gtk_menu_factory_find(factory, path); if (menu_path) gtk_widget_set_sensitive(menu_path->widget, sensitive); else g_warning("Impossible de configurer la sensitivit d'un menu qui n'existe pas : %s", path); } Voici _m_a_i_n_._h : #ifndef __MAIN_H__ #define __MAIN_H__ #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ void file_quit_cmd_callback(GtkWidget *widget, gpointer data); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /* __MAIN_H__ */ Et, enfin, _m_a_i_n_._c : #include #include "main.h" #include "menus.h" int main(int argc, char *argv[]) { GtkWidget *window; GtkWidget *main_vbox; GtkWidget *menubar; GtkAcceleratorTable *accel; gtk_init(&argc, &argv); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_signal_connect(GTK_OBJECT(window), "destroy", GTK_SIGNAL_FUNC(file_quit_cmd_callback), "WM destroy"); gtk_window_set_title(GTK_WINDOW(window), "Usine menu"); gtk_widget_set_usize(GTK_WIDGET(window), 300, 200); main_vbox = gtk_vbox_new(FALSE, 1); gtk_container_border_width(GTK_CONTAINER(main_vbox), 1); gtk_container_add(GTK_CONTAINER(window), main_vbox); gtk_widget_show(main_vbox); get_main_menu(&menubar, &accel); gtk_window_add_accelerator_table(GTK_WINDOW(window), accel); gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0); gtk_widget_show(menubar); gtk_widget_show(window); gtk_main(); return(0); } /* Juste une dmonstration du fonctionnement des fonctions de rappel * lorsqu'on utilise l'usine menus. Souvent, on met tous les rappels * des menus dans un fichier spar, ce qui assure une meilleure * organisation. */ void file_quit_cmd_callback (GtkWidget *widget, gpointer data) { g_print ("%s\n", (char *) data); gtk_exit(0); } Un _m_a_k_e_f_i_l_e pour que cela soit plus facile compiler : CC = gcc PROF = -g C_FLAGS = -Wall $(PROF) -L/usr/local/include -DDEBUG L_FLAGS = $(PROF) -L/usr/X11R6/lib -L/usr/local/lib L_POSTFLAGS = -lgtk -lgdk -lglib -lXext -lX11 -lm PROGNAME = at O_FILES = menus.o main.o $(PROGNAME): $(O_FILES) rm -f $(PROGNAME) $(CC) $(L_FLAGS) -o $(PROGNAME) $(O_FILES) $(L_POSTFLAGS) .c.o: $(CC) -c $(C_FLAGS) $< clean: rm -f core *.o $(PROGNAME) nohup.out distclean: clean rm -f *~ Pour l'instant, il n'y a que cet exemple. Une explication et de nombreux commentaires seront intgrs plus tard. 1122.. WWiiddggeettss nnoonn ddooccuummeennttss On a besoin de leurs auteurs! :). Participez notre didacticiel. Si vous devez utiliser un de ces widgets non documents, je vous recommande fortement de consulter leurs fichiers en-ttes respectifs dans la distribution GTK. Les noms de fonctions du GTK sont trs parlantes. Lorsque vous avez compris comment les choses fonctionnent, il n'est pas difficile de savoir comment utiliser un widget partir des dclarations de ses fonctions. Cela, avec quelques exemples de codes pris ailleurs, devrait ne pas poser de problme. Lorsque vous avez compris toutes les fonctions d'un nouveau widget non document, pensez crire un didacticiel pour que les autres puissent bnficier du temps que vous y avez pass. 1122..11.. EEnnttrreess ddee tteexxttee 1122..22.. SSlleeccttiioonnss ddee ccoouulleeuurrss 1122..33.. CCoonnttrrllee dd''iinntteerrvvaallllee 1122..44.. RRgglleess 1122..55.. BBootteess ddee tteexxttee 1122..66.. PPrrvviissuuaalliissaattiioonn (Ceci peut devoir tre rcrit pour suivre le style du reste de ce didacticiel). Les prvisualisateurs servent plusieurs choses dans GIMP/GTK. La plus importante est celle-ci : les images de haute qualit peuvent occuper des dizaines de mega-octets en mmoire - facilement ! Toute opration sur une image aussi grosse implique un temps de traitement lev. Si cela vous prend 5 10 essais (i.e. 10 20 tapes puisque vous devez recommencer lorsque vous avez fait une erreur) pour choisir la bonne modification, cela prendra littralement des heures pour produire la bonne image - pour peu que vous ne manquiez pas de mmoire avant. Ceux qui on pass des heures dans les chambres noires de dveloppement couleur connaissent cette sensation. Les prvisualisations sont notre planche de salut ! L'aspect pnible de l'attente n'est pas le seul problme. souvent, il est utile de comparer les versions Avant et Aprs cte cte ou, au pire l'une aprs l'autre. Si vous travaillez avec de grosses images et des attentes de 10 secondes, l'obtention des versions Avant et Aprs est, pour le moins, difficile. Pour des images de 30Mo (4"x6", 600dpi, 24 bits), la comparaison cte cte est impossible pour la plupart des gens, et la comparaison squentielle n'est gure mieux. Les prvisualisations sont notre planche de salut ! Mais il y a plus. Les prvisualisations permettent les pr-prvisualisations cte cte. En d'autres termes, vous crivez un plug-in (par exemple la simulation filterpack) qui aurait plusieurs prvisualisations de ce-que-ce-serait-si-vous-faisiez-ceci. Une approche comme celle ci agit comme une sorte de palette de prvisualisation et est trs pratique pour les petites modifications. Utilisons les prvisualisations ! Encore plus : pour certains plug-ins une intervention humaine en temps rel, spcifique aux images, peut s'avrer ncessaire. Dans le plug-in SuperNova, par exemple, on demande l'utilisateur d'entrer les coordonnes du centre de la future supernova. La faon la plus simple de faire cela, vraiment, est de prsenter une prvisualisation l'utilisateur et de lui demander de choisir interactivement le point. Utilisons les prvisualisations ! Enfin, quelques utilisations diverses : on peut utiliser les prvisualisations, mme lorsqu'on ne travaille pas avec de grosses images. Elles sont utiles, par exemple, lorsqu'on veut avoir un rendu de motifs complexes. (Testez le vnrable plug-in Diffraction et d'autres !). Comme autre exemple, regardez le plug-in de rotation de couleur (travail en cours). Vous pouvez aussi utiliser les prvisualisations pour des petits logos dans vos plug-ins et mme pour une photo de vous, l'Auteur. Utilisons les prvisualisations ! Quand ne pas utiliser les prvisualisations N'utilisez pas les prvisualisations pour les graphes, les tracs, etc. GDK est bien plus rapide pour a. N'utilisez les que pour les images ! Utilisons les prvisualisations ! Vous pouvez mettre une prvisualisation dans peu prs n'importe quoi. Dans une vbox, une hbox, un bouton, etc. Mais elles donnent leur meilleur d'elles-mmes dans des cadres resserrs autour d'elles. Les prvisualisations n'ont, par elles-mmes, aucun contour et semblent plates sans eux. (Bien sr, si c'est cet aspect que vous voulez...). Les cadres serrs fournissent les bordures ncessaires. [Image][Image] Les prvisualisations sont, bien des gards, comme tous les autres widgets de GTK (avec tout ce que cela implique) sauf qu'il disposent d'une fonctionnalit supplmentaire : ils doivent tre remplis avec une image ! Nous traiterons d'abord exclusivement de l'aspect GTK des prvisualisations, puis nous verrons comment les remplir. /* Cration d'un widget prvisualisation, * configuration de sa taille et affichage */ GtkWidget *preview; preview=gtk_preview_new(GTK_PREVIEW_COLOR) /* Autre option : GTK_PREVIEW_GRAYSCALE);*/ gtk_preview_size (GTK_PREVIEW (preview), WIDTH, HEIGHT); gtk_widget_show(preview); my_preview_rendering_function(preview); Ah oui, comme je le disais, les prvisualisations rendent mieux dans des cadres : GtkWidget *create_a_preview(int Width, int Height, int Colorfulness) { GtkWidget *preview; GtkWidget *frame; frame = gtk_frame_new(NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_container_border_width (GTK_CONTAINER(frame),0); gtk_widget_show(frame); preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR :GTK_PREVIEW_GRAYSCALE); gtk_preview_size (GTK_PREVIEW (preview), Width, Height); gtk_container_add(GTK_CONTAINER(frame),preview); gtk_widget_show(preview); my_preview_rendering_function(preview); return frame; } Ceci est ma prvisualisation de base. Cette fonction retourne le cadre pre , on peut ainsi le placer ailleurs dans notre interface. Bien sr, on peut passer le cadre pre en paramtre cette fonction. Dans de nombreuses situations, toutefois, le contenu de la prvisualisation est change continuellement par notre application. En ce cas, on peut passer un pointeur vers une prvisualisation la fonction bpp; ReducedImage *temp=(ReducedImage *)malloc(sizeof(ReducedImage)); guchar *tempRGB, *src_row, *tempmask, *src_mask_row,R,G,B; gint i, j, whichcol, whichrow, x1, x2, y1, y2; GPixelRgn srcPR, srcMask; gint NoSelectionMade=TRUE; /* Suppose que l'on traite l'image entire */ gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2); width = x2-x1; height = y2-y1; /* S'il y a une SELECTION, on rcupre ses frontires ! */ if (width != drawable->width && height != drawable->height) NoSelectionMade=FALSE; /* On vrifie si l'utilisateur a rendu une slection active */ /* Ceci sera important plus tard, lorsqu'on crera un masque rduit */ /* Si on veut prvisualiser l'image entire, supprimer ce qui suit ! */ /* Bien sr, s'il n'y a pas de slection, cela n'a aucun effet ! */ if (Selection==ENTIRE_IMAGE) { x1=0; x2=drawable->width; y1=0; y2=drawable->height; } /* Si on veut prvisualiser une slection avec une surface qui l'entoure, */ /* on doit l'agrandir un petit peu. Considrez a comme une devinette. */ if (Selection==SELECTION_IN_CONTEXT) { x1=MAX(0, x1-width/2.0); x2=MIN(drawable->width, x2+width/2.0); y1=MAX(0, y1-height/2.0); y2=MIN(drawable->height, y2+height/2.0); } /* Calcul de la largeur et de la hauteur de la surface rduire. */ width = x2-x1; height = y2-y1; /* Les lignes ci-dessous dterminent la dimension qui sera le cot */ /* le plus long. Cette ide est emprunte au plug-in Supernova. */ /* Je souponne que j'aurais pu y penser moi-mme, mais la vrit */ /* doit tre dite. Le plagiat pue ! */ if (width>height) { RW=LongerSize; RH=(float) height * (float) LongerSize/ (float) width; } else { RH=LongerSize; RW=(float)width * (float) LongerSize/ (float) height; } /* L'image entire est rduite dans une chane ! */ tempRGB = (guchar *) malloc(RW*RH*bytes); tempmask = (guchar *) malloc(RW*RH); gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE); gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE); /* Rservation pour sauver une ligne d'image et une ligne du masque */ src_row = (guchar *) malloc (width*bytes); src_mask_row = (guchar *) malloc (width); for (i=0; i < RH; i++) { whichrow=(float)i*(float)height/(float)RH; gimp_pixel_rgn_get_row (&srcPR, src_row, x1, y1+whichrow, width); gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width); for (j=0; j < RW; j++) { whichcol=(float)j*(float)width/(float)RW; /* Pas de slection = chaque point est compltement slectionn ! */ if (NoSelectionMade) tempmask[i*RW+j]=255; else tempmask[i*RW+j]=src_mask_row[whichcol]; /* Ajout de la ligne la longue chane qui contient maintenant */ /* l'image ! */ tempRGB[i*RW*bytes+j*bytes+0]=src_row[whichcol*bytes+0]; tempRGB[i*RW*bytes+j*bytes+1]=src_row[whichcol*bytes+1]; tempRGB[i*RW*bytes+j*bytes+2]=src_row[whichcol*bytes+2]; /* On s'accroche aussi l'alpha */ if (bytes==4) tempRGB[i*RW*bytes+j*bytes+3]=src_row[whichcol*bytes+3]; } } temp->bpp=bytes; temp->width=RW; temp->height=RH; temp->rgb=tempRGB; temp->mask=tempmask; return temp; } La suite est une fonction de prvisualisation qui utilise le mme type 0 ) return 64; else return 196; } Voici maintenant la fonction de prvisualisationnbsp;: void my_preview_render_function(GtkWidget *preview, gint changewhat, gint changewhich) { gint Inten, bytes=drawable->bpp; gint i, j, k; float partial; gint RW=reduced->width; gint RH=reduced->height; guchar *row=malloc(bytes*RW);; for (i=0; i < RH; i++) { for (j=0; j < RW; j++) { row[j*3+0] = reduced->rgb[i*RW*bytes + j*bytes + 0]; row[j*3+1] = reduced->rgb[i*RW*bytes + j*bytes + 1]; row[j*3+2] = reduced->rgb[i*RW*bytes + j*bytes + 2]; if (bytes==4) for (k=0; k<3; k++) { float transp=reduced->rgb[i*RW*bytes+j*bytes+3]/255.0; row[3*j+k]=transp*a[3*j+k]+(1-transp)*fake_transparency(i,j); } } gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,i,RW); } free(a); gtk_widget_draw(preview,NULL); gdk_flush(); } Fonctions applicables guint gtk_preview_get_type (void); /* Aucune ide */ void gtk_preview_uninit (void); /* Aucune ide */ GtkWidget* gtk_preview_new (GtkPreviewType type); /* Dcrite ci-dessous */ void gtk_preview_size (GtkPreview *preview, gint width, gint height); /* Permet de changer la taille d'une prvisualisation existante */ /* Apparamment, il y a un bug dans GTK qui rend ce traitement */ /* hasardeux. Une mthode pour corriger ce problme consiste */ /* changer manuellement la taille de la fentre contenant la */ /* prvisualisation aprs avoir chang la taille de la */ /* prvisualisation. */ void gtk_preview_put (GtkPreview *preview, GdkWindow *window, GdkGC *gc, gint srcx, gint srcy, gint destx, gint desty, gint width, gint height); /* Aucune ide */ void gtk_preview_put_row (GtkPreview *preview, guchar *src, guchar *dest, gint x, gint y, gint w); /* Aucune ide */ void gtk_preview_draw_row (GtkPreview *preview, guchar *data, gint x, gint y, gint w); /* Dcrite dans le texte */ void gtk_preview_set_expand (GtkPreview *preview, gint expand); /* Aucune ide */ /* Aucune piste pour celles qui suivent mais devrait tre */ /* un standard pour la plupart des widgets. */ void gtk_preview_set_gamma (double gamma); void gtk_preview_set_color_cube (guint nred_shades, guint ngreen_shades, guint nblue_shades, guint ngray_shades); void gtk_preview_set_install_cmap (gint install_cmap); void gtk_preview_set_reserved (gint nreserved); GdkVisual* gtk_preview_get_visual (void); GdkColormap* gtk_preview_get_cmap (void); GtkPreviewInfo* gtk_preview_get_info (void); That's all, folks! 1122..77.. CCoouurrbbeess 1133.. WWiiddggeett EEvveennttBBooxx Il n'est disponible que dans _g_t_k_+_9_7_0_9_1_6_._t_a_r_._g_z et les distributions ultrieures. Certains widgets GTK n'ont pas de fentre X associe, il se dessinent donc sur leurs parents. cause de cela, ils ne peuvent recevoir d'vnements et, s'ils ont une taille incorrecte, ils ne peuvent pas se mettre en place correctement : on peut alors avoir des surimpressions douteuses, etc. Si vous avez besoin de ces widgets, _E_v_e_n_t_B_o_x est fait pour vous. Au premier abord, le widget _E_v_e_n_t_B_o_x peut apparatre comme totalement dnu d'intrt. Il ne dessine rien l'cran et ne rpond aucun venement. Cependant, il joue un rle - il fournit une fentre X pour son widget fils. Ceci est important car de nombreux widgets GTK n'ont pas de fentre X associe. Ne pas avoir de fentre permet d'conomiser de la mmoire mais a aussi quelques inconvnients. Un widget sans fentre ne peut recevoir d'vnement, et ne ralise aucune mise en place de ce qu'il contient. Bien que le nom EventBox insiste sur la fonction de gestion d'vnement, le widget peut aussi tre utilis pour la mise en place (et plus... voir l'exemple ci-dessous). Pour crer un widget EventBox, on utilise : GtkWidget* gtk_event_box_new (void); Un widget fils peut alors tre ajout cet _E_v_e_n_t_B_o_x : gtk_container_add (GTK_CONTAINER(event_box), widget); L'exemple suivant montre l'utilisation d'un _E_v_e_n_t_B_o_x - un label est cr et mis en place sur une petite bote, et configur pour qu'un clic souris sur le label provoque la fin du programme. #include int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *event_box; GtkWidget *label; gtk_init (&argc, &argv); window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Event Box"); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_exit), NULL); gtk_container_border_width (GTK_CONTAINER (window), 10); /* Cration d'un EventBox et ajout de celui-ci dans la fentre. */ event_box = gtk_event_box_new (); gtk_container_add (GTK_CONTAINER(window), event_box); gtk_widget_show (event_box); /* Cration d'un long label */ label = gtk_label_new ("Cliquez ici pour quitter, quitter, quitter, quitter, quitter"); gtk_container_add (GTK_CONTAINER (event_box), label); gtk_widget_show (label); /* Placement serr. */ gtk_widget_set_usize (label, 110, 20); /* Attachement d'une action celui-ci. */ gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK); gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event", GTK_SIGNAL_FUNC (gtk_exit), NULL); /* Encore une fois, vous avez besoin d'une fentre X pour... */ gtk_widget_realize (event_box); gdk_window_set_cursor (event_box->window, gdk_cursor_new (GDK_HAND1)); gtk_widget_show (window); gtk_main (); return 0; } 1144.. CCoonnffiigguurraattiioonn ddeess aattttrriibbuuttss ddee wwiiddggeett Cette section dcrit les fonctions oprant sur les widgets. Elles peuvent tre utilises pour configurer le style, l'espacement, la taille, etc. (Je devrais peut tre faire une section uniquement consacre aux raccourcis clavier.) void gtk_widget_install_accelerator (GtkWidget *widget, GtkAcceleratorTable *table, gchar *signal_name, gchar key, guint8 modifiers); void gtk_widget_remove_accelerator (GtkWidget *widget, GtkAcceleratorTable *table, gchar *signal_name); void gtk_widget_activate (GtkWidget *widget); void gtk_widget_set_name (GtkWidget *widget, gchar *name); gchar* gtk_widget_get_name (GtkWidget *widget); void gtk_widget_set_sensitive (GtkWidget *widget, gint sensitive); void gtk_widget_set_style (GtkWidget *widget, GtkStyle *style); GtkStyle* gtk_widget_get_style (GtkWidget *widget); GtkStyle* gtk_widget_get_default_style (void); void gtk_widget_set_uposition (GtkWidget *widget, gint x, gint y); void gtk_widget_set_usize (GtkWidget *widget, gint width, gint height); void gtk_widget_grab_focus (GtkWidget *widget); void gtk_widget_show (GtkWidget *widget); void gtk_widget_hide (GtkWidget *widget); 1155.. TTeemmppoorriissaattiioonnss,, ffoonnccttiioonnss dd''EE//SS eett dd''aatttteennttee 1155..11.. TTeemmppoorriissaattiioonnss Vous pouvez vous demander comment faire pour que GTK fasse quelque chose d'utile lorsqu'il est dans _g_t_k___m_a_i_n. En fait, on a plusieurs options. L'utilisation des fonctions suivantes permet de crer une temporisation qui sera appele tous les _i_n_t_e_r_v_a_l millisecondes. gint gtk_timeout_add (guint32 interval, GtkFunction function, gpointer data); Le premier paramtre est le nombre de millisecondes entre les appels notre fonction. Le deuxime est la fonction appeler et le troisime est la donne passe cette fonction de rappel. La valeur retourne est un marqueur de type entier qui pourra tre utilis pour arrter la temporisation en appelant : void gtk_timeout_remove (gint tag); On peut aussi stopper la fonction de temporisation en faisant retourner zro ou FALSE notre fonction de rappel. videmment, cela veut dire que si vous voulez que votre fonction continue tre appele, elle doit retourner une valeur non nulle, ou TRUE. La dclaration de votre fonction de rappel doit ressembler a : gint timeout_callback (gpointer data); 1155..22.. SSuurrvveeiillllaannccee ddeess EE//SS Une autre caractristique intressante du GTK est la possibilit de vrifier les donnes d'un descripteur de fichier (celles retournes par _o_p_e_n(2) ou _s_o_c_k_e_t(2)). C'est particulirement pratique pour les applications rseau. La fonction suivante permet cette vrification : gint gdk_input_add (gint source, GdkInputCondition condition, GdkInputFunction function, gpointer data); Le premier paramtre est le descripteur de fichier que l'on veut tudier, le second spcifie ce qu'on veut que le GDK recherche. Cela peut tre : GDK_INPUT_READ - Appel _f_u_n_c_t_i_o_n lorsqu'il y a une donne prte tre lue dans le descripteur de fichier. GDK_INPUT_WRITE - Appel de _f_u_n_c_t_i_o_n lorsque le descripteur de fichier est prt pour une criture. Je suis sr que vous vous doutez, maintenant, que le troisime paramtre est la fonction que l'on veut appeler lorsque les conditions ci-dessus sont satisfaites. Le dernier paramtre est la donne passer cette fonction. La valeur retourne est un marqueur qui pourra tre utilis pour dire au GDK de cesser de surveiller ce descripteur l'aide de la fonction : void gdk_input_remove (gint tag); La fonction de rappel doit tre dclare de la faon suivante : void input_callback (gpointer data, gint source, GdkInputCondition condition); 1155..33.. FFoonnccttiioonnss dd''aatttteennttee Que se passe-t'il si vous avez une fonction qui doit tre appele lorsque rien d'autre ne se passe ? On utilise la fonction suivante qui force GTK appeler _f_u_n_c_t_i_o_n lorsqu'on est en phase d'inaction ; gint gtk_idle_add (GtkFunction function, gpointer data); void gtk_idle_remove (gint tag); Je n'expliquerai pas la signification des paramtres car ils ressemblent beaucoup ceux dj vus ci-dessus. La fonction pointe par le premier paramtre de _g_t_k___i_d_l_e___a_d_d_(_) sera appele chaque occasion. Comme pour les autres, retourner FALSE empchera la fonction d'attente d'tre appele. 1166.. GGeessttiioonn ddeess sslleeccttiioonnss 1166..11.. IInnttrroodduuccttiioonn Un type de communication inter-processus gre par GTK est les _s_l_e_c_t_i_o_n_s. Une slection identifie un morceau de donnes, par exemple une portion de texte slectionne par l'utilisateur avec la souris. Seule une application sur un cran (le _p_r_o_p_r_i_t_a_i_r_e) peut possder une slection particulire un moment donn, ainsi lorsqu'une slection est rclame par une application, le propritaire prcdent doit indiquer l'utilisateur que la slection a t abandonne. Les autres applications peuvent demander le contenu d'une slection sous diffrentes formes appeles _c_i_b_l_e_s. Il peut y avoir un nombre quelconque de slections, mais la plupart des applications X n'en grent qu'une, la _s_l_e_c_t_i_o_n _p_r_i_m_a_i_r_e. Dans la plupart des cas, une application GTK n'a pas besoin de grer elle-mme les slections. Les widgets standards, comme le widget Entre de texte, possdent dj la capacit de rclamer la slection lorsqu'il le faut (par exemple, lorsque l'utilisateur glisse au dessus d'un texte) et de rcuprer le contenu de la slection dtenue par un autre widget ou une autre application (par exemple, lorsque l'utilisateur clique avec le deuxime bouton de la souris). Cependant, il peut il y avoir des cas dans lesquels vous voulez donner aux autres widgets la possibilit de fournir la slection, ou vous dsirez rcuprer des cibles non supportes par dfaut. Un concept fondamental dans la comprhension du fonctionnement des slections est celui d'_a_t_o_m_e. Un atome est un entier qui dfinit de faon unique une chane (sur un affichage particulier). Certains atomes sont prdfinis par le serveur X et, dans certains cas, des constantes dfinies dans _g_t_k_._h correspondent ces atomes. Par exemple, la constante GDK_PRIMARY_SELECTION correspond la chane "PRIMARY". Dans d'autres cas, on doit utiliser les fonctions _g_d_k___a_t_o_m___i_n_t_e_r_n_(_), pour obtenir l'atome correspondant une chane, et _g_d_k___a_t_o_m___n_a_m_e_(_), pour obtenir le nom d'un atome. Les slections et les cibles sont identifis par des atomes. 1166..22.. RRccuupprraattiioonn ddee llaa sslleeccttiioonn La rcupration de la slection est un processus asynchrone. Pour dmarrer le processus, on appelle : gint gtk_selection_convert (GtkWidget *widget, GdkAtom selection, GdkAtom target, guint32 time) Cela _c_o_n_v_e_r_t_i_t la slection dans la forme spcifie par _t_a_r_g_e_t. Si tout est possible, le paramtre _t_i_m_e sera le moment de l'vnement qui a dclench la slection. Ceci aide s'assurer que les vnements arrivent dans l'ordre o l'utilisateur les a demand. Cependant, si cela n'est pas possible (par exemple, lorsque la conversion a t dclenche par un signal "clicked"), alors on peut utiliser la macro GDK_CURRENT_TIME. Quand le propritaire de la slection rpond la requte, un signal "selection_received" est envoy notre application. Le gestionnaire de ce signal reoit un pointeur vers une structure GtkSelectionData dfinie ainsi : struct _GtkSelectionData { GdkAtom selection; GdkAtom target; GdkAtom type; gint format; guchar *data; gint length; }; _s_e_l_e_c_t_i_o_n et _t_a_r_g_e_t sont les valeurs que l'on a donn dans notre appel _g_t_k___s_e_l_e_c_t_i_o_n___c_o_n_v_e_r_t_(_). _t_y_p_e est un atome qui identifie le type de donnes retourn par le propritaire de la slection. Quelques valeurs possibles sont : "STRING", une chane de caractres latin-1, "ATOM", une srie d'atomes, "INTEGER", un entier, etc. La plupart des cibles ne peuvent retourner qu'un type. _f_o_r_m_a_t donne la longueur des units (les caractres, par exemple) en bits. Habituellement, on ne se proccupe pas de cela lorsqu'on reoit des donnes. _d_a_t_a est un pointeur vers la donne retourne et _l_e_n_g_t_h donne la longueur en octets de la donne retourne. Si _l_e_n_g_t_h est ngative, cela indique qu'une erreur est survenue et que la slection ne peut tre rcupre. Ceci peut arriver si aucune application n'est propritaire de la slection, ou si vous avez demand une cible que l'application ne sait pas grer. Le tampon est garanti d'tre un octet plus long que _l_e_n_g_t_h ; l'octet supplmentaire sera toujours zro, et il n'est donc pas ncessaire de faire une copie de chane simplement pour qu'elle soit termine par zro (comme doivent l'tre toutes les chanes C). Dans l'exemple qui suit, on rcupre la cible spciale "TARGETS", qui est une liste de toutes les cibles en lesquelles la slection peut tre convertie. #include void selection_received (GtkWidget *widget, GtkSelectionData *selection_data, gpointer data); /* Gestionnaire de signal invoqu lorsque l'utilisateur clique sur * le bouton Obtenir les cibles . */ void get_targets (GtkWidget *widget, gpointer data) { static GdkAtom targets_atom = GDK_NONE; /* Obtention de l'atome correspondant la chane "TARGETS" */ if (targets_atom == GDK_NONE) targets_atom = gdk_atom_intern ("TARGETS", FALSE); /* Demande de la cible "TARGETS" pour la slection primaire */ gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom, GDK_CURRENT_TIME); } /* Gestionnaire de signal appel quand le propritaire des slections * retourne la donne. */ void selection_received (GtkWidget *widget, GtkSelectionData *selection_data, gpointer data) { GdkAtom *atoms; GList *item_list; int i; /* **** IMPORTANT **** On vrifie si la rcupration s'est bien passe. */ if (selection_data->length < 0) { g_print ("Selection retrieval failed\n"); return; } /* On s'assure que l'on a obtenu la donne sous la forme attendue. */ if (selection_data->type != GDK_SELECTION_TYPE_ATOM) { g_print ("La slection \"TARGETS\" n'a pas t retourne sous la forme d'atomes !\n"); return; } /* Affichage des atomes reus. */ atoms = (GdkAtom *)selection_data->data; item_list = NULL; for (i=0; ilength/sizeof(GdkAtom); i++) { char *name; name = gdk_atom_name (atoms[i]); if (name != NULL) g_print ("%s\n",name); else g_print ("(atome incorrect)\n"); } return; } int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *button; gtk_init (&argc, &argv); /* Cration de la fentre de l'application. */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Slections"); gtk_container_border_width (GTK_CONTAINER (window), 10); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_exit), NULL); /* Cration d'un bouton pour obtenir les cibles */ button = gtk_button_new_with_label ("Obtenir les cibles"); gtk_container_add (GTK_CONTAINER (window), button); gtk_signal_connect (GTK_OBJECT(button), "clicked", GTK_SIGNAL_FUNC (get_targets), NULL); gtk_signal_connect (GTK_OBJECT(button), "selection_received", GTK_SIGNAL_FUNC (selection_received), NULL); gtk_widget_show (button); gtk_widget_show (window); gtk_main (); return 0; } 1166..33.. FFoouurrnniirr llaa sslleeccttiioonn Fournir la slection est un peu plus compliqu. On doit enregistrer les gestionnaires qui seront appels lorsque notre slection est demande. Pour chaque paire slection/cible que l'on grera, on fera un appel : void gtk_selection_add_handler (GtkWidget *widget, GdkAtom selection, GdkAtom target, GtkSelectionFunction function, GtkRemoveFunction remove_func, gpointer data); _w_i_d_g_e_t, _s_e_l_e_c_t_i_o_n et _t_a_r_g_e_t identifient les requtes que ce gestionnaire grera. S'il ne vaut pas NULL, _r_e_m_o_v_e___f_u_n_c sera appel lorsque le gestionnaire de signal est supprim. Ceci est utile, par exemple, pour des langages interprts qui doivent garder une trace du nombre de rfrences _d_a_t_a. La fonction de rappel _f_u_n_c_t_i_o_n doit avoir la signature suivante : typedef void (*GtkSelectionFunction) (GtkWidget *widget, GtkSelectionData *selection_data, gpointer data); Le _G_t_k_S_e_l_e_c_t_i_o_n_D_a_t_a est le mme qu'au dessus, mais, cette fois, nous sommes responsables de l'initialisation de ses champs _t_y_p_e, _f_o_r_m_a_t, _d_a_t_a, et _l_e_n_g_t_h. (Le champ _f_o_r_m_a_t est important ici - le serveur X l'utilise pour savoir si la donne doit tre change par octet ou non. Habituellement, ce sera 8 (un caractre), ou 32 (un entier)). Cette initialisation est faite en utilisant l'appel : void gtk_selection_data_set (GtkSelectionData *selection_data, GdkAtom type, gint format, guchar *data, gint length); Cette fonction s'occupe de faire une copie correcte des donnes afin que l'on n'ait pas se soucier du reste. (On ne doit pas remplir ces champs la main). Lorsque cela est demand par l'utilisateur, on rclame la possession de la slection en appelant : gint gtk_selection_owner_set (GtkWidget *widget, GdkAtom selection, guint32 time); Si une autre application rclame la possession de la slection, on recevra un "selection_clear_event". Comme exemple de fourniture de slection, l'exemple suivant ajoute une fonctionnalit de slection un bouton commutateur. Lorsque ce bouton est appuy, le programme rclame la slection primaire. La seule cible supporte ( part certaines cibles fournies par GTK lui-mme, comme TARGETS ) est STRING . Lorsque celle-ci est demande, on retourne une reprsentation de l'heure sous forme de chane. #include #include /* Fonction de rappel appele lorsque l'utilisateur commute la slection. */ void selection_toggled (GtkWidget *widget, gint *have_selection) { if (GTK_TOGGLE_BUTTON(widget)->active) { *have_selection = gtk_selection_owner_set (widget, GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME); /* Si la demande de slection choue, on remet le bouton en position sortie. */ if (!*have_selection) gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE); } else { if (*have_selection) { /* Avant de nettoyer la selection en mettant son propritaire NULL, * on vrifie que nous sommes bien son propritaire actuel. */ if (gdk_selection_owner_get (GDK_SELECTION_PRIMARY) == widget->window) gtk_selection_owner_set (NULL, GDK_SELECTION_PRIMARY, GDK_CURRENT_TIME); *have_selection = FALSE; } } } /* Appele lorsqu'une autre application demande la slection. */ gint selection_clear (GtkWidget *widget, GdkEventSelection *event, gint *have_selection) { *have_selection = FALSE; gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON(widget), FALSE); return TRUE; } /* Fournit l'heure comme slection. */ void selection_handle (GtkWidget *widget, GtkSelectionData *selection_data, gpointer data) { gchar *timestr; time_t current_time; current_time = time (NULL); timestr = asctime (localtime(¤t_time)); /* Lorsqu'on retourne une chane, elle ne doit pas se terminer par * 0, ce sera fait pour nous. */ gtk_selection_data_set (selection_data, GDK_SELECTION_TYPE_STRING, 8, timestr, strlen(timestr)); } int main (int argc, char *argv[]) { GtkWidget *window; GtkWidget *selection_button; static int have_selection = FALSE; gtk_init (&argc, &argv); /* Cration de la fentre principale. */ window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_window_set_title (GTK_WINDOW (window), "Event Box"); gtk_container_border_width (GTK_CONTAINER (window), 10); gtk_signal_connect (GTK_OBJECT (window), "destroy", GTK_SIGNAL_FUNC (gtk_exit), NULL); /* Cration d'un bouton commutateur pour qu'il agisse comme une slection. */ selection_button = gtk_toggle_button_new_with_label ("Demande de slection"); gtk_container_add (GTK_CONTAINER (window), selection_button); gtk_widget_show (selection_button); gtk_signal_connect (GTK_OBJECT(selection_button), "toggled", GTK_SIGNAL_FUNC (selection_toggled), &have_selection); gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event", GTK_SIGNAL_FUNC (selection_clear), &have_selection); gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY, GDK_SELECTION_TYPE_STRING, selection_handle, NULL, NULL); gtk_widget_show (selection_button); gtk_widget_show (window); gtk_main (); return 0; } 1177.. gglliibb La _g_l_i_b fournit de nombreuses fonctions et dfinitions utiles, prtes tre utilises lorsqu'on cre des applications GDK et GTK. Je les numrerais toutes avec une brve explication. Beaucoup sont des rpliques des fonctions standards de la _l_i_b_c, et je ne les dtaillerais donc pas trop. Ceci doit surtout servir de rfrence afin de savoir ce qui est disponible pour tre utilis. 1177..11.. DDffiinniittiioonnss Les dfinitions pour les bornes de la plupart des types standards sont : G_MINFLOAT G_MAXFLOAT G_MINDOUBLE G_MAXDOUBLE G_MINSHORT G_MAXSHORT G_MININT G_MAXINT G_MINLONG G_MAXLONG Voici aussi les redfinitions de types. Celles qui ne sont pas spcifies sont configures dynamiquement selon l'architecture. vitez surtout de compter sur la taille d'un pointeur si vous voulez un programme portable ! Un pointeur sur un Alpha fait 8 octets, mais il en fait 4 sur un Intel. char gchar; short gshort; long glong; int gint; char gboolean; unsigned char guchar; unsigned short gushort; unsigned long gulong; unsigned int guint; float gfloat; double gdouble; long double gldouble; void* gpointer; gint8 guint8 gint16 guint16 gint32 guint32 1177..22.. LLiisstteess ddoouubblleemmeenntt cchhaanneess Les fonctions suivantes servent crer, grer et dtruire des listes doublement chanes. Je suppose que vous savez ce qu'est une liste chane car leur explication n'entre pas dans le cadre de ce document. Bien sr, il n'y a pas besoin de les connatre pour une utilisation gnrale de GTK, mais c'est bien de savoir comment elles fonctionnent. GList* g_list_alloc (void); void g_list_free (GList *list); void g_list_free_1 (GList *list); GList* g_list_append (GList *list, gpointer data); GList* g_list_prepend (GList *list, gpointer data); GList* g_list_insert (GList *list, gpointer data, gint position); GList* g_list_remove (GList *list, gpointer data); GList* g_list_remove_link (GList *list, GList *link); GList* g_list_reverse (GList *list); GList* g_list_nth (GList *list, gint n); GList* g_list_find (GList *list, gpointer data); GList* g_list_last (GList *list); GList* g_list_first (GList *list); gint g_list_length (GList *list); void g_list_foreach (GList *list, GFunc func, gpointer user_data); 1177..33.. LLiisstteess ssiimmpplleemmeenntt cchhaanneess La plupart des fonctions pour les listes simplement chanes ci-dessous sont identiques celles vues plus haut. Voici une liste complte : GSList* g_slist_alloc (void); void g_slist_free (GSList *list); void g_slist_free_1 (GSList *list); GSList* g_slist_append (GSList *list, gpointer data); GSList* g_slist_prepend (GSList *list, gpointer data); GSList* g_slist_insert (GSList *list, gpointer data, gint position); GSList* g_slist_remove (GSList *list, gpointer data); GSList* g_slist_remove_link (GSList *list, GSList *link); GSList* g_slist_reverse (GSList *list); GSList* g_slist_nth (GSList *list, gint n); GSList* g_slist_find (GSList *list, gpointer data); GSList* g_slist_last (GSList *list); gint g_slist_length (GSList *list); void g_slist_foreach (GSList *list, GFunc func, gpointer user_data); 1177..44.. GGeessttiioonn ddee llaa mmmmooiirree gpointer g_malloc (gulong size); Remplace _m_a_l_l_o_c_(_). On n'a pas besoin de vrifier la valeur de retour car cela est fait pour nous dans cette fonction. gpointer g_malloc0 (gulong size); Identique la prcdente, mais initialise la mmoire zro avant de retourner un pointeur vers la zone rserve. gpointer g_realloc (gpointer mem, gulong size); Ralloue _s_i_z_e octets de mmoire partir de _m_e_m. videmment, la mmoire doit avoir t alloue auparavant. void g_free (gpointer mem); Libre la mmoire. Facile. void g_mem_profile (void); Produit un profil de la mmoire utilise, mais requiert l'ajout de _#_d_e_f_i_n_e _M_E_M___P_R_O_F_I_L_E au dbut de _g_l_i_b_/_g_m_e_m_._c, de refaire un _m_a_k_e et un _m_a_k_e _i_n_s_t_a_l_l. void g_mem_check (gpointer mem); Vrifie qu'un emplacement mmoire est valide. Ncessite que l'on ajoute _#_d_e_f_i_n_e _M_E_M___C_H_E_C_K au dbut de _g_m_e_m_._c que l'on refasse un _m_a_k_e et un _m_a_k_e _i_n_s_t_a_l_l. 1177..55.. TTiimmeerrss Fonctions des timers... GTimer* g_timer_new (void); void g_timer_destroy (GTimer *timer); void g_timer_start (GTimer *timer); void g_timer_stop (GTimer *timer); void g_timer_reset (GTimer *timer); gdouble g_timer_elapsed (GTimer *timer, gulong *microseconds); 1177..66.. GGeessttiioonn ddeess cchhaanneess Un ensemble complet de fonction de gestion des chanes. Elles semblent toutes trs intressantes et sont srement meilleures, bien des gards, que les fonctions C standards, mais elle ncessitent de la documentation. GString* g_string_new (gchar *init); void g_string_free (GString *string, gint free_segment); GString* g_string_assign (GString *lval, gchar *rval); GString* g_string_truncate (GString *string, gint len); GString* g_string_append (GString *string, gchar *val); GString* g_string_append_c (GString *string, gchar c); GString* g_string_prepend (GString *string, gchar *val); GString* g_string_prepend_c (GString *string, gchar c); void g_string_sprintf (GString *string, gchar *fmt, ...); void g_string_sprintfa (GString *string, gchar *fmt, ...); 1177..77.. UUttiilliittaaiirreess eett ffoonnccttiioonnss dd''eerrrreeuurrss gchar* g_strdup (const gchar *str); Remplace la fonction _s_t_r_d_u_p. Elle copie le contenu de la chane d'origine dans la mmoire venant d'tre alloue et retourne un pointeur sur cette zone. gchar* g_strerror (gint errnum); Je recommande de l'utiliser pour tous les messages d'erreur. Elle est beaucoup plus propre et plus portable que _p_e_r_r_o_r_(_) ou les autres. La sortie est habituellement de la forme : nom du programme:fonction qui a chou:fichier ou autre descripteur:strerror Voici un exemple d'appel utilis dans le programme Bonjour tout le monde ! : g_print("bonjour_monde:open:%s:%s\n", filename, g_strerror(errno)); void g_error (gchar *format, ...); Affiche un message d'erreur. Le format est comme _p_r_i_n_t_f, mais il ajoute ** ERROR **: au dbut du message et sort du programme. n'utiliser que pour les erreurs fatales. void g_warning (gchar *format, ...); Comme au dessus, mais ajoute ** WARNING **: , et ne termine pas le programme. void g_message (gchar *format, ...); Affiche message: avant la chane passe en paramtre. void g_print (gchar *format, ...); Remplace _p_r_i_n_t_f_(_). Enfin la dernire fonction : gchar* g_strsignal (gint signum); Affiche le nom du signal systme Unix correspondant au numro de signal. Utile pour les fonctions gnriques de gestion de signaux. Tout ce qui est ci-dessus est plus ou moins vol _g_l_i_b_._h. Si quelqu'un s'occupe de documenter une fonction, qu'il m'envoit un courrier ! 1188.. FFiicchhiieerrss rrcc ddee GGTTKK GTK a sa propre mthode pour grer les configurations par dfaut des applications, en utilisant des fichiers rc. Ceux-ci peuvent tre utiliss pour configurer les couleurs de presque tous les widgets, et pour mettre des pixmaps sur le fond de certains widgets. 1188..11.. FFoonnccttiioonnss ppoouurr lleess ffiicchhiieerrss rrcc Au dmarrage de votre application, ajoutez un appel : void gtk_rc_parse (char *filename); en lui passant le nom de votre fichier rc. Ceci forcera GTK analyser ce fichier et utiliser les configurations de styles pour les types de widgets qui y sont dfinis. Si vous voulez avoir un ensemble particulier de widgets qui prenne le pas sur le style des autres, ou une autre division logique de widgets, utilisez un appel : void gtk_widget_set_name (GtkWidget *widget, gchar *name); En lui passant comme premier paramtre le widget que vous avez cr, et le nom que vous voulez lui donner comme second paramtre. Ceci vous permettra de changer les attributs de ce widget par son nom dans le fichier rc. Si vous utilisez un appel comme celui-ci : button = gtk_button_new_with_label ("Bouton Spcial"); gtk_widget_set_name (button, "bouton special"); Ce bouton s'appelle bouton special et peut tre accd par son nom dans le fichier rc en tant que bouton special.GtkButton . [<--- Vrifiez !] Le fichier rc ci-dessous configure les proprits de la fentre principale et fait hriter tous les fils de celle-ci du style dcrit par bouton_principal . Le code utilis dans l'application est : window = gtk_window_new (GTK_WINDOW_TOPLEVEL); gtk_widget_set_name (window, "fenetre principale"); Et le style est dfini dans le fichier rc avec : widget "fenetre principale.*GtkButton*" style "bouton_principal" Ce qui configure tous les widgets _G_t_k_B_u_t_t_o_n de fentre principale avec le style bouton_principal dfini dans le fichier rc. Ainsi que vous pouvez le voir, il s'agit d'un systme puissant et flexible. Utilisez votre imagination pour en tirer le meilleur. 1188..22.. FFoorrmmaatt ddeess ffiicchhiieerrss rrcc ddee GGTTKK Le format du fichier GTK est illustr dans l'exemple suivant. Il s'agit du fichier _t_e_s_t_g_t_k_r_c de la distribution GTK mais j'ai ajout quelques commentaires et autres choses. Vous pouvez inclure cette explication votre application pour permettre l'utilisateur de rgler finement son application. Il y a plusieurs directives pour changer les attributs d'un widget. +o fg - configure la couleur de premier plan d'un widget. +o bg - configure la couleur d'arrire plan d'un widget. +o bg_pixmap - configure l'arrire plan d'un widget avec un pixmap. +o font - configure la fonte utiliser pour un widget. De plus, un widget peut se trouver dans diffrents tats et l'on peut configurer des couleurs, pixmaps et fontes diffrentes pour chacun d'eux. Ces tats sont : +o NORMAL - L'tat normal d'un widget, sans la souris au dessus de lui, non press, etc. +o PRELIGHT - Lorsque la souris se trouve au dessus du widget, les couleurs dfinies pour cet tat sont actives. +o ACTIVE - Lorsque le widget est press ou cliqu, il devient actif et les attributs associs cet tat sont appliqus. +o INSENSITIVE - Quand un widget est configur pour tre insensible et qu'il ne peut tre activ, il prend ces attributs. +o SELECTED - Lorsqu'un objet est choisi, il prend ces attributs. Lorsqu'on utilise les mots-cls _f_g et _b_g pour configurer les couleurs des widgets, le format est : fg[] = { Red, Green, Blue } O STATE est l'un des tats vus plus haut (PRELIGHT, ACTIVE etc), et o _R_e_d, _G_r_e_e_n et _B_l_u_e sont des valeurs comprises entre 0 et 1.0. { 1.0, 1.0, 1.0 } reprsente la couleur blanche. Ces valeurs doivent tre de type rel ou elles seront considres comme valant 0, ainsi un simple 1 ne marchera pas, il faut mettre 1.0 . Un 0 simple convient car ce n'est pas un problme s'il n'est pas reconnu puisque toutes les valeurs non reconnues sont mises 0. _b_g___p_i_x_m_a_p est trs similaire, sauf que les couleurs sont remplaces par un nom de fichier. _p_i_x_m_a_p___p_a_t_h est une liste de chemins spars par des : . Ces chemins seront parcourus pour chaque pixmap que l'on spcifie. La directive _f_o_n_t est simplement : font = "" O la seule partie difficile est d'arriver comprendre la chane contenant le nom de la fonte. L'utilisation de _x_f_o_n_t_s_e_l ou d'un autre utilitaire semblable peut aider. _w_i_d_g_e_t___c_l_a_s_s configure le style d'une classe de widgets. Ces classes sont listes dans la section sur la hirarchie des widgets. La directive _w_i_d_g_e_t configure un ensemble spcifique de widgets selon un style donn, annulant tout style de configuration pour la classe de widget donne. Ces widgets sont enregistrs dans l'application en utilisant l'appel _g_t_k___w_i_d_g_e_t___s_e_t___n_a_m_e_(_). Ceci vous permet de spcifier les attributs d'un widget, widget par widget, au lieu de configurer les attributs d'une classe entire de widgets. Je vous demande instamment de documenter tous ces widgets spciaux pour que les utilisateurs puisse les adapter leurs besoins. Lorsque le mot-cl _p_a_r_e_n_t est utilis comme attribut, le widget prendra les attributs de son parent dans l'application. Lorsqu'on dfinit un style, on peut assigner les attributs d'un style dj dfini ce nouveau style. style "bouton_principal" = "button" { font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*" bg[PRELIGHT] = { 0.75, 0, 0 } } Cet exemple prend le style "button" et cre un nouveau style "bouton_principal"en changeant simplement la fonte et la couleur de fond pour l'tat PRELIGHT. Bien sr, un bon nombre de ces attributs ne s'applique pas tous les widgets. C'est une question de bon sens. Tout ce qui peut s'appliquer s'applique. 1188..33.. EExxeemmppllee ddee ffiicchhiieerr rrcc # pixmap_path ":::..." # pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps" # # style [= ] # { #