gtk/docs/gtk_tut_fr.sgml

8601 lines
277 KiB
Plaintext
Raw Normal View History

<!doctype linuxdoc system>
<article>
<title>Didacticiel
<author>Ian Main, <tt><htmlurl url="mailto:slow@intergate.bc.ca"
name="slow@intergate.bc.ca"></tt>
<date>January 24, 1998.
<sect>Introduction
<p>
GTK (GIMP Toolkit) a <20>t<EFBFBD> d'abord d<>velopp<70> pour <20>tre une bo<62>te <20>
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 <20> GIMP toolkit <20> car il
fut cr<63><72> pour d<>velopper GIMP, mais il est d<>sormais utilis<69> dans
plusieurs projets de logiciels libres. Les auteurs sont&nbsp;:
<itemize>
<item> Peter Mattis <tt><htmlurl url="mailto:petm@xcf.berkeley.edu"
name="petm@xcf.berkeley.edu"></tt>
<item> Spencer Kimball <tt><htmlurl url="mailto:spencer@xcf.berkeley.edu"
name="spencer@xcf.berkeley.edu"></tt>
<item> Josh MacDonald <tt><htmlurl url="mailto:jmacd@xcf.berkeley.edu"
name="jmacd@xcf.berkeley.edu"></tt>
</itemize>
<p>
GTK est essentiellement une interface de programmation (API) orient<6E>e
objet. Bien qu'il soit enti<74>rement <20>crit en C, il est implant<6E> en
utilisant la notion de classes et de fonctions de rappel (pointeurs
de fonctions).
<p>
Un troisi<73>me composant, appel<65> glib, remplace certains appels
standard et comporte quelques fonctions suppl<70>mentaires pour g<>rer les
listes cha<68>n<EFBFBD>es, etc. Les fonctions de remplacement sont utilis<69>es
pour accro<72>tre la portabilit<69> 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 am<61>liorations par rapport
aux versions de la libc&nbsp;: g_malloc(), par exemple, facilite le
d<EFBFBD>buggage.<p>
Ce didacticiel tente de d<>crire du mieux possible GTK, mais il n'est pas
exhaustif. Il suppose une bonne connaissance du langage C, et de la fa<66>on de
cr<EFBFBD>er des programmes C. Il serait tr<74>s pr<70>cieux au lecteur d'avoir d<>j<EFBFBD> une
exp<EFBFBD>rience de la programmation X, mais cela n'est pas n<>cessaire. Si
l'apprentissage de GTK marque vos d<>buts dans l'approche des widgets, n'h<>sitez
pas <20> faire des commentaires sur ce didacticiel et sur les probl<62>mes qu'il vous
a pos<6F>. Il y a aussi une API C++ pour GTK (GTK--), si vous pr<70>f<EFBFBD>rez utiliser
ce langage, consultez plut<75>t la documentation qui la concerne. Une
encapsulation en Objective C et des liaisons Guile sont <20>galement disponibles,
mais ne seront pas abord<72>es ici.
<p>
J'appr<70>cierais beaucoup avoir un <20>cho des probl<62>mes que vous avez
rencontr<EFBFBD> pour apprendre GTK <20> partir de ce document. De plus, toute
suggestion sur son am<61>lioration est la bienvenue.
<sect>Bien d<>buter
<p>
La premi<6D>re chose <20> faire est, bien s<>r, de r<>cup<75>rer les sources de
GTK et de les installer. Vous pouvez en obtenir la derni<6E>re version
sur <tt/ftp.gimp.org/ dans le r<>pertoire <tt>/pub/gtk</tt>. D'autres
sources d'informations se trouvent sur
<tt>http://www.gimp.org/gtk</tt>. GTK utilise <em/autoconf/ de GNU
pour se configurer. Lorsque vous l'aurez d<>tarr<72>, tapez
<em>./configure --help</em> pour consulter la liste des options.
<p>
Pour commencer notre introduction <20> GTK, nous d<>buterons avec le
programme le plus simple qui soit. Celui-ci cr<63>era une fen<65>tre de
200x200 pixels et ne pourra se terminer qu'en le tuant <20> partir du
shell.
<tscreen><verb>
#include <gtk/gtk.h>
int main (int argc, char *argv[])
{
GtkWidget *window;
gtk_init (&amp;argc, &amp;argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_show (window);
gtk_main ();
return 0;
}
</verb></tscreen>
Tous les programmes inclueront <20>videmment le fichier
<tt>gtk/gtk.h</tt> qui d<>clare les variables, fonctions, structures,
etc. qui seront utilis<69>es par votre application GTK.
<p>
La ligne &nbsp;:
<tscreen><verb>
gtk_init (&amp;argc, &amp;argv);
</verb></tscreen>
appelle la fonction <em/gtk_init(gint *argc, gchar ***argv)/ qui sera
appel<EFBFBD>e dans toutes les applications GTK. Cette fonction configure
certaines choses pour nous, comme l'aspect visuel et les couleurs par
d<EFBFBD>faut, puis appelle <em/gdk_init(gint *argc, gchar ***argv)/. Cette
derni<EFBFBD>re initialise la biblioth<74>que pour qu'elle puisse <20>tre utilis<69>e,
configure les gestionnaires de signaux par d<>faut et v<>rifie les
param<EFBFBD>tres pass<73>s <20> notre application via la ligne de commande en
recherchant l'un des suivants&nbsp;:
<itemize>
<item> <tt/--display/
<item> <tt/--debug-level/
<item> <tt/--no-xshm/
<item> <tt/--sync/
<item> <tt/--show-events/
<item> <tt/--no-show-events/
</itemize>
<p>
Elle les supprime alors de la liste des param<61>tres, en laissant tout
ce qu'elle ne reconna<6E>t pas pour que notre application l'analyse ou
l'ignore. Ceci cr<63>e un ensemble de param<61>tres standards accept<70>s par
toutes les applications GTK.
<p>
Les deux lignes de code suivantes cr<63>ent et affichent une fen<65>tre.
<tscreen><verb>
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_show (window);
</verb></tscreen>
Le param<61>tre <tt/GTK_WINDOW_TOPLEVEL/ pr<70>cise que l'on veut que la
fen<EFBFBD>tre cr<63><72>e suive l'aspect et le placement d<>finis par le
gestionnaire de fen<65>tres. Plut<75>t que de cr<63>er une fen<65>tre de 0x0, une
fen<EFBFBD>tre sans fen<65>tre fille est de 200x200 par d<>faut&nbsp;: on peut
ainsi la manipuler facilement.
<p>
La fonction <em/gtk_widget_show()/ informe GTK que l'on a configur<75>
le widget et qu'il peut l'afficher.
<p>
La ligne suivante lance la boucle principale de traitement de GTK.
<tscreen><verb>
gtk_main ();
</verb></tscreen>
<em/gtk_main()/ est un autre appel que vous verrez dans toute
application GTK. Lorsque le contr<74>le atteind ce point, GTK se met en
attente d'<27>v<EFBFBD>nements X (click sur un bouton, ou appui d'une touche, par
exemple), de timeouts ou d'entr<74>es-sorties fichier. Dans notre exemple
simple, cependant, les <20>v<EFBFBD>nements sont ignor<6F>s.
<sect1><3E> Bonjour tout le monde <20> en GTK
<p>
OK, <20>crivons un programme avec un widget (bouton). C'est le classique <20> Bonjour tout le monde <20> <20> la sauce GTK.
<tscreen><verb>
#include <gtk/gtk.h>
/* fonction de rappel. Dans cet exemple, les param<61>tres sont ignor<6F>s...
* Les fonctions de rappel sont d<>taill<6C>es 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 <20>mettra le signal "destroy". Retourner FALSE signifie que l'on
* ne veut pas que la fen<65>tre soit d<>truite.
* Utilis<69> pour faire appara<72>tre des bo<62>tes de dialogue du type
* <20> <20>tes-vous s<>r de vouloir quitter ? <20> */
/* Remplacez FALSE par TRUE et la fen<65>tre principale sera d<>truite par
* un signal <20> delete_event <20>. */
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 d<>clarer les widgets. */
GtkWidget *window;
GtkWidget *button;
/* Cette fonction est appel<65>e dans toutes les applications GTK.
* Les param<61>tres pass<73>s en ligne de commande sont analys<79>s et
* retourn<72>s <20> l'application. */
gtk_init (&amp;argc, &amp;argv);
/* Cr<43>ation d'une nouvelle fen<65>tre. */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Lorsque la fen<65>tre re<72>oit le signal "delete_event"
* (envoy<6F> par le gestionnaire de fen<65>tres en utilisant l'option
* <20> close <20> ou la barre de titre), on lui demande d'appeler la
* fonction delete_event() d<>finie plus haut. La donn<6E>e pass<73>e en
* param<61>tre <20> la fonction de rappel est NULL et est ignor<6F> dans le
* rappel. */
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
GTK_SIGNAL_FUNC (delete_event), NULL);
/* Ici, on connecte l'<27>venement "destroy" <20> un gestionnaire de signal.
* Cet <20>v<EFBFBD>nement arrive lorsqu'on appelle gtk_widget_destroy() sur la
* fen<65>tre, 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 fen<65>tre. */
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* Cr<43>ation 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() d<>finie plus haut en lui passant NULL en param<61>tre. */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (hello), NULL);
/* Ceci provoquera la destruction de la fen<65>tre 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 fen<65>tres. */
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
/* Insertion du bouton dans la fen<65>tre (container gtk). */
gtk_container_add (GTK_CONTAINER (window), button);
/* L'<27>tape finale consiste <20> afficher ce nouveau widget... */
gtk_widget_show (button);
/* ... et la fen<65>tre. */
gtk_widget_show (window);
/* Toutes les applications GTK doivent avoir un gtk_main().
* Le d<>roulement du programme se termine l<> et attend qu'un
* <20>v<EFBFBD>nement survienne (touche press<73>e ou <20>v<EFBFBD>nement souris). */
gtk_main ();
return 0;
}
</verb></tscreen>
<sect1>Compilation de <20> Bonjour tout le monde <20>
<p>
Supposons que vous avez sauvegard<72> le code pr<70>c<EFBFBD>dent dans un fichier
nomm<EFBFBD> <em/bonjour.c/, pour le compiler tapez la commande
suivante&nbsp;:
<tscreen><verb>
gcc -Wall -g bonjour.c -o bonjour_monde -L/usr/X11R6/lib \
-lgtk -lgdk -lglib -lXext -lX11 -lm
</verb></tscreen>
<p>
Les biblioth<74>ques invoqu<71>es ci-dessus doivent toutes <20>tre dans vos
chemins de recherche par d<>faut, sinon, ajoutez <tt/-L&lt;library
directory&gt;/ pour que <em/gcc/ recherche dans ces r<>pertoires les
biblioth<EFBFBD>ques n<>cessaires. Sur mon syst<73>me Debian GNU/Linux, par exemple,
je dois ajouter <tt>-L/usr/X11R6/lib</> pour qu'il trouve les
biblioth<EFBFBD>ques X11 (NdT&nbsp;: et c'est pareil sur mon syst<73>me Red Hat
Linux...).
<p>
L'ordre des biblioth<74>ques est important. L'<27>diteur de liens doit
conna<EFBFBD>tre les fonctions d'une biblioth<74>que dont il a besoin avant de
les traiter.
<p>
Si vous compilez en utilisant des biblioth<74>ques statiques, l'ordre
dans lequel vous listez les biblioth<74>ques devient tr<74>s
important. L'exemple donn<6E> ci-dessus devrait fonctionner dans tous les
cas.
<p>
Les biblioth<74>ques que l'on utilise sont&nbsp;:
<itemize>
<item>La biblioth<74>que glib (<tt/-lglib/), qui contient diverses
fonctions. Seule <em/g_print()/ est utilis<69>e dans cet exemple. GTK est
construit au dessus de <em/glib/ et vous aurez donc toujours besoin de
celle-ci. Voir la section concernant <ref id="sec_glib" name="glib">
pour plus de d<>tails.
<item>La biblioth<74>que GDK (<tt/-lgdk/), l'enveloppe de Xlib.
<item>La biblioth<74>que GTK (<tt/-lgtk/), la biblioth<74>que des widgets,
construite au dessus de GDK.
<item>La biblioth<74>que Xlib (<tt/-lX11/ utilis<69>e par GDK.
<item>La biblioth<74>que Xext (<tt/-lXext/). Cette derni<6E>re contient le
code pour les pixmaps en m<>moire partag<61>e et les autres extensions X.
<item>La biblioth<74>que math<74>matique (<tt/-lm/). Elle est utilis<69>e pour
diff<EFBFBD>rentes raisons par GTK.
</itemize>
<sect1>Th<54>orie des signaux et des rappels
<p>
Avant de voir en d<>tail le programme <20> Bonjour tout le monde <20>, nous
parlerons d'abord des <20>v<EFBFBD>nements et des fonctions de rappel. GTK est
dirig<EFBFBD> par les <20>v<EFBFBD>nements, ce qui signifie qu'il restera inactif dans
<em/gtk_main/ jusqu'<27> ce qu'un <20>v<EFBFBD>nement survienne et que le contr<74>le
soit pass<73> <20> la fonction appropri<72>e.
<p>
Ce passage du contr<74>le est r<>alis<69> en utilisant le concept de <20> signal
<EFBFBD>. Lorsqu'un <20>v<EFBFBD>nement survient, comme l'appui sur un bouton, le
signal appropri<72> sera <20> <20>mis <20> par le widget qui a <20>t<EFBFBD> press<73>. C'est
de cette fa<66>on que GTK r<>alise la plupart de son travail. Pour qu'un
bouton r<>alise une action, on configure un gestionnaire de signal pour
capturer ces signaux et appeler la fonction ad<61>quate. Ceci est fait
en utilisant une fonction comme&nbsp;:
<tscreen><verb>
gint gtk_signal_connect (GtkObject *object,
gchar *name,
GtkSignalFunc func,
gpointer func_data);
</verb></tscreen>
<p>
O<EFBFBD> le premier param<61>tre est le widget qui <20>mettra le signal, et le
deuxi<EFBFBD>me est le nom du signal que l'on souhaite intercepter. Le
troisi<EFBFBD>me param<61>tre est la fonction que l'on veut appeler quand le
signal est captur<75>, et le quatri<72>me sont les donn<6E>es que l'on souhaite
passer <20> cette fonction.
<p>
La fonction sp<73>cifi<66>e par le troisi<73>me param<61>tre s'appelle une <20>
fonction de rappel <20> et doit <20>tre de la forme&nbsp;:
<tscreen><verb>
void callback_func(GtkWidget *widget, gpointer *callback_data);
</verb></tscreen>
<p>
O<EFBFBD> le premier param<61>tre sera un pointeur vers le widget qui a <20>mis le
signal, et le second un pointeur vers les donn<6E>es pass<73>es par le
dernier param<61>tre de la fonction <em/gtk_signal_connect()/ d<>crite
plus haut.
<p>
Un autre appel utilis<69> dans l'exemple <20> Bonjour tout le monde <20> est&nbsp;:
<tscreen><verb>
gint gtk_signal_connect_object (GtkObject *object,
gchar *name,
GtkSignalFunc func,
GtkObject *slot_object);
</verb></tscreen>
<p>
<em/gtk_signal_connect_object()/ est la m<>me chose que
<em/gtk_signal_connect()/ sauf que la fonction de rappel utilise un
seul param<61>tre&nbsp;: un pointeur vers un objet GTK. Lorsqu'on utilise
cette fonction pour connecter des signaux, le rappel doit <20>tre de
cette forme&nbsp;:
<tscreen><verb>
void callback_func (GtkObject *object);
</verb></tscreen>
<p>
O<EFBFBD> l'objet est d'ordinaire un widget. En g<>n<EFBFBD>ral, on ne configure pas
de rappels pour <em/gtk_signal_connect_object/. D'habitude, ceux-ci sont
utilis<EFBFBD>s pour appeler une fonction GTK acceptant un simple widget ou
objet comme param<61>tre, 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 diff<66>rent de param<61>tres. De nombreuses fonctions de la
biblioth<EFBFBD>que GTK n'acceptent qu'un simple pointeur vers un
<em/GtkWidget/ comme param<61>tre et vous pouvez donc utiliser
<em/gtk_signal_connect_object()/ pour celles-ci, tandis que pour vos
fonctions vous pouvez avoir besoin d'avoir de fournir plus de donn<6E>es
aux fonctions de rappel.
<sect1><3E> Bonjour tout le monde <20> pas <20> pas
<p>
Maintenant que nous connaissons la th<74>orie, clarifions un peu en progressant <20> travers le programme <20> Bonjour tout le monde <20>.
<p>
Voici la fonction de rappel appel<65>e lorsque le bouton est <20> clicked
<EFBFBD>. Dans notre exemple, on ignore le widget et les donn<6E>es mais il
n'est pas difficile de faire quelque chose avec. Le prochain exemple
utilisera le param<61>tre des donn<6E>es pour nous dire quel bouton a <20>t<EFBFBD>
press<EFBFBD>.
<tscreen><verb>
void hello (GtkWidget *widget, gpointer *data)
{
g_print ("Bonjour tout le monde\n");
}
</verb></tscreen>
<p>
Cette fonction de rappel est un peu sp<73>ciale. L'<27>v<EFBFBD>nement
"delete_event" survient lorsque le gestionnaire de fen<65>tres l'envoie <20>
l'application. On doit choisir ce qu'il faut faire de ces
<EFBFBD>v<EFBFBD>nements. On peut les ignorer, leur donner une r<>ponse, ou
simplement quitter l'application.
La valeur que l'on retourne dans cette fonction de rappel permet <20> GTK
de savoir ce qu'il a <20> faire. En retournant FALSE, on l'informe que
l'on ne veut pas que le signal "destroy" soit <20>mis, afin de laisser
notre application tourner. En retournant TRUE, on lui demande
d'<27>mettre "destroy" qui appellera <20> son tour notre gestionnaire du
signal "destroy".
<tscreen><verb>
gint delete_event(GtkWidget *widget, GdkEvent *event, gpointer data)
{
g_print ("le signal delete_event est survenu.\n");
return (FALSE);
}
</verb></tscreen>
<p>
Voici une autre fonction de rappel qui ne fait que quitter
l'application en appelant <em/gtk_main_quit()/. Il n'y a pas grand
chose de plus <20> dire car elle est plut<75>t triviale&nbsp;:
<tscreen><verb>
void destroy (GtkWidget *widget, gpointer *data)
{
gtk_main_quit ();
}
</verb></tscreen>
<p>
Je suppose que vous connaissez la fonction <em/main()/... oui, comme
les autres programmes C, toutes les applications GTK en ont une.
<tscreen><verb>
int main (int argc, char *argv[])
{
</verb></tscreen>
<p>
La partie qui suit d<>clare deux pointeurs sur des structures de type
<em/GtkWidget/. Ceux-ci sont utilis<69>s plus loin pour cr<63>er une
fen<EFBFBD>tre et un bouton.
<tscreen><verb>
GtkWidget *window;
GtkWidget *button;
</verb></tscreen>
<p>
Et revoici notre <em/gtk_init/. Comme pr<70>c<EFBFBD>demment, il initialise le toolkit
et analyse les param<61>tres de la ligne de commande. Il supprime chaque
param<EFBFBD>tre reconnu de la liste et modifie <em/argc/ et <em/argv/ pour
faire comme si ces param<61>tres n'avaient jamais exist<73>, laissant notre
application analyser les param<61>tres restants.
<tscreen><verb>
gtk_init (&amp;argc, &amp;argv);
</verb></tscreen>
<p>
Cr<EFBFBD>ation d'une nouvelle fen<65>tre. C'est plut<75>t classique. La m<>moire
est allou<6F>e pour une structure <em/GtkWidget/ et <em/window/ pointe
donc sur celle-ci. Cela configure une nouvelle fen<65>tre, mais celle-ci
ne sera pas affich<63>e tant que l'on n'a pas appel<65>
<em/gtk_widget_show(window)/ vers la fin de notre programme.
<tscreen><verb>
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
</verb></tscreen>
<p>
Voici maintenant un exemple de connexion d'un gestionnaire de signal <20>
un objet&nbsp;: la fen<65>tre. Le signal "destroy" est captur<75>. Il est
<EFBFBD>mis lorsqu'on utilise le gestionnaire de fen<65>tres pour tuer la
fen<EFBFBD>tre (et que l'on retourne TRUE dans le gestionnaire
"delete_event"), ou lorsqu'on utilise l'appel
<em/gtk_widget_destroy()/ en lui passant le widget <em/window/ comme
objet <20> d<>truire. Ici, on appelle juste la fonction <em/destroy()/
d<EFBFBD>finie ci-dessus avec le param<61>tre NULL, ce qui quitte GTK pour
nous.
<p>
<tt/GTK_OBJECT/ et <tt/GTK_SIGNAL_FUNC/ sont des macros qui r<>alisent
les conversions et les v<>rifications de types pour nous. Elles rendent
aussi le code plus lisible.
<tscreen><verb>
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (destroy), NULL);
</verb></tscreen>
<p>
La fonction suivante sert <20> configurer un attribut d'un objet
container. Elle configure simplement la fen<65>tre 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
<ref id="sec_setting_widget_attributes"
name="Configuration des attributs des widgets">
<p>
<EFBFBD> nouveau, <tt/GTK_CONTAINER/ est une macro r<>alisant la conversion de type.
<tscreen><verb>
gtk_container_border_width (GTK_CONTAINER (window), 10);
</verb></tscreen>
<p>
Cet appel cr<63>e un nouveau bouton. Il alloue l'espace m<>moire pour une
nouvelle structure GtkWidget, l'initialise et fait pointer <em/button/
vers elle. Ce bouton portera le label <20> Bonjour tout le monde <20>
lorsqu'il sera affich<63>.
<tscreen><verb>
button = gtk_button_new_with_label ("Bonjour tout le monde");
</verb></tscreen>
<p>
Maintenant, prenons ce bouton et faisons lui faire quelque chose
d'utile. On lui attache un gestionnaire de signal pour que, lorsqu'il
<EFBFBD>mettra le signal "clicked", notre fonction <em/hello()/ soit
appel<EFBFBD>e. On ignore les param<61>tres et on ne passe donc que la valeur
NULL <20> la fonction de rappel <em/hello()/. <20>videmment, le signal
"clicked" est <20>mis lorsqu'on clique sur le bouton avec la souris.
<tscreen><verb>
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (hello), NULL);
</verb></tscreen>
<p>
On utilisera aussi ce bouton pour quitter notre programme, ce qui
permettra d'illustrer la fa<66>on dont le signal "destroy" peut venir
soit du gestionnaire de fen<65>tres, soit de notre programme. Quand le
bouton est "clicked" comme cela est d<>crit plus haut, il appelle
d'abord la fonction de rappel <em/hello()/ puis celle-ci dans l'ordre
dans lequel elles sont configur<75>es. On peut avoir autant de fonctions
de rappel que l'on d<>sire, elles seront ex<65>cut<75>es selon leur ordre de
connexion. Puisque la fonction <em/gtk_widget_destroy()/ n'accepte que
<em/GtkWidget *widget/ comme param<61>tre, on utilise ici la fonction
<em/gtk_signal_connect_object()/ <20> la place de
<em/gtk_signal_connect()/.
<tscreen><verb>
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
</verb></tscreen>
<p>
Voici un appel de placement, qui sera expliqu<71> en d<>tail plus tard,
mais qui est plut<75>t facile <20> comprendre. Il indique simplement <20> GTK
que le bouton doit <20>tre plac<61> dans la fen<65>tre o<> il s'affichera.
<tscreen><verb> gtk_container_add (GTK_CONTAINER (window), button);
</verb></tscreen>
<p>
Maintenant, nous avons tout configur<75> comme on le souhaitait&nbsp;:
les gestionnaires de signaux sont en place et le bouton est mis dans
la fen<65>tre o<> il doit se trouver. On demande alors <20> GTK de <20> montrer
<EFBFBD> les widgets <20> l'<27>cran. Le widget <em/window/ est affich<63> en dernier
afin que la fen<65>tre enti<74>re surgisse d'un coup plut<75>t que voir d'abord
la fen<65>tre s'afficher puis ensuite le bouton appara<72>tre <20>
l'int<6E>rieur. Il faut dire qu'avec des exemples simples comme celui-ci,
vous ne ferez pas la diff<66>rence.
<tscreen><verb>
gtk_widget_show(button);
gtk_widget_show (window);
</verb></tscreen>
<p>
Bien s<>r, on appelle <em/gtk_main()/ qui attendra les <20>v<EFBFBD>nements
venant du serveur X et demandera aux widgets d'<27>mettre les signaux
lorsque ces <20>v<EFBFBD>nements surviendront.
<tscreen><verb>
gtk_main ();
</verb></tscreen>
Enfin, le <em/return/ final. Il est ex<65>cut<75> lorsque <em/gtk_quit()/ est appel<65>.
<tscreen><verb>
return 0;
</verb></tscreen>
<p>
Lorsque l'on clique sur un bouton GTK, le widget <20>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<63><72> est "clicked", la fonction
<em/hello()/ est appel<65>e avec le param<61>tre NULL, puis le gestionnaire
suivant de ce signal est <20> son tour appel<65>. Il appelle la fonction
<em/gtk_widget_destroy()/ en lui passant le widget <em/window/ comme
param<EFBFBD>tre, ce qui provoque la destruction de celui-ci. Ceci
force la fen<65>tre <20> envoyer un signal "destroy", qui est captur<75> <20> son
tour et appelle notre fonction de rappel <em/destroy()/ qui ferme
simplement GTK.
<p>
Une autre fa<66>on de proc<6F>der consiste <20> utiliser le gestionnaire de
fen<EFBFBD>tres pour d<>truire la fen<65>tre. Cela provoquera l'<27>mission du
signal "delete_event" qui sera pris en charge par notre gestionnaire
<em/delete_event()/. S'il retourne FALSE, la fen<65>tre restera telle
quelle et rien ne se passera. Retourner TRUE forcera GTK <20> <20>mettre
le signal "destroy" qui, bien s<>r, appelera la fonction de rappel
<em/destroy()/ provoquant la sortie du GTK. <p>
On remarquera que ces signaux ne sont pas les m<>mes que les signaux
syst<EFBFBD>mes Unix et ne sont pas implant<6E>s en utilisant ceux-ci, bien que
la terminologie employ<6F>e soit presque identique.
<sect>Continuons
<p>
<sect1>Types de donn<6E>es
<p>
Vous avez probablement not<6F> certaines choses qui n<>cessitent des
explications dans les exemples pr<70>c<EFBFBD>dents. les <em/gint/, <em/gchar/,
etc. que vous avez pu voir sont des red<65>finitions de <em/int/ et
<em/char/, respectivement. Leur raison d'<27>tre est de s'affranchir des
d<EFBFBD>pendances ennuyeuses concernant la taille des types de donn<6E>es
simples lorsqu'on r<>alise des calculs. Un bon exemple est <em/gint32/
qui d<>signera un entier cod<6F> sur 32 bits pour toutes les plateformes,
que ce soit une station Alpha 64 bits ou un PC i386 32 bits. Les
red<EFBFBD>finitions de type sont tr<74>s simples et intuitives. Elles sont
toutes d<>crites dans le fichier <em>glib/glib.h</em> (qui est inclus
par <em/gtk.h/).
<p>
On notera aussi la possibilit<69> d'utiliser un <em/GtkWidget/ lorsque la
fonction attend un <em/GtkObject/. GTK poss<73>de une architecture
orient<EFBFBD>e objet, et un widget est un objet.
<sect1>Compl<70>ments sur les gestionnaires de signaux
<p>
Regardons <20> nouveau la d<>claration de <em/gtk_signal_connect/.
<tscreen><verb>
gint gtk_signal_connect (GtkObject *object, gchar *name,
GtkSignalFunc func, gpointer func_data);
</verb></tscreen>
Vous avez remarqu<71> que le valeur de retour est de type <em/gint/ ? 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 ex<65>cut<75>e <20> son
tour, dans l'ordre dans lequel elle a <20>t<EFBFBD> attach<63>e. Ce marqueur vous
permet d'<27>ter ce rappel de la liste en faisant&nbsp&;:
<tscreen><verb>
void gtk_signal_disconnect (GtkObject *object, gint id);
</verb></tscreen>
Ainsi, en passant le widget dont on veut supprimer le gestionnaire et
le marqueur ou identificateur retourn<72> par l'une des fonctions
<em/signal_connect/, on peut d<>connecter un gestionnaire de signal.
<p>
Une autre fonction permettant de supprimer tous les gestionnaires de
signaux pour un objet est&nbsp;:
<tscreen><verb>
gtk_signal_handlers_destroy (GtkObject *object);
</verb></tscreen>
<p>
Cet appel n'a pas trop besoin d'explications. Il <20>te simplement tous
les gestionnaires de signaux de l'objet pass<73> en param<61>tre.
<sect1>Un <20> Bonjour tout le monde <20> am<61>lior<6F>
<p>
<EFBFBD>tudions une version l<>g<EFBFBD>rement am<61>lior<6F>e avec de meilleurs exemples
de fonctions de rappel. Ceci permettra aussi d'introduire le sujet
suivant&nbsp;: le placement des wigdets.
<tscreen><verb>
#include <gtk/gtk.h>
/* Notre nouveau rappel am<61>lior<6F>. La donn<6E>e pass<73>e <20> cette fonction est
* imprim<69>e sur stdout. */
void rappel (GtkWidget *widget, gpointer *data)
{
g_print ("Re-Bonjour - %s a <20>t<EFBFBD> press<73>\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 d<>clarer les widgets */
GtkWidget *window;
GtkWidget *button;
GtkWidget *box1;
/* Cette fonction est appel<65>e dans toutes les applications GTK.
* Les param<61>tre pass<73>s en ligne de commande sont analys<79>s et
* retourn<72>s <20> l'application. */
gtk_init (&amp;argc, &amp;argv);
/* Cr<43>ation d'une nouvelle fen<65>tre. */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Nouvel appel qui intitule notre nouvelle fen<65>tre
* "Salut les boutons !" */
gtk_window_set_title (GTK_WINDOW (window), "Salut les boutons !");
/* Configuration d'un gestionnaire pour "delete_event" afin de
* quitter imm<6D>diatement GTK. */
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
GTK_SIGNAL_FUNC (delete_event), NULL);
/* Configuration de la largeur du contour de la fen<65>tre. */
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* Cr<43>ation d'une bo<62>te pour y placer les widgets.
* Ceci est d<>crit en d<>tails plus loin dans la section
* <20> placement <20>. La bo<62>te n'est pas mat<61>rialis<69>e, elle est juste
* utilis<69>e comme moyen d'arranger les widgets. */
box1 = gtk_hbox_new(FALSE, 0);
/* On met la bo<62>te dans la fen<65>tre principale. */
gtk_container_add (GTK_CONTAINER (window), box1);
/* On cr<63>e un nouveau bouton portant le label <20> Bouton 1 <20>. */
button = gtk_button_new_with_label ("Bouton 1");
/* Lorsque le bouton est cliqu<71>, on appelle la fonction <20> rappel <20>
* avec un pointeur sur la cha<68>ne <20> Bouton 1 <20> comme param<61>tre. */
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 bo<62>te invisible qui a <20>t<EFBFBD> plac<61>e dans la fen<65>tre. */
gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);
/* N'oubliez jamais cette <20>tape qui indique <20> GTK que la configuration
* de ce bouton est termin<69>e et qu'il peut <20>tre affich<63>. */
gtk_widget_show(button);
/* On fait la m<>me chose pour cr<63>er un deuxi<78>me bouton. */
button = gtk_button_new_with_label ("Bouton 2");
/* On appelle la m<>me fonction de rappel avec un param<61>tre diff<66>rent,
* un pointeur sur la cha<68>ne <20> Bouton 2 <20>. */
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 pr<70>f<EFBFBD>rable d'afficher la fen<65>tre 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 f<>te commence ! */
gtk_main ();
return 0;
}
</verb></tscreen>
<p>
Compilez ce programme en utilisant les m<>mes param<61>tres que pour
l'exemple pr<70>c<EFBFBD>dent. Vous remarquerez que, maintenant, il est plus
difficile de quitter le programme&nbsp;: vous devez utiliser le
gestionnaire de fen<65>tres ou une commande shell pour le d<>truire. Un
bon exercice pour le lecteur serait d'ins<6E>rer un troisi<73>me bouton <20>
Quitter <20> qui permettrait de sortir du programme. Vous pouvez aussi
jouer avec les options de <em/gtk_box_pack_start()/ en lisant la
section suivante. Essayez de redimensionner la fen<65>tre, et observez
son comportement.
<p>
Juste une remarque&nbsp;: il existe une autre constante utilisable
avec <em/gtk_window_new()/ - GTK_WINDOW_DIALOG. Ceci permet
d'interagir de fa<66>on un peu diff<66>rente avec le gestionnaire de
fen<EFBFBD>tres et doit <20>tre utilis<69> pour les fen<65>tres temporaires comme les
bo<EFBFBD>tes de dialogue, par exemple.
<sect>Placement des widgets
<p>
Lorsqu'on cr<63>e une application, on veut mettre plus qu'un simple
bouton dans une fen<65>tre. Notre premier exemple <20> Bonjour le monde <20>
n'utilisait qu'un seul widget et on pouvait donc simplement faire un
appel <20> <em/gtk_container_add/ pour <20> placer <20> le widget dans la
fen<EFBFBD>tre. Mais si l'on d<>sire en mettre plus, comment peut-on contr<74>ler
l'endroit o<> le widget sera positionn<6E> ? C'est ici que le placement
entre en jeu.
<sect1>Th<54>orie des bo<62>tes de placement
<p>
La majeure partie du placement est faites en cr<63>ant des bo<62>tes comme
dans l'exemple ci-dessus. Ce sont des widgets containers invisibles o<>
l'on peut placer nos widgets. Elles existent sous deux formes&nbsp;:
bo<EFBFBD>tes horizontales et bo<62>tes verticales. Lorsque l'on place des
widgets dans une bo<62>te horizontale, les objets sont ins<6E>r<EFBFBD>s
horizontalement de gauche <20> droite ou de droite <20> gauche selon l'appel
utilis<EFBFBD>. Dans une bo<62>te verticale, les widgets sont plac<61>s de haut en
bas ou vice versa. On peut utiliser n'importe quelle combinaison de
bo<EFBFBD>tes <20> l'int<6E>rieur ou <20> c<>t<EFBFBD> d'autres bo<62>tes pour cr<63>er l'effet
d<EFBFBD>sir<EFBFBD>.
<p>
Pour cr<63>er une nouvelle bo<62>te horizontale, on appelle
<em/gtk_hbox_new()/, et pour les bo<62>tes verticales,
<em/gtk_vbox_new()/. Les fonctions <em/gtk_box_pack_start()/ et
<em/gtk_box_pack_end()/ servent <20> placer les objets <20> l'int<6E>rieur de
ces containers. La fonction <em/gtk_box_pack_start()/ placera de haut
en bas dans une bo<62>te verticale et de gauche <20> droite dans une bo<62>te
horizontale. <em/gtk_box_pack_end()/ fera le contraire en pla<6C>ant de
bas en haut et de droite <20> gauche. En utilisant ces fonctions, on peut
aligner <20> droite ou <20> gauche nos widgets et m<>me les m<>langer de
n'importe quelle fa<66>on pour obtenir l'effet d<>sir<69>. Dans la plupart de
nos exemples, on utilisera <em/gtk_box_pack_start()/. Un objet peut
<EFBFBD>tre un autre container ou un widget. En fait, de nombreux widgets
(dont les boutons) sont eux-m<>mes des containers, mais on utilise
g<EFBFBD>n<EFBFBD>ralement seulement un label dans un bouton.
<p>
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
pr<EFBFBD>ciser comment les widgets doivent <20>tre plac<61>s. Comme vous pouvez
l'imaginer, cette m<>thode nous donne pas mal de libert<72> pour placer et
cr<EFBFBD>er les widgets.
<sect1>D<>tails sur les bo<62>tes
<p>
<EFBFBD> cause de cette libert<72>, le placement des bo<62>tes avec GTK peut
para<EFBFBD>tre d<>routant au premier abord. Il existe beaucoup d'options et
il n'est pas tout de suite <20>vident de comprendre comment elles
s'accordent toutes ensemble. En fait, il y a 5 styles de base
diff<EFBFBD>rents.
<p>
<?
<IMG ALIGN="center" SRC="packbox1.gif"
VSPACE="15" HSPACE="10" ALT="Box Packing Example Image" WIDTH="528"
HEIGHT="235">
>
Chaque ligne contient une bo<62>te horizontale (<em/hbox/) contenant
plusieurs boutons. L'appel <20> <em/gtk_box_pack/ indique la fa<66>on dont
sont plac<61>s tous les boutons dans la hbox. Chaque bouton est plac<61>
dans la hbox de la m<>me fa<66>on (m<>mes param<61>tres que la fonction
<em/gtk_box_pack_start()/).
<p>
Voici la d<>claration de la fonction <em/gtk_box_pack_start/.
<tscreen><verb>
void gtk_box_pack_start (GtkBox *box,
GtkWidget *child,
gint expand,
gint fill,
gint padding);
</verb></tscreen>
Le premier param<61>tre est la bo<62>te dans laquelle on place l'objet, le
second est cet objet. Tous les objets sont tous des boutons jusqu'<27>
maintenant, on place donc des boutons dans des bo<62>tes.
<p>
Le param<61>tre <em/expand/ de <em/gtk_box_pack_start()/ ou
<em/gtk_box_pack_end()/ contr<74>le la fa<66>on dont le widget est plac<61>
dans la bo<62>te. S'il vaut TRUE, les widgets sont dispos<6F>s dans la bo<62>te
de fa<66>on <20> en occuper tout l'espace. S'il vaut FALSE, la bo<62>te est
r<EFBFBD>tr<EFBFBD>cie pour correspondre <20> la taille du widget. Mettre <em/expand/ <20>
FALSE vous permettra d'aligner <20> droite et <20> gauche vos
widgets. Sinon, ils s'<27>largiront pour occuper toute la bo<62>te. Le m<>me
effet pourrait <20>tre obtenu en utilisant uniquement une des deux
fonctions <em/gtk_box_pack_start/ ou <em/pack_end/.
<p>
Le param<61>tre <em/fill/ des fonctions <em/gtk_box_pack/ contr<74>le si de
l'espace suppl<70>mentaire doit <20>tre allou<6F> aux objets eux-m<>mes (TRUE),
ou si on doit rajouter de l'espace (<em/padding/) dans la bo<62>te autour
des objets (FALSE). Il n'a de sens que si le param<61>tre <em/expand/
vaut TRUE.
<p>
Lorsque l'on cr<63>e une nouvelle bo<62>te, on utilise une fonction comme&nbsp;:
<tscreen><verb>
GtkWidget * gtk_hbox_new (gint homogeneous,
gint spacing);
</verb></tscreen>
Le param<61>tre <em/homogeneous/ de <em/gtk_hbox_new/ (et c'est la m<>me
chose pour <em/gtk_vbox_new/) v<>rifie que chaque objet de la bo<62>te ait
la m<>me taille (i.e. la m<>me largeur dans une hbox, la m<>me hauteur
dans une vbox). S'il vaut TRUE, le param<61>tre <em/expand/ des fonctions
<em/gtk_box_pack/ sera toujours mis <20> TRUE.
<p>
Quelle est alors la diff<66>rence entre les param<61>tres <em/spacing/
(configur<75> lorsque la bo<62>te est cr<63><72>e) et <em/padding/ (configur<75>
lorque les <20>l<EFBFBD>ments sont plac<61>s) ? <em/spacing/ ajoute de l'espace
entre les objets, et <em/padding/ en ajoute de chaque c<>t<EFBFBD> d'un
objet. La figure suivante devrait <20>clairer tout cela&nbsp;:
<?
<IMG ALIGN="center" SRC="packbox2.gif"
VSPACE="15" HSPACE="10" ALT="Box Packing Example Image" WIDTH="509"
HEIGHT="213">
>
Voici le code utilis<69> pour cr<63>er les images ci-dessus. J'y ai mis beaucoup de
commentaires en esp<73>rant que vous n'aurez pas de probl<62>me pour le
relire. Compilez-le et jouez avec les diff<66>rents param<61>tres.
<sect1>Programme de d<>monstration des placements
<p>
<tscreen><verb>
#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 param<61>tres qui
* nous int<6E>ressent sont pass<73>s <20> cette fonction.
* On n'affiche pas la bo<62>te, 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];
/* Cr<43>ation d'une hbox avec les param<61>tres homogeneous et spacing
* voulus. */
box = gtk_hbox_new (homogeneous, spacing);
/* Cr<43>ation d'une s<>rie de boutons configur<75>s de fa<66>on appropri<72>e */
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);
/* Cr<43>ation d'un bouton portant un label d<>pendant de la valeur
* du param<61>tre 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);
/* M<>me chose que ci-dessus mais sous forme abr<62>g<EFBFBD>e. */
button = gtk_button_new_with_label (fill ? "TRUE," : "FALSE,");
gtk_box_pack_start (GTK_BOX (box), button, expand, fill, padding);
gtk_widget_show (button);
/* R<>cup<75>ration du param<61>tre padding sous forme de cha<68>ne. */
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, <20> ne jamais oublier ! :) */
gtk_init (&amp;argc, &amp;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]);
/* Cr<43>ation de notre fen<65>tre. */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
/* Il ne faut jamais oublier de connecter le signal "destroy" <20> la
* fen<65>tre principale. C'est tr<74>s important pour disposer d'un
* comportement intuitif ad<61>quat. */
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
GTK_SIGNAL_FUNC (delete_event), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* Cr<43>ation d'une bo<62>te verticale (vbox) pour y placer les bo<62>tes
* horizontales.
* Ceci permet de placer les bo<62>tes horizontales contenant les boutons
* les unes au dessus des autres dans cette vbox. */
box1 = gtk_vbox_new (FALSE, 0);
/* L'exemple <20> afficher. Ils correspondent aux images ci-dessus. */
switch (which) {
case 1:
/* Cr<43>ation d'un label. */
label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
/* Alignement du label <20> gauche. On pr<70>cisera 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 bo<62>te verticale (vbox box1). Il ne
* faut pas oublier que les widgets qui s'ajoutent <20> une vbox sont
* plac<61>s 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 bo<62>te :
* 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 bo<62>te :
* 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);
/* Param<61>tres : 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);
/* Cr<43>ation d'un s<>parateur, on verra cela plus tard, mais ils sont
* simples <20> utiliser. */
separator = gtk_hseparator_new ();
/* Placement du s<>parateur dans la vbox. Ne pas oublier que tous les
* widgets sont plac<61>s dans une vbox et qu'il seront plac<61>s
* verticalement. */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
/* Cr<43>ation 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);
/* Param<61>tres : 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);
/* Param<61>tres : 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 s<>parateur */
separator = gtk_hseparator_new ();
/* Les 3 derniers param<61>tres 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:
/* Cr<43>ation d'un label, box1 est une vbox identique <20>
* celle cr<63><72>e au d<>but 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);
/* Param<61>tres : 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);
/* Param<61>tres : 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 param<61>tres 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);
/* Param<61>tres : 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);
/* Param<61>tres : 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 param<61>tres 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 d<>monstration de la possibilit<69> d'utiliser
* gtk_box_pack_end() pour aligner les widgets <20> droite.
* On cr<63>e d'abord une nouvelle bo<62>te comme d'habitude. */
box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
/* On cr<63>e le label qui sera mis <20> la fin. */
label = gtk_label_new ("end");
/* On le place en utilisant gtk_box_pack_end(), il est ainsi
* mis <20> droite de la hbox cr<63><72>e par l'appel <20> 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);
/* S<>parateur pour le bas. */
separator = gtk_hseparator_new ();
/* Configuration du s<>parateur en 400x5 pixels.
* La hbox que l'on a cr<63><72>e aura donc 400 pixels de large,
* et le label "end" sera s<>par<61> des autres de la hbox.
* Sinon, tous les widgets de la hbox seraient plac<61>s les plus
* pr<70>s possible les uns des autres. */
gtk_widget_set_usize (separator, 400, 5);
/* Placement du s<>parateur dans la vbox (box1)
* cr<63><72>e au debut de main(). */
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
gtk_widget_show (separator);
}
/* Cr<43>ation 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 d<>truire la fen<65>tre. Ceci enverra le
* signal "destroy" <20> la fen<65>tre. Ce signal sera <20> son tour captur<75>
* par notre gestionnaire de signal d<>fini plus haut. */
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (gtk_widget_destroy),
GTK_OBJECT (window));
/* Placement du bouton dans la <20> quitbox <20>.
* Les 3 derniers param<61>tres 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 fen<65>tre principale. */
gtk_container_add (GTK_CONTAINER (window), box1);
/* Affichage */
gtk_widget_show (button);
gtk_widget_show (quitbox);
gtk_widget_show (box1);
/* Affichage de la fen<65>tre en dernier */
gtk_widget_show (window);
/* Ne pas oublier notre fonction principale. */
gtk_main ();
/* Le contr<74>le revient ici lorsque gtk_main_quit() est appel<65>e,
* jusqu'<27> ce que gtk_exit() soitutilis<69>e. */
return 0;
}
</verb></tscreen>
<p>
<sect1>Placement avec les tables
<p>
<EFBFBD>tudions une autre m<>thode de placement&nbsp;: les tables. Elles
peuvent s'av<61>rer tr<74>s utiles dans certaines situations.
En utilisant des tables, on cr<63>e une grille dans laquelle on peut
placer les widgets. Ceux-ci peuvent occuper tous les endroits que l'on
d<EFBFBD>sire.
La premi<6D>re chose <20> faire est, bien s<>r, d'<27>tudier la fonction
<em/gtk_table_new/&nbsp;:
<tscreen><verb>
GtkWidget* gtk_table_new (gint rows,
gint columns,
gint homogeneous);
</verb></tscreen>
<p>
Le premier param<61>tre est le nombre de lignes de la table et le
deuxi<EFBFBD>me, le nombre de colonnes.
Le param<61>tre <em/homogeneous/ s'occupe de la fa<66>on dont les cases de
la table seront dimensionn<6E>es. Si homogeneous vaut TRUE, les cases
prennent la taille du plus grand widget de la table. S'il vaut FALSE,
la taille des cases d<>pend du widget le plus haut de la ligne et du
plus large de cette colonne.
Le nombre de lignes et colonnes va de 0 <20> n, o<> n est le nombre sp<73>cifi<66> dans
l'appel <20> <em/gtk_table_new/. Ainsi, avec <em/rows/ = 2 et
<em/columns/ = 2, la table ressemblera <20> ceci&nbsp;:
<tscreen><verb>
0 1 2
0+----------+----------+
| | |
1+----------+----------+
| | |
2+----------+----------+
</verb></tscreen>
<p>
On notera que le syst<73>me de coordonn<6E>es part du coin en haut <20>
gauche. Pour placer un widget dans une case, ou utilise la fonction
suivante&nbsp;:
<tscreen><verb>
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);
</verb></tscreen>
<p>
O<EFBFBD> le premier param<61>tre (<em/table/) est la table que l'on a cr<63><72>e et
le second (<em/child/) est le widget que l'on veut placer dans la
table.
Les param<61>tres <em/left_attach/ et <em/right_attach/ sp<73>cifient
l'emplacement du widget et le nombre de cases <20> utiliser. Par exemple,
si on veut placer un bouton dans le coin inf<6E>rieur droit de la table
d<EFBFBD>crite plus haut et que l'on d<>sire ne remplir QUE cette case,
<em/left_attach/ vaudra 1, <em/right_attach/ vaudra 2; <em/top_attach/
vaudra 1 et <em/bottom_attach/ vaudra 2.
Si on veut un widget occupant toute la ligne sup<75>rieure de notre table, on utilisera
les valeurs 0, 2, 0, 1.
Les param<61>tres <em/xoptions/ et <em/yoptions/ servent <20> pr<70>ciser les
options de placement et peuvent <20>tre combin<69>es par un OU logique pour
permettre des options multiples.
Ces options sont&nbsp;:
<itemize>
<item>GTK_FILL - Si la case de la table est plus large que le widget, et que
GTK_FILL est sp<73>cifi<66>, le widget s'<27>largira pour occuper toute la place
disponible.
<item>GTK_SHRINK - Si la table a moins de place qu'il ne lui en faut
(g<>n<EFBFBD>ralement, <20> cause d'un redimensionnement de la fen<65>tre par
l'utilisateur), les widgets sont alors simplement pouss<73>s vers le bas
de la fen<65>tre et disparaissent. Si GTK_SHRINK est sp<73>cifi<66>, les
widgets se r<>duiront en m<>me temps que la table.
<item>GTK_EXPAND - Cette option provoque l'extension de la table pour
qu'elle utilise tout l'espace restant dans la fen<65>tre.
</itemize>
Le param<61>tres de <em/padding/ jouent le m<>me r<>le que pour les bo<62>tes,
il cr<63>ent une zone libre, sp<73>cifi<66>e en pixels, autour du widget.
gtk_table_attach() a BEAUCOUP d'options. Voici donc une fonction-raccourci&nbsp;:
<tscreen><verb>
void gtk_table_attach_defaults (GtkTable *table,
GtkWidget *widget,
gint left_attach,
gint right_attach,
gint top_attach,
gint bottom_attach);
</verb></tscreen>
<em/xoptions/ et <em/options/ valent par d<>faut GTK_FILL | GTK_EXPAND,
et <em/xpadding/ et <em/ypadding/ valent 0. Les autres param<61>tres sont
les m<>mes que ceux de la fonction pr<70>c<EFBFBD>dente.
Il existe aussi les fonctions <em/gtk_table_set_row_spacing()/ et
<em/gtk_table_set_col_spacing()/. Elles permettent de placer des
espaces apr<70>s une ligne ou une colonne.
<tscreen><verb>
void gtk_table_set_row_spacing (GtkTable *table,
gint row,
gint spacing);
</verb></tscreen>
et
<tscreen><verb>
void gtk_table_set_col_spacing (GtkTable *table,
gint column,
gint spacing);
</verb></tscreen>
Pour les colonnes, l'espace est ajout<75> <20> droite de la colonne et pour
les lignes, il est ajout<75> en dessous.
On peut aussi configurer un espacement pour toutes les lignes et/ou
colonnes avec&nbsp;:
<tscreen><verb>
void gtk_table_set_row_spacings (GtkTable *table,
gint spacing);
</verb></tscreen>
<p>
Et,
<tscreen><verb>
void gtk_table_set_col_spacings (GtkTable *table,
gint spacing);
</verb></tscreen>
<p>
Avec ces appels, la derni<6E>re ligne et la derni<6E>re colonne n'ont pas
d'espace suppl<70>mentaire.
<sect1>Exemple de placement avec table
<p>
Pour le moment, <20>tudiez l'exemple sur les tables (testgtk.c) distribu<62>
avec les sources de GTK.
<sect>Vue d'ensemble des widgets
<p>
<p>
Les <20>tapes pour cr<63>er un widget en GTK sont&nbsp;:
<enum>
<item> <em/gtk_*_new()/ - une des fonctions disponibles pour cr<63>er un
nouveau widget. Ces fonctions sont d<>crites dans cette section.
<item> Connexion de tous les signaux que l'on souhaite utiliser avec
les gestionnaires ad<61>quats.
<item> Configuration des attributs du widget.
<item> Placement du widget dans un container en utilisant un appel appropri<72> comme
<em/gtk_container_add()/ ou <em/gtk_box_pack_start()/.
<item> Affichage du widget gr<67>ce <20> <em/gtk_widget_show()/.
</enum>
<p>
<em/gtk_widget_show()/ permet <20> GTK de savoir que l'on a fini de
configurer les attributs du widget et qu'il est pr<70>t <20> <20>tre
affich<EFBFBD>. On peut aussi utiliser <em/gtk_widget_hide()/ pour le faire
dispara<EFBFBD>tre. L'ordre dans lequel on affiche les widgets n'est pas
important, mais il est pr<70>f<EFBFBD>rable d'afficher la fen<65>tre en dernier
pour qu'elle surgisse d'un seul coup plut<75>t que de voir les diff<66>rents
widgets appara<72>tre <20> l'<27>cran au fur et <20> mesure. Les fils d'un widget
(une fen<65>tre est aussi un widget) ne seront pas affich<63>s tant que la
fen<EFBFBD>tre elle-m<>me n'est pas affich<63>e par la fonction
<em/gtk_widget_show()/.
<sect1> Conversions de types
<p>
Vous remarquerez, au fur et <20> mesure que vous progressez, que GTK
utilise un syst<73>me de coercition de type. Celle-ci est toujours
r<EFBFBD>alis<EFBFBD>e en utilisant des macros qui v<>rifient si l'objet donn<6E> peut
<EFBFBD>tre converti et qui r<>alisent cette coercition. Les macros que vous
rencontrerez le plus sont&nbsp;:
<itemize>
<item> GTK_WIDGET(widget)
<item> GTK_OBJECT(object)
<item> GTK_SIGNAL_FUNC(function)
<item> GTK_CONTAINER(container)
<item> GTK_WINDOW(window)
<item> GTK_BOX(box)
</itemize>
Elles sont toutes utilis<69>es pour convertir les param<61>tres des
fonctions. Vous les verrez dans les exemples et, en r<>gle g<>n<EFBFBD>rale,
vous saurez les utiliser simplement en regardant la
d<EFBFBD>claration d'une fonction.
Comme vous pouvez le voir dans la hi<68>rarchie de classes ci-dessous,
tous les <em/GtkWidgets/ d<>rivent d'une classe de base
<em/GtkObject/. Ceci signifie que vous pouvez utiliser un widget <20>
chaque fois qu'une fonction requiert un objet - il suffit d'utiliser
la macro GTK_OBJECT().
Par exemple&nbsp;:
<tscreen><verb>
gtk_signal_connect(GTK_OBJECT(button), "clicked",
GTK_SIGNAL_FUNC(fonction_rappel), donnee_de_rappel);
</verb></tscreen>
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
hi<EFBFBD>rarchie de classe ci-dessous, vous remarquerez que beaucoup de
widgets viennent de la classe <em/GtkContainer/. N'importe lequel de
ces widgets peut <20>tre utilis<69> avec la macro GTK_CONTAINER pour <20>tre
pass<EFBFBD> en param<61>tre <20> une fonction qui attend un container.
Malheureusement, ces macros ne peuvent <20>tre couvertes en d<>tail dans
ce didacticiel, Je vous recommande donc de jeter un coup d'oeil sur
les fichier en-t<>tes GTK&nbsp;: ils peuvent s'av<61>rer tr<74>s
instructifs. En fait, il n'est pas difficile de comprendre comment
fonctionne un widget, il suffit d'<27>tudier les d<>clarations des
fonctions.
<p>
<sect1>La hi<68>rarchie des widgets
<p>
Voici l'arbre de la hi<68>rarchie de classes utilis<69>es pour implanter les widgets.
<tscreen><verb>
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
</verb></tscreen>
<p>
<sect1>Widgets sans fen<65>tre
<p>
Les widgets suivants n'ont pas de fen<65>tre associ<63>e. Si vous voulez
capturer des <20>v<EFBFBD>nements, vous devez utiliser <em/GtkEventBox/.
Reportez-vous <20> la section sur
<ref id="sec_The_EventBox_Widget" name="Le widget EventBox">
<tscreen><verb>
GtkAlignment
GtkArrow
GtkBin
GtkBox
GtkImage
GtkItem
GtkLabel
GtkPaned
GtkPixmap
GtkScrolledWindow
GtkSeparator
GtkTable
GtkViewport
GtkAspectFrame
GtkFrame
GtkVPaned
GtkHPaned
GtkVBox
GtkHBox
GtkVSeparator
GtkHSeparator
</verb></tscreen>
<p>
Nous continuerons notre exploration de GTK en examinant chaque widget
tour <20> tour, cr<63>ant quelques fonctions simples pour les afficher. Une
autre source int<6E>ressante est le programme <em/testgtk.c/ livr<76> avec
GTK. Il se trouve dans le r<>pertoire <em>gtk/</em>
<sect>Widgets boutons
<p>
<sect1>Boutons normaux
<p>
On a d<>j<EFBFBD> presque vu tout ce qu'il y avait <20> voir sur le widget
bouton. Il est tr<74>s simple. Cependant, il y a deux fa<66>ons de cr<63>er un
bouton. On peut utiliser <em/gtk_button_new_with_label()/ pour cr<63>er
un bouton avec un label, ou <em/gtk_button_new()/ pour cr<63>er un bouton
vide. Dans ce dernier cas, c'est <20> vous de placer un label ou un
pixmap sur celui-ci. Pour ce faire, cr<63>ez une bo<62>te, puis placez vos
objets dans celle-ci en utilisant la fonction habituelle
<em/gtk_box_pack_start/, utilisez alors <em/gtk_container_add/ pour
placer la bo<62>te dans le bouton.
<p>
Voici un exemple d'utilisation de <em/gtk_button_new()/ pour cr<63>er un
bouton contenant une image et un label. J'ai s<>par<61> du reste le code
qui cr<63>e une bo<62>te pour que vous puissiez l'utiliser dans vos
programmes.
<tscreen><verb>
#include <gtk/gtk.h>
/* Cr<43>ation d'une hbox avec une image et un label. Cette fonction
* retourne la bo<62>te... */
GtkWidget *xpm_label_box (GtkWidget *parent, gchar *xpm_filename,
gchar *label_text)
{
GtkWidget *box1;
GtkWidget *label;
GtkWidget *pixmapwid;
GdkPixmap *pixmap;
GdkBitmap *mask;
GtkStyle *style;
/* Cr<43>ation 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 conna<6E>t la vraie raison, qu'il
* m'<27>claire sur ce point. */
style = gtk_widget_get_style(parent);
/* Chargement de xpm pour cr<63>er une image */
pixmap = gdk_pixmap_create_from_xpm (parent->window, &amp;mask,
&amp;style->bg[GTK_STATE_NORMAL],
xpm_filename);
pixmapwid = gtk_pixmap_new (pixmap, mask);
/* Cr<43>ation d'un label */
label = gtk_label_new (label_text);
/* placement de l'image et du label dans la bo<62>te */
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 <20>t<EFBFBD> press<73>\n", (char *) data);
}
int main (int argc, char *argv[])
{
/* GtkWidget est le type utilis<69> pour d<>clarer les widgets */
GtkWidget *window;
GtkWidget *button;
GtkWidget *box1;
gtk_init (&amp;argc, &amp;argv);
/* Cr<43>ation d'une fen<65>tre */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Pixmap'd Buttons!");
/* Il est pr<70>f<EFBFBD>rable de faire cela pour toutes les fen<65>tres */
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* Configuration du bord de la fen<65>tre */
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* Cr<43>ation d'un bouton */
button = gtk_button_new ();
/* Vous devriez <20>tre habitu<74> <20> voir ces fonctions maintenant */
gtk_signal_connect (GTK_OBJECT (button), "clicked",
GTK_SIGNAL_FUNC (callback), (gpointer) "cool button");
/* Appel de notre fonction de cr<63>ation de bo<62>te */
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;
}
</verb></tscreen>
La fonction <em/xpm_label_box()/ peut <20>tre utilis<69>e pour placer des
xpms et des labels sur tout widget qui peut <20>tre container.
<sect1> Boutons commutateurs
<p>
Les boutons commutateurs ressemblent beaucoup aux boutons normaux, sauf
qu'ils seront toujours alternativement dans un <20>tat ou dans un
autre. Le changement d'<27>tat s'effectue par un click. Ils peuvent <20>tre
enfonc<EFBFBD>s et, lorsqu'on clique dessus, ils se rel<65>vent. Re-cliquez,
et ils se renfoncent.
Les boutons commutateurs sont la base des cases <20> cocher ou des boutons
radio, donc la plupart des appels utilis<69>s pour les boutons commutateurs
sont h<>rit<69>s par les cases <20> cocher et les boutons radio. J'insisterai
l<EFBFBD> dessus quand nous les aborderons.
Cr<EFBFBD>ation d'un bouton commutateur :
<tscreen><verb>
GtkWidget* gtk_toggle_button_new (void);
GtkWidget* gtk_toggle_button_new_with_label (gchar *label);
</verb></tscreen>
<p>
Comme vous pouvez l'imaginer, elles fonctionnent comme celles des
boutons normaux. La premi<6D>re cr<63>e un bouton commutateur vide et la
deuxi<EFBFBD>me un bouton commutateur contenant d<>j<EFBFBD> un label.
<p>
Pour r<>cup<75>rer l'<27>tat d'un commutateur et cela comprend aussi les
cases <20> cocher et les boutons radio, on utilise une macro comme nous
le montrons dans l'exemple qui suit et qui teste l'<27>tat du commutateur
dans une fonction de rappel. Le signal qui nous int<6E>resse et qui est
<EFBFBD>mis par les boutons commutateurs (ce qui comprend aussi les cases <20>
cocher et les boutons radio), est le signal "toggled". Pour v<>rifier
l'<27>tat de ces boutons, on configure un gestionnaire de signal qui
capture "toggled" et utilise la macro pour d<>terminer l'<27>tat. La
fonction de rappel ressemblera <20> ceci&nbsp;:
<tscreen><verb>
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 rel<65>ch<63>. */
} else {
/* le bouton est enfonc<6E> */
}
}
</verb></tscreen>
<!--
COMMENTED!
<tscreen><verb>
guint gtk_toggle_button_get_type (void);
</verb></tscreen>
<p>
No idea... they all have this, but I dunno what it is :)
<tscreen><verb>
void gtk_toggle_button_set_mode (GtkToggleButton *toggle_button,
gint draw_indicator);
</verb></tscreen>
<p>
No idea.
-->
<p>
L'appel qui suit peut <20>tre utilis<69> pour configurer l'<27>tat d'un bouton
commutateur et de ses descendants, les cases <20> cocher et les boutons
radio. On lui passe notre bouton en premier param<61>tre et TRUE ou
FALSE pour sp<73>cifier s'il doit <20>tre rel<65>ch<63> ou enfonc<6E>. Par d<>faut, il
est rel<65>ch<63> (FALSE).
<tscreen><verb>
void gtk_toggle_button_set_state (GtkToggleButton *toggle_button,
gint state);
</verb></tscreen>
<p>
On notera que lorsqu'on utilise cette fonction, et que l'<27>tat est
modifi<EFBFBD>, cela force le bouton <20> <20>mettre un signal "clicked".
<tscreen><verb>
void gtk_toggle_button_toggled (GtkToggleButton *toggle_button);
</verb></tscreen>
<p>
Cet appel ne fait que commuter le bouton et <20>mettre le signal "toggled".
<sect1> Cases <20> cocher
<p>
Les cases <20> cocher h<>ritent de nombreuses propri<72>t<EFBFBD>s et fonctions des
boutons commutateurs, mais ont un aspect diff<66>rent. Au lieu d'<27>tre des
boutons contenant du texte, ce sont de petits carr<72>s avec un texte sur
leur droite. Il sont souvent utilis<69>s pour valider ou non des options
dans les applications.
Les deux fonctions de cr<63>ation sont identiques <20> celles des boutons normaux.
<tscreen><verb>
GtkWidget* gtk_check_button_new (void);
GtkWidget* gtk_check_button_new_with_label (gchar *label);
</verb></tscreen>
La fonction <em/new_with_label/ cr<63>e une case <20> cocher avec un texte <20>
cot<EFBFBD> d'elle.
La v<>rification de l'<27>tat d'une case <20> cocher est identique <20> celle
des boutons commutateurs.
<sect1> Boutons radio
<p>
Les boutons radio ressemblent aux cases <20> cocher sauf qu'ils sont
group<EFBFBD>s de fa<66>on <20> ce qu'un seul d'entre-eux puisse <20>tre s<>lectionn<6E> <20>
un moment donn<6E>. Ils sont utilis<69>s par les applications lorsqu'il
s'agit d'effectuer un choix dans une liste d'options.
La cr<63>ation d'un bouton radio s'effectue gr<67>ce <20> l'un des appels
suivants&nbsp;:
<tscreen><verb>
GtkWidget* gtk_radio_button_new (GSList *group);
GtkWidget* gtk_radio_button_new_with_label (GSList *group,
gchar *label);
</verb></tscreen>
<p>
On notera le param<61>tre suppl<70>mentaire de ces fonctions. Elles
n<EFBFBD>cessitent un groupe pour r<>aliser correctement leur t<>che. Le
premier appel doit passer NULL au premier param<61>tre puis on peut cr<63>er un
groupe en utilisant&nbsp;:
<tscreen><verb>
GSList* gtk_radio_button_group (GtkRadioButton *radio_button);
</verb></tscreen>
<p>
On passe alors ce groupe en premier param<61>tre des appels suivants aux fonctions de cr<63>ation. Il est pr<70>f<EFBFBD>rable, aussi, de pr<70>ciser quel bouton doit <20>tre choisi par d<>faut avec la fonction&nbsp;:
<tscreen><verb>
void gtk_toggle_button_set_state (GtkToggleButton *toggle_button,
gint state);
</verb></tscreen>
<p>
Celle-ci est d<>crite dans la section sur les boutons commutateurs et fonctionne
exactement de la m<>me fa<66>on.
<p>
[Mettre ici un exemple d'utilisation de tout cela car je crois que cela ferait
beaucoup de bien...]
<sect> Widgets divers
<p>
<sect1> Labels
<p>
Les labels sont tr<74>s utilis<69>s dans GTK et sont relativement
simples. Ils n'<27>mettent pas de signaux car ils n'ont pas de fen<65>tre X
qui leur est associ<63>e. Si vous avez besoin de capturer des signaux ou
de faire des coupures (<28> clippings <20>), utilisez un widget EventBox.
Pour cr<63>er un label, on utilise&nbsp;:
<tscreen><verb>
GtkWidget* gtk_label_new (char *str);
</verb></tscreen>
O<EFBFBD> l'unique param<61>tre est la cha<68>ne de caract<63>res que l'on veut que le
label affiche.
Pour changer le texte d'un label apr<70>s sa cr<63>ation, on utilise la fonction&nbsp;:
<tscreen><verb>
void gtk_label_set (GtkLabel *label,
char *str);
</verb></tscreen>
<p>
o<EFBFBD> le premier param<61>tre est le label que l'on veut modifier, que l'on
convertit en utilisant la macro GTK_LABEL(), et le second est la
nouvelle cha<68>ne.
L'espace n<>cessaire <20> la nouvelle cha<68>ne sera automatiquement ajust<73>
si n<>cessaire.
Pour r<>cup<75>rer la cha<68>ne courante, on utilise la fonction&nbsp;:
<tscreen><verb>
void gtk_label_get (GtkLabel *label,
char **str);
</verb></tscreen>
o<EFBFBD> le premier param<61>tre est le label dont on veut r<>cup<75>rer la cha<68>ne
et le second sert <20> retourner cette cha<68>ne.
<sect1>Le widget bulle d'aide
<p>
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 <20> utiliser, on ne donnera donc pas d'exemple. Si vous
voulez voir du code, consultez le programme <em/testgtk.c/ distribu<62>
avec GTK.
<p>
Certains widgets (comme les labels) ne fonctionnent pas avec les
bulles d'aide.
<p>
Le premier appel que vous utiliserez sera pour cr<63>er une nouvelle
bulle d'aide. Vous n'avez besoin que de le faire une fois dans une
fonction donn<6E>e. Le <em/GtkTooltip/ que cette fonction retourne peut
<EFBFBD>tre utilis<69> pour cr<63>er plusieurs bulles d'aide.
<tscreen><verb>
GtkTooltips *gtk_tooltips_new (void);
</verb></tscreen>
Lorsque vous avez cr<63><72> une nouvelle bulle d'aide et le widget sur lequel vous
voulez l'utiliser, vous n'avez qu'<27> faire cet appel pour la configurer&nbsp;:
<tscreen><verb>
void gtk_tooltips_set_tips (GtkTooltips *tooltips,
GtkWidget *widget,
gchar *tips_text);
</verb></tscreen>
Les param<61>tres sont la bulle d'aide d<>j<EFBFBD> cr<63><72>e, suivi du widget pour
lequel vous voulez voir appara<72>tre cette bulle et le texte que vous
voulez qu'elle contienne.
<p>
Voici un petit exemple&nbsp;:
<tscreen><verb>
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");
</verb></tscreen>
D'autres fonctions peuvent <20>tre utilis<69>es avec les bulles d'aide. Je ne ferais que les <20>num<75>rer et les d<>crire bri<72>vement.
<tscreen><verb>
void gtk_tooltips_destroy (GtkTooltips *tooltips);
</verb></tscreen>
Destruction de bulles d'aide.
<tscreen><verb>
void gtk_tooltips_enable (GtkTooltips *tooltips);
</verb></tscreen>
Activation d'un ensemble de bulles d'aide d<>sactiv<69>es.
<tscreen><verb>
void gtk_tooltips_disable (GtkTooltips *tooltips);
</verb></tscreen>
D<EFBFBD>sactivation d'un ensemble de bulles d'aide activ<69>es.
<tscreen><verb>
void gtk_tooltips_set_delay (GtkTooltips *tooltips,
gint delay);
</verb></tscreen> Configure le nombre de millisecondes pendant lequel
le pointeur soit se trouver sur le widget avant que la bulle d'aide
n'apparaisse. Par d<>faut, ce d<>lai est de 1000 millisecondes, soit 1
seconde.
<tscreen><verb>
void gtk_tooltips_set_tips (GtkTooltips *tooltips,
GtkWidget *widget,
gchar *tips_text);
</verb></tscreen>
Change le texte d'une bulle d'aide d<>j<EFBFBD> cr<63><72>e.
<tscreen><verb>
void gtk_tooltips_set_colors (GtkTooltips *tooltips,
GdkColor *background,
GdkColor *foreground);
</verb></tscreen>
Configure les couleurs de fond et de premier plan des bulles
d'aides. Je ne sais toujours pas comment sp<73>cifier les couleurs...
<p>
Et c'est tout concernant les fonctions associ<63>es aux bulles
d'aide. C'est plus que vous ne vouliez s<>rement en savoir :)
<sect1> Barres de progression
<p>
Les barres de progression sont utilis<69>es pour afficher la progression
d'une op<6F>ration. Elles sont tr<74>s simple <20> utiliser comme vous pourrez
le constater en <20>tudiant le code ci-dessous. Commen<65>ons d'abord par
l'appel permettant de cr<63>er une nouvelle barre.
<tscreen><verb>
GtkWidget *gtk_progress_bar_new (void);
</verb></tscreen>
Maintenant que la barre est cr<63><72>e, nous pouvons l'utiliser.
<tscreen><verb>
void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage);
</verb></tscreen>
Le premier param<61>tre est la barre de progression sur laquelle on veut
agir, et le second est le pourcentage <20> effectu<74> <20>, signifiant le
remplissage de la barres de 0 <20> 100 % (r<>el compris entre 0 et 1).
Les barres de progression sont g<>n<EFBFBD>ralement utilis<69>es avec les d<>lais
d'expiration ou autres fonctions identiques (voir la section sur <ref
id="sec_timeouts" name="Expirations, fonctions d'E/S et d'attente">)
pour donner l'illusion du multi-t<>ches. Toutes emploient la fonction
<em/gtk_progress_bar_update/ de la m<>me fa<66>on.
Voici un exemple de barre de progression mise <20> jour par des
expirations. Ce code montre aussi comment r<>initialiser une barre.
<tscreen><verb>
#include <gtk/gtk.h>
static int ptimer = 0;
int pstat = TRUE;
/* Cette fonction incr<63>mente et met <20> jour la barre de progression,
* elle la r<>initialise si pstat vaut FALSE */
gint progress (gpointer data)
{
gfloat pvalue;
/* r<>cup<75>ration 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 r<>initialisation 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 (&amp;argc, &amp;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);
/* Cr<43>e 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 d<>lai d'expiration pour g<>rer automatiquement la
* mise <20> jour de la barre */
ptimer = gtk_timeout_add (100, progress, pbar);
/* Ce bouton indique <20> la barre qu'elle doit se r<>initialiser */
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;
}
</verb></tscreen>
Dans ce petit programme, il y a quatre parties concernant le
fonctionnement g<>n<EFBFBD>ral des barres de progression, nous les <20>tudierons
dans l'ordre de leurs appels.
<tscreen><verb>
pbar = gtk_progress_bar_new ();
</verb></tscreen>
Cet appel cr<63>e une nouvelle barre, nomm<6D>e <em/pbar/.
<tscreen><verb>
ptimer = gtk_timeout_add (100, progress, pbar);
</verb></tscreen>
Cet appel utilise des d<>lais d'expiration pour permettre un intervalle
de temps constant. ces d<>lais ne sont pas n<>cessaires <20> l'utilisation
des barres de progression.
<tscreen><verb>
pvalue = GTK_PROGRESS_BAR (data)->percentage;
</verb></tscreen>
Ce code assigne <20> <em/pvalue/ la valeur du pourcentage de la barre.
<tscreen><verb>
gtk_progress_bar_update (GTK_PROGRESS_BAR (data), pvalue);
</verb></tscreen>
Finalement, ce code met <20> jour la barre avec la valeur de <em/pvalue/.
Et c'est tout ce qu'il y a <20> savoir sur les barres de
progression. Amusez-vous bien.
<sect1> Bo<42>tes de dialogue
<p>
Les widgets bo<62>tes de dialogue sont tr<74>s simples&nbsp;: ce sont
simplement des fen<65>tres avec plusieurs choses d<>j<EFBFBD> plac<61>es dedans. La
structure d'une bo<62>te de dialogue est&nbsp;:
<tscreen><verb>
struct GtkDialog
{
GtkWindow window;
GtkWidget *vbox;
GtkWidget *action_area;
};
</verb></tscreen>
Comme vous le voyez, cela cr<63>e simplement une fen<65>tre et la place dans
une vbox suivie d'un s<>parateur et d'une hbox pour la <20> zone d'action <20>.
Le widget bo<62>te de dialogue peut servir <20> produire des messages pour
l'utilisateur ainsi qu'<27> d'autres t<>ches. Il est vraiment rudimentaire
et il n'y a qu'une seule fonction pour les bo<62>tes de dialogue&nbsp;:
<tscreen><verb>
GtkWidget* gtk_dialog_new (void);
</verb></tscreen>
Ainsi, pour cr<63>er un nouveau dialogue, on utilise&nbsp;:
<tscreen><verb>
GtkWidget window;
window = gtk_dialog_new ();
</verb></tscreen>
Ceci cr<63>era la bo<62>te de dialogue et c'est maintenant <20> vous de
l'utiliser. Vous pouvez, par exemple, placer un bouton dans la zone
d'action en faisant quelque chose comme&nbsp;:
<tscreen><verb>
button = ...
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button,
TRUE, TRUE, 0);
gtk_widget_show (button);
</verb></tscreen>
Et vous pouvez aussi ajouter un label <20> la zone de la vboxb&nbsp;:
<tscreen><verb>
label = gtk_label_new ("Les bo<62>tes de dialogues sont pratiques");
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), label, TRUE,
TRUE, 0);
gtk_widget_show (label);
</verb></tscreen>
Comme exemple d'utilisation d'une bo<62>te de dialogue, vous pourriez
mettre deux boutons dans la zone d'action (un bouton <20> Annuler <20> et un
bouton <20> Ok <20>) et un label dans la zone de la vbox posant une question
<EFBFBD> l'utilisateur ou signalant une erreur, etc. Vous pouvez alors
attacher un signal diff<66>rent <20> chacun des boutons et r<>aliser
l'op<6F>ration que l'utilisateur a choisie.
<sect1> Pixmaps
<p>
Les pixmaps sont des structures de donn<6E>es contenant des
images. Celles-ci peuvent <20>tre utilis<69>es <20> diff<66>rents endroits, mais
le plus souvent comme ic<69>nes 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 <em/GdkPixmap/ en utilisant les fonctions de la couche
GDK. Les pixmaps peuvent soit <20>tre cr<63><72>s <20> partir de donn<6E>es en
memoire, ou <20> partir de donn<6E>es lues dans un fichier. Nous utiliserons
chacun des appels pour cr<63>er un pixmap.
<tscreen><verb>
GdkPixmap *gdk_bitmap_create_from_data( GdkWindow *window,
gchar *data,
gint width,
gint height );
</verb></tscreen>
<p>
Cette fonction sert <20> cr<63>er un pixmap mono-plan (2 couleurs) <20> partir
de donn<6E>es en m<>moire. Chaque bit de la donn<6E>e <em/data/. <em/width/
et <em/height/ sont exprim<69>s en pixels. Le pointeur vers un
<em/GdkWindow/ pointe sur la fen<65>tre courante car les ressources d'un
pixmap n'ont de signification que dans le contexte de l'<27>cran o<> il
doit s'afficher.
<tscreen><verb>
GdkPixmap* gdk_pixmap_create_from_data( GdkWindow *window,
gchar *data,
gint width,
gint height,
gint depth,
GdkColor *fg,
GdkColor *bg );
</verb></tscreen>
Cette fonction est utilis<69>e pour cr<63>er un pixmap d'une profondeur
donn<EFBFBD>e (nombre de couleurs) <20> partir de la donn<6E>e sp<73>cifi<66>e pas
<em/data/. <em/fg/ et <em/bg/ sont les couleurs <20> utiliser pour
l'avant et l'arri<72>re-plan.
<tscreen><verb>
GdkPixmap* gdk_pixmap_create_from_xpm( GdkWindow *window,
GdkBitmap **mask,
GdkColor *transparent_color,
const gchar *filename );
</verb></tscreen>
Le format XPM est une repr<70>sentation des pixmaps reconnue par le syst<73>me X Window. Il est largement utilis<69> et de nombreux utilitaires pour cr<63>er des fichiers d'images <20> ce format sont disponibles. Le fichier <em/filename/ doit contenir une image dans ce format qui sera charg<72>e dans la structure pixmap. Le masque <em/mask/ indique quels sont les bits opaques du pixmap. Tous les autres bits sont coloris<69>s en utilisant la couleur sp<73>cifi<66>e par <em/transparent_color/. Un exemple d'utilisation est pr<70>sent<6E> ci-dessous.
<tscreen><verb>
GdkPixmap* gdk_pixmap_create_from_xpm_d (GdkWindow *window,
GdkBitmap **mask,
GdkColor *transparent_color,
gchar **data);
</verb></tscreen>
De petites images peuvent <20>tre int<6E>gr<67>es dans un programme sous la forme de
donn<EFBFBD>es <em/data/ au format XPM. Un pixmap est cr<63><72> en utilisant ces donn<6E>es au
lieu de les lire dans un fichier. Un exemple de telles donn<6E>es est&nbsp;:
<tscreen><verb>
/* 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. ",
" ......... ",
" ",
" "};
</verb></tscreen>
<tscreen><verb>
void gdk_pixmap_destroy( GdkPixmap *pixmap );
</verb></tscreen>
<p>
Lorsqu'on a utilis<69> un pixmap et que l'on en a plus besoin tout de suite, il est pr<70>f<EFBFBD>rable de lib<69>rer la ressource en utilisant un appel <20> <em/gdk_pixmap_destroy/. Les pixmaps doivent <20>tre consid<69>r<EFBFBD>es comme une ressource pr<70>cieuse.
Quand le pixmap est cr<63><72>, on peut l'afficher comme un widget GTK. On
doit cr<63>er un widget pixmap qui contiendra le pixmap GDK. Ceci est
r<EFBFBD>alis<EFBFBD> de la fa<66>on suivante&nbsp;:
<tscreen><verb>
GtkWidget* gtk_pixmap_new( GdkPixmap *pixmap,
GdkBitmap *mask );
</verb></tscreen>
<p>
Les autres fonctions pour les widgets pixmap sont&nbsp;:
<tscreen><verb>
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);
</verb></tscreen>
<p>
<em/gtk_pixmap_set/ sert <20> changer le pixmap pris en charge par le widget. <em/val/ est le pixmap cr<63><72> par le GDK.
Voici un exemple illustrant l'utilisation d'un pixmap dans un bouton&nbsp;:
<tscreen><verb>
#include <gtk/gtk.h>
/* donn<6E>es XPM d'une ic<69>ne "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 appel<65>e
* via le signal "delete_event" */
void close_application( GtkWidget *widget, GdkEvent *event, gpointer *data )
{
gtk_main_quit();
}
/* Invoqu<71>e lorsque le bouton est cliqu<71>. Affiche simplement
* un message. */
void button_clicked( GtkWidget *widget, gpointer *data )
{
printf( "bouton cliqu<71>\n" );
}
int main( int argc, char *argv[] )
{
/* GtkWidget est le type pour d<>clarer les widgets */
GtkWidget *window, *pixmapwid, *button;
GdkPixmap *pixmap;
GdkBitmap *mask;
GtkStyle *style;
/* Cr<43>e la fen<65>tre principale et attache le signal "delete_event" pour
* terminer l'application */
gtk_init( &amp;argc, &amp;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 cr<63>er le pixmap */
style = gtk_widget_get_style( window );
pixmap = gdk_pixmap_create_from_xpm_d( window->window, &amp;mask,
&amp;style->bg[GTK_STATE_NORMAL],
(gchar **)xpm_data );
/* Cr<43>ation d'un widget pixmap GTK pour contenir le pixmap GDK */
pixmapwid = gtk_pixmap_new( pixmap, mask );
gtk_widget_show( pixmapwid );
/* Cr<43>ation 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 fen<65>tre */
gtk_main ();
return 0;
}
</verb></tscreen>
Pour charger un fichier <20> partir d'un fichier XPM appel<65>
<em/icon0.xpm/ se trouvant dans le r<>pertoire courant, on aurait cr<63><72>
le pixmap ainsi&nbsp;:
<tscreen><verb>
/* Charge un pixmap <20> partir d'un fichier */
pixmap = gdk_pixmap_create_from_xpm( window->window, &amp;mask,
&amp;style->bg[GTK_STATE_NORMAL],
"./icon0.xpm" );
pixmapwid = gtk_pixmap_new( pixmap, mask );
gtk_widget_show( pixmapwid );
gtk_container_add( GTK_CONTAINER(window), pixmapwid );
</verb></tscreen>
Utilisation des formes
<p>
Un d<>savantage de l'utilisation des pixmaps est que l'objet affich<63>
est toujours rectangulaire, quelle que soit l'image. On voudrait
pouvoir cr<63>er des bureaux et des applications avec des ic<69>nes ayant
des formes plus naturelles. Par exemple, pour une interface de jeu, on
aimerait avoir des boutons ronds <20> pousser. Pour faire cela, on doit
utiliser des fen<65>tres avec des formes.
Une fen<65>tre avec forme est simplement un pixmap dont les pixels du
fond sont transparents. Ainsi, lorsque l'image d'arri<72>re-plan est
multicolore, on ne la cache pas avec le bord de notre ic<69>ne. L'exemple
suivant affiche une image de brouette sur le bureau.
<tscreen><verb>
#include <gtk/gtk.h>
/* 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",
"&amp; 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&amp; ",
" *=-;#::o+ ",
" >,<12#:34 ",
" 45671#:X3 ",
" +89<02qwo ",
"e* >,67;ro ",
"ty> 459@>+&amp;&amp; ",
"$2u+ ><ipas8* ",
"%$;=* *3:.Xa.dfg> ",
"Oh$;ya *3d.a8j,Xe.d3g8+ ",
" Oh$;ka *3d$a8lz,,xxc:.e3g54 ",
" Oh$;kO *pd$%svbzz,sxxxxfX..&amp;wn> ",
" Oh$@mO *3dthwlsslszjzxxxxxxx3:td8M4 ",
" Oh$@g&amp; *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B* ",
" Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5&amp; ",
" Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM* ",
" OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ",
" 2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ",
" :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo",
" +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g",
" *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&amp;en",
" p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>",
" OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B ",
" 3206Bwxxszx%et.eaAp77m77mmmf3&amp;eeeg* ",
" @26MvzxNzvlbwfpdettttttttttt.c,n&amp; ",
" *;16=lsNwwNwgsvslbwwvccc3pcfu<o ",
" p;<69BvwwsszslllbBlllllllu<5+ ",
" OS0y6FBlvvvzvzss,u=Blllj=54 ",
" c1-699Blvlllllu7k96MMMg4 ",
" *10y8n6FjvllllB<166668 ",
" S-kg+>666<M<996-y6n<8* ",
" p71=4 m69996kD8Z-66698&amp;&amp; ",
" &amp;i0ycm6n4 ogk17,0<6666g ",
" N-k-<> >=01-kuu666> ",
" ,6ky&amp; &amp;46-10ul,66, ",
" Ou0<> o66y<ulw<66&amp; ",
" *kk5 >66By7=xu664 ",
" <<M4 466lj<Mxu66o ",
" *>> +66uv,zN666* ",
" 566,xxj669 ",
" 4666FF666> ",
" >966666M ",
" oM6668+ ",
" *4 ",
" ",
" "};
/* Termine l'application lorsqu'elle est appel<65>e
* (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;
/* cr<63>e la fen<65>tre principale et attache le signal "delete_event"
* pour terminer l'application. On notera que la fen<65>tre principale
* n'a pas de barre de titre car nous la faisons surgir. */
gtk_init (&amp;argc, &amp;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);
/* Cr<43>ation 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, &amp;mask,
&amp;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-m<>me */
gtk_widget_shape_combine_mask( window, mask, 0, 0 );
/* Affichage de la fen<65>tre */
gtk_widget_set_uposition( window, 20, 400 );
gtk_widget_show( window );
gtk_main ();
return 0;
}
</verb></tscreen>
<p>
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 <20>
un clic souris qui provoquera l'arr<72>t de l'application.
<tscreen><verb>
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 );
</verb></tscreen>
<sect> Widgets containers
<sect1> Bloc-notes
<p>
Le widget bloc-notes est un ensemble de <20> pages <20> qui se
chevauchent. Chaque page contient des informations
diff<EFBFBD>rentes. R<>cemment, 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
s<EFBFBD>paration de leur affichage.
Le premier appel de fonction que l'on doit conna<6E>tre est, vous
l'aviez devin<69>, celui qui cr<63>e un widget bloc-notes.
<tscreen><verb>
GtkWidget* gtk_notebook_new (void);
</verb></tscreen>
Lorsque le bloc-notes a <20>t<EFBFBD> cr<63><72>, il y a 12 fonctions permettant de
travailler sur les blocs-notes. <20>tudions-les s<>par<61>ment.
La premi<6D>re permet de positionner les indicateurs de pages. Ceux-ci
(d<>sign<67>s par le mot <20> tab <20> (signet)), peuvent se trouver en haut, en
bas, <20> gauche ou <20> droite des pages.
<tscreen><verb>
void gtk_notebook_set_tab_pos (GtkNotebook *notebook, GtkPositionType pos);
</verb></tscreen>
<em/GtkPositionType/ peut prendre les valeurs suivantes qu'il n'est
pas n<>cessaire d'expliquer&nbsp;:
<itemize>
<item> GTK_POS_LEFT
<item> GTK_POS_RIGHT
<item> GTK_POS_TOP
<item> GTK_POS_BOTTOM
</itemize>
GTK_POS_TOP est la valeur par d<>faut.
La fonction suivante permet d'ajouter des pages <20> un bloc-notes. Il y
a trois fa<66>ons d'ajouter des pages. Regardons les deux premi<6D>res qui
sont tr<74>s semblables.
<tscreen><verb>
void gtk_notebook_append_page (GtkNotebook *notebook, GtkWidget *child,
GtkWidget *tab_label);
void gtk_notebook_prepend_page (GtkNotebook *notebook, GtkWidget *child,
GtkWidget *tab_label);
</verb></tscreen>
Ces fonctions ajoutent des pages au bloc-notes<em/*notebook/ en les
ins<EFBFBD>rant <20> la fin (<em/append/) ou au d<>but
(<em/prepend/). <em/*child/ est le widget qui est plac<61> dans la page
du bloc-notes, et <em/*tab_label/ est le label de la page qui est
ajout<EFBFBD>e.
La troisi<73>me fonction ajoutant une page <20> un bloc-notes conserve
toutes les propri<72>t<EFBFBD>s des deux pr<70>c<EFBFBD>dentes, mais elle nous permet en
plus de sp<73>cifier la position o<> l'on d<>sire ins<6E>rer cette page.
<tscreen><verb>
void gtk_notebook_insert_page (GtkNotebook *notebook, GtkWidget *child,
GtkWidget *tab_label, gint position);
</verb></tscreen>
Les param<61>tres sont les m<>mes que <em/_append_/ et <em/_prepend_/ sauf
qu'il y en a un de plus&nbsp;: <em/position/. Celui-ci sert <20>
sp<EFBFBD>cifier l'endroit o<> cette page sera ins<6E>r<EFBFBD>e.
Maintenant que nous savons ins<6E>rer une page, voyons comment en supprimer une.
<tscreen><verb>
void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num);
</verb></tscreen>
Cette fonction <20>te la page sp<73>cifi<66>e par <em/page_num/ du widget
<em/*notebook/.
Pour conna<6E>tre la page courante d'un bloc-notes, on dispose de la
fonction&nbsp;:
<tscreen><verb>
gint gtk_notebook_current_page (GtkNotebook *notebook);
</verb></tscreen>
Les deux fonctions suivantes permettent de passer <20> la page suivante
ou pr<70>c<EFBFBD>dente d'un bloc-notes. Il suffit de faire l'appel de la
fonction ad<61>quate avec le widget sur lequel on veut
op<EFBFBD>rer. Remarque&nbsp;: lorsqu'on est sur la derni<6E>re page du
bloc-notes et que l'on appelle <em/gtk_notebook_next_page/, on revient
<EFBFBD> la premi<6D>re page. De m<>me, si l'on est sur la premi<6D>re page et que
l'onappelle <em/gtk_notebook_prev_page/, on se retrouve sur sa
derni<EFBFBD>re page.
<tscreen><verb>
void gtk_notebook_next_page (GtkNoteBook *notebook);
void gtk_notebook_prev_page (GtkNoteBook *notebook);
</verb></tscreen>
La fonction qui suit permet de choisir la page <20> active <20>. Si vous
voulez ouvrir le bloc-notes <20> la page 5, par exemple, vous utiliserez
cette fonction. Sans elle, le bloc-notes s'ouvre sur sa premi<6D>re page
par d<>faut.
<tscreen><verb>
void gtk_notebook_set_page (GtkNotebook *notebook, gint page_num);
</verb></tscreen>
Les deux fonctions suivantes ajoutent ou <20>tent les indicateurs de page
et le contour du bloc-notes, respectivement.
<tscreen><verb>
void gtk_notebook_set_show_tabs (GtkNotebook *notebook, gint show_tabs);
void gtk_notebook_set_show_border (GtkNotebook *notebook, gint show_border);
</verb></tscreen>
<em/show_tabs/ et <em/show_border/ peuvent valoir TRUE ou FALSE (0 ou 1).
Voyons maintenant un exemple, il est tir<69> du code de <em/testgtk.c/ de
la distribution GTK et montre l'utilisation des 13 fonctions. Ce petit
programme cr<63>e une fen<65>tre contenant un bloc-notes et six boutons. Le
bloc-notes contient 11 pages, ajout<75>es par trois moyens
diff<EFBFBD>rents&nbsp;: <20> la fin, au milieu et au d<>but. Les boutons
permettent de faire tourner les indicateurs de page, ajouter/<2F>ter les
indicateurs et le contour, <20>ter une page, passer <20> la page suivante et
pr<EFBFBD>c<EFBFBD>dente, et sortir du programme.
<tscreen><verb>
#include <gtk/gtk.h>
/* 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 rafra<72>chir le widget --
* ce qui force le widget <20> 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 (&amp;argc, &amp;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);
/* Cr<43>ation 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 <20> 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 <20> un endroit pr<70>cis. */
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 d<>but 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 d<>part (page 4) */
gtk_notebook_set_page (GTK_NOTEBOOK(notebook), 3);
/* Cr<43>ation 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 pr<70>c<EFBFBD>dente");
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;
}
</verb></tscreen>
<p>
En esp<73>rant que ceci vous aide <20> cr<63>er des blocs-notes pour vos
applications GTK.
<sect1> Fen<65>tres avec barres de d<>filement
<p>
Les fen<65>tres avec barres de d<>filement servent <20> cr<63>er des zones
d<EFBFBD>filantes <20> l'int<6E>rieur d'une vraie fen<65>tre. On peut ins<6E>rer
n'importe quel widget dans ces fen<65>tres, ils seront accessibles quelle
que soit leur taille en utilisant les barres de d<>filement.
La fonction suivante sert <20> cr<63>er une fen<65>tre avec barre de
d<EFBFBD>filement&nbsp;:
<tscreen><verb>
GtkWidget* gtk_scrolled_window_new (GtkAdjustment *hadjustment,
GtkAdjustment *vadjustment);
</verb></tscreen>
<p>
Le premier param<61>tre est l'ajustement horizontal, et le second
l'ajustement vertical. Ils sont presque toujours positionn<6E>s <20> NULL.
<tscreen><verb>
void gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window,
GtkPolicyType hscrollbar_policy,
GtkPolicyType vscrollbar_policy);
</verb></tscreen>
Cela permet de configurer le fonctionnement des barres de
d<EFBFBD>filement. Le premier param<61>tre est la fen<65>tre <20> d<>filement que l'on
veut modifier, le second configure le fonctionnement de la barre
horizontale et le troisi<73>me celui de la barre verticale.
Ce fonctionnement peut <20>tre GTK_POLICY AUTOMATIC ou GTK_POLICY_ALWAYS.
GTK_POLICY_AUTOMATIC d<>cidera automatiquement de votre besoin en
barres de d<>filement, alors que GTK_POLICY_ALWAYS mettra toujours
celles-ci.
Voici un exemple simple qui place 100 boutons commutateurs dans une
fen<EFBFBD>tre <20> d<>filement. Je n'ai comment<6E> que les parties qui sont
nouvelles pour vous.
<tscreen><verb>
#include <gtk/gtk.h>
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 (&amp;argc, &amp;argv);
/* Cr<43>ation d'une bo<62>te de dialogue pour y placer la fen<65>tre <20> d<>filement.
* Une bo<62>te de dialogue est une fen<65>tre comme les autres sauf qu'elle contient
* une vbox et un s<>parateur horizontal. Ce n'est qu'un raccourci pour cr<63>er 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);
/* Cr<43>ation d'une fen<65>tre <20> d<>filement. */
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 d<>cide automatiquement s'il faut ou non des barres,
* GTK_POLICY_ALWAYS met toujours des barres
* Le premier param<61>tre correspond <20> la barre horizontale,
* le second <20> la barre verticale. */
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
/* Cr<43>ation d'une bo<62>te de dialogue */
gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window,
TRUE, TRUE, 0);
gtk_widget_show (scrolled_window);
/* Cr<43>ation 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 fen<65>tre <20> d<>filement */
gtk_container_add (GTK_CONTAINER (scrolled_window), table);
gtk_widget_show (table);
/* Cr<43>e 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 <20> Fermer <20> en bas de la bo<62>te 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 <20> bouton par d<>faut <20>. */
GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->action_area), button, TRUE, TRUE, 0);
/* R<>cup<75>re le bouton par d<>faut. Le fait de presser la touche <20> Entr<74>e <20>
* activera le bouton. */
gtk_widget_grab_default (button);
gtk_widget_show (button);
gtk_widget_show (window);
gtk_main();
return(0);
}
</verb></tscreen>
<p>
Essayez de changer la taille de la fen<65>tre et faites attention aux
r<EFBFBD>actions des barres de d<>filement. On peut aussi utiliser la fonction
<em/gtk_widget_set_usize()/ pour configurer la taille par d<>faut de la
fen<EFBFBD>tre et des autres widgets.
<sect>Widgets listes
<p>
Le widget <em/GtkList/ sert de container vertical pour des widgets <em/GtkListItem/.
Un widget <em/GtkList/ poss<73>de sa propre fen<65>tre pour recevoir les
<EFBFBD>v<EFBFBD>nements et sa propre couleur de fond qui est habituellement
blanche. Comme il est directement d<>riv<69> de <em/GtkContainer/, il peut
<EFBFBD>tre trait<69> comme tel en utilisant la macro GTK_CONTAINER(List)&nbsp;:
voir le widget <em/GtkContainer/ pour en savoir plus.
On doit d'abord conna<6E>tre l'utilisation des <em/GList/ et des
fonctions <em/g_list_*()/ qui leur sont li<6C>es pour pouvoir utiliser
pleinement le widget <em/GtkList/.
Un champ de la structure d'un widget <em/GtkList/ nous int<6E>resse particuli<6C>rement&nbsp;:
<tscreen><verb>
struct _GtkList
{
...
GList *selection;
guint selection_mode;
...
};
</verb></tscreen>
Le champ <em/selection/ d'un <em/GtkList/ pointe sur une liste cha<68>n<EFBFBD>e
de tous les items qui sont s<>lectionn<6E>s, ou vaut NULL si aucune
s<EFBFBD>lection n'est faite. Ainsi, pour conna<6E>tre la s<>lection courante, on
consulte le champ <em/GTK_LIST()->selection/ mais on ne doit pas le
modifier car ses champs internes sont g<>r<EFBFBD>s par les fonctions
<em/gtk_list_*()/.
Le champ <em/selection_mode/ d<>termine les options de s<>lection d'un
<em/GtkList/ et donc le contenu du champ du
<em/GTK_LIST()->selection/&nbsp;:
<em/selection_mode/ peut avoir l'une des valeurs suivantes&nbsp;:
<itemize>
<item> GTK_SELECTION_SINGLE - <em/selection/ vaut NULL ou contient un
pointeur vers un seul item s<>lectionn<6E>.
<item> GTK_SELECTION_BROWSE - <em/selection/ 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.
<item> GTK_SELECTION_MULTIPLE - <em/selection/ vaut NULL si aucun item
n'est s<>lectionn<6E> ou pointe vers le premier item s<>lectionn<6E>. Ce
dernier point <20> son tour vers le second item, etc.
<item> GTK_SELECTION_EXTENDED - <em/selection/ vaut toujours NULL.
</itemize>
<p>
La valeur par d<>faut est GTK_SELECTION_MULTIPLE.
<sect1>Signaux
<p>
<tscreen><verb>
void GtkList::selection_changed (GtkList *LIST)
</verb></tscreen>
Ce signal sera invoqu<71> <20> chaque fois que le champ s<>lection d'un
GtkList a chang<6E>. Cela arrive lorsqu'un fils d'un GtkList a <20>t<EFBFBD>
s<EFBFBD>lectionn<EFBFBD> ou d<>s<EFBFBD>lectionn<6E>.
<tscreen><verb>
void GtkList::select_child (GtkList *LIST, GtkWidget *CHILD)
</verb></tscreen>
Ce signal est invoqu<71> lorsqu'un fils du GtkList va <20>tre
s<EFBFBD>lectionn<EFBFBD>. Ceci arrive principalement lors d'appels <20>
<em/gtk_list_select_item(), gtk_list_select_child()/ et lors d'appuis
de boutons. Quelques fois, il est indirectement d<>clench<63> lorsque des
fils sont ajout<75>s ou supprim<69>s du GtkList.
<tscreen><verb>
void GtkList::unselect_child (GtkList *LIST, GtkWidget *CHILD)
</verb></tscreen>
Ce signal est invoqu<71> lorsqu'un fils du GtkList va <20>tre
d<EFBFBD>s<EFBFBD>lectionn<EFBFBD>. Cela arrive principalement lors d'appels <20>
<em/gtk_list_unselect_item(), gtk_list_unselect_child()/, et lors
d'appuis de boutons. Quelques fois, il est indirectement d<>clench<63>
lorsque des fils sont ajout<75>s ou supprim<69>s du GtkList.
<sect1>Fonctions
<p>
<tscreen><verb>
guint gtk_list_get_type (void)
</verb></tscreen>
Retourne l'identificateur de type <20> GtkList <20>.
<tscreen><verb>
GtkWidget* gtk_list_new (void)
</verb></tscreen>
Cr<EFBFBD>e un nouvel objet <20> GtkList <20>. Le nouveau widget est retourn<72> sous
la forme d'un pointeur vers un objet <20> GtkWidget <20>. NULL est retourn<72>
en cas d'erreur.
<tscreen><verb>
void gtk_list_insert_items (GtkList *LIST, GList *ITEMS, gint POSITION)
</verb></tscreen>
Ins<EFBFBD>re des items dans <em/LIST/, <20> partir de <em/POSITION/.
<em/ITEMS/ est une liste doublement cha<68>n<EFBFBD>e o<> chaque noeud doit
pointer vers un nouveau <em/GtkListItem/. Les noeuds <em/GList/ de
<em/ITEMS/ sont pris en charge par <em/LIST/.
<tscreen><verb>
void gtk_list_append_items (GtkList *LIST, GList *ITEMS)
</verb></tscreen>
Ins<EFBFBD>re des items <20> la fin de <em/LIST/ selon le m<>me principe que
<em/gtk_list_insert_items/. Les noeuds <em/GList/ de <em/ITEMS/ sont
pris en charge par <em/LIST/.
<tscreen><verb>
void gtk_list_prepend_items (GtkList *LIST, GList *ITEMS)
</verb></tscreen>
Ins<EFBFBD>re des items au d<>but de <em/LIST/ selon le m<>me principe que
<em/gtk_list_insert_items/. Les noeuds <em/GList/ de <em/ITEMS/ sont
pris en charge par <em/LIST/.
<tscreen><verb>
void gtk_list_remove_items (GtkList *LIST, GList *ITEMS)
</verb></tscreen>
<EFBFBD>te des items de <em/LIST/. <em/ITEMS/ est une liste doublement
cha<EFBFBD>n<EFBFBD>e dont chaque noeud pointe vers un fils direct de <em/LIST/. Il
est de la responsabilit<69> de l'appelant de faire un appel <20>
<em/g_list_free(ITEMS)/ apr<70>s cela. L'appelant doit aussi d<>truire
lui-m<>me les items.
<tscreen><verb>
void gtk_list_clear_items (GtkList *LIST, gint START, gint END)
</verb></tscreen>
<EFBFBD>te et d<>truit des items de <em/LIST/. Un widget est concern<72> si sa
position courante dans <em/LIST/ est dans l'intervalle sp<73>cifi<66> par
<em/START/ et <em/END/.
<tscreen><verb>
void gtk_list_select_item (GtkList *LIST, gint ITEM)
</verb></tscreen>
Invoque le signal <em/GtkList::select_child/ pour un item sp<73>cifi<66> par
sa position courante dans <em/LIST/.
<tscreen><verb>
void gtk_list_unselect_item (GtkList *LIST, gint ITEM)
</verb></tscreen>
Invoque le signal <em/GtkList::unselect_child/ pour un item sp<73>cifi<66> par
sa position courante dans <em/LIST/.
<tscreen><verb>
void gtk_list_select_child (GtkList *LIST, GtkWidget *CHILD)
</verb></tscreen>
Invoque le signal <em/GtkList::select_child/ pour le fils <em/CHILD/ sp<73>cifi<66>.
<tscreen><verb>
void gtk_list_unselect_child (GtkList *LIST, GtkWidget *CHILD)
</verb></tscreen>
Invoque le signal <em/GtkList::unselect_child/ pour le fils <em/CHILD/ sp<73>cifi<66>.
<tscreen><verb>
gint gtk_list_child_position (GtkList *LIST, GtkWidget *CHILD)
</verb></tscreen>
Retourne la position de <em/CHILD/ dans <em/LIST/. Retourne -1 en cas d'erreur.
<tscreen><verb>
void gtk_list_set_selection_mode (GtkList *LIST, GtkSelectionMode MODE)
</verb></tscreen>
Configure <em/LIST/ dans le mode de s<>lection <em/MODE/ qui peut <20>tre
GTK_SELECTION_SINGLE, GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE ou
GTK_SELECTION_EXTENDED.
<tscreen><verb>
GtkList* GTK_LIST (gpointer OBJ)
</verb></tscreen>
Convertit un pointeur g<>n<EFBFBD>rique en <20> <\em GtkList*\ <20>. Voir
<em/Standard Macros::/, pour plus d'informations.
<tscreen><verb>
GtkListClass* GTK_LIST_CLASS (gpointer CLASS)
</verb></tscreen>
Convertit un pointeur g<>n<EFBFBD>rique en <20> GtkListClass* <20>. Voir
<em/Standard Macros::/, pour plus d'informations.
<tscreen><verb>
gint GTK_IS_LIST (gpointer OBJ)
</verb></tscreen>
D<EFBFBD>termine si un pointeur g<>n<EFBFBD>rique r<>f<EFBFBD>rence un objet <20> GtkList <20>. Voir
<em/Standard Macros::/, pour plus d'informations.
<sect1>Exemple
<p>
Voici un programme affichant les changements de s<>lection dans une
<em/GtkList/ et permettant d'<27> emprisonner <20> des items en les
s<EFBFBD>lectionnant avec le bouton droit de la souris.
<tscreen><verb>
/* Compilez ce programme avec :
* $ gcc -L/usr/X11R6/lib/ -I/usr/local/include/ -lgtk -lgdk -lglib -lX11 -lm -Wall main.c
*/
#include <gtk/gtk.h>
#include <stdio.h>
/* Cha<68>ne pour stocker les donn<6E>es 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(&amp;argc, &amp;argv);
/* Cr<43>ation d'une fen<65>tre pour placer tous les widgets.
* Connexion de gtk_main_quit() <20> l'<27>v<EFBFBD>nement "destroy" de
* la fen<65>tre afin de prendre en charge les <20>v<EFBFBD>nements <20> fermeture d'une
* fen<65>tre <20> du gestionnaire de fen<65>tre. */
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);
/* <20> l'int<6E>rieur de la fen<65>tre, on a besoin d'une bo<62>te 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);
/* Fen<65>tre <20> d<>filement pour placer le widget GtkList <20> l'int<6E>rieur. */
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);
/* Cr<43>ation du widget GtkList
* Connexion du gestionnaire de signal sigh_print_selection() au signal
* "selection_changed" du GtkList pour afficher les items s<>lectionn<6E>s
* <20> chaque fois que la s<>lection 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);
/* Cr<43>ation d'une <20> Prison <20> 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
* <20> mise au arr<72>ts <20> des items du GtkList. */
gtk_signal_connect(GTK_OBJECT(gtklist),
"button_release_event",
GTK_SIGNAL_FUNC(sigh_button_event),
frame);
/* Cr<43>ation d'un s<>parateur. */
separator=gtk_hseparator_new();
gtk_container_add(GTK_CONTAINER(vbox), separator);
gtk_widget_show(separator);
/* Cr<43>ation d'un bouton et connexion de son signal "clicked" <20> la
* destruction de la fen<65>tre. */
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));
/* Cr<43>ation 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 <20> 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), &amp;string);
gtk_object_set_data(GTK_OBJECT(list_item),
list_item_data_key,
string);
}
/* Cr<43>ation de 5 autres labels. Cette fois-ci, on utilise
* gtk_list_item_new_with_label(). On ne peut interroger la cha<68>ne
* 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 m<>me
* cha<68>ne de texte pour l'ajouter au items que l'on place dans une liste
* doublement cha<68>n<EFBFBD>e (GList). On les ajoute alors par un simple appel <20>
* gtk_list_append_items().
* Comme on utilise g_list_prepend() pour mettre les items dans la liste
* doublement cha<68>n<EFBFBD>e, leur ordre sera d<>croissant (au lieu d'<27>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 int<6E>gr<67>");
}
gtk_list_append_items(GTK_LIST(gtklist), dlist);
/* Enfin, on veut voir la fen<65>tre... */
gtk_widget_show(window);
/* Lancement de la boucle principale de gtk */
gtk_main();
/* On arrive ici apr<70>s que gtk_main_quit() ait <20>t<EFBFBD> appel<65>e lorsque
* la fen<65>tre principale a <20>t<EFBFBD> d<>truite. */
}
/* Gestionnaire de signal connect<63> aux <20>v<EFBFBD>nements boutons presser/rel<65>cher
* du GtkList. */
void
sigh_button_event (GtkWidget *gtklist,
GdkEventButton *event,
GtkWidget *frame)
{
/* On ne fait quelque chose que si le troisi<73>me bouton (celui de droite) a <20>t<EFBFBD>
* rel<65>ch<63>. */
if (event->type==GDK_BUTTON_RELEASE &amp;&amp;
event->button==3) {
GList *dlist, *free_list;
GtkWidget *new_prisoner;
/* On recherche l'item s<>lectionn<6E> <20> ce moment pr<70>cis.
* 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 d<>j<EFBFBD> prisonniers et on les
* remet dans la liste.
* Il ne faut pas oublier de lib<69>rer la liste doublement
* cha<68>n<EFBFBD>e 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'<27>te du GtkList et on le place
* dans le cadre <20> Prison <20>. On doit d<>s<EFBFBD>lectionner 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<65> lorsque le GtkList
* <20>met le signal "selection_changed". */
void
sigh_print_selection (GtkWidget *gtklist,
gpointer func_data)
{
GList *dlist;
/* Recherche dans la liste doublement cha<68>n<EFBFBD>e des items s<>lectionn<6E>s
* du GtkList, <20> faire en lecture seulement ! */
dlist=GTK_LIST(gtklist)->selection;
/* S'il n'y a pas d'items s<>lectionn<6E>, il n'y a rien d'autre <20> faire que
* de le dire <20> l'utilisateur. */
if (!dlist) {
g_print("S<>lection nettoy<6F>e\n");
return;
}
/* Ok, on a une s<>lection et on l'affiche. */
g_print("La s<>lection est ");
/* On r<>cup<75>re l'item dans la liste doublement cha<68>n<EFBFBD>e
* puis on interroge la donn<6E>e associ<63>e 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");
}
</verb></tscreen>
<sect1>Widget item de liste
<p>
Le widget <em/GtkListItem/ sert de container pour contenir un fils,
lui fournissant des fonctions de s<>lection/d<>s<EFBFBD>selection exactement
comme le widget GtkList les demande pour ses fils.
Un <em/GtkListItem/ a sa propre fen<65>tre pour recevoir les <20>v<EFBFBD>nements et a
sa propre couleur de fond, habituellement blanche.
Comme il est directement d<>riv<69> d'un <em/GtkItem/, il peut <20>tre trait<69>
comme tel en utilisant la macro GTK_ITEM(ListItem), reportez-vous <20> la
section sur le widget GtkItem pour plus de d<>tail sur celui-ci.
Habituellement, un <em/GtkListItem/ contient juste un label pour
identifier, par exemple, un nom de fichier dans un <em/GtkList/ -- la
fonction appropri<72>e <em/gtk_list_item_new_with_label()/ est donc
fournie. Le m<>me effet peut <20>tre obtenu en cr<63>ant un <em/GtkLabel/ <20>
part, en configurant son alignement avec <em/xalign/=0 et
<em/yalign/=0.5 suivi d'un ajout ult<6C>rieur au <em/GtkListItem/.
Tout comme on n'est pas forc<72> d'ajouter un <em/GtkLabel/ <20> un
<em/GtkListItem/, on peut aussi ajouter un <em/GtkVBox/ ou un
<em/GtkArrow/ etc. <20> un <em/GtkListItem/.
<sect1>Signaux
<p>
Un <em/GtkListItem/ ne cr<63>e pas de nouveaux signaux par lui-m<>me, mais
h<EFBFBD>rite de ceux d'un <em/GtkItem/. Voir <em/GtkItem::/, pour plus
d'informations.
<sect1>Fonctions
<p>
<tscreen><verb>
guint gtk_list_item_get_type (void)
</verb></tscreen>
Retourne l'identificateur du type <20> GtkListItem <20>.
<tscreen><verb>
GtkWidget* gtk_list_item_new (void)
</verb></tscreen>
Cr<EFBFBD>ation d'un objet <em/GtkListItem/. Le nouveau widget est retourn<72>
sous la forme d'un pointeur vers un objet <em/GtkWidget/. NULL est
retourn<EFBFBD> en cas d'erreur.
<tscreen><verb>
GtkWidget* gtk_list_item_new_with_label (gchar *LABEL)
</verb></tscreen>
Cr<EFBFBD>ation d'un objet <em/GtkListItem/ ayant un simple <em/GtkLabel/
comme seul fils. Le nouveau widget est retourn<72> sous la forme d'un
pointeur vers un objet <em/GtkWidget/. NULL est retourn<72> en cas
d'erreur.
<tscreen><verb>
void gtk_list_item_select (GtkListItem *LIST_ITEM)
</verb></tscreen>
Cette fonction est surtout un emballage de <em/gtk_item_select
(GTK_ITEM (list_item))/ qui <20>mettra le signal <em/GtkItem::select/.
Voir <em/GtkItem::/, pour plus d'informations.
<tscreen><verb>
void gtk_list_item_deselect (GtkListItem *LIST_ITEM)
</verb></tscreen>
Cette fonction est surtout un emballage de <em/gtk_item_deselect
(GTK_ITEM (list_item))/ qui <20>mettra le signal
<em/GtkItem::deselect/. Voir <em/GtkItem::/, pour plus d'informations.
<tscreen><verb>
GtkListItem* GTK_LIST_ITEM (gpointer OBJ)
</verb></tscreen>
Convertit un pointeur g<>n<EFBFBD>rique en <em/GtkListItem*/. Voir
<em/Standard Macros::/ pour plus d'informations.
<tscreen><verb>
GtkListItemClass* GTK_LIST_ITEM_CLASS (gpointer CLASS)
</verb></tscreen>
Convertit un pointeur g<>n<EFBFBD>rique en <em/GtkListItemClass*/. Voir
<em/Standard Macros::/ pour plus d'informations.
<tscreen><verb>
gint GTK_IS_LIST_ITEM (gpointer OBJ)
</verb></tscreen>
D<EFBFBD>termine si un pointeur g<>n<EFBFBD>rique se r<>f<EFBFBD>re <20> un objet
<em/GtkListItem/. Voir <em/Standard Macros::/ pour plus
d'informations.
<sect1>Exemple
<p>
L'exemple des GtkList couvre aussi l'utilisation des
GtkListItem. <20>tudiez-le attentivement.
<sect>Widgets s<>lections de fichiers
<p>
Le widget s<>lection de fichier est un moyen simple et rapide pour
afficher un fichier dans une bo<62>te de dialogue. Il est complet, avec
des boutons Ok, Annuler et Aide. C'est un bon moyen de raccourcir les
temps de programmation.
Pour cr<63>er une bo<62>te de s<>lection de fichier, on utilise&nbsp;:
<tscreen><verb>
GtkWidget* gtk_file_selection_new (gchar *title);
</verb></tscreen>
Pour configurer le nom de fichier, par exemple pour aller dans un
r<EFBFBD>pertoire pr<70>cis ou donner un nom de fichier par d<>faut, on utilise
la fonction&nbsp;:
<tscreen><verb>
void gtk_file_selection_set_filename (GtkFileSelection *filesel, gchar *filename);
</verb></tscreen>
Pour r<>cup<75>rer le texte que l'utilisateur a entr<74>, ou sur lequel il a
cliqu<EFBFBD>, on utilisera la fonction&nbsp;:
<tscreen><verb>
gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel);
</verb></tscreen>
Des pointeurs permettent d'acc<63>der aux widgets contenus dans la widget
de s<>lection de fichiers. Ce sont&nbsp;:
<itemize>
<item>dir_list
<item>file_list
<item>selection_entry
<item>selection_text
<item>main_vbox
<item>ok_button
<item>cancel_button
<item>help_button
</itemize>
Le plus souvent, on utilise les pointeurs <em/ok_button, cancel_button/, et
<em/help_button/ pour pr<70>ciser leurs utilisations.
Voici un exemple emprunt<6E> <20> <em/testgtk.c/ et modifi<66> pour fonctionner
tout seul. Comme vous le verrez, il n'y a pas grand chose <20> faire pour
cr<EFBFBD>er un wigdget de s<>lection de fichier. Cependant, dans cet exemple,
si le bouton Aide appara<72>t <20> l'<27>cran, il ne fait rien car aucun signal
ne lui est attach<63>.
<tscreen><verb>
#include <gtk/gtk.h>
/* R<>cup<75>re le nom de fichier s<>lectionn<6E> 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 (&amp;argc, &amp;argv);
/* Cr<43>ation d'un widget de s<>lection de fichier. */
filew = gtk_file_selection_new ("File selection");
gtk_signal_connect (GTK_OBJECT (filew), "destroy",
(GtkSignalFunc) destroy, &amp;filew);
/* Connexion de ok_button <20> 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 d<>truire 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 d<>faut. */
gtk_file_selection_set_filename (GTK_FILE_SELECTION(filew),
"penguin.png");
gtk_widget_show(filew);
gtk_main ();
return 0;
}
</verb></tscreen>
<sect>Widgets Menu
<p>
Il y a deux fa<66>ons de cr<63>er des menus, la facile et la compliqu<71>e. Les
deux ont leur utilit<69>, mais on peut g<>n<EFBFBD>ralement utiliser l'<em/usine
<EFBFBD> menus/ (c'est la m<>thode facile...). La m<>thode <20> compliqu<71>e <20>
consiste <20> cr<63>er tous les menus en utilisant directement les
appels. La m<>thode facile consiste <20> utiliser les appels
<em/gtk_menu_factory/. C'est beaucoup plus simple, mais chaque
approche a ses avantages et inconv<6E>nients.
L'usine <20> menus est beaucoup plus facile <20> utiliser, elle facilite
aussi l'ajout d'autres menus. Par contre, <20>crire quelques fonctions
permettant de cr<63>er des menus en utilisant la m<>thode manuelle peut
<EFBFBD>tre le d<>but d'un long chemin avant une quelconque utilisation. Avec
l'usine <20> menus, il n'est pas possible d'ajouter des images ou des <20> /
<EFBFBD> aux menus.
<p>
<sect1>Cr<43>ation manuelle de menus
<p>
Selon la tradition p<>dagogique, nous commencerons par le plus compliqu<71> <tt/:)/
<p>
Regardons les fonctions utilis<69>es pour cr<63>er les menus. La premi<6D>re sert <20> cr<63>er un nouveau menu.
<tscreen><verb>
GtkWidget *gtk_menu_bar_new()
</verb></tscreen>
Cette fonction cr<63>e une nouvelle barre de menu. On utilise la fonction
<em/gtk_container_add/ pour la placer dans une fen<65>tre, ou les
fonctions <em/box_pack/ pour la placer dans une bo<62>te - la m<>me que
pour les boutons.
<tscreen><verb>
GtkWidget *gtk_menu_new();
</verb></tscreen>
Cette fonction retourne un pointeur vers un nouveau menu, il n'est
jamais montr<74> (avec <em/gtk_widget_show/), il ne fait que contenir les
items du menu. Ceci deviendra plus clair lorsque nous <20>tudierons
l'exemple ci-dessous.
<p>
Les deux appels suivants servent <20> cr<63>er des items de menu qui seront
plac<EFBFBD>s dans le menu.
<tscreen><verb>
GtkWidget *gtk_menu_item_new()
</verb></tscreen>
et
<tscreen><verb>
GtkWidget *gtk_menu_item_new_with_label(const char *label)
</verb></tscreen>
Ces appels servent <20> cr<63>er les menus qui doivent <20>tre affich<63>s. On
doit bien faire la diff<66>rence entre un <20> menu <20> qui est cr<63><72> avec
<em/gtk_menu_new()/ et un <20> item de menu <20> cr<63><72> avec les fonctions
<em/gtk_menu_item_new()/. L'item de menu sera un v<>ritable bouton avec
une action associ<63>e alors qu'un menu sera un container contenant les
items.
<tscreen><verb>
gtk_menu_item_append()
gtk_menu_item_set_submenu()
</verb></tscreen>
Les fonctions <em/gtk_menu_new_with_label()/ et <em/gtk_menu_new()/
sont telles que vous les attendiez apr<70>s avoir <20>tudi<64> les
boutons. L'une cr<63>e un nouvel item de menu contenant d<>j<EFBFBD> un label, et
l'autre ne fait que cr<63>er un item de menu vide.
<p>
Voici les <20>tapes pour cr<63>er une barre de menu avec des menus attach<63>s&nbsp;:
<itemize>
<item>Cr<43>er un nouveau menu avec <em/gtk_menu_new()/ <item>Cr<43>er un
item de menu avec <em/gtk_menu_item_new()/. Ce sera la racine du
menu, le texte apparaissant ici sera aussi sur la barre de menu.
<item>Utiliser plusieurs appels <20> <em/gtk_menu_item_new()/ pour
chaque item que l'on d<>sire dans le menu. Utiliser
<em/gtk_menu_item_append()/ pour placer chacun de ces items les uns
apr<EFBFBD>s les autres. Cela cr<63>e une liste d'items de menu.
<item>Utiliser <em/gtk_menu_item_set_submenu()/ pour attacher les
items de menus nouvellement cr<63><72>s <20> l'item de menu racine (celui cr<63><72>
<EFBFBD> la seconde <20>tape).
<item>Cr<43>er une nouvelle barre de menu avec
<em/gtk_menu_bar_new()/. Cette <20>tape ne doit <20>tre faite qu'une fois
lorsque l'on cr<63>e une s<>rie de menu sur une seule barre de menus.
<item>Utiliser <em/gtk_menu_bar_append()/ pour placer le menu racine
dans la barre de menu.
</itemize>
<p>
La cr<63>ation d'un menu surgissant est presque identique. La diff<66>rence
est que le menu n'est pas post<73> <20> automatiquement <20> par une barre de
menu, mais explicitement en appelant la fonction <em/gtk_menu_popup()/
par un <20>v<EFBFBD>nement <20> bouton press<73> <20>.
Suivez ces <20>tapes&nbsp;
<itemize>
<item>Cr<43>er une fonction de gestion d'<27>v<EFBFBD>nement. Elle doit avoir le prototype
<tscreen>
static gint handler(GtkWidget *widget, GdkEvent *event);
</tscreen>
et elle utilisera l'<27>v<EFBFBD>nement <em/event/ pour savoir o<> faire surgir
le menu.
<item>Ce gestionnaire, si l'<27>v<EFBFBD>nement est un appui sur un
bouton souris, traite <em/event/ comme un <20>v<EFBFBD>nement bouton (ce qu'il
est) et l'utilise, de la fa<66>on indiqu<71>e dans le code d'exemple, pour
passer l'information <20> <em/gtk_menu_popup()/.
<item> Lier ce gestionnaire <20> un widget avec&nbsp;:
<tscreen>
gtk_signal_connect_object(GTK_OBJECT(widget), "event",
GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu));
</tscreen>
o<EFBFBD> <em/widget/ est le widget auquel vous le liez, <em/handler/ est
le gestionnaire, et <em/menu/ est un menu cr<63><72> avec
<em/gtk_menu_new()/. Cela peut <20>tre un menu qui est aussi post<73> par
une barre de menu, comme le montre l'exemple.
</itemize>
<p>
<sect1>Exemple de menu manuel
<p>
<tscreen><verb>
#include <gtk/gtk.h>
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 (&amp;argc, &amp;argv);
/* Cr<43>ation d'un fen<65>tre */
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 <20> menu racine <20> de l'application. */
menu = gtk_menu_new();
/* Voici le menu racine dont le label sera le nom du menu affich<63> sur la barre
* de menu. Il n'a pas de gestionnaire de signal attach<63> car il ne fait
* qu'afficher le reste du menu lorsqu'il est press<73>. */
root_menu = gtk_menu_item_new_with_label("Menu racine");
gtk_widget_show(root_menu);
/* Puis, on cr<63>e une petite boucle cr<63>ant trois entr<74>es pour <20> menu test <20>
* Notez l'appel <20> gtk_menu_append(). Ici, on ajoute une liste d'items <20>
* 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);
/* Cr<43>ation 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'int<6E>ressant lorsque l'item est
* s<>lectionn<6E>. */
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 sp<73>cifi<66> que nous voulons que notre nouveau <20> menu <20>
* soit le menu du <20> menu racine <20>. */
gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);
/* Cr<43>ation 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);
/* Cr<43>ation d'une barre de menus pour contenir les menus. Puis, on
* l'ajoute <20> notre fen<65>tre principale. */
menu_bar = gtk_menu_bar_new();
gtk_box_pack_start(GTK_BOX(vbox), menu_bar, FALSE, FALSE, 2);
gtk_widget_show(menu_bar);
/* Cr<43>ation 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 <20> la barre de menu --
* c'est l'item de menu racine sur lequel je me suis d<>cha<68>n<EFBFBD> ;-) */
gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);
/* Affichage de la fen<65>tre. */
gtk_widget_show(window);
gtk_main ();
return 0;
}
/* On r<>pond <20> un appui sur le bouton en postant un nouveau menu pass<73> comme
* un widget.
*
* On remarque que le param<61>tre "widget" est le menu <20> poster, PAS le bouton
* qui a <20>t<EFBFBD> press<73>. */
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 <20> l'appelant que l'on a g<>r<EFBFBD> cet <20>v<EFBFBD>nement. */
return TRUE;
}
/* On indique <20> l'appelant que l'on n'a pas g<>r<EFBFBD> cet <20>v<EFBFBD>nement. */
return FALSE;
}
/* Affiche une cha<68>ne lorsqu'un item de menu est choisi. */
static void menuitem_response (GtkWidget *widget, gchar *string)
{
printf("%s\n", string);
}
</verb></tscreen>
Vous pouvez aussi configurer un item de menu pour qu'il ne soit pas
s<EFBFBD>lectionnable et, en utilisant une table de raccourcis clavier, lier
des touches aux fonctions du menu.
<p>
<sect1>Utilisation de GtkMenuFactory
<p>
Maintenant que nous avons explor<6F> la voie difficile, nous allons voir
l'utilisation des appels <em/gtk_menu_factory./
<p>
<sect1>Exemple d'usine <20> menu
<p>
Voici un exemple utilisant l'usine <20> menu de GTK. Le premier
fichier est <em/menus.h/. Nous s<>parerons <em/menus.c/ et <em/main.c/ <20>
cause des variables globales utilis<69>es dans le fichier <em/menus.c/.
<tscreen><verb>
#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__ */
</verb></tscreen>
<p>
Voici le fichier <em/menus.c/&nbsp;:
<tscreen><verb>
#include <gtk/gtk.h>
#include <strings.h>
#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 utilis<69>e pour cr<63>er les menus. Le premier champ
* est la cha<68>ne de d<>finition du menu. Le second, la touche de raccourci
* utilis<69>e pour acc<63>der <20> cette fonction du menu avec le clavier.
* Le troisi<73>me est la fonction de rappel <20> utiliser lorsque l'item de menu
* est choisi (par la touche de raccourci ou avec la souris). Le dernier
* <20>l<EFBFBD>ment est la donn<6E>e <20> passer <20> la fonction de rappel. */
static GtkMenuEntry menu_items[] =
{
{"<Main>/Fichier/Nouveau", "<control>N", NULL, NULL},
{"<Main>/Fichier/Ouvrir", "<control>O", NULL, NULL},
{"<Main>/Fichier/Sauver", "<control>S", NULL, NULL},
{"<Main>/Fichier/Sauver sous", NULL, NULL, NULL},
{"<Main>/Fichier/<separator>", NULL, NULL, NULL},
{"<Main>/Fichier/Quitter", "<control>Q", file_quit_cmd_callback, "OK, c'est fini"},
{"<Main>/Options/Test", NULL, NULL, NULL}
};
/* Calcul du nombre d'<27>l<EFBFBD>ments 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], "<Main>");
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, "<control>");
if (modifiers & GDK_SHIFT_MASK)
strcat(accel, "<shift>");
if (modifiers & GDK_MOD1_MASK)
strcat(accel, "<alt>");
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<69> d'un menu qui n'existe pas : %s", path);
}
</verb></tscreen>
<p>
Voici <em/main.h/&nbsp;:
<tscreen><verb>
#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__ */
</verb></tscreen>
<p>
Et, enfin, <em/main.c/&nbsp;:
<tscreen><verb>
#include <gtk/gtk.h>
#include "main.h"
#include "menus.h"
int main(int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *main_vbox;
GtkWidget *menubar;
GtkAcceleratorTable *accel;
gtk_init(&amp;argc, &amp;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 <20> 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(&amp;menubar, &amp;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 d<>monstration du fonctionnement des fonctions de rappel
* lorsqu'on utilise l'usine <20> menus. Souvent, on met tous les rappels
* des menus dans un fichier s<>par<61>, ce qui assure une meilleure
* organisation. */
void file_quit_cmd_callback (GtkWidget *widget, gpointer data)
{
g_print ("%s\n", (char *) data);
gtk_exit(0);
}
</verb></tscreen>
<p>
Un <em/makefile/ pour que cela soit plus facile <20> compiler&nbsp;:
<tscreen><verb>
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 *~
</verb></tscreen>
<p>
Pour l'instant, il n'y a que cet exemple. Une explication et de nombreux commentaires seront int<6E>gr<67>s plus tard.
<sect>Widgets non document<6E>s
<p>
On a besoin de leurs auteurs! :). Participez <20> notre didacticiel.
Si vous devez utiliser un de ces widgets non document<6E>s, je vous recommande fortement de consulter leurs fichiers en-t<>tes respectifs dans la distribution GTK. Les noms de fonctions du GTK sont tr<74>s parlantes. Lorsque vous avez compris comment les choses fonctionnent, il n'est pas difficile de savoir comment utiliser un widget <20> partir des d<>clarations de ses fonctions. Cela, avec quelques exemples de codes pris ailleurs, devrait ne pas poser de probl<62>me.
Lorsque vous avez compris toutes les fonctions d'un nouveau widget non document<6E>, pensez <20> <20>crire un didacticiel pour que les autres puissent b<>n<EFBFBD>ficier du temps que vous y avez pass<73>.
<sect1>Entr<74>es de texte
<p>
<sect1>S<>lections de couleurs
<p>
<sect1>Contr<74>le d'intervalle
<p>
<sect1>R<>gles
<p>
<sect1>Bo<42>tes de texte
<p>
<sect1>Pr<50>visualisation
<p>
(Ceci peut devoir <20>tre r<><72>crit pour suivre le style du reste de ce
didacticiel).
<tscreen><verb>
Les pr<70>visualisateurs servent <20> plusieurs choses dans GIMP/GTK. La
plus importante est celle-ci&nbsp;: les images de haute qualit<69> peuvent
occuper des dizaines de mega-octets en m<>moire - facilement ! Toute
op<EFBFBD>ration sur une image aussi grosse implique un temps de traitement
<EFBFBD>lev<EFBFBD>. Si cela vous prend 5 <20> 10 essais (i.e. 10 <20> 20 <20>tapes puisque
vous devez recommencer lorsque vous avez fait une erreur) pour choisir
la bonne modification, cela prendra litt<74>ralement des heures pour
produire la bonne image - pour peu que vous ne manquiez pas de m<>moire
avant. Ceux qui on pass<73> des heures dans les chambres noires de
d<EFBFBD>veloppement couleur connaissent cette sensation. Les
pr<EFBFBD>visualisations sont notre planche de salut !
L'aspect p<>nible de l'attente n'est pas le seul probl<62>me. souvent, il
est utile de comparer les versions <20> Avant <20> et <20> Apr<70>s <20> c<>te <20> c<>te
ou, au pire l'une apr<70>s l'autre. Si vous travaillez avec de grosses
images et des attentes de 10 secondes, l'obtention des versions <20>
Avant <20> et <20> Apr<70>s <20> est, pour le moins, difficile. Pour des images de
30Mo (4"x6", 600dpi, 24 bits), la comparaison c<>te <20> c<>te est
impossible pour la plupart des gens, et la comparaison s<>quentielle
n'est gu<67>re mieux. Les pr<70>visualisations sont notre planche de salut !
Mais il y a plus. Les pr<70>visualisations permettent les
pr<EFBFBD>-pr<70>visualisations c<>te <20> c<>te. En d'autres termes, vous <20>crivez un
plug-in (par exemple la simulation filterpack) qui aurait plusieurs
pr<EFBFBD>visualisations de ce-que-ce-serait-si-vous-faisiez-ceci. Une approche
comme celle ci agit comme une sorte de palette de pr<70>visualisation et
est tr<74>s pratique pour les petites modifications. Utilisons les
pr<EFBFBD>visualisations !
Encore plus&nbsp;: pour certains plug-ins une intervention humaine en
temps r<>el, sp<73>cifique aux images, peut s'av<61>rer n<>cessaire. Dans le
plug-in SuperNova, par exemple, on demande <20> l'utilisateur d'entrer
les coordonn<6E>es du centre de la future supernova. La fa<66>on la plus
simple de faire cela, vraiment, est de pr<70>senter une pr<70>visualisation
<EFBFBD> l'utilisateur et de lui demander de choisir interactivement le
point. Utilisons les pr<70>visualisations !
Enfin, quelques utilisations diverses&nbsp;: on peut utiliser les
pr<EFBFBD>visualisations, m<>me 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 v<>n<EFBFBD>rable 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
pr<EFBFBD>visualisations pour des petits logos dans vos plug-ins et m<>me pour
une photo de vous, l'Auteur. Utilisons les pr<70>visualisations !
Quand ne pas utiliser les pr<70>visualisations
N'utilisez pas les pr<70>visualisations pour les graphes, les trac<61>s,
etc. GDK est bien plus rapide pour <20>a. N'utilisez les que pour les
images !
Utilisons les pr<70>visualisations !
Vous pouvez mettre une pr<70>visualisation dans <20> peu pr<70>s n'importe
quoi. Dans une vbox, une hbox, un bouton, etc. Mais elles donnent leur
meilleur d'elles-m<>mes dans des cadres resserr<72>s autour d'elles. Les
pr<EFBFBD>visualisations n'ont, par elles-m<>mes, aucun contour et semblent
plates sans eux. (Bien s<>r, si c'est cet aspect que vous
voulez...). Les cadres serr<72>s fournissent les bordures n<>cessaires.
[Image][Image]
Les pr<70>visualisations sont, <20> bien des <20>gards, comme tous les autres
widgets de GTK (avec tout ce que cela implique) sauf qu'il disposent
d'une fonctionnalit<69> suppl<70>mentaire&nbsp;: ils doivent <20>tre remplis
avec une image ! Nous traiterons d'abord exclusivement de l'aspect GTK
des pr<70>visualisations, puis nous verrons comment les remplir.
/* Cr<43>ation d'un widget pr<70>visualisation,
* 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 pr<70>visualisations rendent mieux dans
des cadres&nbsp;:
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 pr<70>visualisation de base. Cette fonction retourne le cadre
<EFBFBD> p<>re <20>, on peut ainsi le placer ailleurs dans notre interface. Bien
s<EFBFBD>r, on peut passer le cadre <20> p<>re <20> en param<61>tre <20> cette
fonction. Dans de nombreuses situations, toutefois, le contenu de la
pr<EFBFBD>visualisation est chang<6E>e continuellement par notre application. En
ce cas, on peut passer un pointeur vers une pr<70>visualisation <20> la
fonction <em/create_a_preview()/ et avoir ainsi un contr<74>le sur elle
plus tard.
Un point plus important qui pourra un jour vous faire <20>conomiser
beaucoup de temps. Quelques fois, il est souhaitable de mettre un
label <20> votre pr<70>visualisation. Par exemple, on peut nommer la
pr<EFBFBD>visualisation contenant l'image originale <20> Original <20> et celle
contenant l'image modifi<66>e <20> Moins Originale <20>. Il peut vous arriver
de placer la pr<70>visualisation avec le label appropri<72> dans une
vbox. L'effet inattendu est que si le label est plus large que la
pr<EFBFBD>visualisation (taille de cette derni<6E>re, taille de la fonte du
label, etc), le cadre s'<27>largit et ne convient plus <20> la
pr<EFBFBD>visualisation. Le m<>me probl<62>me se passera probablement dans
d'autres situations aussi.
[Image]
La solution consiste <20> placer la pr<70>visualisation et le label dans une
table de 2x2 en les attachant avec les param<61>tres suivants (c'est
l'une des possibilit<69>s, bien s<>r. La cl<63> consiste <20> ne pas mettre
GTK_FILL dans le second attachement)<29>nbsp;:
gtk_table_attach(GTK_TABLE(table),label,0,1,0,1,
0,
GTK_EXPAND|GTK_FILL,
0,0);
gtk_table_attach(GTK_TABLE(table),frame,0,1,1,2,
GTK_EXPAND,
GTK_EXPAND,
0,0);
Et voici le r<>sultat&nbsp;:
[Image]
Divers
Rendre une pr<70>visualisation cliquable se fait tr<74>s facilement en la pla<6C>ant dans un bouton. Cela ajoute aussi une bordure agr<67>able autour de la pr<70>visualisation et vous n'avez m<>me pas besoin de la mettre dans un cadre. Voir le plug-in Filter Pack Simulation comme exemple.
Remplir une pr<70>visualisation
Afin de nous familiariser avec les bases de ce remplissage, cr<63>ons le motif suivant&nbsp;:
[Image]
void
my_preview_rendering_function(GtkWidget *preview)
{
#define SIZE 100
#define HALF (SIZE/2)
guchar *row=(guchar *) malloc(3*SIZE); /* 3 bits par point */
gint i, j; /* Coordonn<6E>es */
double r, alpha, x, y;
if (preview==NULL) return; /* J'ajoute g<>n<EFBFBD>ralement ceci quand je */
/* veux <20>viter des plantages stupides */
/* Vous devez vous assurer que tout a */
/* <20>t<EFBFBD> correctement initialis<69> ! */
for (j=0; j < ABS(cos(2*alpha)) ) { /* Sommes-nous dans la forme ? */
/* glib.h contient ABS(x). */
row[i*3+0] = sqrt(1-r)*255; /* Definit rouge */
row[i*3+1] = 128; /* Definit vert */
row[i*3+2] = 224; /* Definit bleu */
} /* "+0" est pour l'alignement ! */
else {
row[i*3+0] = r*255;
row[i*3+1] = ABS(sin((float)i/SIZE*2*PI))*255;
row[i*3+2] = ABS(sin((float)j/SIZE*2*PI))*255;
}
}
gtk_preview_draw_row( GTK_PREVIEW(preview),row,0,j,SIZE);
/* Ins<6E>re "row" dans "preview" en partant du point de */
/* coordonn<6E>es (0,j) premi<6D>re colonne, j_i<5F>me ligne allant de SIZE */
/* pixels vers la droite */
}
free(row); /* on r<>cup<75>re un peu d'espace */
gtk_widget_draw(preview,NULL); /* qu'est-ce que <20>a fait ? */
gdk_flush(); /* et <20>a ? */
}
Ceux qui n'utilisent pas GIMP en ont suffisamment vu pour faire
d<EFBFBD>j<EFBFBD> beaucoup de choses. Pour ceux qui l'utilisent, j'ai quelques
pr<EFBFBD>cisions <20> ajouter.
Pr<EFBFBD>visualisation d'image
Il est pratique de conserver une version r<>duite de l'image ayant
juste assez de pixels pour remplir la pr<70>visualisation. Ceci est
possible en choisissant chaque <20>ni<6E>me pixel o<> n est le ratio de la
taille de l'image par rapport <20> la taille de la visualisation. Toutes
les op<6F>rations suivantes (y compris le remplissage des
pr<EFBFBD>visualisations) sont alors r<>alis<69>es seulement sur le nombre r<>duit
de pixels. Ce qui suit est mon implantation de la r<>duction d'image
(Gardez <20> l'esprit que je n'ai que quelques notions de base en C !).
(ATTENTION : CODE NON TEST<53> !!!)
typedef struct {
gint width;
gint height;
gint bbp;
guchar *rgb;
guchar *mask;
} ReducedImage;
enum {
SELECTION_ONLY,
SELCTION_IN_CONTEXT,
ENTIRE_IMAGE
};
ReducedImage *Reduce_The_Image(GDrawable *drawable,
GDrawable *mask,
gint LongerSize,
gint Selection)
{
/* Cette fonction r<>duit l'image <20> la taille de pr<70>visualisation choisie */
/* La taille de la pr<70>visualisation est d<>termin<69>e par LongerSize, i.e. */
/* la plus grande des deux dimensions. Ne fonctionne qu'avec des images */
/* RGB ! */
gint RH, RW; /* Hauteur et Largeur r<>duites */
gint width, height; /* Largeur et Hauteur de la surface <20> r<>duire */
gint bytes=drawable->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 enti<74>re */
gimp_drawable_mask_bounds (drawable->id, &amp;x1, &amp;y1, &amp;x2, &amp;y2);
width = x2-x1;
height = y2-y1;
/* S'il y a une SELECTION, on r<>cup<75>re ses fronti<74>res ! */
if (width != drawable->width &amp;&amp; height != drawable->height)
NoSelectionMade=FALSE;
/* On v<>rifie si l'utilisateur a rendu une s<>lection active */
/* Ceci sera important plus tard, lorsqu'on cr<63>era un masque r<>duit */
/* Si on veut pr<70>visualiser l'image enti<74>re, supprimer ce qui suit ! */
/* Bien s<>r, s'il n'y a pas de s<>lection, cela n'a aucun effet ! */
if (Selection==ENTIRE_IMAGE) {
x1=0;
x2=drawable->width;
y1=0;
y2=drawable->height;
}
/* Si on veut pr<70>visualiser une s<>lection avec une surface qui l'entoure, */
/* on doit l'agrandir un petit peu. Consid<69>rez <20>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 <20> r<>duire. */
width = x2-x1;
height = y2-y1;
/* Les lignes ci-dessous d<>terminent la dimension qui sera le cot<6F> */
/* le plus long. Cette id<69>e est emprunt<6E>e au plug-in Supernova. */
/* Je soup<75>onne que j'aurais pu y penser moi-m<>me, mais la v<>rit<69> */
/* doit <20>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 enti<74>re est r<>duite dans une cha<68>ne ! */
tempRGB = (guchar *) malloc(RW*RH*bytes);
tempmask = (guchar *) malloc(RW*RH);
gimp_pixel_rgn_init (&amp;srcPR, drawable, x1, y1, width, height, FALSE, FALSE);
gimp_pixel_rgn_init (&amp;srcMask, mask, x1, y1, width, height, FALSE, FALSE);
/* R<>servation 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 (&amp;srcPR, src_row, x1, y1+whichrow, width);
gimp_pixel_rgn_get_row (&amp;srcMask, src_mask_row, x1, y1+whichrow, width);
for (j=0; j < RW; j++) {
whichcol=(float)j*(float)width/(float)RW;
/* Pas de s<>lection = chaque point est compl<70>tement s<>lectionn<6E> ! */
if (NoSelectionMade)
tempmask[i*RW+j]=255;
else
tempmask[i*RW+j]=src_mask_row[whichcol];
/* Ajout de la ligne <20> la longue cha<68>ne 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 <20> 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 pr<70>visualisation qui utilise le m<>me type
<em/ReducedImage/ ! On remarque qu'elle utilise une fausse
transparence (au moyen de <em/fake_transparancy/ qui est d<>fini comme
suit&nbsp;:
gint fake_transparency(gint i, gint j)
{
if ( ((i%20)- 10) * ((j%20)- 10)>0 )
return 64;
else
return 196;
}
Voici maintenant la fonction de pr<70>visualisation<6F>nbsp;:
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 id<69>e */
void gtk_preview_uninit (void);
/* Aucune id<69>e */
GtkWidget* gtk_preview_new (GtkPreviewType type);
/* D<>crite ci-dessous */
void gtk_preview_size (GtkPreview *preview,
gint width,
gint height);
/* Permet de changer la taille d'une pr<70>visualisation existante */
/* Apparamment, il y a un bug dans GTK qui rend ce traitement */
/* hasardeux. Une m<>thode pour corriger ce probl<62>me consiste <20> */
/* changer manuellement la taille de la fen<65>tre contenant la */
/* pr<70>visualisation apr<70>s avoir chang<6E> la taille de la */
/* pr<70>visualisation. */
void gtk_preview_put (GtkPreview *preview,
GdkWindow *window,
GdkGC *gc,
gint srcx,
gint srcy,
gint destx,
gint desty,
gint width,
gint height);
/* Aucune id<69>e */
void gtk_preview_put_row (GtkPreview *preview,
guchar *src,
guchar *dest,
gint x,
gint y,
gint w);
/* Aucune id<69>e */
void gtk_preview_draw_row (GtkPreview *preview,
guchar *data,
gint x,
gint y,
gint w);
/* D<>crite dans le texte */
void gtk_preview_set_expand (GtkPreview *preview,
gint expand);
/* Aucune id<69>e */
/* Aucune piste pour celles qui suivent mais devrait <20>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!
</verb></tscreen>
<sect1>Courbes
<p>
<sect>Widget EventBox <label id="sec_The_EventBox_Widget">
<p>
Il n'est disponible que dans <em/gtk+970916.tar.gz/ et les
distributions ult<6C>rieures. <p> Certains widgets GTK n'ont pas de
fen<EFBFBD>tre X associ<63>e, il se dessinent donc sur leurs parents. <20> cause de
cela, ils ne peuvent recevoir d'<27>v<EFBFBD>nements et, s'ils ont une taille
incorrecte, ils ne peuvent pas se mettre en place correctement&nbsp;: on peut
alors avoir des surimpressions douteuses, etc. Si vous avez besoin de
ces widgets, <em/EventBox/ est fait pour vous.
Au premier abord, le widget <em/EventBox/ peut appara<72>tre comme
totalement d<>nu<6E> d'int<6E>r<EFBFBD>t. Il ne dessine rien <20> l'<27>cran et ne r<>pond
<EFBFBD> aucun <20>venement. Cependant, il joue un r<>le - il fournit une fen<65>tre
X pour son widget fils. Ceci est important car de nombreux widgets GTK
n'ont pas de fen<65>tre X associ<63>e. Ne pas avoir de fen<65>tre permet
d'<27>conomiser de la m<>moire mais a aussi quelques inconv<6E>nients. Un
widget sans fen<65>tre ne peut recevoir d'<27>v<EFBFBD>nement, et ne r<>alise aucune
mise en place de ce qu'il contient. Bien que le nom <20> EventBox <20>
insiste sur la fonction de gestion d'<27>v<EFBFBD>nement, le widget peut aussi
<EFBFBD>tre utilis<69> pour la mise en place (et plus... voir l'exemple
ci-dessous).
<p>
Pour cr<63>er un widget EventBox, on utilise&nbsp;:
<tscreen><verb>
GtkWidget* gtk_event_box_new (void);
</verb></tscreen>
<p>
Un widget fils peut alors <20>tre ajout<75> <20> cet <em/EventBox/&nbsp;:
<tscreen><verb>
gtk_container_add (GTK_CONTAINER(event_box), widget);
</verb></tscreen>
<p>
L'exemple suivant montre l'utilisation d'un <em/EventBox/ - un label
est cr<63><72> et mis en place sur une petite bo<62>te, et configur<75> pour qu'un
clic souris sur le label provoque la fin du programme.
<tscreen><verb>
#include <gtk/gtk.h>
int
main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *event_box;
GtkWidget *label;
gtk_init (&amp;argc, &amp;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);
/* Cr<43>ation d'un EventBox et ajout de celui-ci dans la fen<65>tre. */
event_box = gtk_event_box_new ();
gtk_container_add (GTK_CONTAINER(window), event_box);
gtk_widget_show (event_box);
/* Cr<43>ation 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<72>. */
gtk_widget_set_usize (label, 110, 20);
/* Attachement d'une action <20> 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 fen<65>tre 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;
}
</verb></tscreen>
<sect>Configuration des attributs de widget<label
id="sec_setting_widget_attributes">
<p>
Cette section d<>crit les fonctions op<6F>rant sur les widgets. Elles
peuvent <20>tre utilis<69>es pour configurer le style, l'espacement, la
taille, etc.
(Je devrais peut <20>tre faire une section uniquement consacr<63>e aux
raccourcis clavier.)
<tscreen><verb>
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);
</verb></tscreen>
<sect>Temporisations, fonctions d'E/S et d'attente<label id="sec_timeouts">
<p>
<sect1>Temporisations
<p>
Vous pouvez vous demander comment faire pour que GTK fasse quelque
chose d'utile lorsqu'il est dans <em/gtk_main/. En fait, on a
plusieurs options. L'utilisation des fonctions suivantes permet de
cr<EFBFBD>er une temporisation qui sera appel<65>e tous les
<em/interval/ millisecondes.
<tscreen><verb>
gint gtk_timeout_add (guint32 interval,
GtkFunction function,
gpointer data);
</verb></tscreen>
Le premier param<61>tre est le nombre de millisecondes entre les appels <20>
notre fonction. Le deuxi<78>me est la fonction <20> appeler et le troisi<73>me
est la donn<6E>e pass<73>e <20> cette fonction de rappel. La valeur retourn<72>e
est un <20> marqueur <20> de type entier qui pourra <20>tre utilis<69> pour
arr<EFBFBD>ter la temporisation en appelant&nbsp;:
<tscreen><verb>
void gtk_timeout_remove (gint tag);
</verb></tscreen>
On peut aussi stopper la fonction de temporisation en faisant
retourner z<>ro ou FALSE <20> notre fonction de rappel. <20>videmment, cela
veut dire que si vous voulez que votre fonction continue <20> <20>tre
appel<EFBFBD>e, elle doit retourner une valeur non nulle, ou TRUE.
La d<>claration de votre fonction de rappel doit ressembler <20> <20>a&nbsp;:
<tscreen><verb>
gint timeout_callback (gpointer data);
</verb></tscreen>
<sect1>Surveillance des E/S
<p>
Une autre caract<63>ristique int<6E>ressante du GTK est la possibilit<69> de
v<EFBFBD>rifier les donn<6E>es d'un descripteur de fichier (celles retourn<72>es
par <em/open/(2) ou <em/socket/(2)). C'est particuli<6C>rement pratique pour les
applications r<>seau. La fonction suivante permet cette
v<EFBFBD>rification&nbsp;:
<tscreen><verb>
gint gdk_input_add (gint source,
GdkInputCondition condition,
GdkInputFunction function,
gpointer data);
</verb></tscreen>
Le premier param<61>tre est le descripteur de fichier que l'on veut
<EFBFBD>tudier, le second sp<73>cifie ce qu'on veut que le GDK recherche. Cela
peut <20>tre&nbsp;:
<p>
GDK_INPUT_READ - Appel <em/function/ lorsqu'il y a une donn<6E>e pr<70>te <20>
<EFBFBD>tre lue dans le descripteur de fichier.
<p>
GDK_INPUT_WRITE - Appel de <em/function/ lorsque le descripteur de
fichier est pr<70>t pour une <20>criture.
<p>
Je suis s<>r que vous vous doutez, maintenant, que le troisi<73>me
param<EFBFBD>tre est la fonction que l'on veut appeler lorsque les conditions
ci-dessus sont satisfaites. Le dernier param<61>tre est la donn<6E>e <20>
passer <20> cette fonction.
<p>
La valeur retourn<72>e est un marqueur qui pourra <20>tre utilis<69> pour dire
au GDK de cesser de surveiller ce descripteur <20> l'aide de la fonction&nbsp;:
<p>
<tscreen><verb>
void gdk_input_remove (gint tag);
</verb></tscreen>
<p>
La fonction de rappel doit <20>tre d<>clar<61>e de la fa<66>on suivante&nbsp;:
<p>
<tscreen><verb>
void input_callback (gpointer data, gint source,
GdkInputCondition condition);
</verb></tscreen>
<p>
<sect1>Fonctions d'attente
<p>
Que se passe-t'il si vous avez une fonction qui doit <20>tre appel<65>e
lorsque rien d'autre ne se passe ? On utilise la fonction suivante qui force
GTK <20> appeler <em/function/ lorsqu'on est en phase d'inaction&nbsp;;
<tscreen><verb>
gint gtk_idle_add (GtkFunction function,
gpointer data);
</verb></tscreen>
<tscreen><verb>
void gtk_idle_remove (gint tag);
</verb></tscreen>
<p>
Je n'expliquerai pas la signification des param<61>tres car ils
ressemblent beaucoup <20> ceux d<>j<EFBFBD> vus ci-dessus. La fonction point<6E>e
par le premier param<61>tre de <em/gtk_idle_add()/ sera appel<65>e <20> chaque
occasion. Comme pour les autres, retourner FALSE emp<6D>chera la fonction
d'attente d'<27>tre appel<65>e.
<sect>Gestion des s<>lections
<sect1>Introduction
<p>
Un type de communication inter-processus g<>r<EFBFBD>e par GTK est les
<em>s<>lections</em>. Une s<>lection identifie un morceau de donn<6E>es,
par exemple une portion de texte s<>lectionn<6E>e par l'utilisateur avec
la souris. Seule une application sur un <20>cran (le <em/propri<72>taire/)
peut poss<73>der une s<>lection particuli<6C>re <20> un moment donn<6E>, ainsi
lorsqu'une s<>lection est r<>clam<61>e par une application, le propri<72>taire
pr<EFBFBD>c<EFBFBD>dent doit indiquer <20> l'utilisateur que la s<>lection a <20>t<EFBFBD>
abandonn<EFBFBD>e. Les autres applications peuvent demander le contenu d'une
s<EFBFBD>lection sous diff<66>rentes formes appel<65>es <em/cibles/. Il peut y
avoir un nombre quelconque de s<>lections, mais la plupart des
applications X n'en g<>rent qu'une, la <em/s<>lection primaire/.
<p>
Dans la plupart des cas, une application GTK n'a pas besoin de g<>rer elle-m<>me
les s<>lections. Les widgets standards, comme le widget Entr<74>e de texte,
poss<EFBFBD>dent d<>j<EFBFBD> la capacit<69> de r<>clamer la s<>lection lorsqu'il le faut (par
exemple, lorsque l'utilisateur glisse au dessus d'un texte) et de r<>cup<75>rer le
contenu de la s<>lection d<>tenue par un autre widget ou une autre application
(par exemple, lorsque l'utilisateur clique avec le deuxi<78>me bouton de la
souris). Cependant, il peut il y avoir des cas dans lesquels vous voulez donner
aux autres widgets la possibilit<69> de fournir la s<>lection, ou vous d<>sirez
r<EFBFBD>cup<EFBFBD>rer des cibles non support<72>es par d<>faut.
<p>
Un concept fondamental dans la compr<70>hension du fonctionnement des
s<EFBFBD>lections est celui d'<em/atome/. Un atome est un entier qui d<>finit
de fa<66>on unique une cha<68>ne (sur un affichage particulier). Certains
atomes sont pr<70>d<EFBFBD>finis par le serveur X et, dans certains cas, des
constantes d<>finies dans <em/gtk.h/ correspondent <20> ces atomes. Par
exemple, la constante GDK_PRIMARY_SELECTION correspond <20> la cha<68>ne
"PRIMARY". Dans d'autres cas, on doit utiliser les fonctions
<em/gdk_atom_intern()/, pour obtenir l'atome correspondant <20> une
cha<EFBFBD>ne, et <em/gdk_atom_name()/, pour obtenir le nom d'un atome. Les
s<EFBFBD>lections et les cibles sont identifi<66>s par des atomes.
<sect1>R<>cup<75>ration de la s<>lection
<p>
La r<>cup<75>ration de la s<>lection est un processus asynchrone. Pour d<>marrer le processus, on appelle&nbsp;:
<tscreen><verb>
gint gtk_selection_convert (GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
guint32 time)
</verb</tscreen>
Cela <em>convertit</em> la s<>lection dans la forme sp<73>cifi<66>e par
<em/target/. Si tout est possible, le param<61>tre <em/time/ sera le
moment de l'<27>v<EFBFBD>nement qui a d<>clench<63> la s<>lection. Ceci aide <20>
s'assurer que les <20>v<EFBFBD>nements arrivent dans l'ordre o<> l'utilisateur
les a demand<6E>. Cependant, si cela n'est pas possible (par exemple,
lorsque la conversion a <20>t<EFBFBD> d<>clench<63>e par un signal "clicked"), alors
on peut utiliser la macro GDK_CURRENT_TIME.
<p>
Quand le propri<72>taire de la s<>lection r<>pond <20> la requ<71>te, un signal
"selection_received" est envoy<6F> <20> notre application. Le gestionnaire
de ce signal re<72>oit un pointeur vers une structure
<tt/GtkSelectionData/ d<>finie ainsi&nbsp;:
<tscreen><verb>
struct _GtkSelectionData
{
GdkAtom selection;
GdkAtom target;
GdkAtom type;
gint format;
guchar *data;
gint length;
};
</verb></tscreen>
<em/selection/ et <em/target/ sont les valeurs que l'on a donn<6E> dans
notre appel <em/gtk_selection_convert()/. <em/type/ est un atome qui
identifie le type de donn<6E>es retourn<72> par le propri<72>taire de la
s<EFBFBD>lection. Quelques valeurs possibles sont&nbsp;: "STRING", une cha<68>ne
de caract<63>res latin-1, "ATOM", une s<>rie d'atomes, "INTEGER", un
entier, etc. La plupart des cibles ne peuvent retourner qu'un
type. <em/format/ donne la longueur des unit<69>s (les caract<63>res, par
exemple) en bits. Habituellement, on ne se pr<70>occupe pas de cela
lorsqu'on re<72>oit des donn<6E>es. <em/data/ est un pointeur vers la donn<6E>e
retourn<EFBFBD>e et <em/length/ donne la longueur en octets de la donn<6E>e
retourn<EFBFBD>e. Si <em/length/ est n<>gative, cela indique qu'une erreur est
survenue et que la s<>lection ne peut <20>tre r<>cup<75>r<EFBFBD>e. Ceci peut arriver
si aucune application n'est propri<72>taire de la s<>lection, ou si vous
avez demand<6E> une cible que l'application ne sait pas g<>rer. Le tampon
est garanti d'<27>tre un octet plus long que <em/length/ ; l'octet
suppl<EFBFBD>mentaire sera toujours z<>ro, et il n'est donc pas n<>cessaire de
faire une copie de cha<68>ne simplement pour qu'elle soit termin<69>e par
z<EFBFBD>ro (comme doivent l'<27>tre toutes les cha<68>nes C).
<p>
Dans l'exemple qui suit, on r<>cup<75>re la cible sp<73>ciale "TARGETS", qui
est une liste de toutes les cibles en lesquelles la s<>lection peut
<EFBFBD>tre convertie.
<tscreen><verb>
#include <gtk/gtk.h>
void selection_received (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data);
/* Gestionnaire de signal invoqu<71> lorsque l'utilisateur clique sur
* le bouton <20> Obtenir les cibles <20>. */
void get_targets (GtkWidget *widget, gpointer data)
{
static GdkAtom targets_atom = GDK_NONE;
/* Obtention de l'atome correspondant <20> la cha<68>ne "TARGETS" */
if (targets_atom == GDK_NONE)
targets_atom = gdk_atom_intern ("TARGETS", FALSE);
/* Demande de la cible "TARGETS" pour la s<>lection primaire */
gtk_selection_convert (widget, GDK_SELECTION_PRIMARY, targets_atom,
GDK_CURRENT_TIME);
}
/* Gestionnaire de signal appel<65> quand le propri<72>taire des s<>lections
* retourne la donn<6E>e. */
void selection_received (GtkWidget *widget, GtkSelectionData *selection_data,
gpointer data)
{
GdkAtom *atoms;
GList *item_list;
int i;
/* **** IMPORTANT **** On v<>rifie si la r<>cup<75>ration s'est bien pass<73>e. */
if (selection_data->length < 0)
{
g_print ("Selection retrieval failed\n");
return;
}
/* On s'assure que l'on a obtenu la donn<6E>e sous la forme attendue. */
if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
{
g_print ("La s<>lection \"TARGETS\" n'a pas <20>t<EFBFBD> retourn<72>e sous la forme d'atomes !\n");
return;
}
/* Affichage des atomes re<72>us. */
atoms = (GdkAtom *)selection_data->data;
item_list = NULL;
for (i=0; i<selection_data->length/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 (&amp;argc, &amp;argv);
/* Cr<43>ation de la fen<65>tre de l'application. */
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "S<>lections");
gtk_container_border_width (GTK_CONTAINER (window), 10);
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
/* Cr<43>ation 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;
}
</verb></tscreen>
<sect1>Fournir la s<>lection
<p>
Fournir la s<>lection est un peu plus compliqu<71>. On doit enregistrer
les gestionnaires qui seront appel<65>s lorsque notre s<>lection est
demand<EFBFBD>e. Pour chaque paire s<>lection/cible que l'on g<>rera, on fera
un appel <20>&nbsp;:
<tscreen><verb>
void gtk_selection_add_handler (GtkWidget *widget,
GdkAtom selection,
GdkAtom target,
GtkSelectionFunction function,
GtkRemoveFunction remove_func,
gpointer data);
</verb></tscreen>
<em/widget/, <em/selection/ et <em/target/ identifient les requ<71>tes
que ce gestionnaire g<>rera. S'il ne vaut pas NULL, <em/remove_func/
sera appel<65> lorsque le gestionnaire de signal est supprim<69>. Ceci est
utile, par exemple, pour des langages interpr<70>t<EFBFBD>s qui doivent garder
une trace du nombre de r<>f<EFBFBD>rences <20> <em/data/.
<p>
La fonction de rappel <em/function/ doit avoir la signature suivante&nbsp;:
<tscreen><verb>
typedef void (*GtkSelectionFunction) (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data);
</verb></tscreen>
Le <em/GtkSelectionData/ est le m<>me qu'au dessus, mais, cette fois,
nous sommes responsables de l'initialisation de ses champs <em/type/,
<em/format/, <em/data/, et <em/length/. (Le champ <em/format/ est
important ici - le serveur X l'utilise pour savoir si la donn<6E>e doit
<EFBFBD>tre <20>chang<6E>e par octet ou non. Habituellement, ce sera 8 (un
caract<EFBFBD>re), ou 32 (un entier)). Cette initialisation est faite en
utilisant l'appel&nbsp;:
<tscreen><verb>
void gtk_selection_data_set (GtkSelectionData *selection_data,
GdkAtom type,
gint format,
guchar *data,
gint length);
</verb></tscreen>
Cette fonction s'occupe de faire une copie correcte des donn<6E>es afin
que l'on n'ait pas <20> se soucier du reste. (On ne doit pas remplir ces
champs <20> la main).
<p>
Lorsque cela est demand<6E> par l'utilisateur, on r<>clame la possession
de la s<>lection en appelant&nbsp;:
<tscreen><verb>
gint gtk_selection_owner_set (GtkWidget *widget,
GdkAtom selection,
guint32 time);
</verb></tscreen>
Si une autre application r<>clame la possession de la s<>lection, on
recevra un "selection_clear_event".
Comme exemple de fourniture de s<>lection, l'exemple suivant ajoute une
fonctionnalit<EFBFBD> de s<>lection <20> un bouton commutateur. Lorsque ce bouton
est appuy<75>, le programme r<>clame la s<>lection primaire. La seule cible
support<EFBFBD>e (<28> part certaines cibles fournies par GTK lui-m<>me, comme
<EFBFBD> TARGETS <20>) est <20> STRING <20>. Lorsque celle-ci est demand<6E>e, on
retourne une repr<70>sentation de l'heure sous forme de cha<68>ne.
<tscreen><verb>
#include <gtk/gtk.h>
#include <time.h>
/* Fonction de rappel appel<65>e lorsque l'utilisateur commute la s<>lection. */
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 s<>lection <20>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 propri<72>taire <20> NULL,
* on v<>rifie que nous sommes bien son propri<72>taire 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;
}
}
}
/* Appel<65>e lorsqu'une autre application demande la s<>lection. */
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 s<>lection. */
void selection_handle (GtkWidget *widget,
GtkSelectionData *selection_data,
gpointer data)
{
gchar *timestr;
time_t current_time;
current_time = time (NULL);
timestr = asctime (localtime(&amp;current_time));
/* Lorsqu'on retourne une cha<68>ne, 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 (&amp;argc, &amp;argv);
/* Cr<43>ation de la fen<65>tre 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);
/* Cr<43>ation d'un bouton commutateur pour qu'il agisse comme une s<>lection. */
selection_button = gtk_toggle_button_new_with_label ("Demande de s<>lection");
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), &amp;have_selection);
gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event",
GTK_SIGNAL_FUNC (selection_clear), &amp;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;
}
</verb></tscreen>
<sect>glib<label id="sec_glib">
<p>
La <em/glib/ fournit de nombreuses fonctions et d<>finitions utiles,
pr<EFBFBD>tes <20> <20>tre utilis<69>es lorsqu'on cr<63>e des applications GDK et GTK. Je
les <20>num<75>rerais toutes avec une br<62>ve explication. Beaucoup sont des
r<EFBFBD>pliques des fonctions standards de la <em/libc/, et je ne les
d<EFBFBD>taillerais donc pas trop. Ceci doit surtout servir de r<>f<EFBFBD>rence afin
de savoir ce qui est disponible pour <20>tre utilis<69>.
<sect1>D<>finitions
<p>
Les d<>finitions pour les bornes de la plupart des types standards sont&nbsp;:
<tscreen><verb>
G_MINFLOAT
G_MAXFLOAT
G_MINDOUBLE
G_MAXDOUBLE
G_MINSHORT
G_MAXSHORT
G_MININT
G_MAXINT
G_MINLONG
G_MAXLONG
</verb></tscreen>
Voici aussi les red<65>finitions de types. Celles qui ne sont pas
sp<EFBFBD>cifi<EFBFBD>es sont configur<75>es dynamiquement selon l'architecture. <20>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.
<tscreen><verb>
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
</verb></tscreen>
<sect1>Listes doublement cha<68>n<EFBFBD>es
<p>
Les fonctions suivantes servent <20> cr<63>er, g<>rer et d<>truire des listes
doublement cha<68>n<EFBFBD>es. Je suppose que vous savez ce qu'est une liste
cha<EFBFBD>n<EFBFBD>e car leur explication n'entre pas dans le cadre de ce
document. Bien s<>r, il n'y a pas besoin de les conna<6E>tre pour une
utilisation g<>n<EFBFBD>rale de GTK, mais c'est bien de savoir comment elles
fonctionnent.
<tscreen><verb>
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);
</verb></tscreen>
<sect1>Listes simplement cha<68>n<EFBFBD>es
<p>
La plupart des fonctions pour les listes simplement cha<68>n<EFBFBD>es
ci-dessous sont identiques <20> celles vues plus haut. Voici une liste
compl<EFBFBD>te&nbsp;:
<tscreen><verb>
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);
</verb></tscreen>
<sect1>Gestion de la m<>moire
<p>
<tscreen><verb>
gpointer g_malloc (gulong size);
</verb></tscreen>
Remplace <em/malloc()/. On n'a pas besoin de v<>rifier la valeur de
retour car cela est fait pour nous dans cette fonction.
<tscreen><verb>
gpointer g_malloc0 (gulong size);
</verb></tscreen>
Identique <20> la pr<70>c<EFBFBD>dente, mais initialise la m<>moire <20> z<>ro avant de
retourner un pointeur vers la zone r<>serv<72>e.
<tscreen><verb>
gpointer g_realloc (gpointer mem,
gulong size);
</verb></tscreen>
R<EFBFBD>alloue <em/size/ octets de m<>moire <20> partir de <em/mem/. <20>videmment,
la m<>moire doit avoir <20>t<EFBFBD> allou<6F>e auparavant.
<tscreen><verb>
void g_free (gpointer mem);
</verb></tscreen>
Lib<EFBFBD>re la m<>moire. Facile.
<tscreen><verb>
void g_mem_profile (void);
</verb></tscreen>
Produit un profil de la m<>moire utilis<69>e, mais requiert l'ajout de
<em/#define MEM_PROFILE/ au d<>but de <em>glib/gmem.c</em>,
de refaire un <em/make/ et un <em/make install/.
<tscreen><verb>
void g_mem_check (gpointer mem);
</verb></tscreen>
V<EFBFBD>rifie qu'un emplacement m<>moire est valide. N<>cessite que l'on
ajoute <em/#define MEM_CHECK/ au d<>but de <em/gmem.c/ que l'on refasse
un <em/make/ et un <em/make install/.
<sect1>Timers
<p>
Fonctions des timers...
<tscreen><verb>
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);
</verb></tscreen>
<sect1>Gestion des cha<68>nes
<p>
Un ensemble complet de fonction de gestion des cha<68>nes. Elles semblent
toutes tr<74>s int<6E>ressantes et sont s<>rement meilleures, <20> bien des
<EFBFBD>gards, que les fonctions C standards, mais elle n<>cessitent de la
documentation.
<tscreen><verb>
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,
...);
</verb></tscreen>
<sect1>Utilitaires et fonctions d'erreurs
<p>
<tscreen><verb>
gchar* g_strdup (const gchar *str);
</verb></tscreen>
Remplace la fonction <em/strdup/. Elle copie le contenu de la cha<68>ne
d'origine dans la m<>moire venant d'<27>tre allou<6F>e et retourne un
pointeur sur cette zone.
<tscreen><verb>
gchar* g_strerror (gint errnum);
</verb></tscreen>
Je recommande de l'utiliser pour tous les messages d'erreur. Elle est
beaucoup plus propre et plus portable que <em/perror()/ ou les
autres. La sortie est habituellement de la forme&nbsp;:
<tscreen><verb>
nom du programme:fonction qui a <20>chou<6F>:fichier ou autre descripteur:strerror
</verb></tscreen>
Voici un exemple d'appel utilis<69> dans le programme <20> Bonjour tout le monde ! <20>&nbsp;:
<tscreen><verb>
g_print("bonjour_monde:open:%s:%s\n", filename, g_strerror(errno));
</verb></tscreen>
<tscreen><verb>
void g_error (gchar *format, ...);
</verb></tscreen>
Affiche un message d'erreur. Le format est comme <em/printf/, mais il
ajoute <20> ** ERROR **: <20> au d<>but du message et sort du programme. <20>
n'utiliser que pour les erreurs fatales.
<tscreen><verb>
void g_warning (gchar *format, ...);
</verb></tscreen>
Comme au dessus, mais ajoute <20> ** WARNING **: <20>, et ne termine pas le
programme.
<tscreen><verb>
void g_message (gchar *format, ...);
</verb></tscreen>
Affiche <20> message: <20> avant la cha<68>ne pass<73>e en param<61>tre.
<tscreen><verb>
void g_print (gchar *format, ...);
</verb></tscreen>
Remplace <em/printf()/.
Enfin la derni<6E>re fonction&nbsp;:
<tscreen><verb>
gchar* g_strsignal (gint signum);
</verb></tscreen>
Affiche le nom du signal syst<73>me Unix correspondant au num<75>ro de
signal. Utile pour les fonctions g<>n<EFBFBD>riques de gestion de signaux.
Tout ce qui est ci-dessus est plus ou moins vol<6F> <20> <em/glib.h/. Si
quelqu'un s'occupe de documenter une fonction, qu'il m'envoit un
courrier !
<sect>Fichiers rc de GTK
<p>
GTK a sa propre m<>thode pour g<>rer les configurations par d<>faut des
applications, en utilisant des fichiers <tt/rc/. Ceux-ci peuvent <20>tre
utilis<EFBFBD>s pour configurer les couleurs de presque tous les widgets, et
pour mettre des pixmaps sur le fond de certains widgets.
<sect1>Fonctions pour les fichiers rc
<p>
Au d<>marrage de votre application, ajoutez un appel <20>&nbsp;:
<tscreen><verb>
void gtk_rc_parse (char *filename);
</verb></tscreen>
<p>
en lui passant le nom de votre fichier rc. Ceci forcera GTK <20> analyser
ce fichier et <20> utiliser les configurations de styles pour les types
de widgets qui y sont d<>finis.
<p>
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 <20>&nbsp;:
<tscreen><verb>
void gtk_widget_set_name (GtkWidget *widget,
gchar *name);
</verb></tscreen>
<p>
En lui passant comme premier param<61>tre le widget que vous avez cr<63><72>,
et le nom que vous voulez lui donner comme second param<61>tre. Ceci vous
permettra de changer les attributs de ce widget par son nom dans le
fichier rc.
<p>
Si vous utilisez un appel comme celui-ci&nbsp;:
<tscreen><verb>
button = gtk_button_new_with_label ("Bouton Sp<53>cial");
gtk_widget_set_name (button, "bouton special");
</verb></tscreen>
<p>
Ce bouton s'appelle <20> bouton special <20> et peut <20>tre acc<63>d<EFBFBD> par son nom
dans le fichier rc en tant que <20> bouton special.GtkButton <20>. [<---
V<EFBFBD>rifiez !]
<p>
Le fichier rc ci-dessous configure les propri<72>t<EFBFBD>s de la fen<65>tre
principale et fait h<>riter tous les fils de celle-ci du style d<>crit
par <20> bouton_principal <20>. Le code utilis<69> dans l'application
est&nbsp;:
<tscreen><verb>
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_widget_set_name (window, "fenetre principale");
</verb></tscreen>
<p>
Et le style est d<>fini dans le fichier rc avec&nbsp;:
<tscreen><verb>
widget "fenetre principale.*GtkButton*" style "bouton_principal"
</verb></tscreen>
<p>
Ce qui configure tous les widgets <em/GtkButton/ de <20> fen<65>tre
principale <20> avec le style <20> bouton_principal <20> d<>fini dans le fichier
rc.
<p>
Ainsi que vous pouvez le voir, il s'agit d'un syst<73>me puissant et
flexible. Utilisez votre imagination pour en tirer le meilleur.
<sect1>Format des fichiers rc de GTK
<p>
Le format du fichier GTK est illustr<74> dans l'exemple suivant. Il
s'agit du fichier <em/testgtkrc/ de la distribution GTK mais j'ai
ajout<EFBFBD> quelques commentaires et autres choses. Vous pouvez inclure
cette explication <20> votre application pour permettre <20> l'utilisateur
de r<>gler finement son application.
<p>
Il y a plusieurs directives pour changer les attributs d'un widget.
<itemize>
<item>fg - configure la couleur de premier plan d'un widget.
<item>bg - configure la couleur d'arri<72>re plan d'un widget.
<item>bg_pixmap - configure l'arri<72>re plan d'un widget avec un pixmap.
<item>font - configure la fonte <20> utiliser pour un widget.
</itemize>
<p>
De plus, un widget peut se trouver dans diff<66>rents <20>tats et l'on peut
configurer des couleurs, pixmaps et fontes diff<66>rentes pour chacun
d'eux. Ces <20>tats sont&nbsp;:
<itemize>
<item>NORMAL - L'<27>tat normal d'un widget, sans la souris au dessus de
lui, non press<73>, etc.
<item>PRELIGHT - Lorsque la souris se trouve au dessus du widget, les
couleurs d<>finies pour cet <20>tat sont actives.
<item>ACTIVE - Lorsque le widget est press<73> ou cliqu<71>, il devient
actif et les attributs associ<63>s <20> cet <20>tat sont appliqu<71>s.
<item>INSENSITIVE - Quand un widget est configur<75> pour <20>tre
insensible et qu'il ne peut <20>tre activ<69>, il prend ces attributs.
<item>SELECTED - Lorsqu'un objet est choisi, il prend ces attributs.
</itemize>
<p>
Lorsqu'on utilise les mots-cl<63>s <20> <em/fg/ <20> et <20> <em/bg/ <20> pour
configurer les couleurs des widgets, le format est&nbsp;:
<tscreen><verb> fg[<STATE>] = { Red, Green, Blue } </verb></tscreen>
<p>
O<EFBFBD> STATE est l'un des <20>tats vus plus haut (PRELIGHT, ACTIVE etc), et
o<EFBFBD> <em/Red/, <em/Green/ et <em/Blue/ sont des valeurs comprises entre
0 et 1.0. { 1.0, 1.0, 1.0 } repr<70>sente la couleur blanche. Ces
valeurs doivent <20>tre de type r<>el ou elles seront consid<69>r<EFBFBD>es comme
valant 0, ainsi un simple <20> 1 <20> ne marchera pas, il faut mettre <20> 1.0
<EFBFBD>. Un <20> 0 <20> simple convient car ce n'est pas un probl<62>me s'il n'est
pas reconnu puisque toutes les valeurs non reconnues sont mises <20> 0.
<p>
<em/bg_pixmap/ est tr<74>s similaire, sauf que les couleurs sont
remplac<EFBFBD>es par un nom de fichier.
<em/pixmap_path/ est une liste de chemins s<>par<61>s par des <20> : <20>. Ces
chemins seront parcourus pour chaque pixmap que l'on sp<73>cifie.
<p>
La directive <em/font/ est simplement&nbsp;:
<tscreen><verb>
font = "<font name>"
</verb></tscreen>
<p>
O<EFBFBD> la seule partie difficile est d'arriver <20> comprendre la cha<68>ne
contenant le nom de la fonte. L'utilisation de <em/xfontsel/ ou d'un
autre utilitaire semblable peut aider.
<p>
<EFBFBD> <em/widget_class/ <20> configure le style d'une classe de widgets. Ces
classes sont list<73>es dans la section sur la hi<68>rarchie des widgets.
<p>
La directive <20> <em/widget/ <20> configure un ensemble sp<73>cifique de
widgets selon un style donn<6E>, annulant tout style de configuration
pour la classe de widget donn<6E>e. Ces widgets sont enregistr<74>s dans
l'application en utilisant l'appel <em/gtk_widget_set_name()/. Ceci
vous permet de sp<73>cifier les attributs d'un widget, widget par widget,
au lieu de configurer les attributs d'une classe enti<74>re de
widgets. Je vous demande instamment de documenter tous ces widgets
sp<EFBFBD>ciaux pour que les utilisateurs puisse les adapter <20> leurs besoins.
<p>
Lorsque le mot-cl<63> <20> <em/parent/ <20> est utilis<69> comme attribut, le
widget prendra les attributs de son parent dans l'application.
<p>
Lorsqu'on d<>finit un style, on peut assigner les attributs d'un style
d<EFBFBD>j<EFBFBD> d<>fini <20> ce nouveau style.
<tscreen><verb>
style "bouton_principal" = "button"
{
font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
bg[PRELIGHT] = { 0.75, 0, 0 }
}
</verb></tscreen>
<p>
Cet exemple prend le style "button" et cr<63>e un nouveau style
"bouton_principal"en changeant simplement la fonte et la couleur de
fond pour l'<27>tat PRELIGHT.
<p>
Bien s<>r, un bon nombre de ces attributs ne s'applique pas <20> tous les
widgets. C'est une question de bon sens. Tout ce qui peut s'appliquer
s'applique.
<sect1>Exemple de fichier rc
<p>
<tscreen><verb>
# pixmap_path "<dir 1>:<dir 2>:<dir 3>:..."
#
pixmap_path "/usr/include/X11R6/pixmaps:/home/imain/pixmaps"
#
# style <name> [= <name>]
# {
# <option>
# }
#
# widget <widget_set> style <style_name>
# widget_class <widget_class_set> style <style_name>
# Voici une liste des <20>tats possibles. Remarquez que certains ne s'appliquent
# pas <20> certains widgets.
#
# NORMAL - L'<27>tat normal d'un widget, sans la souris au dessus de lui,
# non press<73>, etc.
#
# PRELIGHT - Lorsque la souris se trouve au dessus du widget, les couleurs
# d<>finies pour cet <20>tat sont actives.
#
# ACTIVE - Lorsque le widget est press<73> ou cliqu<71>, il devient actif et les
# attributs associ<63>s <20> cet <20>tat sont appliqu<71>s.
#
# INSENSITIVE - Quand un widget est configur<75> pour <20>tre insensible, et qu'il
# ne peut <20>tre activ<69>, il prend ces attributs.
#
# SELECTED - Lorsqu'un objet est choisi, il prend ces attributs.
#
# Avec ces <20>tats, on peut configurer les attributs des widgets dans chacun
# de ces <20>tats en utilisant les directives suivantes.
#
# fg - configure la couleur de premier plan d'un widget.
# bg - configure la couleur d'arri<72>re plan d'un widget.
# bg_pixmap - configure l'arri<72>re plan d'un widget avec un pixmap.
# font - configure la fonte <20> utiliser pour un widget.
# Configuration d'un style appel<65> "button". Le nom n'est pas important
# car il est assign<67> aux widgets r<>els <20> la fin du fichier.
style "window"
{
#Configure l'espace autour de la fen<65>tre avec le pixmap sp<73>cifi<66>.
#bg_pixmap[<STATE>] = "<pixmap filename>"
bg_pixmap[NORMAL] = "warning.xpm"
}
style "scale"
{
#Configure la couleur de premier plan (celle de la fonte) <20> rouge
#lorsqu'on est dans l'<27>tat "NORMAL".
fg[NORMAL] = { 1.0, 0, 0 }
#Configure le pixmap d'arri<72>re plan de ce widget <20> celui de son parent.
bg_pixmap[NORMAL] = "<parent>"
}
style "button"
{
# Voici tous les <20>tats possibles pour un bouton. Le seul qui ne peut
# s'appliquer est l'<27>tat SELECTED.
fg[PRELIGHT] = { 0, 1.0, 1.0 }
bg[PRELIGHT] = { 0, 0, 1.0 }
bg[ACTIVE] = { 1.0, 0, 0 }
fg[ACTIVE] = { 0, 1.0, 0 }
bg[NORMAL] = { 1.0, 1.0, 0 }
fg[NORMAL] = { .99, 0, .99 }
bg[INSENSITIVE] = { 1.0, 1.0, 1.0 }
fg[INSENSITIVE] = { 1.0, 0, 1.0 }
}
# Dans cet exemple, on h<>rite des attributs du style "button" puis on
# <20>crase la fonte et la couleur de fond pour cr<63>er un nouveau style
# "main_button".
style "main_button" = "button"
{
font = "-adobe-helvetica-medium-r-normal--*-100-*-*-*-*-*-*"
bg[PRELIGHT] = { 0.75, 0, 0 }
}
style "toggle_button" = "button"
{
fg[NORMAL] = { 1.0, 0, 0 }
fg[ACTIVE] = { 1.0, 0, 0 }
# Configure le pixmap de fond du toggle_button <20> celui de son widget
# parent (comme d<>fini dans l'application).
bg_pixmap[NORMAL] = "<parent>"
}
style "text"
{
bg_pixmap[NORMAL] = "marble.xpm"
fg[NORMAL] = { 1.0, 1.0, 1.0 }
}
style "ruler"
{
font = "-adobe-helvetica-medium-r-normal--*-80-*-*-*-*-*-*"
}
# pixmap_path "~/.pixmaps"
# Configuration des types de widget pour utiliser les styles d<>finis
# plus haut.
# Les types de widget sont list<73>s dans la hi<68>rarchie des classes, mais
# peut probablement <20>tre list<73>e dans ce document pour que l'utilisateur
# puisse s'y r<>f<EFBFBD>rer.
widget_class "GtkWindow" style "window"
widget_class "GtkDialog" style "window"
widget_class "GtkFileSelection" style "window"
widget_class "*Gtk*Scale" style "scale"
widget_class "*GtkCheckButton*" style "toggle_button"
widget_class "*GtkRadioButton*" style "toggle_button"
widget_class "*GtkButton*" style "button"
widget_class "*Ruler" style "ruler"
widget_class "*GtkText" style "text"
# Configure tous les boutons fils de la "main window" avec le style
# main_button. Ceci doit <20>tre document<6E> pour en tirer profit.
widget "main window.*GtkButton*" style "main_button"
</verb></tscreen>
<sect><3E>criture de vos propres widgets
<p>
<sect1>Vue d'ensemble
<p>
Bien que la distribution GTK fournisse de nombreux types de widgets
qui devraient couvrir la plupart des besoins de base, il peut arriver
un moment o<> vous aurez besoin de cr<63>er votre propre type de
widget. Comme GTK utilise l'h<>ritage de widget de fa<66>on intensive et
qu'il y a d<>j<EFBFBD> un widget ressemblant <20> celui que vous voulez, il est
souvent possible de cr<63>er un nouveau type de widget en seulement
quelques lignes de code. Mais, avant de travailler sur un nouveau
widget, il faut v<>rifier d'abord que quelqu'un ne l'a pas d<>j<EFBFBD>
<EFBFBD>crit. Ceci <20>viter la duplication des efforts et maintient au minimum
le nombre de widgets, ce qui permet de garder la coh<6F>rence du code et
de l'interface des diff<66>rentes applications. Un effet de bord est que,
lorsque l'on a cr<63><72> un nouveau widget, il faut l'annoncer afin que les
autres puissent en b<>n<EFBFBD>ficier. Le meilleur endroit pour faire cela
est, sans doute, la <tt>gtk-list</tt>.
<sect1>Anatomie d'un widget
<p>
Afin de cr<63>er un nouveau widget, il importe de comprendre comment
fonctionnent les objets GTK. Cette section ne se veut <20>tre qu'un
rapide survol. Consultez la documentation de r<>f<EFBFBD>rence pour plus de
d<EFBFBD>tails.
<p>
Les widgets sont implant<6E>s selon une m<>thode orient<6E>e
objet. Cependant, ils sont <20>crits en C standard. Ceci am<61>liore
beaucoup la portabilit<69> et la stabilit<69>, par contre cela signifie que
celui qui <20>crit des widgets doit faire attention <20> certains d<>tails
d'implantation. Les informations communes <20> toutes les instances d'une
classe de widget (tous les widgets boutons, par exemple) sont stock<63>es
dans la <em/structure de la classe/. Il n'y en a qu'une copie dans
laquelle sont stock<63>es les informations sur les signaux de la
classe (fonctionnement identique aux fonctions virtuelles en C). Pour
permettre l'h<>ritage, le premier champ de la structure de classe doit
<EFBFBD>tre une copie de la structure de classe du p<>re. La d<>claration de la
structure de classe de <em/GtkButton/ ressemble <20> ceci&nbsp;:
<tscreen><verb>
struct _GtkButtonClass
{
GtkContainerClass parent_class;
void (* pressed) (GtkButton *button);
void (* released) (GtkButton *button);
void (* clicked) (GtkButton *button);
void (* enter) (GtkButton *button);
void (* leave) (GtkButton *button);
};
</verb></tscreen>
<p>
Lorsqu'un bouton est trait<69> comme un container (par exemple, lorsqu'il
change de taille), sa structure de classe peut <20>tre convertie en
<em/GtkContainerClass/ et les champs ad<61>quats utilis<69>s pour g<>rer les
signaux.
<p>
Il y a aussi une structure pour chaque widget cr<63><72> sur une base
d'instance. Cette structure a des champs pour stocker les informations
qui sont diff<66>rentes pour chaque instance du widget. Nous l'appelerons
<em/structure d'objet/. Pour la classe <em/Button/, elle ressemble
<EFBFBD>&nbsp;:
<tscreen><verb>
struct _GtkButton
{
GtkContainer container;
GtkWidget *child;
guint in_button : 1;
guint button_down : 1;
};
</verb></tscreen>
<p>
Notez que, comme pour la structure de classe, le premier champ est la
structure d'objet de la classe parente, cette structure peut donc <20>tre
convertie dans la structure d'objet de la classe parente si besoin
est.
<sect1> Cr<43>ation d'un widget compos<6F>
<sect2> Introduction
<p>
Un type de widget qui peut <20>tre int<6E>ressant <20> cr<63>er est un widget qui
est simplement un agr<67>gat d'autres widgets GTK. Ce type de widget ne
fait rien qui ne pourrait <20>tre fait sans cr<63>er de nouveaux widgets,
mais offre une m<>thode pratique pour empaqueter les <20>l<EFBFBD>ments d'une
interface utilisateur afin de la r<>utiliser facilement. Les widgets
<em/FileSelection/ et <em/ColorSelection/ de la distribution standard
sont des exemples de ce type de widget.
<p>
L'exemple de widget que nous allons cr<63>er dans cette section cr<63>era un
widget <em/Tictactoe/, un tableau de 3x3 boutons commutateurs qui
d<EFBFBD>clenche un signal lorsque tous les boutons d'une ligne, d'une
colonne, ou d'une diagonale sont press<73>s.
<sect2> Choix d'une classe parent
<p>
La classe parent d'un widget compos<6F> est, typiquement, la classe
container contenant tous les <20>l<EFBFBD>ments du widget compos<6F>. Par exemple,
la classe parent du widget <em/FileSelection/ est la classe
<em/Dialog/. Comme nos boutons seront mis sous la forme d'un tableau,
il semble naturel d'utiliser la classe <em/GtkTable/ comme
parent. Malheureusement, cela ne peut marcher. La cr<63>ation d'un widget
est divis<69>e en deux fonctions -- <em/WIDGETNAME_new()/ que
l'utilisateur appelle, et <em/WIDGETNAME_init()/ qui r<>alise le
travail d'initialisation du widget ind<6E>pendamment des param<61>tre pass<73>s
<EFBFBD> la fonction <tt/_new()/. Les widgets fils n'appellent que la
fonction <em/_init/ de leur widget parent. Mais cette division du
travail ne fonctionne pas bien avec les tableaux qui, lorsqu'ils sont
cr<EFBFBD><EFBFBD>s, ont besoin de conna<6E>tre leue nombre de lignes et de
colonnes. Sauf <20> dupliquer la plupart des fonctionnalit<69>s de
<em/gtk_table_new()/ dans notre widget <em/Tictactoe/, nous ferions
mieux d'<27>viter de le d<>river de <em/GtkTable/. Pour cette raison, nous
la d<>riverons plut<75>t de <em/GtkVBox/ et nous placerons notre table
dans la VBox.
<sect2> The header file
<p>
Chaque classe de widget poss<73>de un fichier en-t<>te qui d<>clare les
structures d'objet et de classe pour ce widget, en plus de fonctions
publiques. Quelques caract<63>ristiques m<>ritent d'<27>tre indiqu<71>es. Afin
d'<27>viter des d<>finitions multiples, on enveloppe le fichier en-t<>te
avec&nbsp:
<tscreen><verb>
#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
.
.
.
#endif /* __TICTACTOE_H__ */
</verb></tscreen>
Et, pour faire plaisir aux programmes C++ qui inclueront ce fichier, on l'enveloppe aussi dans&nbsp;:
<tscreen><verb>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
.
.
.
#ifdef __cplusplus
}
#endif /* __cplusplus */
</verb></tscreen>
En plus des fonctions et structures, nous d<>clarons trois macros
standard, <tt/TICTACTOE(obj)/, <tt/TICTACTOE_CLASS(class)/, et
<tt/IS_TICTACTOE(obj)/, qui, respectivement, convertissent un pointeur
en un pointeur vers une structure d'objet ou de classe, et v<>rifient
si un objet est un widget Tictactoe.
<p>
Voici le fichier en-t<>te complet&nbsp;:
<tscreen><verb>
#ifndef __TICTACTOE_H__
#define __TICTACTOE_H__
#include <gdk/gdk.h>
#include <gtk/gtkvbox.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define TICTACTOE(obj) GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
#define TICTACTOE_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
#define IS_TICTACTOE(obj) GTK_CHECK_TYPE (obj, tictactoe_get_type ())
typedef struct _Tictactoe Tictactoe;
typedef struct _TictactoeClass TictactoeClass;
struct _Tictactoe
{
GtkVBox vbox;
GtkWidget *buttons[3][3];
};
struct _TictactoeClass
{
GtkVBoxClass parent_class;
void (* tictactoe) (Tictactoe *ttt);
};
guint tictactoe_get_type (void);
GtkWidget* tictactoe_new (void);
void tictactoe_clear (Tictactoe *ttt);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __TICTACTOE_H__ */
</verb></tscreen>
<sect2>La fonction <tt/_get_type()/
<p>
Continuons maintenant avec l'implantation de notre widget. La fonction
centrale pour chaque widget est <em/WIDGETNAME_get_type()/. Cette
fonction, lorsqu'elle est appel<65>e pour la premi<6D>re fois, informe le
GTK de la classe et r<>cup<75>re un ID permettant d'identifier celle-ci de
fa<EFBFBD>on unique. Lors des appels suivants, elle ne fait que retourner cet
ID.
<tscreen><verb>
guint
tictactoe_get_type ()
{
static guint ttt_type = 0;
if (!ttt_type)
{
GtkTypeInfo ttt_info =
{
"Tictactoe",
sizeof (Tictactoe),
sizeof (TictactoeClass),
(GtkClassInitFunc) tictactoe_class_init,
(GtkObjectInitFunc) tictactoe_init,
(GtkArgFunc) NULL,
};
ttt_type = gtk_type_unique (gtk_vbox_get_type (), &amp;ttt_info);
}
return ttt_type;
}
</verb></tscreen>
<p>
La structure <em/GtkTypeInfo/ est d<>finie de la fa<66>on suivante&nbsp;:
<tscreen><verb>
struct _GtkTypeInfo
{
gchar *type_name;
guint object_size;
guint class_size;
GtkClassInitFunc class_init_func;
GtkObjectInitFunc object_init_func;
GtkArgFunc arg_func;
};
</verb></tscreen>
<p>
Les champs de cette structure s'expliquent d'eux-m<>mes. Nous
ignorerons le champ <em/arg_func/ ici&nbsp;: il a un r<>le important
permettant aux options des widgets d'<27>tre correctement initialis<69>es <20>
partir des langages interpr<70>t<EFBFBD>s, mais cette fonctionnalit<69> est encore
tr<EFBFBD>s peu implant<6E>e. Lorsque GTK dispose d'une copie correctement
remplie de cette structure, il sait comment cr<63>er des objets d'un type
particulier de widget.
<sect2>La fonction <em/_class_init()/
<p>
La fonction <em/WIDGETNAME_class_init()/ initialise les champs de la
structure de classe du widget et configure tous les signaux de cette
classe. Pour notre widget Tictactoe, cet appel est&nbsp;:
<tscreen><verb>
enum {
TICTACTOE_SIGNAL,
LAST_SIGNAL
};
static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
static void
tictactoe_class_init (TictactoeClass *class)
{
GtkObjectClass *object_class;
object_class = (GtkObjectClass*) class;
tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe",
GTK_RUN_FIRST,
object_class->type,
GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
gtk_signal_default_marshaller, GTK_ARG_NONE, 0);
gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
class->tictactoe = NULL;
}
</verb></tscreen>
<p>
Notre widget n'a qu'un signal&nbsp;: "tictactoe", invoqu<71> lorsqu'une
ligne, une colonne ou une diagonale est compl<70>tement remplie. Tous les
widgets compos<6F>s n'ont pas besoin de signaux. Si vous lisez ceci pour
la premi<6D>re fois, vous pouvez passer directement <20> la section suivante
car les choses vont se compliquer un peu
La fonction :
<tscreen><verb>
gint gtk_signal_new (gchar *name,
GtkSignalRunType run_type,
gint object_type,
gint function_offset,
GtkSignalMarshaller marshaller,
GtkArgType return_val,
gint nparams,
...);
</verb></tscreen>
cr<EFBFBD>e un nouveau signal. Les param<61>tres sont :
<itemize>
<item> <em/name/ : Le nom du signal signal.
<item> <em/run_type/ : Indique si le gestionnaire par d<>faut doit <20>tre
lanc<EFBFBD> avant ou apr<70>s le gestionnaire de l'utilisateur. Le plus
souvent, ce sera <tt/GTK_RUN_FIRST/, ou <tt/GTK_RUN_LAST/, bien qu'il
y ait d'autres possibilit<69>s.
<item> <em/object_type/ : L'ID de l'objet auquel s'applique ce signal
(il s'appliquera aussi au descendants de l'objet).
<item> <em/function_offset/ : L'offset d'un pointeur vers le
gestionnaire par d<>faut dans la structure de classe.
<item> <em/marshaller/ : Fonction utilis<69>e pour invoquer le
gestionnaire de signal. Pour les gestionnaires de signaux n'ayant pas
d'autres param<61>tres que l'objet <20>metteur et les donn<6E>es utilisateur,
on peut utiliser la fonction pr<70>d<EFBFBD>finie
<em/gtk_signal_default_marshaller()/.
<item> <em/return_val/ : Type de la valeur retourn<72>e.
<item> <em/nparams/ : Nombre de param<61>tres du gestionnaire de signal
(autres que les deux par d<>faut mentionn<6E>s plus haut).
<item> <em/.../ : Types des param<61>tres.
</itemize>
Lorsque l'on sp<73>cifie les types, on utilise l'<27>num<75>ration
<em/GtkArgType/&nbsp;:
<tscreen><verb>
typedef enum
{
GTK_ARG_INVALID,
GTK_ARG_NONE,
GTK_ARG_CHAR,
GTK_ARG_SHORT,
GTK_ARG_INT,
GTK_ARG_LONG,
GTK_ARG_POINTER,
GTK_ARG_OBJECT,
GTK_ARG_FUNCTION,
GTK_ARG_SIGNAL
} GtkArgType;
</verb></tscreen>
<p>
<em/gtk_signal_new()/ retourne un identificateur entier pour le
signal, que l'on stocke dans le tableau <em/tictactoe_signals/, indic<69>
par une <20>num<75>ration (conventionnellement, les <20>l<EFBFBD>ments de
l'<27>num<75>ration sont le nom du signal, en majuscules, mais, ici, il y
aurait un conflit avec la macro <tt/TICTACTOE()/, nous l'appellerons
donc <tt/TICTACTOE_SIGNAL/ <20> la place.
Apr<EFBFBD>s avoir cr<63><72> nos signaux, nous devons demander <20> GTK d'associer
ceux-ci <20> la classe Tictactoe. Ceci est fait en appelant
<em/gtk_object_class_add_signals()/. Puis nous configurons le pointeur
qui pointe sur le gestionnaire par d<>faut du signal "tictactoe" <20>
NULL, pour indiquer qu'il n'y a pas d'action par d<>faut.
<sect2>La fonction <em/_init()/
<p>
Chaque classe de widget a aussi besoin d'une fonction pour initialiser
la structure d'objet. Habituellement, cette fonction a le r<>le, plut<75>t
limit<EFBFBD>, d'initialiser les champs de la structure avec des valeurs par
d<EFBFBD>faut. Cependant, pour les widgets compos<6F>s, cette fonction cr<63>e
aussi les widgets composants.
<tscreen><verb>
static void
tictactoe_init (Tictactoe *ttt)
{
GtkWidget *table;
gint i,j;
table = gtk_table_new (3, 3, TRUE);
gtk_container_add (GTK_CONTAINER(ttt), table);
gtk_widget_show (table);
for (i=0;i<3; i++)
for (j=0;j<3; j++)
{
ttt->buttons[i][j] = gtk_toggle_button_new ();
gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
i, i+1, j, j+1);
gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled",
GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
gtk_widget_show (ttt->buttons[i][j]);
}
}
</verb></tscreen>
<sect2> Et le reste...
<p>
Il reste une fonction que chaque widget (sauf pour les types widget de
base, comme <em/GtkBin/, qui ne peuvent <20>tre instanci<63>s) <20> besoin
d'avoir -- celle que l'utilisateur appelle pour cr<63>er un objet de ce
type. Elle est conventionnellement appel<65>e <em/WIDGETNAME_new()/. Pour
certains widgets, par pour ceux de Tictactoe, cette fonction prend des
param<EFBFBD>tres et r<>alise certaines initialisations d<>pendantes des
param<EFBFBD>tres. Les deux autres fonctions sont sp<73>cifiques au widget
Tictactoe.
<p>
<em/tictactoe_clear()/ est une fonction publique qui remet tous les
boutons du widget en position rel<65>ch<63>e. Notez l'utilisation de
<em/gtk_signal_handler_block_by_data()/ pour emp<6D>cher notre
gestionnaire de signaux des boutons commutateurs d'<27>tre d<>clench<63> sans
besoin.
<p>
<em/tictactoe_toggle()/ est le gestionnaire de signal invoqu<71>
lorsqu'on clique sur un bouton. Il v<>rifie s'il y a des combinaisons
gagnantes concernant le bouton qui vient d'<27>tre commut<75> et, si c'est
le cas, <20>met le signal "tictactoe".
<tscreen><verb>
GtkWidget*
tictactoe_new ()
{
return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
}
void
tictactoe_clear (Tictactoe *ttt)
{
int i,j;
for (i=0;i<3;i++)
for (j=0;j<3;j++)
{
gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
FALSE);
gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
}
}
static void
tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
{
int i,k;
static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 1, 2 }, { 0, 1, 2 } };
static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
{ 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
{ 0, 1, 2 }, { 2, 1, 0 } };
int success, found;
for (k=0; k<8; k++)
{
success = TRUE;
found = FALSE;
for (i=0;i<3;i++)
{
success = success &amp;&amp;
GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
found = found ||
ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
}
if (success &amp;&amp; found)
{
gtk_signal_emit (GTK_OBJECT (ttt),
tictactoe_signals[TICTACTOE_SIGNAL]);
break;
}
}
}
</verb></tscreen>
<p>
Enfin, un exemple de programme utilisant notre widget Tictactoe&nbsp;
<tscreen><verb>
#include <gtk/gtk.h>
#include "tictactoe.h"
/* Invoqu<71> lorsqu'une ligne, une colonne ou une diagonale est compl<70>te */
void win (GtkWidget *widget, gpointer data)
{
g_print ("Ouais !\n");
tictactoe_clear (TICTACTOE (widget));
}
int main (int argc, char *argv[])
{
GtkWidget *window;
GtkWidget *ttt;
gtk_init (&amp;argc, &amp;argv);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
gtk_signal_connect (GTK_OBJECT (window), "destroy",
GTK_SIGNAL_FUNC (gtk_exit), NULL);
gtk_container_border_width (GTK_CONTAINER (window), 10);
/* Cr<43>ation d'un widget Tictactoe */
ttt = tictactoe_new ();
gtk_container_add (GTK_CONTAINER (window), ttt);
gtk_widget_show (ttt);
/* On lui attache le signal "tictactoe" */
gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe",
GTK_SIGNAL_FUNC (win), NULL);
gtk_widget_show (window);
gtk_main ();
return 0;
}
</verb></tscreen>
<sect1> Cr<43>ation d'un widget <20> partir de z<>ro
<sect2> Introduction
<p>
Dans cette section, nous en apprendrons plus sur la fa<66>on dont les
widgets s'affichent eux-m<>mes <20> l'<27>cran et comment ils interagissent
avec les <20>v<EFBFBD>nements. Comme exemple, nous cr<63>erons un widget d'appel
t<EFBFBD>lephonique interactif avec un pointeur que l'utilisateur pourra
d<EFBFBD>placer pour initialiser la valeur.
<sect2>Afficher un widget <20> l'<27>cran
<p>
Il y a plusieurs <20>tapes mises en jeu lors de l'affichage. Lorsque le widget est
cr<EFBFBD><EFBFBD> par l'appel <em/WIDGETNAME_new()/, plusieurs autres fonctions
suppl<EFBFBD>mentaires sont requises.
<itemize>
<item> <em/WIDGETNAME_realize()/ s'occupe de cr<63>er une fen<65>tre X pour le
widget, s'il en a une.
<item> <em/WIDGETNAME_map()/ est invoqu<71>e apr<70>s l'appel de
<em/gtk_widget_show()/. Elle s'assure que le widget est bien trac<61> <20> l'<27>cran
(<em/mapp<70>/). Dans le cas d'une classe container, elle doit aussi appeler des
fonctions <em/map()/> pour chaque widget fils.
<item> <em/WIDGETNAME_draw()/ est invoqu<71>e lorsque <em/gtk_widget_draw()/ est
appel<EFBFBD> pour le widget ou l'un de ces anc<6E>tres. Elle r<>alise les v<>ritables
appels aux fonctions de dessin pour tracer le widget <20> l'<27>cran. Pour les
widgets containers, cette fonction doit appeler <em/gtk_widget_draw()/ pour ses
widgets fils.
<item> <em/WIDGETNAME_expose()/ est un gestionnaire pour les <20>v<EFBFBD>nements
d'exposition du widget. Il r<>alise les appels n<>cessaires aux fonctions de
dessin pour tracer la partie expos<6F>e <20> l'<27>cran. Pour les widgets containers,
cette fonction doit g<>n<EFBFBD>rer les <20>v<EFBFBD>nements d'exposition pour ses widgets
enfants n'ayant pas leurs propres fen<65>tres (s'ils ont leurs propres fen<65>tres, X
g<EFBFBD>n<EFBFBD>rera les <20>v<EFBFBD>nements d'exposition n<>cessaires).
</itemize>
<p>
Vous avez pu noter que les deux derni<6E>res fonctions sont assez similaires --
chacune se charge de tracer le widget <20> l'<27>cran. En fait, de nombreux types de
widgets ne se pr<70>occupent pas vraiment de la diff<66>rence entre les deux. La
fonction <em/draw()/ par d<>faut de la classe widget g<>n<EFBFBD>re simplement un
<EFBFBD>v<EFBFBD>nement d'exposition synth<74>tique pour la zone <20> redessiner. Cependant,
certains types de widgets peuvent <20>conomiser du travail en faisant la
diff<EFBFBD>rence entre les deux fonctions. Par exemple, si un widget a plusieurs
fen<EFBFBD>tres X et puisque les <20>v<EFBFBD>nements d'exposition identifient la fen<65>tre
expos<EFBFBD>e, il peut redessiner seulement la fen<65>tre concern<72>e, ce qui n'est pas
possible avec des appels <20> <em/draw()/.
<p>
Les widgets container, m<>me s'ils ne se soucient pas eux-m<>mes de la
diff<EFBFBD>rence, ne peuvent pas utiliser simplement la fonction <em/draw()/ car
leurs widgets enfants tiennent compte de cette diff<66>rence. Cependant, ce serait
du gaspillage de dupliquer le code de trac<61> pour les deux
fonctions. Conventionnellement, de tels widgets poss<73>dent une fonction nomm<6D>e
<em/WIDGETNAME_paint()/ qui r<>alise le v<>ritable travail de trac<61> du widget et
qui est appel<65>e par les fonctions <tt/draw()/ et <tt/expose()/.
<p>
Dans notre exemple, comme le widget d'appel n'est pas un widget container et
n'a qu'une fen<65>tre, nous pouvons utiliser l'approche la plus simple&nbsp;:
utiliser la fonction <em/draw()/ par d<>faut et n'implanter que la fonction
<em/expose()/.
<sect2>Origines du widget Dial
<p>
Exactement comme les animaux terrestres ne sont que des variantes des premiers
amphibiens qui ramp<6D>rent hors de la boue, les widgets GTK sont des variantes
d'autres widgets, d<>j<EFBFBD> <20>crits. Ainsi, bien que cette section s'appelle <20> cr<63>er
un widget <20> partir de z<>ro <20>, le widget Dial commence r<>ellement avec le code
source du widget Range. Celui-ci a <20>t<EFBFBD> pris comme point de d<>part car ce serait
bien que notre Dial ait la m<>me interface que les widgets Scale qui ne sont que
des descendants sp<73>cialis<69>s du widget Range. Par cons<6E>quent, bien que le code
source soit pr<70>sent<6E> ci-dessous sous une forme achev<65>e, cela n'implique pas
qu'il a <20>t<EFBFBD> <20>crit <em/deus ex machina/. De plus, si vous ne savez pas comment
fonctionnent les widgets Scale du point de vue du programmeur de l'application,
il est pr<70>f<EFBFBD>rable de les <20>tudier avant de continuer.
<sect2>Les bases
<p>
Un petite partie de notre widget devrait ressembler au widget Tictactoe. Nous
avons d'abord le fichier en-t<>te&nbsp;:
<tscreen><verb>
/* GTK - The GIMP Toolkit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#ifndef __GTK_DIAL_H__
#define __GTK_DIAL_H__
#include <gdk/gdk.h>
#include <gtk/gtkadjustment.h>
#include <gtk/gtkwidget.h>
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#define GTK_DIAL(obj) GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
#define GTK_DIAL_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
#define GTK_IS_DIAL(obj) GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
typedef struct _GtkDial GtkDial;
typedef struct _GtkDialClass GtkDialClass;
struct _GtkDial
{
GtkWidget widget;
/* politique de mise <20> jour
(GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
guint policy : 2;
/* Le bouton qui est press<73>, 0 si aucun */
guint8 button;
/* Dimensions des composants de dial */
gint radius;
gint pointer_width;
/* ID du timer de mise <20> jour, 0 si aucun */
guint32 timer;
/* Angle courant*/
gfloat angle;
/* Anciennes valeurs d'ajustement stock<63>es. On sait donc quand quelque
chose change */
gfloat old_value;
gfloat old_lower;
gfloat old_upper;
/* L'objet ajustment qui stocke les donn<6E>es de cet appel */
GtkAdjustment *adjustment;
};
struct _GtkDialClass
{
GtkWidgetClass parent_class;
};
GtkWidget* gtk_dial_new (GtkAdjustment *adjustment);
guint gtk_dial_get_type (void);
GtkAdjustment* gtk_dial_get_adjustment (GtkDial *dial);
void gtk_dial_set_update_policy (GtkDial *dial,
GtkUpdateType policy);
void gtk_dial_set_adjustment (GtkDial *dial,
GtkAdjustment *adjustment);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __GTK_DIAL_H__ */
</verb></tscreen>
Comme il y a plus de choses <20> faire avec ce widget par rapport <20> l'autre, nous
avons plus de champs dans la structure de donn<6E>es, mais <20> part <20>a, les choses
sont plut<75>t similaires.
<p>
Puis, apr<70>s avoir inclus les fichiers en-t<>te et d<>clar<61> quelques constantes,
nous devons fournir quelques fonctions pour donner des informations sur le
widget et pour l'initialiser&nbsp;:
<tscreen><verb>
#include <math.h>
#include <stdio.h>
#include <gtk/gtkmain.h>
#include <gtk/gtksignal.h>
#include "gtkdial.h"
#define SCROLL_DELAY_LENGTH 300
#define DIAL_DEFAULT_SIZE 100
/* D<>clararations des prototypes */
[ omis pour gagner de la place ]
/* Donn<6E>es locales */
static GtkWidgetClass *parent_class = NULL;
guint
gtk_dial_get_type ()
{
static guint dial_type = 0;
if (!dial_type)
{
GtkTypeInfo dial_info =
{
"GtkDial",
sizeof (GtkDial),
sizeof (GtkDialClass),
(GtkClassInitFunc) gtk_dial_class_init,
(GtkObjectInitFunc) gtk_dial_init,
(GtkArgFunc) NULL,
};
dial_type = gtk_type_unique (gtk_widget_get_type (), &amp;dial_info);
}
return dial_type;
}
static void
gtk_dial_class_init (GtkDialClass *class)
{
GtkObjectClass *object_class;
GtkWidgetClass *widget_class;
object_class = (GtkObjectClass*) class;
widget_class = (GtkWidgetClass*) class;
parent_class = gtk_type_class (gtk_widget_get_type ());
object_class->destroy = gtk_dial_destroy;
widget_class->realize = gtk_dial_realize;
widget_class->expose_event = gtk_dial_expose;
widget_class->size_request = gtk_dial_size_request;
widget_class->size_allocate = gtk_dial_size_allocate;
widget_class->button_press_event = gtk_dial_button_press;
widget_class->button_release_event = gtk_dial_button_release;
widget_class->motion_notify_event = gtk_dial_motion_notify;
}
static void
gtk_dial_init (GtkDial *dial)
{
dial->button = 0;
dial->policy = GTK_UPDATE_CONTINUOUS;
dial->timer = 0;
dial->radius = 0;
dial->pointer_width = 0;
dial->angle = 0.0;
dial->old_value = 0.0;
dial->old_lower = 0.0;
dial->old_upper = 0.0;
dial->adjustment = NULL;
}
GtkWidget*
gtk_dial_new (GtkAdjustment *adjustment)
{
GtkDial *dial;
dial = gtk_type_new (gtk_dial_get_type ());
if (!adjustment)
adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
gtk_dial_set_adjustment (dial, adjustment);
return GTK_WIDGET (dial);
}
static void
gtk_dial_destroy (GtkObject *object)
{
GtkDial *dial;
g_return_if_fail (object != NULL);
g_return_if_fail (GTK_IS_DIAL (object));
dial = GTK_DIAL (object);
if (dial->adjustment)
gtk_object_unref (GTK_OBJECT (dial->adjustment));
if (GTK_OBJECT_CLASS (parent_class)->destroy)
(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}
</verb></tscreen>
Notez que cette fonction <em/init()/ fait moins de choses que pour le widget
Tictactoe car ce n'est pas un widget compos<6F> et que la fonction <em/new()/ en
fait plus car elle a maintenant un param<61>tre. Notez aussi que lorsque nous
stockons un pointeur vers l'objet Adjustement, nous incr<63>mentons son nombre de
r<EFBFBD>f<EFBFBD>rences (et nous le d<>cr<63>mentons lorsque nous ne l'utilisons plus) afin que
GTK puisse savoir quand il pourra <20>tre d<>truit sans danger.
<p>
Il y a aussi quelques fonctions pour manipuler les options du widget&nbsp;:
<tscreen><verb>
GtkAdjustment*
gtk_dial_get_adjustment (GtkDial *dial)
{
g_return_val_if_fail (dial != NULL, NULL);
g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);
return dial->adjustment;
}
void
gtk_dial_set_update_policy (GtkDial *dial,
GtkUpdateType policy)
{
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
dial->policy = policy;
}
void
gtk_dial_set_adjustment (GtkDial *dial,
GtkAdjustment *adjustment)
{
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
if (dial->adjustment)
{
gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial);
gtk_object_unref (GTK_OBJECT (dial->adjustment));
}
dial->adjustment = adjustment;
gtk_object_ref (GTK_OBJECT (dial->adjustment));
gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
(GtkSignalFunc) gtk_dial_adjustment_changed,
(gpointer) dial);
gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
(GtkSignalFunc) gtk_dial_adjustment_value_changed,
(gpointer) dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
gtk_dial_update (dial);
}
</verb></tscreen>
<sect2> <em/gtk_dial_realize()/
<p>
Nous arrivons maintenant <20> quelques nouveaux types de fonctions. D'abord, nous
avons une fonction qui r<>alise la cr<63>ation de la fen<65>tre X. Notez que l'on
passe un masque <20> la fonction <em/gdk_window_new()/ pour sp<73>cifier quels sont
les champs de la structure GdkWindowAttr qui contiennent des donn<6E>es (les
autres recevront des valeurs par d<>faut). Notez aussi la fa<66>on dont est cr<63><72> le
masque d'<27>v<EFBFBD>nement du widget. On appelle <em/gtk_widget_get_events()/ pour
r<EFBFBD>cup<EFBFBD>rer le masque d'<27>v<EFBFBD>nement que l'utilisateur a sp<73>cifi<66> pour ce widget
(avec <em/gtk_widget_set_events()/) et ajouter les <20>v<EFBFBD>nements qui nous
int<EFBFBD>ressent.
<p>
Apr<EFBFBD>s avoir cr<63><72> la fen<65>tre, nous configurons son style et son fond et mettons
un pointeur vers le widget dans le champ user de la GdkWindow. Cette derni<6E>re
<EFBFBD>tape permet <20> GTK de distribuer les <20>v<EFBFBD>nements pour cette fen<65>tre au widget
correct.
<tscreen><verb>
static void
gtk_dial_realize (GtkWidget *widget)
{
GtkDial *dial;
GdkWindowAttr attributes;
gint attributes_mask;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
dial = GTK_DIAL (widget);
attributes.x = widget->allocation.x;
attributes.y = widget->allocation.y;
attributes.width = widget->allocation.width;
attributes.height = widget->allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.window_type = GDK_WINDOW_CHILD;
attributes.event_mask = gtk_widget_get_events (widget) |
GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
GDK_POINTER_MOTION_HINT_MASK;
attributes.visual = gtk_widget_get_visual (widget);
attributes.colormap = gtk_widget_get_colormap (widget);
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
widget->window = gdk_window_new (widget->parent->window, &amp;attributes, attributes_mask);
widget->style = gtk_style_attach (widget->style, widget->window);
gdk_window_set_user_data (widget->window, widget);
gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
}
</verb></tscreen>
<sect2>N<>gotiation de la taille
<p>
Avant le premier affichage de la fen<65>tre contenant un widget et <20> chaque fois
que la forme de la fen<65>tre change, GTK demande <20> chaque widget fils la taille
qu'il d<>sire avoir. Cette requ<71>te est g<>r<EFBFBD>e par la fonction
<em/gtk_dial_size_request()/. Comme notre widget n'est pas un widget container,
et n'a pas de contraintes r<>elles sur sa taille, nous ne faisons que retourner
une valeur raisonnable par d<>faut.
<tscreen><verb>
static void
gtk_dial_size_request (GtkWidget *widget,
GtkRequisition *requisition)
{
requisition->width = DIAL_DEFAULT_SIZE;
requisition->height = DIAL_DEFAULT_SIZE;
}
</verb></tscreen>
<p>
Lorsque tous les widgets on demand<6E> une taille id<69>ale, le forme de la fen<65>tre
est calcul<75>e et chaque widget fils est averti de sa taille. Habituellement, ce
sera autant que la taille requise, mais si, par exemple, l'utilisateur a
redimensionn<EFBFBD> la fen<65>tre, cette taille peut occasionnellement <20>tre plus petite
que la taille requise. La notification de la taille est g<>r<EFBFBD>e par la fonction
<em/gtk_dial_size_allocate()/. Notez qu'en m<>me temps qu'elle calcule les
tailles de certains composants pour une utilisation future, cette routine fait
aussi le travail de base consistant <20> d<>placer les widgets X Window dans leur
nouvelles positions et tailles.
<tscreen><verb>
static void
gtk_dial_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkDial *dial;
g_return_if_fail (widget != NULL);
g_return_if_fail (GTK_IS_DIAL (widget));
g_return_if_fail (allocation != NULL);
widget->allocation = *allocation;
if (GTK_WIDGET_REALIZED (widget))
{
dial = GTK_DIAL (widget);
gdk_window_move_resize (widget->window,
allocation->x, allocation->y,
allocation->width, allocation->height);
dial->radius = MAX(allocation->width,allocation->height) * 0.45;
dial->pointer_width = dial->radius / 5;
}
}
</verb></tscreen>.
<sect2> <em/gtk_dial_expose()/
<p>
Comme cela est mentionn<6E> plus haut, tout le dessin de ce widget est r<>alis<69>
dans le gestionnaire pour les <20>v<EFBFBD>nements d'exposition. Il n'y a pas grand chose
de plus <20> dire l<> dessus, sauf constater l'utilisation de la fonction
<em/gtk_draw_polygon/ pour dessiner le pointeur avec une forme en trois
dimensions selon les couleurs stock<63>es dans le style du widget.
style.
<tscreen><verb>
static gint
gtk_dial_expose (GtkWidget *widget,
GdkEventExpose *event)
{
GtkDial *dial;
GdkPoint points[3];
gdouble s,c;
gdouble theta;
gint xc, yc;
gint tick_length;
gint i;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
if (event->count > 0)
return FALSE;
dial = GTK_DIAL (widget);
gdk_window_clear_area (widget->window,
0, 0,
widget->allocation.width,
widget->allocation.height);
xc = widget->allocation.width/2;
yc = widget->allocation.height/2;
/* Draw ticks */
for (i=0; i<25; i++)
{
theta = (i*M_PI/18. - M_PI/6.);
s = sin(theta);
c = cos(theta);
tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
gdk_draw_line (widget->window,
widget->style->fg_gc[widget->state],
xc + c*(dial->radius - tick_length),
yc - s*(dial->radius - tick_length),
xc + c*dial->radius,
yc - s*dial->radius);
}
/* Draw pointer */
s = sin(dial->angle);
c = cos(dial->angle);
points[0].x = xc + s*dial->pointer_width/2;
points[0].y = yc + c*dial->pointer_width/2;
points[1].x = xc + c*dial->radius;
points[1].y = yc - s*dial->radius;
points[2].x = xc - s*dial->pointer_width/2;
points[2].y = yc - c*dial->pointer_width/2;
gtk_draw_polygon (widget->style,
widget->window,
GTK_STATE_NORMAL,
GTK_SHADOW_OUT,
points, 3,
TRUE);
return FALSE;
}
</verb></tscreen>
<sect2>Gestion des <20>v<EFBFBD>nements
<p>
Le reste du code du widget g<>re diff<66>rents types d'<27>v<EFBFBD>nements et n'est pas
trop diff<66>rent de ce que l'on trouve dans la plupart des applications GTK. Deux
types d'<27>v<EFBFBD>nements peuvent survenir -- l'utilisateur peut cliquer sur le widget
avec la souris et faire glisser pour d<>placer le pointeur, ou bien la valeur de
l'objet Adjustment peut changer <20> cause d'une circonstance ext<78>rieure.
<p>
Lorsque l'utilisateur clique sur le widget, on v<>rifie si le clic s'est bien
pass<EFBFBD> pr<70>s du pointeur et si c'est le cas, on stocke alors le bouton avec
lequel l'utilisateur a cliqu<71> dans le champ <em/button/ de la structure du
widget et on r<>cup<75>re tous les <20>v<EFBFBD>nements souris avec un appel <20>
<em/gtk_grab_add()/. Un d<>placement ult<6C>rieur de la souris provoque le recalcul
de la valeur de contr<74>le (par la fonction <em/gtk_dial_update_mouse/). Selon la
politique qui a <20>t<EFBFBD> choisie, les <20>v<EFBFBD>nements "value_changed" sont, soit g<>n<EFBFBD>r<EFBFBD>s
instantan<EFBFBD>ment (<em/GTK_UPDATE_CONTINUOUS/), apr<70>s un d<>lai ajout<75> au timer
avec <em/gtk_timeout_add()/ (<em/GTK_UPDATE_DELAYED/), ou seulement lorsque le
bouton est rel<65>ch<63> (<em/GTK_UPDATE_DISCONTINUOUS/).
<tscreen><verb>
static gint
gtk_dial_button_press (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
gint dx, dy;
double s, c;
double d_parallel;
double d_perpendicular;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
/* D<>termine si le bouton press<73> est dans la r<>gion du pointeur.
On fait cela en calculant les distances parall<6C>le et perpendiculaire
du point o<> la souris a <20>t<EFBFBD> press<73>e par rapport <20> la ligne passant
par le pointeur */
dx = event->x - widget->allocation.width / 2;
dy = widget->allocation.height / 2 - event->y;
s = sin(dial->angle);
c = cos(dial->angle);
d_parallel = s*dy + c*dx;
d_perpendicular = fabs(s*dx - c*dy);
if (!dial->button &&
(d_perpendicular < dial->pointer_width/2) &&
(d_parallel > - dial->pointer_width))
{
gtk_grab_add (widget);
dial->button = event->button;
gtk_dial_update_mouse (dial, event->x, event->y);
}
return FALSE;
}
static gint
gtk_dial_button_release (GtkWidget *widget,
GdkEventButton *event)
{
GtkDial *dial;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button == event->button)
{
gtk_grab_remove (widget);
dial->button = 0;
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_timeout_remove (dial->timer);
if ((dial->policy != GTK_UPDATE_CONTINUOUS) &&
(dial->old_value != dial->adjustment->value))
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
return FALSE;
}
static gint
gtk_dial_motion_notify (GtkWidget *widget,
GdkEventMotion *event)
{
GtkDial *dial;
GdkModifierType mods;
gint x, y, mask;
g_return_val_if_fail (widget != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
dial = GTK_DIAL (widget);
if (dial->button != 0)
{
x = event->x;
y = event->y;
if (event->is_hint || (event->window != widget->window))
gdk_window_get_pointer (widget->window, &amp;x, &amp;y, &amp;mods);
switch (dial->button)
{
case 1:
mask = GDK_BUTTON1_MASK;
break;
case 2:
mask = GDK_BUTTON2_MASK;
break;
case 3:
mask = GDK_BUTTON3_MASK;
break;
default:
mask = 0;
break;
}
if (mods & mask)
gtk_dial_update_mouse (dial, x,y);
}
return FALSE;
}
static gint
gtk_dial_timer (GtkDial *dial)
{
g_return_val_if_fail (dial != NULL, FALSE);
g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
if (dial->policy == GTK_UPDATE_DELAYED)
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
return FALSE;
}
static void
gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
{
gint xc, yc;
gfloat old_value;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
xc = GTK_WIDGET(dial)->allocation.width / 2;
yc = GTK_WIDGET(dial)->allocation.height / 2;
old_value = dial->adjustment->value;
dial->angle = atan2(yc-y, x-xc);
if (dial->angle < -M_PI/2.)
dial->angle += 2*M_PI;
if (dial->angle < -M_PI/6)
dial->angle = -M_PI/6;
if (dial->angle > 7.*M_PI/6.)
dial->angle = 7.*M_PI/6.;
dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
(dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
if (dial->adjustment->value != old_value)
{
if (dial->policy == GTK_UPDATE_CONTINUOUS)
{
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
else
{
gtk_widget_draw (GTK_WIDGET(dial), NULL);
if (dial->policy == GTK_UPDATE_DELAYED)
{
if (dial->timer)
gtk_timeout_remove (dial->timer);
dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
(GtkFunction) gtk_dial_timer,
(gpointer) dial);
}
}
}
}
</verb></tscreen>
<p>
Les changements de l'Adjustement par des moyens ext<78>rieurs sont communiqu<71>s <20>
notre widget par les signaux "changed" et "value_changed". Les gestionnaires
pour ces fonctions appellent <em/gtk_dial_update()/ pour valider les
param<EFBFBD>tres, calculer le nouvel angle du pointeur et redessiner le widget (en
appelant <em/gtk_widget_draw()/).
<tscreen><verb>
static void
gtk_dial_update (GtkDial *dial)
{
gfloat new_value;
g_return_if_fail (dial != NULL);
g_return_if_fail (GTK_IS_DIAL (dial));
new_value = dial->adjustment->value;
if (new_value < dial->adjustment->lower)
new_value = dial->adjustment->lower;
if (new_value > dial->adjustment->upper)
new_value = dial->adjustment->upper;
if (new_value != dial->adjustment->value)
{
dial->adjustment->value = new_value;
gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
}
dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
(dial->adjustment->upper - dial->adjustment->lower);
gtk_widget_draw (GTK_WIDGET(dial), NULL);
}
static void
gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if ((dial->old_value != adjustment->value) ||
(dial->old_lower != adjustment->lower) ||
(dial->old_upper != adjustment->upper))
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
dial->old_lower = adjustment->lower;
dial->old_upper = adjustment->upper;
}
}
static void
gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
gpointer data)
{
GtkDial *dial;
g_return_if_fail (adjustment != NULL);
g_return_if_fail (data != NULL);
dial = GTK_DIAL (data);
if (dial->old_value != adjustment->value)
{
gtk_dial_update (dial);
dial->old_value = adjustment->value;
}
}
</verb></tscreen>
<sect2>Am<41>liorations possibles
<p>
Le widget Dial d<>crit jusqu'<27> maintenant ex<65>cute <20> peu pr<70>s 670 lignes de
code. Bien que cela puisse sembler beaucoup, nous en avons vraiment fait
beaucoup avec ce code, notamment parce que la majeure partie de cette longueur
est due aux en-t<>tes et <20> la pr<70>paration. Cependant, certaines am<61>liorations
peuvent <20>tre apport<72>es <20> ce widget&nbsp;:
<itemize>
<item> Si vous testez ce widget, vous vous apercevrez qu'il y a un peu de
scintillement lorsque le pointeur est d<>plac<61>. Ceci est d<> au fait que le
widget entier est effac<61>, puis redessin<69> <20> chaque mouvement du
pointeur. Souvent, la meilleure fa<66>on de g<>rer ce probl<62>me est de dessiner sur
un pixmap non affich<63>, puis de copier le r<>sultat final sur l'<27>cran en une
seule <20>tape (le widget <em/ProgressBar/ se dessine de cette fa<66>on).
<item> L'utilisateur devrait pouvoir utiliser les fl<66>ches du curseur vers le
haut et vers le bas pour incr<63>menter et d<>cr<63>menter la valeur.
<item> Ce serait bien si le widget avait des boutons pour augmenter et diminuer
la valeur dans de petites ou de grosses proportions. Bien qu'il serait possible
d'utiliser les widgets <em/Button/ pour cela, nous voudrions aussi que les
boutons s'auto-r<>p<EFBFBD>tent lorsqu'ils sont maintenus appuy<75>s, comme font les
fl<EFBFBD>ches d'une barre de d<>filement. La majeure partie du code pour implanter ce
type de comportement peut se trouver dans le widget <em/GtkRange/.
<item> Le widget Dial pourrait <20>tre fait dans un widget container avec un seul
widget fils positionn<6E>e en bas, entre les boutons mentionn<6E>s
ci-dessus. L'utilisateur pourrait alors ajouter au choix, un widget label ou
entr<EFBFBD>e pour afficher la valeur courante de l'appel.
</itemize>
<sect1>En savoir plus
<p>
Seule une petite partie des nombreux d<>tails de la cr<63>ation des widgets a pu
<EFBFBD>tre d<>crite. Si vous d<>sirez <20>crire vos propres widgets, la meilleure source
d'exemples est le source de GTK lui-m<>me. Posez-vous quelques questions sur les
widgets que vous voulez <20>crire&nbsp;: est-ce un widget container ? poss<73>de-t-il
sa propre fen<65>tre ? est-ce une modification d'un widget existant ? Puis,
trouvez un widget identique et commencez <20> faire les modifications. Bonne
chance !
<sect>Scribble, un programme simple de dessin
<sect1>Pr<50>sentation
<p>
Dans cette section, nous construirons un programme simple de dessin. Ce
faisant, nous examinerons comment g<>rer les <20>v<EFBFBD>nements souris, comment dessiner
dans une fen<65>tre, et comment mieux dessiner en utilisant un pixmap en arri<72>re
plan. Apr<70>s avoir cr<63><72> ce programme, nous l'<27>tendrons en lui ajoutant le
support des p<>riph<70>riques <em/Xinput/, comme les tables de trac<61>. GTK dispose
de routines de support qui facilitent beaucoup l'obtention des informations
<EFBFBD>tendues (comme la pression et l'inclinaison du stylet) <20> partir de tels
p<EFBFBD>riph<EFBFBD>riques.
<sect1>Gestion d'<27>v<EFBFBD>nement
<p>
Les signaux GTK que nous avons d<>j<EFBFBD> vus concernent les actions de haut niveau,
comme la s<>lection d'un choix d'un menu. Cependant, il est quelques fois utile
de conna<6E>tre les cas de bas niveau, comme le d<>placement de la souris, ou la
pression d'une touche. Il existe aussi des signaux GTK correspondant <20> ces
<em/<2F>v<EFBFBD>nements/ bas niveau. Les gestionnaires de ces signaux ont un param<61>tre
suppl<EFBFBD>mentaire qui est un pointeur vers une structure contenant des
informations sur l'<27>v<EFBFBD>nement. Par exemple, les gestionnaires des <20>v<EFBFBD>nements de
d<EFBFBD>placement recoivent un param<61>tre vers une structure <em/GdkEventMotion/ qui
ressemble (en partie) <20> ceci&nbsp;:
<tscreen><verb>
struct _GdkEventMotion
{
GdkEventType type;
GdkWindow *window;
guint32 time;
gdouble x;
gdouble y;
...
guint state;
...
};
</verb></tscreen>
<em/type/ sera initialis<69> avec le type de l'<27>v<EFBFBD>nement, ici
<em/GDK_MOTION_NOTIFY/, <em/window/ est la fen<65>tre dans laquelle l'<27>v<EFBFBD>nement
est survenu. <em/x/ et <em/y/ donnent les coordonn<6E>es de l'<27>v<EFBFBD>nement et
<em/state/ sp<73>cifie l'<27>tat du modificateur lorsque l'<27>v<EFBFBD>nement s'est produit
(c'est-<2D>-dire quelles sont les touches de modification et les boutons souris
qui ont <20>t<EFBFBD> press<73>s). Il s'agit d'un OU bit <20> bit de l'une des valeurs
suivantes&nbsp;:
<tscreen><verb>
GDK_SHIFT_MASK
GDK_LOCK_MASK
GDK_CONTROL_MASK
GDK_MOD1_MASK
GDK_MOD2_MASK
GDK_MOD3_MASK
GDK_MOD4_MASK
GDK_MOD5_MASK
GDK_BUTTON1_MASK
GDK_BUTTON2_MASK
GDK_BUTTON3_MASK
GDK_BUTTON4_MASK
GDK_BUTTON5_MASK
</verb></tscreen>
<p>
Comme pour les autres signaux, on appelle <em/gtk_signal_connect()/ pour
d<EFBFBD>terminer ce qui se passe lorsqu'un <20>v<EFBFBD>nement survient. Mais nous devons aussi
faire en sorte que GTK sache de quels <20>v<EFBFBD>nements nous voulons <20>tre
avertis. Pour ce faire, on appelle la fonction&nbsp;:
<tscreen><verb>
void gtk_widget_set_events (GtkWidget *widget,
gint events);
</verb></tscreen>
Le deuxi<78>me champ sp<73>cifie les <20>v<EFBFBD>nements qui nous int<6E>ressent. Il s'agit d'un
OU bit <20> bit de constantes qui indiquent diff<66>rent types d'<27>v<EFBFBD>nements. Pour
r<EFBFBD>f<EFBFBD>rence ult<6C>rieure, les types d'<27>v<EFBFBD>nements sont&nbsp;:
<tscreen><verb>
GDK_EXPOSURE_MASK
GDK_POINTER_MOTION_MASK
GDK_POINTER_MOTION_HINT_MASK
GDK_BUTTON_MOTION_MASK
GDK_BUTTON1_MOTION_MASK
GDK_BUTTON2_MOTION_MASK
GDK_BUTTON3_MOTION_MASK
GDK_BUTTON_PRESS_MASK
GDK_BUTTON_RELEASE_MASK
GDK_KEY_PRESS_MASK
GDK_KEY_RELEASE_MASK
GDK_ENTER_NOTIFY_MASK
GDK_LEAVE_NOTIFY_MASK
GDK_FOCUS_CHANGE_MASK
GDK_STRUCTURE_MASK
GDK_PROPERTY_CHANGE_MASK
GDK_PROXIMITY_IN_MASK
GDK_PROXIMITY_OUT_MASK
</verb></tscreen>
Il y a quelques points subtils qui doivent <20>tre observ<72>s lorsqu'on appelle
<em/gtk_widget_set_events()/. D'abord, elle doit <20>tre appel<65>e avant que la
fen<EFBFBD>tre X d'un widget GTK soit cr<63><72>e. En pratique, cela signifie que l'on doit
l'appeler imm<6D>diatement apr<70>s avoir cr<63><72> le widget. Ensuite, le widget doit
avoir une fen<65>tre X associ<63>e. Pour des raisons d'efficacit<69>, de nombreux types
de widgets n'ont pas de fen<65>tre propre, mais se dessinent dans la fen<65>tre de
leur parent. Ces widgets sont&nbsp;:
<tscreen><verb>
GtkAlignment
GtkArrow
GtkBin
GtkBox
GtkImage
GtkItem
GtkLabel
GtkPaned
GtkPixmap
GtkScrolledWindow
GtkSeparator
GtkTable
GtkViewport
GtkAspectFrame
GtkFrame
GtkVPaned
GtkHPaned
GtkVBox
GtkHBox
GtkVSeparator
GtkHSeparator
</verb></tscreen>
Pour capturer les <20>v<EFBFBD>nements pour ces widgets, on doit utiliser un widget
<em/EventBox/. Voir la section sur <ref id="sec_The_EventBox_Widget" name="Le
widget EventBox"> pour plus de d<>tails.
<p>
Pour notre programme de dessin, on veut savoir quand le bouton de la souris est
press<EFBFBD> et quand la souris est d<>plac<61>e, nous indiquons donc
<em/GDK_POINTER_MOTION_MASK/ et <em/GDK_BUTTON_PRESS_MASK/. On veut aussi
savoir quand il est n<>cessaire de redessiner notre fen<65>tre, on indique donc
<em/GDK_EXPOSURE_MASK/. Bien que nous voulions <20>tre avertis via un <20>v<EFBFBD>nement
<em/Configure/ lorsque la taille de notre fen<65>tre change, on n'a pas besoin de
pr<EFBFBD>ciser le flag <em/GDK_STRUCTURE_MASK/ correspondant car il est
automatiquement sp<73>cifi<66> pour chaque fen<65>tre.
<p>
Il arrive cependant qu'il puisse y avoir un probl<62>me en indiquant seulement
<em/GDK_POINTER_MOTION_MASK/. Cela fera que le serveur ajoutera un nouvel
<EFBFBD>v<EFBFBD>nement de d<>placement <20> la file des <20>v<EFBFBD>nements <20> chaque fois que
l'utilisateur d<>place la souris. Si cela nous prend 0,1 seconde pour g<>rer un
<EFBFBD>v<EFBFBD>nement de d<>placement, si le serveur X n'ajoute un nouvel <20>v<EFBFBD>nement de
d<EFBFBD>placement dans la queue que toutes les 0,05 secondes, nous serons vite <20> la
tra<EFBFBD>ne de l'utilisateur. Si l'utilisateur dessine pendant 5 secondes, cela nous
prendra 5 secondes de plus pour le traiter apr<70>s qu'il ait rel<65>ch<63> le bouton de
la souris ! Ce que l'on voudrait, c'est ne r<>cup<75>rer qu'un <20>v<EFBFBD>nement de
d<EFBFBD>placement pour chaque <20>v<EFBFBD>nement que l'on traite. Pour cela, il faut pr<70>ciser
<em/GDK_POINTER_MOTION_HINT_MASK/.
<p>
Avec <em/GDK_POINTER_MOTION_HINT_MASK/, le serveur nous envoit un <20>v<EFBFBD>nement de
d<EFBFBD>placement la premi<6D>re fois que la pointeur se d<>place apr<70>s <20>tre entr<74> dans
la fen<65>tre, ou apr<70>s un <20>v<EFBFBD>nement d'appui ou de rel<65>chement d'un bouton. Les
<EFBFBD>v<EFBFBD>nements de d<>placement suivants seront supprim<69>s jusqu'<27> ce que l'on demande
explicitement la position du pointeur en utilisant la fonction&nbsp;:
<tscreen><verb>
GdkWindow* gdk_window_get_pointer (GdkWindow *window,
gint *x,
gint *y,
GdkModifierType *mask);
</verb></tscreen>
(Il existe une autre fonction, <em/gtk_widget_get_pointer()/ qui poss<73>de une
interface simple, mais n'est pas tr<74>s utile car elle ne fait que r<>cup<75>rer la
position de la souris et ne se pr<70>occupe pas de savoir si les boutons sont
press<EFBFBD>s).
<p>
Le code pour configurer les <20>v<EFBFBD>nements pour notre fen<65>tre ressemble alors
<EFBFBD>&nbsp;:
<tscreen><verb>
gtk_signal_connect (GTK_OBJECT (drawing_area), "expose_event",
(GtkSignalFunc) expose_event, NULL);
gtk_signal_connect (GTK_OBJECT(drawing_area),"configure_event",
(GtkSignalFunc) configure_event, NULL);
gtk_signal_connect (GTK_OBJECT (drawing_area), "motion_notify_event",
(GtkSignalFunc) motion_notify_event, NULL);
gtk_signal_connect (GTK_OBJECT (drawing_area), "button_press_event",
(GtkSignalFunc) button_press_event, NULL);
gtk_widget_set_events (drawing_area, GDK_EXPOSURE_MASK
| GDK_LEAVE_NOTIFY_MASK
| GDK_BUTTON_PRESS_MASK
| GDK_POINTER_MOTION_MASK
| GDK_POINTER_MOTION_HINT_MASK);
</verb></tscreen>
Nous garderons les gestionnaires de "expose_event" et "configure_event" pour
plus tard. Les gestionnaires de "motion_notify_event" et "button_press_event"
sont tr<74>s simples&nbsp;:
<tscreen><verb>
static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
if (event->button == 1 &amp;&amp; pixmap != NULL)
draw_brush (widget, event->x, event->y);
return TRUE;
}
static gint
motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
{
int x, y;
GdkModifierType state;
if (event->is_hint)
gdk_window_get_pointer (event->window, &amp;x, &amp;y, &amp;state);
else
{
x = event->x;
y = event->y;
state = event->state;
}
if (state &amp; GDK_BUTTON1_MASK &amp;&amp; pixmap != NULL)
draw_brush (widget, x, y);
return TRUE;
}
</verb></tscreen>
<sect1>Le widget DrawingArea et le dessin
<p>
Revenons au processus de dessin sur l'<27>cran. Le widget que l'on utilise pour
ceci est le widget <em/DrawingArea/. Un tel widget est essentiellement une
fen<EFBFBD>tre X et rien de plus. Il s'agit d'une toile vide sur laquelle nous pouvons
dessiner ce que nous voulons.
Une zone de dessin est cr<63><72>e avec l'appel&nbsp;:
<tscreen><verb>
GtkWidget* gtk_drawing_area_new (void);
</verb></tscreen>
Une taille par d<>faut peut <20>tre donn<6E>e au widget par l'appel&nbsp;:
<tscreen><verb>
void gtk_drawing_area_size (GtkDrawingArea *darea,
gint width,
gint height);
</verb></tscreen>
Cette taille par d<>faut peu <20>tre surcharg<72>e en appelant
<em/gtk_widget_set_usize()/ et celle-ci, <20> son tour, peut <20>tre surcharg<72>e si
l'utilisateur modifie manuellement la taille de la fen<65>tre contenant la zone de
dessin.
<p>
Il faut noter que lorsque l'on cr<63>e un widget <em/DrawingArea/, nous sommes
<em>compl<70>tement</em> responsable du dessin du contenu. Si notre fen<65>tre est
cach<EFBFBD>e puis red<65>couverte, nous recevrons un <20>v<EFBFBD>nement d'exposition et devrons
redessiner ce qui a <20>t<EFBFBD> cach<63> auparavant.
<p>
Devoir se rappeler tout ce qui a <20>t<EFBFBD> dessin<69> <20> l'<27>cran pour pouvoir
correctement le redessiner peut s'av<61>rer, c'est le moins que l'on puisse dire,
p<EFBFBD>nible. De plus, cela peut <20>tre visible si des portions de la fen<65>tre sont
effac<EFBFBD>es puis redessin<69>es <20>tape par <20>tape. La solution <20> ce probl<62>me est
d'utiliser un <em/pixmap d'arri<72>re-plan/ qui n'est pas sur l'<27>cran. Au lieu de
dessiner directement <20> l'<27>cran, on dessine sur une image stock<63>e dans la
m<EFBFBD>moire du serveur et qui n'est pas affich<63>e, puis, lorsque l'image change ou
lorsque de nouvelles portions de l'image sont affich<63>es, on copie les parties
ad<EFBFBD>quates sur l'<27>cran.
<p>
Pour cr<63>er un pixmap m<>moire, on appelle la fonction&nbsp;:
<tscreen><verb>
GdkPixmap* gdk_pixmap_new (GdkWindow *window,
gint width,
gint height,
gint depth);
</verb></tscreen>
Le param<61>tre <em/windows/ indique une fen<65>tre GTK de laquelle ce pixmap tire
certaines de ses propri<72>t<EFBFBD>s. <em/width/ et <em/height/ pr<70>cisent la taille du
pixmap. <em/depth/ pr<70>cise la <em/profondeur de couleur/, c'est-<2D>-dire le
nombre de bits par pixel, de la nouvelle fen<65>tre. Si cette profondeur vaut
<em/-1/, elle correspondra <20> celle de <em/window/.
<p>
Nous cr<63>ons le pixmap dans notre gestionnaire "configure_event". Cet <20>v<EFBFBD>nement
est g<>n<EFBFBD>r<EFBFBD> <20> chaque fois que la fen<65>tre change de taille, y compris lorsqu'elle
initialement cr<63><72>e.
<tscreen><verb>
/* Pixmap d'arri<72>re-plan pour la zone de dessin */
static GdkPixmap *pixmap = NULL;
/* Cr<43>ation d'un nouveau pixmap d'arri<72>re-plan de la taille voulue */
static gint
configure_event (GtkWidget *widget, GdkEventConfigure *event)
{
if (pixmap)
{
gdk_pixmap_destroy(pixmap);
}
pixmap = gdk_pixmap_new(widget->window,
widget->allocation.width,
widget->allocation.height,
-1);
gdk_draw_rectangle (pixmap,
widget->style->white_gc,
TRUE,
0, 0,
widget->allocation.width,
widget->allocation.height);
return TRUE;
}
</verb></tscreen>
L'appel <20> <em/gdk_draw_rectangle()/ remet le pixmap <20> blanc. Nous en dirons un
peu plus dans un moment.
<p>
Notre gestionnaire d'<27>v<EFBFBD>nement d'exposition copie alors simplement la partie
concern<EFBFBD>es du pixmap sur l'<27>cran (on d<>termine la zone qu'il faut redessiner en
utilisant le champ <em/event->area/ de l'<27>v<EFBFBD>nement d'exposition)&nbsp;:
<tscreen><verb>
/* Remplit l'<27>cran <20> partir du pixmap d'arri<72>re-plan */
static gint
expose_event (GtkWidget *widget, GdkEventExpose *event)
{
gdk_draw_pixmap(widget->window,
widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
pixmap,
event->area.x, event->area.y,
event->area.x, event->area.y,
event->area.width, event->area.height);
return FALSE;
}
</verb></tscreen>
Nous avons vu comment garder l'<27>cran <20> jour avec notre pixmap, mais comment
dessine-t-on r<>ellement ce que l'on veut dans le pixmap ? Il existe un grand
nombre d'appels dans la biblioth<74>que GDK de GTK pour dessiner sur des
<em>dessinables</em>. Un dessinable est simplement quelque chose sur lequel on
peut dessiner. Cela peut <20>tre une fen<65>tre, un pixmap, ou un bitmap (une image
en noir et blanc). Nous avons d<>j<EFBFBD> vu plus haut deux de ces appels,
<em/gdk_draw_rectangle()/ et <em/gdk_draw_pixmap()/. La liste compl<70>te
est&nbsp;:
<tscreen><verb>
gdk_draw_line ()
gdk_draw_rectangle ()
gdk_draw_arc ()
gdk_draw_polygon ()
gdk_draw_string ()
gdk_draw_text ()
gdk_draw_pixmap ()
gdk_draw_bitmap ()
gdk_draw_image ()
gdk_draw_points ()
gdk_draw_segments ()
</verb></tscreen>
Consultez la documentation de r<>f<EFBFBD>rence ou le fichier en-t<>te
<em/&lt;gdk/gdk.h&gt;/ pour plus de d<>tails sur ces fonctions. Celles-ci
partagent toutes les m<>mes deux param<61>tres. Le premier est le dessinable sur
lequel dessiner, le second est un <em/contexte graphique/ (GC).
<p>
Un contexte graphique encapsule l'information sur des choses comme les couleurs
de premier et d'arri<72>re plan et la largeur de ligne. GDK poss<73>de un ensemble
complet de fonctions pour cr<63>er et manipuler les contextes graphiques, mais,
pour simplifier, nous n'utiliserons que les contextes graphiques
pr<EFBFBD>d<EFBFBD>finis. Chaque widget a un style associ<63> (qui peut <20>tre modifi<66> dans un
fichier gtkrc, voir la section sur les fichiers rc de GTK). Celui-ci, entre
autres choses, stocke plusieurs contextes graphiques. Quelques exemples d'acc<63>s
<EFBFBD> ces contextes sont&nbsp;:
<tscreen><verb>
widget->style->white_gc
widget->style->black_gc
widget->style->fg_gc[GTK_STATE_NORMAL]
widget->style->bg_gc[GTK_WIDGET_STATE(widget)]
</verb></tscreen>
Les champs <em/fg_gc, bg_gc, dark_gc/ et <em/light_gc/ sont index<65>s par un
param<EFBFBD>tre de type <em/GtkStateType/ qui peut prendre les valeurs&nbsp;:
<tscreen><verb>
GTK_STATE_NORMAL,
GTK_STATE_ACTIVE,
GTK_STATE_PRELIGHT,
GTK_STATE_SELECTED,
GTK_STATE_INSENSITIVE
</verb></tscreen>
Par exemple, pour <em/GTK_STATE_SELECTED/, la couleur de premier plan par
d<EFBFBD>faut est blanc et la couleur d'arri<72>re plan par d<>faut est bleu fonc<6E>.
<p>
Notre fonction <em/draw_brush()/, qui r<>alise le dessin <20> l'<27>cran est alors&nbsp;:
<tscreen><verb>
/* Dessine un rectangle <20> l'<27>cran */
static void
draw_brush (GtkWidget *widget, gdouble x, gdouble y)
{
GdkRectangle update_rect;
update_rect.x = x - 5;
update_rect.y = y - 5;
update_rect.width = 10;
update_rect.height = 10;
gdk_draw_rectangle (pixmap,
widget->style->black_gc,
TRUE,
update_rect.x, update_rect.y,
update_rect.width, update_rect.height);
gtk_widget_draw (widget, &amp;update_rect);
}
</verb></tscreen>
Apr<EFBFBD>s avoir dessin<69> le rectangle repr<70>sentant la brosse sur le pixmap, nous
appelons la fonction&nbsp;:
<tscreen><verb>
void gtk_widget_draw (GtkWidget *widget,
GdkRectangle *area);
</verb></tscreen>
qui indique <20> X que la zone donn<6E>e par le param<61>tre <em/area/ a besoin d'<27>tre
mise <20> jour. X g<>n<EFBFBD>rera <20>ventuellement un <20>v<EFBFBD>nement d'exposition (en combinant
peut-<2D>tre les zones pass<73>s dans plusieurs appels <20> <em/gtk_widget_draw()/) ce
qui forcera notre gestionnaire d'<27>v<EFBFBD>nement d'exposition <20> copier les parties
ad<EFBFBD>quates <20> l'<27>cran.
<p>
Nous avons maintenant couvert enti<74>rement le programme de dessin, sauf quelques
d<EFBFBD>tails banals comme la cr<63>ation de la fen<65>tre principale. Le code source
complet est disponible <20> l'endroit o<> vous avez obtenu ce didacticiel.
<sect1>Ajouter le support XInput
<p>
Il est maintenant possible d'acheter des p<>riph<70>riques bon march<63>, comme les
tablettes graphiques qui permettent d'exprimer beaucoup plus facilement son
talent qu'avec une souris. La fa<66>on la plus simple pour utiliser de tels
p<EFBFBD>riph<EFBFBD>riques est simplement de le faire comme un remplacement de la souris,
mais cela ne tire pas partie des nombreux avantages de ces p<>riph<70>riques,
comme&nbsp;:
<itemize>
<item> Sensibilit<69> <20> la pression ;
<item> rapport d'inclinaison ;
<item> positionnement au dessous du pixel ;
<item> entr<74>es multiples (par exemple, un stylet avec pointe et gomme).
</itemize>
Pour des informations sur l'extension XInput, voir <url
url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
name="XInput-HOWTO">.
<p>
Si l'on examine la d<>finition compl<70>te de, par exemple, la structure
<em/GdkEventMotion/, on voit qu'elle poss<73>de des champs pour supporter des
informations <20>tendues sur les p<>riph<70>riques.
<tscreen><verb>
struct _GdkEventMotion
{
GdkEventType type;
GdkWindow *window;
guint32 time;
gdouble x;
gdouble y;
gdouble pressure;
gdouble xtilt;
gdouble ytilt;
guint state;
gint16 is_hint;
GdkInputSource source;
guint32 deviceid;
};
</verb></tscreen>
<em/pressure/ indique la pression comme un nombre r<>el compris entre 0 et 1.
<em/xtilt/ et <em/ytilt/ peuvent prendre des valeurs entre -1 et 1,
correspondant au degr<67> d'inclinaison dans chaque direction, <em/source/ et
<em/deviceid/ pr<70>cisent de deux fa<66>ons diff<66>rentes le p<>riph<70>rique pour lequel
l'<27>v<EFBFBD>nement est survenus. <em/source/ donne une simple information sur le type
du p<>riph<70>rique. Il peut prendre l'une des valeurs suivantes&nbsp;:
<tscreen><verb>
GDK_SOURCE_MOUSE
GDK_SOURCE_PEN
GDK_SOURCE_ERASER
GDK_SOURCE_CURSOR
</verb></tscreen>
<tt/deviceid/ pr<70>cise un ID num<75>rique unique pour le p<>riph<70>rique. Il peut <20>tre
utilis<EFBFBD> pour trouver des informations suppl<70>mentaires sur le p<>riph<70>rique en
utilisant l'appel <em/gdk_input_list_devices()/ (voir ci-dessous). La valeur
sp<EFBFBD>ciale <em/GDK_CORE_POINTER/ sert <20> d<>signer le p<>riph<70>rique de pointage
principal (habituellement la souris).
<sect2>Valider l'information suppl<70>mentaire sur un p<>riph<70>rique
<p>
Pour indiquer <20> GTK que l'on d<>sire obtenir des informations suppl<70>mentaires
sur le p<>riph<70>rique, on a simplement besoin d'ajouter une ligne <20> nos
programmes.
<tscreen><verb>
gtk_widget_set_extension_events (drawing_area, GDK_EXTENSION_EVENTS_CURSOR);
</verb></tscreen>
En donnant la valeur <em/GDK_EXTENSION_EVENTS_CURSOR/, on indique que nous
d<EFBFBD>sirons les <20>v<EFBFBD>nements d'extension, mais seulement si l'on ne doit pas
dessiner notre propre curseur. Voir la section <ref
id="sec_Further_Sophistications" name="Sophistications suppl<70>mentaires">
ci-dessous pour des plus de d<>tails sur le dessin du curseur. Nous pourrions
aussi donner les valeurs <em/GDK_EXTENSION_EVENTS_ALL/ si nous voulons dessiner
notre propre curseur, ou <tt/GDK_EXTENSION_EVENTS_NONE/ pour revenir <20> la
situation par d<>faut.
<p>
Toutefois, nous ne sommes pas compl<70>tement <20> la fin de l'histoire. Par d<>faut,
aucun p<>riph<70>rique d'extension n'est autoris<69>. Nous avons besoin d'un m<>canisme
pour que les utilisateurs puissent autoriser et configurer leur extensions. GTK
dispose du widget <em/InputDialog/ pour automatiser cette t<>che. La proc<6F>dure
suivante g<>re un widget InputDialog. Elle cr<63>e le dialogue s'il n'est pas
pr<EFBFBD>sent et le place au premier plan sinon.
<tscreen><verb>
void
input_dialog_destroy (GtkWidget *w, gpointer data)
{
*((GtkWidget **)data) = NULL;
}
void
create_input_dialog ()
{
static GtkWidget *inputd = NULL;
if (!inputd)
{
inputd = gtk_input_dialog_new();
gtk_signal_connect (GTK_OBJECT(inputd), "destroy",
(GtkSignalFunc)input_dialog_destroy, &amp;inputd);
gtk_signal_connect_object (GTK_OBJECT(GTK_INPUT_DIALOG(inputd)->close_button),
"clicked",
(GtkSignalFunc)gtk_widget_hide,
GTK_OBJECT(inputd));
gtk_widget_hide ( GTK_INPUT_DIALOG(inputd)->save_button);
gtk_widget_show (inputd);
}
else
{
if (!GTK_WIDGET_MAPPED(inputd))
gtk_widget_show(inputd);
else
gdk_window_raise(inputd->window);
}
}
</verb></tscreen>
(vous pouvez remarquer la fa<66>on dont nous g<>rons ce dialogue. En le connectant
au signal "destroy", nous nous assurons que nous ne garderons pas un pointeur
sur le dialogue apr<70>s l'avoir d<>truit -- cela pourrait provoquer une erreur de
segmentation).
<p>
InputDialog a deux boutons "Close" et "Save", qui n'ont pas d'actions qui leur
sont assign<67>es par d<>faut. Dans la fonction ci-dessus, nous associons <20> "Close"
le masquage du dialogue et nous cachons le bouton "Save" car nous n'implantons
pas la sauvegarde des options XInput dans ce programme.
<sect2>Utiliser l'information suppl<70>mentaire d'un p<>riph<70>rique
<p>
Lorsque l'on a valid<69> le p<>riph<70>rique, on peut simplement utiliser
l'information suppl<70>mentaire des champs des structures d'<27>v<EFBFBD>nement. En fait, il
est toujours prident d'utiliser ces informations car ces champs auront des
valeurs par d<>faut judicieuses m<>me lorsque les <20>v<EFBFBD>nements suppl<70>mentaires ne
sont pas autoris<69>s.
<p>
La seule modification consiste <20> appeler <em/gdk_input_window_get_pointer()/ au
lieu de <em/gdk_window_get_pointer/. Cela est n<>cessaire car
<em/gdk_window_get_pointer/ ne retourne pas l'information suppl<70>mentaire.
<tscreen><verb>
void gdk_input_window_get_pointer (GdkWindow *window,
guint32 deviceid,
gdouble *x,
gdouble *y,
gdouble *pressure,
gdouble *xtilt,
gdouble *ytilt,
GdkModifierType *mask);
</verb></tscreen>
Lorsque l'on appelle cette fonction, on doit pr<70>ciser l'ID du p<>riph<70>rique
ainsi que la fen<65>tre. Habituellement, on aura obtenu cet ID par le champ
<em/deviceid/ d'une structure d'<27>v<EFBFBD>nement. Cette fonction retournera une valeur
coh<EFBFBD>rente lorsque les <20>v<EFBFBD>nements ne sont pas autoris<69>s (dans ce cas,
<em/event->deviceid/ aura la valeur <em/GDK_CORE_POINTER/).
La structure de base des gestionnaires d'<27>v<EFBFBD>nements de d<>placement et de bouton
press<EFBFBD> ne change donc pas trop -- il faut juste ajouter le code permettant de
traiter l'information suppl<70>mentaire.
<tscreen><verb>
static gint
button_press_event (GtkWidget *widget, GdkEventButton *event)
{
print_button_press (event->deviceid);
if (event->button == 1 &amp;&amp; pixmap != NULL)
draw_brush (widget, event->source, event->x, event->y, event->pressure);
return TRUE;
}
static gint
motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
{
gdouble x, y;
gdouble pressure;
GdkModifierType state;
if (event->is_hint)
gdk_input_window_get_pointer (event->window, event->deviceid,
&amp;x, &amp;y, &amp;pressure, NULL, NULL, &amp;state);
else
{
x = event->x;
y = event->y;
pressure = event->pressure;
state = event->state;
}
if (state &amp; GDK_BUTTON1_MASK &amp;&amp; pixmap != NULL)
draw_brush (widget, event->source, x, y, pressure);
return TRUE;
}
</verb></tscreen>
On doit aussi faire quelquechose de cette nouvelle information. Notre nouvelle
fonction <em/draw_brush()/ dessine avec une couleur diff<66>rente pour chaque
<em/event->source/ et change la taille du pinceau selon la pression.
<tscreen><verb>
/* Dessine un rectangle <20> l'<27>cran, la taille d<>pend de la pression,
et la couleur d<>pend du type de p<>riph<70>rique */
static void
draw_brush (GtkWidget *widget, GdkInputSource source,
gdouble x, gdouble y, gdouble pressure)
{
GdkGC *gc;
GdkRectangle update_rect;
switch (source)
{
case GDK_SOURCE_MOUSE:
gc = widget->style->dark_gc[GTK_WIDGET_STATE (widget)];
break;
case GDK_SOURCE_PEN:
gc = widget->style->black_gc;
break;
case GDK_SOURCE_ERASER:
gc = widget->style->white_gc;
break;
default:
gc = widget->style->light_gc[GTK_WIDGET_STATE (widget)];
}
update_rect.x = x - 10 * pressure;
update_rect.y = y - 10 * pressure;
update_rect.width = 20 * pressure;
update_rect.height = 20 * pressure;
gdk_draw_rectangle (pixmap, gc, TRUE,
update_rect.x, update_rect.y,
update_rect.width, update_rect.height);
gtk_widget_draw (widget, &amp;update_rect);
}
</verb></tscreen>
<sect2>En savoir plus sur un p<>riph<70>rique
<p>
Notre programme affichera le nom du p<>riph<70>rique qui a g<>n<EFBFBD>r<EFBFBD> chaque appui de
bouton. Pour trouver le nom d'un p<>riph<70>rique, nous appelons la fonction&nbsp;:
<tscreen><verb>
GList *gdk_input_list_devices (void);
</verb></tscreen>
qui retourne une GList (un type de liste cha<68>n<EFBFBD>e de la biblioth<74>que glib) de
structures GdkDeviceInfo. La structure GdkDeviceInfo est d<>finie de la fa<66>on
suivante&nbsp;:
<tscreen><verb>
struct _GdkDeviceInfo
{
guint32 deviceid;
gchar *name;
GdkInputSource source;
GdkInputMode mode;
gint has_cursor;
gint num_axes;
GdkAxisUse *axes;
gint num_keys;
GdkDeviceKey *keys;
};
</verb></tscreen>
La plupart de ces champs sont des informations de configuration que l'on peut
ignorer sauf si l'on implante la sauvegarde de la configuration XInput. Celui
qui nous int<6E>resse est <em/name/ qui est, tout simplement, le nom que X donne
au p<>riph<70>rique. L'autre champ, qui n'est pas une information de configuration,
est <em/has_cursor/. Si <em/has_cursor/ est faux, on doit dessiner notre propre
curseur, mais puisque nous avons pr<70>cis<69> <em/GDK_EXTENSION_EVENTS_CURSOR/, nous
n'avons pas <20> nous en pr<70>occuper.
<p>
Notre fonction <em/print_button_press()/ ne fait parcourir la liste retourn<72>e
jusqu'<27> trouver une correspondance, puis affiche le nom du p<>riph<70>rique.
<tscreen><verb>
static void
print_button_press (guint32 deviceid)
{
GList *tmp_list;
/* gdk_input_list_devices retourne une liste interne, nous ne devons donc
pas la lib<69>rer apr<70>s */
tmp_list = gdk_input_list_devices();
while (tmp_list)
{
GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;
if (info->deviceid == deviceid)
{
printf("Bouton press<73> sur le p<>riph<70>rique '%s'\n", info->name);
return;
}
tmp_list = tmp_list->next;
}
}
</verb></tscreen>
Ceci termine les modifications de notre programme <20> XInputize <20>. Comme pour la
premi<EFBFBD>re version, le code complet est disponible <20> l'endroit o<> vous avez
obtenu ce didacticiel.
<sect2>Sophistications suppl<70>mentaires<label id="sec_Further_Sophistications">
<p>
Bien que notre programme supporte maintenant XInput, il y manque des
caract<EFBFBD>ristiques que l'on souhaite trouver dans les applications
compl<EFBFBD>tes. D'abord, l'utilisateur ne veut probablement pas avoir <20> configurer
ses p<>riph<70>riques <20> chaque fois qu'il lance le programme et nous devons donc
lui permettre de sauvegarder la configuration du p<>riph<70>rique. Ceci est r<>alis<69>
en parcourant ce que retourne <em/gdk_input_list_devices()/ et en <20>crivant la
configuration dans un fichier.
<p>
Pour restaurer un <20>tat au prochain d<>marrage du programme, GDK dispose de
fonctions pour changer la configuration des p<>riph<70>riques&nbsp;:
<tscreen><verb>
gdk_input_set_extension_events()
gdk_input_set_source()
gdk_input_set_mode()
gdk_input_set_axes()
gdk_input_set_key()
</verb></tscreen>
(La liste retourn<72>e par <em/gdk_input_list_devices()/ ne doit pas <20>tre modifi<66>e
directement). Un exemple est donn<6E> dans le programme de dessin <em/gsumi/
(disponible <20> l'adresse <htmlurl
url="http://www.msc.cornell.edu/~otaylor/gsumi/"
name="http://www.msc.cornell.edu/~otaylor/gsumi/">). De plus, ce serait
pratique d'avoir une m<>thode standard pour faire cela pour toutes les
applications. Ceci appartient probablement <20> un niveau l<>g<EFBFBD>rement sup<75>rieur <20>
GTK, peut-<2D>tre dans la biblioth<74>que GNOME.
<p>
Une autre grosse omission que nous avons mentionn<6E>e plus haut est l'absence de
dessin du curseur. Les plates-formes autres qu'XFree86 n'autorisent pas encore
l'utilisation simultan<61>e d'un p<>riph<70>rique comme pointeur de base et comme
pointeur d'une application. Lisez le <url
url="http://www.msc.cornell.edu/~otaylor/xinput/XInput-HOWTO.html"
name="XInput-HOWTO"> pour plus d'informations l<>-dessus. Ceci signifie que les
applications qui veulent atteindre le plus de monde possible doivent dessiner
leur propre curseur.
<p>
Une application qui dessine son propre curseur doit faire deux choses&nbsp;:
d<EFBFBD>terminer si le p<>riph<70>rique courant a besoin ou non d'un curseur dessin<69> et
d<EFBFBD>terminer si le p<>riph<70>rique courant est <20> proximit<69> (si celui-ci est une
tablette de dessin, il est pratique de faire dispara<72>tre le curseur lorsque le
stylet est en dehors de la tablette. Lorsque le p<>riph<70>rique voit le stylet, on
dit qu'il est <20> <20> proximit<69> <20>). La premi<6D>re v<>rification est faite en
recherchant dans la liste des p<>riph<70>riques, comme nous l'avons fait pour
obtenir le nom du p<>riph<70>rique. La deuxi<78>me est r<>alis<69>e en choisissant des
<EFBFBD>v<EFBFBD>nements "proximity_out". Une exemple de dessin d'un curseur personnel est
donn<EFBFBD> dans le programme <em/testinput/ de la distribution GTK.
<sect>Conseils pour l'<27>criture d'applications GTK
<p>
Cette section est simplement un regroupement de lignes de conduites g<>n<EFBFBD>rales
et sages, ainsi que d'astuces pour cr<63>er des applications GTK correctes. Elle
est totalement inutile pour l'instant car il ne s'agit que d'une phrase :)
Utilisez <em/autoconf/ et <em/automake/ de GNU ! Ce sont vos amis :) J'ai en
projet de les pr<70>senter bri<72>vement ici.
<sect>Contributions
<p>
Ce document, comme beaucoup d'autres beaux logiciels, a <20>t<EFBFBD> cr<63><72> (NdT&nbsp;: et
traduit) par des
volontaires b<>n<EFBFBD>voles. Si vous vous y connaissez sur certains aspects de GTK
qui ne sont pas encore document<6E>s, soyez gentils de contribuer <20> ce document.
<p>
Si vous d<>cidez de contribuer, envoyez-moi (Ian Main) votre texte <20>
<tt><htmlurl url="mailto:slow@intergate.bc.ca"
name="slow@intergate.bc.ca"></tt>. La totalit<69> de ce document est libre et
tout ajout que vous pourriez y faire doit l'<27>tre <20>galement. Ainsi, tout le
monde peut utiliser n'importe quelle partie de vos exemples dans les
programmes, les copies de ce document peuvent <20>tre distribu<62>es <20> volont<6E>, etc.
<p>
Merci.
<sect>Remerciements
<p>
Je voudrai remercier les personnes suivantes pour leurs contributions <20> ce texte&nbsp;:
<itemize>
<item>Bawer Dagdeviren, <tt><htmlurl url="mailto:chamele0n@geocities.com"
name="chamele0n@geocities.com"></tt> pour le didacticiel sur les menus.
<item>Raph Levien, <tt><htmlurl url="mailto:raph@acm.org"
name="raph@acm.org"></tt>
pour <em/bonjour tout le monde/ <20> la GTK, le placement des widgets et pour sa
sagesse. Il a aussi g<>n<EFBFBD>reusement donn<6E> un abri <20> ce didacticiel.
<item>Peter Mattis, <tt><htmlurl url="mailto:petm@xcf.berkeley.edu"
name="petm@xcf.berkeley.edu"></tt> pour le programme GTK le plus simple et pour
sa capacit<69> <20> le faire :)
<item>Werner Koch <tt><htmlurl url="mailto:werner.koch@guug.de"
name="werner.koch@guug.de"></tt> pour la conversion du texte original en SGML,
et pour la hi<68>rarchie des classes de widget.
<item>Mark Crichton <tt><htmlurl url="mailto:crichton@expert.cc.purdue.edu"
name="crichton@expert.cc.purdue.edu"></tt> pour le code de l'usine <20> menus et
pour le didacticiel de placement des tables.
<item>Owen Taylor <tt><htmlurl url="mailto:owt1@cornell.edu"
name="owt1@cornell.edu"></tt> pour la section sur le widget EventBox (et le
patch de la distribution). Il est aussi responsable des sections sur l'<27>criture
de vos propres widgets GTK et de l'application exemple. Merci beaucoup <20> Owen
pour toute son aide !
<item>Mark VanderBoom <tt><htmlurl url="mailto:mvboom42@calvin.edu"
name="mvboom42@calvin.edu"></tt> pour son merveilleux travail sur les widgets
Notebook, Progress Bar, Dialog et File selection. Merci beaucoup, Mark ! Ton
aide a <20>t<EFBFBD> tr<74>s pr<70>cieuse.
<item>Tim Janik <tt><htmlurl url="mailto:timj@psynet.net"
name="timj@psynet.net"></tt> pour son beau travail sur le widget Lists.
Merci Tim :)
<item>Rajat Datta <tt><htmlurl url="mailto:rajat@ix.netcom.com"
name="rajat@ix.netcom.com"</tt> pour son excellent travail sur le didacticiel
sur les Pixmaps.
<item>Michael K. Johnson <tt><htmlurl url="mailto:johnsonm@redhat.com"
name="johnsonm@redhat.com"></tt> pour ses infos et le code pour les menus.
</itemize>
<p>
Et <20> tous ceux d'entre vous qui ont comment<6E> et aid<69> <20> am<61>liorer ce document.
<p>
Merci.
<sect>Copyright
<p>
Ce didacticiel est Copyright (C) 1997 Ian Main
<p>
Ce programme est un logiciel libre ; vous pouvez le redistribuer et/ou le
modifier sous les termes de la licence publique g<>n<EFBFBD>rale GNU telle qu'elle est
publi<EFBFBD>e par la Free Software Foundation ; soit la version 2 de la licence, ou
(comme vous voulez) toute version ult<6C>rieure.
<p>
Ce programme est distribu<62> dans l'espoir qu'il sera utile, mais SANS AUCUNE
GARANTIE ; m<>me sans la garantie de COMMERCIALIT<49> ou d'AD<41>QUATION A UN BUT
PARTICULIER. Voir la licence publique g<>n<EFBFBD>rale GNU pour plus de d<>tails.
<p>
Vous devriez avoir re<72>u une copie de la licence publique g<>n<EFBFBD>rale GNU avec ce
programme ; si ce n'est pas le cas, <20>crivez <20> la Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
<sect1>Notes du traducteur
Ce document a <20>t<EFBFBD> adapt<70> en Fran<61>ais par <url url="mailto:jaco@dotcom.fr"
name="<22>ric Jacoboni">. Toute remarque sur cette adaptation sera la bienvenue.
<p>
Merci <20> <url url="mailto:kheops@linux-kheops.com" name="Jo<4A>l Bernier"> pour
avoir initi<74> cette adaptation, et <20> <url url="mailto:vincent@debian.org"
name="Vincent Renardias"> pour sa relecture.
</article>