forked from AuroraMiddleware/gtk
8601 lines
277 KiB
Plaintext
8601 lines
277 KiB
Plaintext
<!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 été d'abord développé pour être une boîte à
|
|
outils pour GIMP (General Image Manipulation Program). GTK est
|
|
construit sur GDK (GIMP Drawing Kit) qui est, avant tout, une
|
|
encapsulation des fonctions Xlib. On l'appelle « GIMP toolkit » car il
|
|
fut créé pour développer GIMP, mais il est désormais utilisé dans
|
|
plusieurs projets de logiciels libres. Les auteurs sont :
|
|
<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ée
|
|
objet. Bien qu'il soit entièrement écrit en C, il est implanté en
|
|
utilisant la notion de classes et de fonctions de rappel (pointeurs
|
|
de fonctions).
|
|
<p>
|
|
Un troisième composant, appelé glib, remplace certains appels
|
|
standard et comporte quelques fonctions supplémentaires pour gérer les
|
|
listes chaînées, etc. Les fonctions de remplacement sont utilisées
|
|
pour accroître la portabilité de GTK car certaines de ces fonctions,
|
|
comme g_strerror(), ne sont pas disponibles ou ne sont pas standard
|
|
sur d'autres Unix. D'autres comportent des améliorations par rapport
|
|
aux versions de la libc : g_malloc(), par exemple, facilite le
|
|
dé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çon de
|
|
créer des programmes C. Il serait très précieux au lecteur d'avoir déjà une
|
|
expé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 à faire des commentaires sur ce didacticiel et sur les problèmes qu'il vous
|
|
a posé. Il y a aussi une API C++ pour GTK (GTK--), si vous préférez utiliser
|
|
ce langage, consultez plutôt la documentation qui la concerne. Une
|
|
encapsulation en Objective C et des liaisons Guile sont également disponibles,
|
|
mais ne seront pas abordées ici.
|
|
<p>
|
|
J'apprécierais beaucoup avoir un écho des problèmes que vous avez
|
|
rencontré pour apprendre GTK à partir de ce document. De plus, toute
|
|
suggestion sur son amélioration est la bienvenue.
|
|
|
|
<sect>Bien débuter
|
|
<p>
|
|
La première chose à faire est, bien sûr, de récupérer les sources de
|
|
GTK et de les installer. Vous pouvez en obtenir la derniè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é, tapez
|
|
<em>./configure --help</em> pour consulter la liste des options.
|
|
<p>
|
|
Pour commencer notre introduction à GTK, nous débuterons avec le
|
|
programme le plus simple qui soit. Celui-ci créera une fenêtre de
|
|
200x200 pixels et ne pourra se terminer qu'en le tuant à partir du
|
|
shell.
|
|
|
|
<tscreen><verb>
|
|
#include <gtk/gtk.h>
|
|
|
|
int main (int argc, char *argv[])
|
|
{
|
|
GtkWidget *window;
|
|
|
|
gtk_init (&argc, &argv);
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
gtk_widget_show (window);
|
|
|
|
gtk_main ();
|
|
|
|
return 0;
|
|
}
|
|
</verb></tscreen>
|
|
|
|
Tous les programmes inclueront évidemment le fichier
|
|
<tt>gtk/gtk.h</tt> qui déclare les variables, fonctions, structures,
|
|
etc. qui seront utilisées par votre application GTK.
|
|
<p>
|
|
La ligne :
|
|
|
|
<tscreen><verb>
|
|
gtk_init (&argc, &argv);
|
|
</verb></tscreen>
|
|
|
|
appelle la fonction <em/gtk_init(gint *argc, gchar ***argv)/ qui sera
|
|
appelée dans toutes les applications GTK. Cette fonction configure
|
|
certaines choses pour nous, comme l'aspect visuel et les couleurs par
|
|
défaut, puis appelle <em/gdk_init(gint *argc, gchar ***argv)/. Cette
|
|
dernière initialise la bibliothèque pour qu'elle puisse être utilisée,
|
|
configure les gestionnaires de signaux par défaut et vérifie les
|
|
paramètres passés à notre application via la ligne de commande en
|
|
recherchant l'un des suivants :
|
|
|
|
<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ètres, en laissant tout
|
|
ce qu'elle ne reconnaît pas pour que notre application l'analyse ou
|
|
l'ignore. Ceci crée un ensemble de paramètres standards acceptés par
|
|
toutes les applications GTK.
|
|
<p>
|
|
Les deux lignes de code suivantes créent et affichent une fenêtre.
|
|
|
|
<tscreen><verb>
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
gtk_widget_show (window);
|
|
</verb></tscreen>
|
|
|
|
Le paramètre <tt/GTK_WINDOW_TOPLEVEL/ précise que l'on veut que la
|
|
fenêtre créée suive l'aspect et le placement définis par le
|
|
gestionnaire de fenêtres. Plutôt que de créer une fenêtre de 0x0, une
|
|
fenêtre sans fenêtre fille est de 200x200 par défaut : on peut
|
|
ainsi la manipuler facilement.
|
|
<p>
|
|
La fonction <em/gtk_widget_show()/ informe GTK que l'on a configuré
|
|
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ôle atteind ce point, GTK se met en
|
|
attente d'événements X (click sur un bouton, ou appui d'une touche, par
|
|
exemple), de timeouts ou d'entrées-sorties fichier. Dans notre exemple
|
|
simple, cependant, les événements sont ignorés.
|
|
|
|
|
|
<sect1>« Bonjour tout le monde » en GTK
|
|
<p>
|
|
OK, écrivons un programme avec un widget (bouton). C'est le classique « Bonjour tout le monde » à la sauce GTK.
|
|
|
|
<tscreen><verb>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
/* fonction de rappel. Dans cet exemple, les paramètres sont ignorés...
|
|
* Les fonctions de rappel sont détaillé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 émettra le signal "destroy". Retourner FALSE signifie que l'on
|
|
* ne veut pas que la fenêtre soit détruite.
|
|
* Utilisé pour faire apparaître des boîtes de dialogue du type
|
|
* « Êtes-vous sûr de vouloir quitter ? » */
|
|
|
|
/* Remplacez FALSE par TRUE et la fenêtre principale sera détruite par
|
|
* un signal « delete_event ». */
|
|
|
|
return (FALSE);
|
|
}
|
|
|
|
/* Autre fonction de rappel */
|
|
|
|
void destroy (GtkWidget *widget, gpointer data)
|
|
{
|
|
gtk_main_quit ();
|
|
}
|
|
|
|
int main (int argc, char *argv[])
|
|
{
|
|
/* GtkWidget est le type pour déclarer les widgets. */
|
|
|
|
GtkWidget *window;
|
|
GtkWidget *button;
|
|
|
|
/* Cette fonction est appelée dans toutes les applications GTK.
|
|
* Les paramètres passés en ligne de commande sont analysés et
|
|
* retournés à l'application. */
|
|
|
|
gtk_init (&argc, &argv);
|
|
|
|
/* Création d'une nouvelle fenêtre. */
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
|
|
/* Lorsque la fenêtre reçoit le signal "delete_event"
|
|
* (envoyé par le gestionnaire de fenêtres en utilisant l'option
|
|
* « close » ou la barre de titre), on lui demande d'appeler la
|
|
* fonction delete_event() définie plus haut. La donnée passée en
|
|
* paramètre à la fonction de rappel est NULL et est ignoré dans le
|
|
* rappel. */
|
|
|
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|
GTK_SIGNAL_FUNC (delete_event), NULL);
|
|
|
|
/* Ici, on connecte l'évenement "destroy" à un gestionnaire de signal.
|
|
* Cet événement arrive lorsqu'on appelle gtk_widget_destroy() sur la
|
|
* fenê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être. */
|
|
|
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|
|
|
/* Cré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ètre. */
|
|
|
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|
GTK_SIGNAL_FUNC (hello), NULL);
|
|
|
|
/* Ceci provoquera la destruction de la fenê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êtres. */
|
|
|
|
gtk_signal_connect_object (GTK_OBJECT (button), "clicked",
|
|
GTK_SIGNAL_FUNC (gtk_widget_destroy),
|
|
GTK_OBJECT (window));
|
|
|
|
/* Insertion du bouton dans la fenêtre (container gtk). */
|
|
|
|
gtk_container_add (GTK_CONTAINER (window), button);
|
|
|
|
/* L'étape finale consiste à afficher ce nouveau widget... */
|
|
|
|
gtk_widget_show (button);
|
|
|
|
/* ... et la fenê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
|
|
* événement survienne (touche pressée ou événement souris). */
|
|
|
|
gtk_main ();
|
|
|
|
return 0;
|
|
}
|
|
</verb></tscreen>
|
|
|
|
<sect1>Compilation de « Bonjour tout le monde »
|
|
<p>
|
|
Supposons que vous avez sauvegardé le code précédent dans un fichier
|
|
nommé <em/bonjour.c/, pour le compiler tapez la commande
|
|
suivante :
|
|
|
|
<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èques invoquées ci-dessus doivent toutes être dans vos
|
|
chemins de recherche par défaut, sinon, ajoutez <tt/-L<library
|
|
directory>/ pour que <em/gcc/ recherche dans ces répertoires les
|
|
bibliothèques nécessaires. Sur mon système Debian GNU/Linux, par exemple,
|
|
je dois ajouter <tt>-L/usr/X11R6/lib</> pour qu'il trouve les
|
|
bibliothèques X11 (NdT : et c'est pareil sur mon système Red Hat
|
|
Linux...).
|
|
<p>
|
|
L'ordre des bibliothèques est important. L'éditeur de liens doit
|
|
connaître les fonctions d'une bibliothèque dont il a besoin avant de
|
|
les traiter.
|
|
<p>
|
|
Si vous compilez en utilisant des bibliothèques statiques, l'ordre
|
|
dans lequel vous listez les bibliothèques devient très
|
|
important. L'exemple donné ci-dessus devrait fonctionner dans tous les
|
|
cas.
|
|
<p>
|
|
Les bibliothèques que l'on utilise sont :
|
|
<itemize>
|
|
<item>La bibliothèque glib (<tt/-lglib/), qui contient diverses
|
|
fonctions. Seule <em/g_print()/ est utilisé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èque GDK (<tt/-lgdk/), l'enveloppe de Xlib.
|
|
|
|
<item>La bibliothèque GTK (<tt/-lgtk/), la bibliothèque des widgets,
|
|
construite au dessus de GDK.
|
|
|
|
<item>La bibliothèque Xlib (<tt/-lX11/ utilisée par GDK.
|
|
|
|
<item>La bibliothèque Xext (<tt/-lXext/). Cette dernière contient le
|
|
code pour les pixmaps en mémoire partagée et les autres extensions X.
|
|
|
|
<item>La bibliothèque mathématique (<tt/-lm/). Elle est utilisée pour
|
|
différentes raisons par GTK.
|
|
</itemize>
|
|
|
|
<sect1>Théorie des signaux et des rappels
|
|
<p>
|
|
Avant de voir en détail le programme « Bonjour tout le monde », nous
|
|
parlerons d'abord des événements et des fonctions de rappel. GTK est
|
|
dirigé par les événements, ce qui signifie qu'il restera inactif dans
|
|
<em/gtk_main/ jusqu'à ce qu'un événement survienne et que le contrôle
|
|
soit passé à la fonction appropriée.
|
|
<p>
|
|
Ce passage du contrôle est réalisé en utilisant le concept de « signal
|
|
». Lorsqu'un événement survient, comme l'appui sur un bouton, le
|
|
signal approprié sera « émis » par le widget qui a été pressé. C'est
|
|
de cette faç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équate. Ceci est fait
|
|
en utilisant une fonction comme :
|
|
|
|
<tscreen><verb>
|
|
gint gtk_signal_connect (GtkObject *object,
|
|
gchar *name,
|
|
GtkSignalFunc func,
|
|
gpointer func_data);
|
|
</verb></tscreen>
|
|
<p>
|
|
Où le premier paramètre est le widget qui émettra le signal, et le
|
|
deuxième est le nom du signal que l'on souhaite intercepter. Le
|
|
troisième paramètre est la fonction que l'on veut appeler quand le
|
|
signal est capturé, et le quatrième sont les données que l'on souhaite
|
|
passer à cette fonction.
|
|
<p>
|
|
La fonction spécifiée par le troisième paramètre s'appelle une «
|
|
fonction de rappel » et doit être de la forme :
|
|
|
|
<tscreen><verb>
|
|
void callback_func(GtkWidget *widget, gpointer *callback_data);
|
|
</verb></tscreen>
|
|
<p>
|
|
Où le premier paramètre sera un pointeur vers le widget qui a émis le
|
|
signal, et le second un pointeur vers les données passées par le
|
|
dernier paramètre de la fonction <em/gtk_signal_connect()/ décrite
|
|
plus haut.
|
|
<p>
|
|
Un autre appel utilisé dans l'exemple « Bonjour tout le monde » est :
|
|
|
|
<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ètre : un pointeur vers un objet GTK. Lorsqu'on utilise
|
|
cette fonction pour connecter des signaux, le rappel doit être de
|
|
cette forme :
|
|
|
|
<tscreen><verb>
|
|
void callback_func (GtkObject *object);
|
|
</verb></tscreen>
|
|
<p>
|
|
Où l'objet est d'ordinaire un widget. En général, on ne configure pas
|
|
de rappels pour <em/gtk_signal_connect_object/. D'habitude, ceux-ci sont
|
|
utilisés pour appeler une fonction GTK acceptant un simple widget ou
|
|
objet comme paramè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érent de paramètres. De nombreuses fonctions de la
|
|
bibliothèque GTK n'acceptent qu'un simple pointeur vers un
|
|
<em/GtkWidget/ comme paramè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ées
|
|
aux fonctions de rappel.
|
|
|
|
<sect1>« Bonjour tout le monde » pas à pas
|
|
<p>
|
|
Maintenant que nous connaissons la théorie, clarifions un peu en progressant à travers le programme « Bonjour tout le monde ».
|
|
<p>
|
|
Voici la fonction de rappel appelée lorsque le bouton est « clicked
|
|
». Dans notre exemple, on ignore le widget et les données mais il
|
|
n'est pas difficile de faire quelque chose avec. Le prochain exemple
|
|
utilisera le paramètre des données pour nous dire quel bouton a été
|
|
pressé.
|
|
|
|
<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éciale. L'événement
|
|
"delete_event" survient lorsque le gestionnaire de fenêtres l'envoie à
|
|
l'application. On doit choisir ce qu'il faut faire de ces
|
|
évé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 à GTK
|
|
de savoir ce qu'il a à faire. En retournant FALSE, on l'informe que
|
|
l'on ne veut pas que le signal "destroy" soit émis, afin de laisser
|
|
notre application tourner. En retournant TRUE, on lui demande
|
|
d'émettre "destroy" qui appellera à son tour notre gestionnaire du
|
|
signal "destroy".
|
|
|
|
<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 à dire car elle est plutôt triviale :
|
|
|
|
<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és plus loin pour créer une
|
|
fenêtre et un bouton.
|
|
|
|
<tscreen><verb>
|
|
GtkWidget *window;
|
|
GtkWidget *button;
|
|
</verb></tscreen>
|
|
<p>
|
|
Et revoici notre <em/gtk_init/. Comme précédemment, il initialise le toolkit
|
|
et analyse les paramètres de la ligne de commande. Il supprime chaque
|
|
paramètre reconnu de la liste et modifie <em/argc/ et <em/argv/ pour
|
|
faire comme si ces paramètres n'avaient jamais existé, laissant notre
|
|
application analyser les paramètres restants.
|
|
|
|
<tscreen><verb>
|
|
gtk_init (&argc, &argv);
|
|
</verb></tscreen>
|
|
<p>
|
|
Création d'une nouvelle fenêtre. C'est plutôt classique. La mémoire
|
|
est allouée pour une structure <em/GtkWidget/ et <em/window/ pointe
|
|
donc sur celle-ci. Cela configure une nouvelle fenêtre, mais celle-ci
|
|
ne sera pas affichée tant que l'on n'a pas appelé
|
|
<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 à
|
|
un objet : la fenêtre. Le signal "destroy" est capturé. Il est
|
|
émis lorsqu'on utilise le gestionnaire de fenêtres pour tuer la
|
|
fenê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 à détruire. Ici, on appelle juste la fonction <em/destroy()/
|
|
définie ci-dessus avec le paramè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 à configurer un attribut d'un objet
|
|
container. Elle configure simplement la fenê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>
|
|
À 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é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 « Bonjour tout le monde »
|
|
lorsqu'il sera affiché.
|
|
|
|
<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
|
|
émettra le signal "clicked", notre fonction <em/hello()/ soit
|
|
appelée. On ignore les paramètres et on ne passe donc que la valeur
|
|
NULL à la fonction de rappel <em/hello()/. Évidemment, le signal
|
|
"clicked" est é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çon dont le signal "destroy" peut venir
|
|
soit du gestionnaire de fenê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ées. On peut avoir autant de fonctions
|
|
de rappel que l'on désire, elles seront exécutées selon leur ordre de
|
|
connexion. Puisque la fonction <em/gtk_widget_destroy()/ n'accepte que
|
|
<em/GtkWidget *widget/ comme paramètre, on utilise ici la fonction
|
|
<em/gtk_signal_connect_object()/ à 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é en détail plus tard,
|
|
mais qui est plutôt facile à comprendre. Il indique simplement à GTK
|
|
que le bouton doit être placé dans la fenêtre où il s'affichera.
|
|
|
|
<tscreen><verb> gtk_container_add (GTK_CONTAINER (window), button);
|
|
</verb></tscreen>
|
|
<p>
|
|
Maintenant, nous avons tout configuré comme on le souhaitait :
|
|
les gestionnaires de signaux sont en place et le bouton est mis dans
|
|
la fenêtre où il doit se trouver. On demande alors à GTK de « montrer
|
|
» les widgets à l'écran. Le widget <em/window/ est affiché en dernier
|
|
afin que la fenêtre entière surgisse d'un coup plutôt que voir d'abord
|
|
la fenêtre s'afficher puis ensuite le bouton apparaître à
|
|
l'intérieur. Il faut dire qu'avec des exemples simples comme celui-ci,
|
|
vous ne ferez pas la diffé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 événements
|
|
venant du serveur X et demandera aux widgets d'émettre les signaux
|
|
lorsque ces événements surviendront.
|
|
<tscreen><verb>
|
|
gtk_main ();
|
|
</verb></tscreen>
|
|
Enfin, le <em/return/ final. Il est exécuté lorsque <em/gtk_quit()/ est appelé.
|
|
<tscreen><verb>
|
|
return 0;
|
|
</verb></tscreen>
|
|
<p>
|
|
Lorsque l'on clique sur un bouton GTK, le widget émet un
|
|
signal "clicked". Afin de pouvoir utiliser cette information, notre
|
|
programme configure un gestionnaire pour capturer ce signal. Ce
|
|
gestionnaire appelle la fonction de notre choix. Dans notre exemple,
|
|
lorsque le bouton que l'on a créé est "clicked", la fonction
|
|
<em/hello()/ est appelée avec le paramètre NULL, puis le gestionnaire
|
|
suivant de ce signal est à son tour appelé. Il appelle la fonction
|
|
<em/gtk_widget_destroy()/ en lui passant le widget <em/window/ comme
|
|
paramètre, ce qui provoque la destruction de celui-ci. Ceci
|
|
force la fenêtre à envoyer un signal "destroy", qui est capturé à son
|
|
tour et appelle notre fonction de rappel <em/destroy()/ qui ferme
|
|
simplement GTK.
|
|
<p>
|
|
Une autre façon de procéder consiste à utiliser le gestionnaire de
|
|
fenêtres pour détruire la fenêtre. Cela provoquera l'émission du
|
|
signal "delete_event" qui sera pris en charge par notre gestionnaire
|
|
<em/delete_event()/. S'il retourne FALSE, la fenêtre restera telle
|
|
quelle et rien ne se passera. Retourner TRUE forcera GTK à é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èmes Unix et ne sont pas implantés en utilisant ceux-ci, bien que
|
|
la terminologie employée soit presque identique.
|
|
|
|
|
|
<sect>Continuons
|
|
<p>
|
|
<sect1>Types de données
|
|
<p>
|
|
Vous avez probablement noté certaines choses qui nécessitent des
|
|
explications dans les exemples précédents. les <em/gint/, <em/gchar/,
|
|
etc. que vous avez pu voir sont des redéfinitions de <em/int/ et
|
|
<em/char/, respectivement. Leur raison d'être est de s'affranchir des
|
|
dépendances ennuyeuses concernant la taille des types de données
|
|
simples lorsqu'on réalise des calculs. Un bon exemple est <em/gint32/
|
|
qui désignera un entier codé sur 32 bits pour toutes les plateformes,
|
|
que ce soit une station Alpha 64 bits ou un PC i386 32 bits. Les
|
|
redéfinitions de type sont trè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é d'utiliser un <em/GtkWidget/ lorsque la
|
|
fonction attend un <em/GtkObject/. GTK possède une architecture
|
|
orientée objet, et un widget est un objet.
|
|
|
|
<sect1>Compléments sur les gestionnaires de signaux
|
|
<p>
|
|
Regardons à 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é 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écutée à son
|
|
tour, dans l'ordre dans lequel elle a été attachée. Ce marqueur vous
|
|
permet d'ôter ce rappel de la liste en faisant &;:
|
|
|
|
<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é 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 :
|
|
|
|
<tscreen><verb>
|
|
gtk_signal_handlers_destroy (GtkObject *object);
|
|
</verb></tscreen>
|
|
<p>
|
|
Cet appel n'a pas trop besoin d'explications. Il ôte simplement tous
|
|
les gestionnaires de signaux de l'objet passé en paramètre.
|
|
|
|
|
|
<sect1>Un « Bonjour tout le monde » amélioré
|
|
<p>
|
|
Étudions une version légèrement améliorée avec de meilleurs exemples
|
|
de fonctions de rappel. Ceci permettra aussi d'introduire le sujet
|
|
suivant : le placement des wigdets.
|
|
|
|
<tscreen><verb>
|
|
#include <gtk/gtk.h>
|
|
|
|
/* Notre nouveau rappel amélioré. La donnée passée à cette fonction est
|
|
* imprimée sur stdout. */
|
|
|
|
void rappel (GtkWidget *widget, gpointer *data)
|
|
{
|
|
g_print ("Re-Bonjour - %s a été pressé\n", (char *) data);
|
|
}
|
|
|
|
/* Un autre rappel */
|
|
|
|
void delete_event (GtkWidget *widget, GdkEvent *event, gpointer *data)
|
|
{
|
|
gtk_main_quit ();
|
|
}
|
|
|
|
int main (int argc, char *argv[])
|
|
{
|
|
/* GtkWidget est le type pour déclarer les widgets */
|
|
|
|
GtkWidget *window;
|
|
GtkWidget *button;
|
|
GtkWidget *box1;
|
|
|
|
/* Cette fonction est appelée dans toutes les applications GTK.
|
|
* Les paramètre passés en ligne de commande sont analysés et
|
|
* retournés à l'application. */
|
|
|
|
gtk_init (&argc, &argv);
|
|
|
|
/* Création d'une nouvelle fenêtre. */
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
|
|
/* Nouvel appel qui intitule notre nouvelle fenê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é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être. */
|
|
|
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|
|
|
/* Création d'une boîte pour y placer les widgets.
|
|
* Ceci est décrit en détails plus loin dans la section
|
|
* « placement ». La boîte n'est pas matérialisée, elle est juste
|
|
* utilisée comme moyen d'arranger les widgets. */
|
|
|
|
box1 = gtk_hbox_new(FALSE, 0);
|
|
|
|
/* On met la boîte dans la fenêtre principale. */
|
|
|
|
gtk_container_add (GTK_CONTAINER (window), box1);
|
|
|
|
/* On crée un nouveau bouton portant le label « Bouton 1 ». */
|
|
|
|
button = gtk_button_new_with_label ("Bouton 1");
|
|
|
|
/* Lorsque le bouton est cliqué, on appelle la fonction « rappel »
|
|
* avec un pointeur sur la chaîne « Bouton 1 » comme paramè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îte invisible qui a été placée dans la fenêtre. */
|
|
|
|
gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);
|
|
|
|
/* N'oubliez jamais cette étape qui indique à GTK que la configuration
|
|
* de ce bouton est terminée et qu'il peut être affiché. */
|
|
|
|
gtk_widget_show(button);
|
|
|
|
/* On fait la même chose pour créer un deuxième bouton. */
|
|
|
|
button = gtk_button_new_with_label ("Bouton 2");
|
|
|
|
/* On appelle la même fonction de rappel avec un paramètre différent,
|
|
* un pointeur sur la chaîne « Bouton 2 ». */
|
|
|
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|
GTK_SIGNAL_FUNC (rappel), (gpointer) "Bouton 2");
|
|
|
|
gtk_box_pack_start(GTK_BOX(box1), button, TRUE, TRUE, 0);
|
|
|
|
/* L'ordre dans lequel on affiche les boutons n'est pas vraiment
|
|
* important, mais il est préférable d'afficher la fenê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ètres que pour
|
|
l'exemple précédent. Vous remarquerez que, maintenant, il est plus
|
|
difficile de quitter le programme : vous devez utiliser le
|
|
gestionnaire de fenêtres ou une commande shell pour le détruire. Un
|
|
bon exercice pour le lecteur serait d'insérer un troisième bouton «
|
|
Quitter » 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être, et observez
|
|
son comportement.
|
|
<p>
|
|
Juste une remarque : il existe une autre constante utilisable
|
|
avec <em/gtk_window_new()/ - GTK_WINDOW_DIALOG. Ceci permet
|
|
d'interagir de façon un peu différente avec le gestionnaire de
|
|
fenêtres et doit être utilisé pour les fenêtres temporaires comme les
|
|
boîtes de dialogue, par exemple.
|
|
|
|
<sect>Placement des widgets
|
|
<p>
|
|
Lorsqu'on crée une application, on veut mettre plus qu'un simple
|
|
bouton dans une fenêtre. Notre premier exemple « Bonjour le monde »
|
|
n'utilisait qu'un seul widget et on pouvait donc simplement faire un
|
|
appel à <em/gtk_container_add/ pour « placer » le widget dans la
|
|
fenêtre. Mais si l'on désire en mettre plus, comment peut-on contrôler
|
|
l'endroit où le widget sera positionné ? C'est ici que le placement
|
|
entre en jeu.
|
|
|
|
<sect1>Théorie des boîtes de placement
|
|
<p>
|
|
La majeure partie du placement est faites en créant des boî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 :
|
|
boîtes horizontales et boîtes verticales. Lorsque l'on place des
|
|
widgets dans une boîte horizontale, les objets sont insérés
|
|
horizontalement de gauche à droite ou de droite à gauche selon l'appel
|
|
utilisé. Dans une boîte verticale, les widgets sont placés de haut en
|
|
bas ou vice versa. On peut utiliser n'importe quelle combinaison de
|
|
boîtes à l'intérieur ou à côté d'autres boîtes pour créer l'effet
|
|
désiré.
|
|
<p>
|
|
Pour créer une nouvelle boîte horizontale, on appelle
|
|
<em/gtk_hbox_new()/, et pour les boîtes verticales,
|
|
<em/gtk_vbox_new()/. Les fonctions <em/gtk_box_pack_start()/ et
|
|
<em/gtk_box_pack_end()/ servent à placer les objets à l'intérieur de
|
|
ces containers. La fonction <em/gtk_box_pack_start()/ placera de haut
|
|
en bas dans une boîte verticale et de gauche à droite dans une boîte
|
|
horizontale. <em/gtk_box_pack_end()/ fera le contraire en plaçant de
|
|
bas en haut et de droite à gauche. En utilisant ces fonctions, on peut
|
|
aligner à droite ou à gauche nos widgets et même les mélanger de
|
|
n'importe quelle façon pour obtenir l'effet désiré. Dans la plupart de
|
|
nos exemples, on utilisera <em/gtk_box_pack_start()/. Un objet peut
|
|
ê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éné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éciser comment les widgets doivent être placés. Comme vous pouvez
|
|
l'imaginer, cette méthode nous donne pas mal de liberté pour placer et
|
|
créer les widgets.
|
|
|
|
<sect1>Détails sur les boîtes
|
|
<p>
|
|
À cause de cette liberté, le placement des boîtes avec GTK peut
|
|
paraître déroutant au premier abord. Il existe beaucoup d'options et
|
|
il n'est pas tout de suite évident de comprendre comment elles
|
|
s'accordent toutes ensemble. En fait, il y a 5 styles de base
|
|
diffé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îte horizontale (<em/hbox/) contenant
|
|
plusieurs boutons. L'appel à <em/gtk_box_pack/ indique la façon dont
|
|
sont placés tous les boutons dans la hbox. Chaque bouton est placé
|
|
dans la hbox de la même façon (mêmes paramè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ètre est la boîte dans laquelle on place l'objet, le
|
|
second est cet objet. Tous les objets sont tous des boutons jusqu'à
|
|
maintenant, on place donc des boutons dans des boîtes.
|
|
<p>
|
|
Le paramètre <em/expand/ de <em/gtk_box_pack_start()/ ou
|
|
<em/gtk_box_pack_end()/ contrôle la façon dont le widget est placé
|
|
dans la boîte. S'il vaut TRUE, les widgets sont disposés dans la boîte
|
|
de façon à en occuper tout l'espace. S'il vaut FALSE, la boîte est
|
|
rétrécie pour correspondre à la taille du widget. Mettre <em/expand/ à
|
|
FALSE vous permettra d'aligner à droite et à gauche vos
|
|
widgets. Sinon, ils s'élargiront pour occuper toute la boîte. Le même
|
|
effet pourrait être obtenu en utilisant uniquement une des deux
|
|
fonctions <em/gtk_box_pack_start/ ou <em/pack_end/.
|
|
<p>
|
|
Le paramètre <em/fill/ des fonctions <em/gtk_box_pack/ contrôle si de
|
|
l'espace supplémentaire doit être alloué aux objets eux-mêmes (TRUE),
|
|
ou si on doit rajouter de l'espace (<em/padding/) dans la boîte autour
|
|
des objets (FALSE). Il n'a de sens que si le paramètre <em/expand/
|
|
vaut TRUE.
|
|
<p>
|
|
Lorsque l'on crée une nouvelle boîte, on utilise une fonction comme :
|
|
|
|
<tscreen><verb>
|
|
GtkWidget * gtk_hbox_new (gint homogeneous,
|
|
gint spacing);
|
|
</verb></tscreen>
|
|
|
|
Le paramè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î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ètre <em/expand/ des fonctions
|
|
<em/gtk_box_pack/ sera toujours mis à TRUE.
|
|
<p>
|
|
Quelle est alors la différence entre les paramètres <em/spacing/
|
|
(configuré lorsque la boîte est créée) et <em/padding/ (configuré
|
|
lorque les éléments sont placés) ? <em/spacing/ ajoute de l'espace
|
|
entre les objets, et <em/padding/ en ajoute de chaque côté d'un
|
|
objet. La figure suivante devrait éclairer tout cela :
|
|
|
|
<?
|
|
<IMG ALIGN="center" SRC="packbox2.gif"
|
|
VSPACE="15" HSPACE="10" ALT="Box Packing Example Image" WIDTH="509"
|
|
HEIGHT="213">
|
|
>
|
|
|
|
Voici le code utilisé pour créer les images ci-dessus. J'y ai mis beaucoup de
|
|
commentaires en espérant que vous n'aurez pas de problème pour le
|
|
relire. Compilez-le et jouez avec les différents paramè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ètres qui
|
|
* nous intéressent sont passés à cette fonction.
|
|
* On n'affiche pas la boî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éation d'une hbox avec les paramètres homogeneous et spacing
|
|
* voulus. */
|
|
|
|
box = gtk_hbox_new (homogeneous, spacing);
|
|
|
|
/* Création d'une série de boutons configurés de façon approprié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éation d'un bouton portant un label dépendant de la valeur
|
|
* du paramè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égé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ération du paramètre padding sous forme de chaî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, à ne jamais oublier ! :) */
|
|
|
|
gtk_init (&argc, &argv);
|
|
|
|
if (argc != 2) {
|
|
fprintf (stderr, "usage : %s num, où num vaut 1, 2, ou 3.\n", *argv);
|
|
|
|
/* Nettoyage dans GTK et sortie avec un code d'erreur de 1 */
|
|
gtk_exit (1);
|
|
}
|
|
|
|
which = atoi (argv[1]);
|
|
|
|
/* Création de notre fenêtre. */
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
|
|
/* Il ne faut jamais oublier de connecter le signal "destroy" à la
|
|
* fenêtre principale. C'est très important pour disposer d'un
|
|
* comportement intuitif adéquat. */
|
|
|
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|
GTK_SIGNAL_FUNC (delete_event), NULL);
|
|
|
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|
|
|
|
|
/* Création d'une boîte verticale (vbox) pour y placer les boîtes
|
|
* horizontales.
|
|
* Ceci permet de placer les boîtes horizontales contenant les boutons
|
|
* les unes au dessus des autres dans cette vbox. */
|
|
|
|
box1 = gtk_vbox_new (FALSE, 0);
|
|
|
|
/* L'exemple à afficher. Ils correspondent aux images ci-dessus. */
|
|
|
|
switch (which) {
|
|
case 1:
|
|
/* Création d'un label. */
|
|
|
|
label = gtk_label_new ("gtk_hbox_new (FALSE, 0);");
|
|
|
|
/* Alignement du label à gauche. On pré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îte verticale (vbox box1). Il ne
|
|
* faut pas oublier que les widgets qui s'ajoutent à une vbox sont
|
|
* placé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î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î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è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éation d'un séparateur, on verra cela plus tard, mais ils sont
|
|
* simples à utiliser. */
|
|
|
|
separator = gtk_hseparator_new ();
|
|
|
|
/* Placement du séparateur dans la vbox. Ne pas oublier que tous les
|
|
* widgets sont placés dans une vbox et qu'il seront placés
|
|
* verticalement. */
|
|
|
|
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
|
|
gtk_widget_show (separator);
|
|
|
|
/* Cré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è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è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è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éation d'un label, box1 est une vbox identique à
|
|
* celle créé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è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è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è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è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è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è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é d'utiliser
|
|
* gtk_box_pack_end() pour aligner les widgets à droite.
|
|
* On crée d'abord une nouvelle boîte comme d'habitude. */
|
|
|
|
box2 = make_box (FALSE, 0, FALSE, FALSE, 0);
|
|
|
|
/* On crée le label qui sera mis à la fin. */
|
|
|
|
label = gtk_label_new ("end");
|
|
|
|
/* On le place en utilisant gtk_box_pack_end(), il est ainsi
|
|
* mis à droite de la hbox créée par l'appel à make_box(). */
|
|
|
|
gtk_box_pack_end (GTK_BOX (box2), label, FALSE, FALSE, 0);
|
|
|
|
/* Affichage du label. */
|
|
|
|
gtk_widget_show (label);
|
|
|
|
/* Placement de box2 dans box1 (la vbox, vous vous rappelez ? :) */
|
|
|
|
gtk_box_pack_start (GTK_BOX (box1), box2, FALSE, FALSE, 0);
|
|
gtk_widget_show (box2);
|
|
|
|
/* Séparateur pour le bas. */
|
|
|
|
separator = gtk_hseparator_new ();
|
|
|
|
/* Configuration du séparateur en 400x5 pixels.
|
|
* La hbox que l'on a créée aura donc 400 pixels de large,
|
|
* et le label "end" sera séparé des autres de la hbox.
|
|
* Sinon, tous les widgets de la hbox seraient placés les plus
|
|
* près possible les uns des autres. */
|
|
|
|
gtk_widget_set_usize (separator, 400, 5);
|
|
|
|
/* Placement du séparateur dans la vbox (box1)
|
|
* créée au debut de main(). */
|
|
|
|
gtk_box_pack_start (GTK_BOX (box1), separator, FALSE, TRUE, 5);
|
|
gtk_widget_show (separator);
|
|
}
|
|
|
|
/* Cré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être. Ceci enverra le
|
|
* signal "destroy" à la fenêtre. Ce signal sera à son tour capturé
|
|
* 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 « quitbox ».
|
|
* Les 3 derniers paramè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ê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être en dernier */
|
|
|
|
gtk_widget_show (window);
|
|
|
|
/* Ne pas oublier notre fonction principale. */
|
|
|
|
gtk_main ();
|
|
|
|
/* Le contrôle revient ici lorsque gtk_main_quit() est appelée,
|
|
* jusqu'à ce que gtk_exit() soitutilisée. */
|
|
|
|
return 0;
|
|
}
|
|
</verb></tscreen>
|
|
|
|
<p>
|
|
<sect1>Placement avec les tables
|
|
<p>
|
|
Étudions une autre méthode de placement : les tables. Elles
|
|
peuvent s'avérer très utiles dans certaines situations.
|
|
|
|
En utilisant des tables, on crée une grille dans laquelle on peut
|
|
placer les widgets. Ceux-ci peuvent occuper tous les endroits que l'on
|
|
désire.
|
|
|
|
La première chose à faire est, bien sûr, d'étudier la fonction
|
|
<em/gtk_table_new/ :
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_table_new (gint rows,
|
|
gint columns,
|
|
gint homogeneous);
|
|
</verb></tscreen>
|
|
<p>
|
|
Le premier paramètre est le nombre de lignes de la table et le
|
|
deuxième, le nombre de colonnes.
|
|
|
|
Le paramètre <em/homogeneous/ s'occupe de la façon dont les cases de
|
|
la table seront dimensionné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 à n, où n est le nombre spécifié dans
|
|
l'appel à <em/gtk_table_new/. Ainsi, avec <em/rows/ = 2 et
|
|
<em/columns/ = 2, la table ressemblera à ceci :
|
|
|
|
<tscreen><verb>
|
|
0 1 2
|
|
0+----------+----------+
|
|
| | |
|
|
1+----------+----------+
|
|
| | |
|
|
2+----------+----------+
|
|
</verb></tscreen>
|
|
<p>
|
|
On notera que le système de coordonnées part du coin en haut à
|
|
gauche. Pour placer un widget dans une case, ou utilise la fonction
|
|
suivante :
|
|
|
|
<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ù le premier paramètre (<em/table/) est la table que l'on a créée et
|
|
le second (<em/child/) est le widget que l'on veut placer dans la
|
|
table.
|
|
|
|
Les paramètres <em/left_attach/ et <em/right_attach/ spécifient
|
|
l'emplacement du widget et le nombre de cases à utiliser. Par exemple,
|
|
si on veut placer un bouton dans le coin inférieur droit de la table
|
|
dé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érieure de notre table, on utilisera
|
|
les valeurs 0, 2, 0, 1.
|
|
|
|
Les paramètres <em/xoptions/ et <em/yoptions/ servent à préciser les
|
|
options de placement et peuvent être combinées par un OU logique pour
|
|
permettre des options multiples.
|
|
|
|
Ces options sont :
|
|
<itemize>
|
|
<item>GTK_FILL - Si la case de la table est plus large que le widget, et que
|
|
GTK_FILL est spécifié, le widget s'é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éralement, à cause d'un redimensionnement de la fenêtre par
|
|
l'utilisateur), les widgets sont alors simplement poussés vers le bas
|
|
de la fenêtre et disparaissent. Si GTK_SHRINK est spécifié, 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être.
|
|
</itemize>
|
|
|
|
Le paramêtres de <em/padding/ jouent le même rôle que pour les boîtes,
|
|
il créent une zone libre, spécifiée en pixels, autour du widget.
|
|
|
|
gtk_table_attach() a BEAUCOUP d'options. Voici donc une fonction-raccourci :
|
|
|
|
<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ètres sont
|
|
les mêmes que ceux de la fonction précé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è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é à droite de la colonne et pour
|
|
les lignes, il est ajouté en dessous.
|
|
|
|
On peut aussi configurer un espacement pour toutes les lignes et/ou
|
|
colonnes avec :
|
|
|
|
<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ère ligne et la dernière colonne n'ont pas
|
|
d'espace supplémentaire.
|
|
|
|
<sect1>Exemple de placement avec table
|
|
<p>
|
|
Pour le moment, étudiez l'exemple sur les tables (testgtk.c) distribué
|
|
avec les sources de GTK.
|
|
|
|
<sect>Vue d'ensemble des widgets
|
|
<p>
|
|
<p>
|
|
Les étapes pour créer un widget en GTK sont :
|
|
<enum>
|
|
<item> <em/gtk_*_new()/ - une des fonctions disponibles pour cré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équats.
|
|
|
|
<item> Configuration des attributs du widget.
|
|
|
|
<item> Placement du widget dans un container en utilisant un appel approprié comme
|
|
<em/gtk_container_add()/ ou <em/gtk_box_pack_start()/.
|
|
|
|
<item> Affichage du widget grâce à <em/gtk_widget_show()/.
|
|
</enum>
|
|
<p>
|
|
<em/gtk_widget_show()/ permet à GTK de savoir que l'on a fini de
|
|
configurer les attributs du widget et qu'il est prêt à être
|
|
affiché. On peut aussi utiliser <em/gtk_widget_hide()/ pour le faire
|
|
disparaître. L'ordre dans lequel on affiche les widgets n'est pas
|
|
important, mais il est préférable d'afficher la fenêtre en dernier
|
|
pour qu'elle surgisse d'un seul coup plutôt que de voir les différents
|
|
widgets apparaître à l'écran au fur et à mesure. Les fils d'un widget
|
|
(une fenêtre est aussi un widget) ne seront pas affichés tant que la
|
|
fenêtre elle-même n'est pas affichée par la fonction
|
|
<em/gtk_widget_show()/.
|
|
|
|
<sect1> Conversions de types
|
|
<p>
|
|
Vous remarquerez, au fur et à mesure que vous progressez, que GTK
|
|
utilise un système de coercition de type. Celle-ci est toujours
|
|
réalisée en utilisant des macros qui vérifient si l'objet donné peut
|
|
être converti et qui réalisent cette coercition. Les macros que vous
|
|
rencontrerez le plus sont :
|
|
|
|
<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ées pour convertir les paramètres des
|
|
fonctions. Vous les verrez dans les exemples et, en règle générale,
|
|
vous saurez les utiliser simplement en regardant la
|
|
déclaration d'une fonction.
|
|
|
|
Comme vous pouvez le voir dans la hié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 à
|
|
chaque fois qu'une fonction requiert un objet - il suffit d'utiliser
|
|
la macro GTK_OBJECT().
|
|
|
|
Par exemple :
|
|
|
|
<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érarchie de classe ci-dessous, vous remarquerez que beaucoup de
|
|
widgets viennent de la classe <em/GtkContainer/. N'importe lequel de
|
|
ces widgets peut être utilisé avec la macro GTK_CONTAINER pour être
|
|
passé en paramètre à une fonction qui attend un container.
|
|
|
|
Malheureusement, ces macros ne peuvent ê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 : ils peuvent s'avérer très
|
|
instructifs. En fait, il n'est pas difficile de comprendre comment
|
|
fonctionne un widget, il suffit d'étudier les déclarations des
|
|
fonctions.
|
|
|
|
<p>
|
|
<sect1>La hiérarchie des widgets
|
|
<p>
|
|
Voici l'arbre de la hiérarchie de classes utilisé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être
|
|
<p>
|
|
Les widgets suivants n'ont pas de fenêtre associée. Si vous voulez
|
|
capturer des événements, vous devez utiliser <em/GtkEventBox/.
|
|
Reportez-vous à 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 à tour, créant quelques fonctions simples pour les afficher. Une
|
|
autre source intéressante est le programme <em/testgtk.c/ livré avec
|
|
GTK. Il se trouve dans le répertoire <em>gtk/</em>
|
|
|
|
<sect>Widgets boutons
|
|
<p>
|
|
<sect1>Boutons normaux
|
|
<p>
|
|
On a déjà presque vu tout ce qu'il y avait à voir sur le widget
|
|
bouton. Il est très simple. Cependant, il y a deux façons de créer un
|
|
bouton. On peut utiliser <em/gtk_button_new_with_label()/ pour créer
|
|
un bouton avec un label, ou <em/gtk_button_new()/ pour créer un bouton
|
|
vide. Dans ce dernier cas, c'est à vous de placer un label ou un
|
|
pixmap sur celui-ci. Pour ce faire, créez une boî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îte dans le bouton.
|
|
<p>
|
|
Voici un exemple d'utilisation de <em/gtk_button_new()/ pour créer un
|
|
bouton contenant une image et un label. J'ai séparé du reste le code
|
|
qui crée une boîte pour que vous puissiez l'utiliser dans vos
|
|
programmes.
|
|
|
|
|
|
<tscreen><verb>
|
|
#include <gtk/gtk.h>
|
|
|
|
|
|
/* Création d'une hbox avec une image et un label. Cette fonction
|
|
* retourne la boî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é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ît la vraie raison, qu'il
|
|
* m'éclaire sur ce point. */
|
|
|
|
style = gtk_widget_get_style(parent);
|
|
|
|
/* Chargement de xpm pour créer une image */
|
|
|
|
pixmap = gdk_pixmap_create_from_xpm (parent->window, &mask,
|
|
&style->bg[GTK_STATE_NORMAL],
|
|
xpm_filename);
|
|
pixmapwid = gtk_pixmap_new (pixmap, mask);
|
|
|
|
/* Création d'un label */
|
|
|
|
label = gtk_label_new (label_text);
|
|
|
|
/* placement de l'image et du label dans la boî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 été pressé\n", (char *) data);
|
|
}
|
|
|
|
|
|
int main (int argc, char *argv[])
|
|
{
|
|
/* GtkWidget est le type utilisé pour déclarer les widgets */
|
|
|
|
GtkWidget *window;
|
|
GtkWidget *button;
|
|
GtkWidget *box1;
|
|
|
|
gtk_init (&argc, &argv);
|
|
|
|
/* Création d'une fenêtre */
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
|
|
gtk_window_set_title (GTK_WINDOW (window), "Pixmap'd Buttons!");
|
|
|
|
/* Il est préférable de faire cela pour toutes les fenêtres */
|
|
|
|
gtk_signal_connect (GTK_OBJECT (window), "destroy",
|
|
GTK_SIGNAL_FUNC (gtk_exit), NULL);
|
|
|
|
|
|
/* Configuration du bord de la fenêtre */
|
|
|
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|
|
|
/* Création d'un bouton */
|
|
|
|
button = gtk_button_new ();
|
|
|
|
/* Vous devriez être habitué à voir ces fonctions maintenant */
|
|
|
|
gtk_signal_connect (GTK_OBJECT (button), "clicked",
|
|
GTK_SIGNAL_FUNC (callback), (gpointer) "cool button");
|
|
|
|
/* Appel de notre fonction de création de boî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 être utilisée pour placer des
|
|
xpms et des labels sur tout widget qui peut être container.
|
|
|
|
<sect1> Boutons commutateurs
|
|
<p>
|
|
Les boutons commutateurs ressemblent beaucoup aux boutons normaux, sauf
|
|
qu'ils seront toujours alternativement dans un état ou dans un
|
|
autre. Le changement d'état s'effectue par un click. Ils peuvent être
|
|
enfoncés et, lorsqu'on clique dessus, ils se relèvent. Re-cliquez,
|
|
et ils se renfoncent.
|
|
|
|
Les boutons commutateurs sont la base des cases à cocher ou des boutons
|
|
radio, donc la plupart des appels utilisés pour les boutons commutateurs
|
|
sont hérités par les cases à cocher et les boutons radio. J'insisterai
|
|
là dessus quand nous les aborderons.
|
|
|
|
Cré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ère crée un bouton commutateur vide et la
|
|
deuxième un bouton commutateur contenant déjà un label.
|
|
<p>
|
|
Pour récupérer l'état d'un commutateur et cela comprend aussi les
|
|
cases à cocher et les boutons radio, on utilise une macro comme nous
|
|
le montrons dans l'exemple qui suit et qui teste l'état du commutateur
|
|
dans une fonction de rappel. Le signal qui nous intéresse et qui est
|
|
émis par les boutons commutateurs (ce qui comprend aussi les cases à
|
|
cocher et les boutons radio), est le signal "toggled". Pour vérifier
|
|
l'état de ces boutons, on configure un gestionnaire de signal qui
|
|
capture "toggled" et utilise la macro pour déterminer l'état. La
|
|
fonction de rappel ressemblera à ceci :
|
|
|
|
<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âché. */
|
|
|
|
} else {
|
|
|
|
/* le bouton est enfoncé */
|
|
}
|
|
}
|
|
</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 être utilisé pour configurer l'état d'un bouton
|
|
commutateur et de ses descendants, les cases à cocher et les boutons
|
|
radio. On lui passe notre bouton en premier paramètre et TRUE ou
|
|
FALSE pour spécifier s'il doit être relâché ou enfoncé. Par défaut, il
|
|
est relâché (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'état est
|
|
modifié, cela force le bouton à é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 émettre le signal "toggled".
|
|
|
|
<sect1> Cases à cocher
|
|
<p>
|
|
Les cases à cocher héritent de nombreuses propriétés et fonctions des
|
|
boutons commutateurs, mais ont un aspect différent. Au lieu d'être des
|
|
boutons contenant du texte, ce sont de petits carrés avec un texte sur
|
|
leur droite. Il sont souvent utilisés pour valider ou non des options
|
|
dans les applications.
|
|
|
|
Les deux fonctions de création sont identiques à 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ée une case à cocher avec un texte à
|
|
coté d'elle.
|
|
|
|
La vérification de l'état d'une case à cocher est identique à celle
|
|
des boutons commutateurs.
|
|
|
|
<sect1> Boutons radio
|
|
<p>
|
|
Les boutons radio ressemblent aux cases à cocher sauf qu'ils sont
|
|
groupés de façon à ce qu'un seul d'entre-eux puisse être sélectionné à
|
|
un moment donné. Ils sont utilisés par les applications lorsqu'il
|
|
s'agit d'effectuer un choix dans une liste d'options.
|
|
|
|
La création d'un bouton radio s'effectue grâce à l'un des appels
|
|
suivants :
|
|
|
|
<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ètre supplémentaire de ces fonctions. Elles
|
|
nécessitent un groupe pour réaliser correctement leur tâche. Le
|
|
premier appel doit passer NULL au premier paramètre puis on peut créer un
|
|
groupe en utilisant :
|
|
|
|
<tscreen><verb>
|
|
GSList* gtk_radio_button_group (GtkRadioButton *radio_button);
|
|
</verb></tscreen>
|
|
|
|
<p>
|
|
On passe alors ce groupe en premier paramètre des appels suivants aux fonctions de création. Il est préférable, aussi, de préciser quel bouton doit être choisi par défaut avec la fonction :
|
|
|
|
<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ç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ès utilisés dans GTK et sont relativement
|
|
simples. Ils n'émettent pas de signaux car ils n'ont pas de fenêtre X
|
|
qui leur est associée. Si vous avez besoin de capturer des signaux ou
|
|
de faire des coupures (« clippings »), utilisez un widget EventBox.
|
|
|
|
Pour créer un label, on utilise :
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_label_new (char *str);
|
|
</verb></tscreen>
|
|
|
|
Où l'unique paramètre est la chaîne de caractères que l'on veut que le
|
|
label affiche.
|
|
|
|
Pour changer le texte d'un label après sa création, on utilise la fonction :
|
|
|
|
<tscreen><verb>
|
|
void gtk_label_set (GtkLabel *label,
|
|
char *str);
|
|
</verb></tscreen>
|
|
<p>
|
|
où le premier paramè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îne.
|
|
|
|
L'espace nécessaire à la nouvelle chaîne sera automatiquement ajusté
|
|
si nécessaire.
|
|
|
|
Pour récupérer la chaîne courante, on utilise la fonction :
|
|
|
|
<tscreen><verb>
|
|
void gtk_label_get (GtkLabel *label,
|
|
char **str);
|
|
</verb></tscreen>
|
|
|
|
où le premier paramètre est le label dont on veut récupérer la chaîne
|
|
et le second sert à retourner cette chaî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 à utiliser, on ne donnera donc pas d'exemple. Si vous
|
|
voulez voir du code, consultez le programme <em/testgtk.c/ distribué
|
|
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éer une nouvelle
|
|
bulle d'aide. Vous n'avez besoin que de le faire une fois dans une
|
|
fonction donnée. Le <em/GtkTooltip/ que cette fonction retourne peut
|
|
être utilisé pour créer plusieurs bulles d'aide.
|
|
|
|
<tscreen><verb>
|
|
GtkTooltips *gtk_tooltips_new (void);
|
|
</verb></tscreen>
|
|
|
|
Lorsque vous avez créé une nouvelle bulle d'aide et le widget sur lequel vous
|
|
voulez l'utiliser, vous n'avez qu'à faire cet appel pour la configurer :
|
|
|
|
<tscreen><verb>
|
|
void gtk_tooltips_set_tips (GtkTooltips *tooltips,
|
|
GtkWidget *widget,
|
|
gchar *tips_text);
|
|
</verb></tscreen>
|
|
|
|
Les paramètres sont la bulle d'aide déjà créée, suivi du widget pour
|
|
lequel vous voulez voir apparaître cette bulle et le texte que vous
|
|
voulez qu'elle contienne.
|
|
<p>
|
|
Voici un petit exemple :
|
|
|
|
<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 être utilisées avec les bulles d'aide. Je ne ferais que les énumérer et les décrire briè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ées.
|
|
|
|
<tscreen><verb>
|
|
void gtk_tooltips_disable (GtkTooltips *tooltips);
|
|
</verb></tscreen>
|
|
|
|
Désactivation d'un ensemble de bulles d'aide activé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à créé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écifier les couleurs...
|
|
<p>
|
|
Et c'est tout concernant les fonctions associé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ées pour afficher la progression
|
|
d'une opération. Elles sont très simple à utiliser comme vous pourrez
|
|
le constater en étudiant le code ci-dessous. Commençons d'abord par
|
|
l'appel permettant de créer une nouvelle barre.
|
|
|
|
<tscreen><verb>
|
|
GtkWidget *gtk_progress_bar_new (void);
|
|
</verb></tscreen>
|
|
|
|
Maintenant que la barre est créée, nous pouvons l'utiliser.
|
|
|
|
<tscreen><verb>
|
|
void gtk_progress_bar_update (GtkProgressBar *pbar, gfloat percentage);
|
|
</verb></tscreen>
|
|
|
|
Le premier paramètre est la barre de progression sur laquelle on veut
|
|
agir, et le second est le pourcentage « effectué », signifiant le
|
|
remplissage de la barres de 0 à 100 % (réel compris entre 0 et 1).
|
|
|
|
Les barres de progression sont généralement utilisé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çon.
|
|
|
|
Voici un exemple de barre de progression mise à 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émente et met à jour la barre de progression,
|
|
* elle la réinitialise si pstat vaut FALSE */
|
|
|
|
gint progress (gpointer data)
|
|
{
|
|
gfloat pvalue;
|
|
|
|
/* récupé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 (&argc, &argv);
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
|
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|
GTK_SIGNAL_FUNC (destroy), NULL);
|
|
|
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|
|
|
table = gtk_table_new(3,2,TRUE);
|
|
gtk_container_add (GTK_CONTAINER (window), table);
|
|
|
|
label = gtk_label_new ("Exemple de barre de progression");
|
|
gtk_table_attach_defaults(GTK_TABLE(table), label, 0,2,0,1);
|
|
gtk_widget_show(label);
|
|
|
|
/* Cré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 à jour de la barre */
|
|
|
|
ptimer = gtk_timeout_add (100, progress, pbar);
|
|
|
|
/* Ce bouton indique à 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éral des barres de progression, nous les étudierons
|
|
dans l'ordre de leurs appels.
|
|
|
|
<tscreen><verb>
|
|
pbar = gtk_progress_bar_new ();
|
|
</verb></tscreen>
|
|
|
|
Cet appel crée une nouvelle barre, nommé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 à l'utilisation
|
|
des barres de progression.
|
|
|
|
<tscreen><verb>
|
|
pvalue = GTK_PROGRESS_BAR (data)->percentage;
|
|
</verb></tscreen>
|
|
|
|
Ce code assigne à <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 à jour la barre avec la valeur de <em/pvalue/.
|
|
|
|
Et c'est tout ce qu'il y a à savoir sur les barres de
|
|
progression. Amusez-vous bien.
|
|
|
|
<sect1> Boîtes de dialogue
|
|
<p>
|
|
|
|
Les widgets boîtes de dialogue sont très simples : ce sont
|
|
simplement des fenêtres avec plusieurs choses déjà placées dedans. La
|
|
structure d'une boîte de dialogue est :
|
|
|
|
<tscreen><verb>
|
|
struct GtkDialog
|
|
{
|
|
GtkWindow window;
|
|
|
|
GtkWidget *vbox;
|
|
GtkWidget *action_area;
|
|
};
|
|
</verb></tscreen>
|
|
|
|
Comme vous le voyez, cela crée simplement une fenêtre et la place dans
|
|
une vbox suivie d'un séparateur et d'une hbox pour la « zone d'action ».
|
|
|
|
Le widget boîte de dialogue peut servir à produire des messages pour
|
|
l'utilisateur ainsi qu'à d'autres tâches. Il est vraiment rudimentaire
|
|
et il n'y a qu'une seule fonction pour les boîtes de dialogue :
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_dialog_new (void);
|
|
</verb></tscreen>
|
|
|
|
Ainsi, pour créer un nouveau dialogue, on utilise :
|
|
|
|
<tscreen><verb>
|
|
GtkWidget window;
|
|
window = gtk_dialog_new ();
|
|
</verb></tscreen>
|
|
|
|
Ceci créera la boîte de dialogue et c'est maintenant à vous de
|
|
l'utiliser. Vous pouvez, par exemple, placer un bouton dans la zone
|
|
d'action en faisant quelque chose comme :
|
|
|
|
<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 à la zone de la vboxb :
|
|
|
|
<tscreen><verb>
|
|
label = gtk_label_new ("Les boî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îte de dialogue, vous pourriez
|
|
mettre deux boutons dans la zone d'action (un bouton « Annuler » et un
|
|
bouton « Ok ») et un label dans la zone de la vbox posant une question
|
|
à l'utilisateur ou signalant une erreur, etc. Vous pouvez alors
|
|
attacher un signal différent à chacun des boutons et réaliser
|
|
l'opération que l'utilisateur a choisie.
|
|
|
|
|
|
<sect1> Pixmaps
|
|
<p>
|
|
Les pixmaps sont des structures de données contenant des
|
|
images. Celles-ci peuvent être utilisées à différents endroits, mais
|
|
le plus souvent comme icô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 être créés à partir de données en
|
|
memoire, ou à partir de données lues dans un fichier. Nous utiliserons
|
|
chacun des appels pour cré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 à créer un pixmap mono-plan (2 couleurs) à partir
|
|
de données en mémoire. Chaque bit de la donnée <em/data/. <em/width/
|
|
et <em/height/ sont exprimés en pixels. Le pointeur vers un
|
|
<em/GdkWindow/ pointe sur la fenêtre courante car les ressources d'un
|
|
pixmap n'ont de signification que dans le contexte de l'é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ée pour créer un pixmap d'une profondeur
|
|
donnée (nombre de couleurs) à partir de la donnée spécifiée pas
|
|
<em/data/. <em/fg/ et <em/bg/ sont les couleurs à utiliser pour
|
|
l'avant et l'arriè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ésentation des pixmaps reconnue par le système X Window. Il est largement utilisé et de nombreux utilitaires pour créer des fichiers d'images à ce format sont disponibles. Le fichier <em/filename/ doit contenir une image dans ce format qui sera chargée dans la structure pixmap. Le masque <em/mask/ indique quels sont les bits opaques du pixmap. Tous les autres bits sont colorisés en utilisant la couleur spécifiée par <em/transparent_color/. Un exemple d'utilisation est présenté 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 être intégrées dans un programme sous la forme de
|
|
données <em/data/ au format XPM. Un pixmap est créé en utilisant ces données au
|
|
lieu de les lire dans un fichier. Un exemple de telles données est :
|
|
|
|
<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é un pixmap et que l'on en a plus besoin tout de suite, il est préférable de libérer la ressource en utilisant un appel à <em/gdk_pixmap_destroy/. Les pixmaps doivent être considérées comme une ressource précieuse.
|
|
|
|
Quand le pixmap est créé, on peut l'afficher comme un widget GTK. On
|
|
doit créer un widget pixmap qui contiendra le pixmap GDK. Ceci est
|
|
réalisé de la façon suivante :
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_pixmap_new( GdkPixmap *pixmap,
|
|
GdkBitmap *mask );
|
|
</verb></tscreen>
|
|
<p>
|
|
Les autres fonctions pour les widgets pixmap sont :
|
|
|
|
<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 à changer le pixmap pris en charge par le widget. <em/val/ est le pixmap créé par le GDK.
|
|
|
|
Voici un exemple illustrant l'utilisation d'un pixmap dans un bouton :
|
|
|
|
<tscreen><verb>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
|
|
/* données XPM d'une icô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ée
|
|
* via le signal "delete_event" */
|
|
|
|
void close_application( GtkWidget *widget, GdkEvent *event, gpointer *data )
|
|
{
|
|
gtk_main_quit();
|
|
}
|
|
|
|
|
|
/* Invoquée lorsque le bouton est cliqué. Affiche simplement
|
|
* un message. */
|
|
|
|
void button_clicked( GtkWidget *widget, gpointer *data )
|
|
{
|
|
printf( "bouton cliqué\n" );
|
|
}
|
|
|
|
|
|
int main( int argc, char *argv[] )
|
|
{
|
|
/* GtkWidget est le type pour déclarer les widgets */
|
|
|
|
GtkWidget *window, *pixmapwid, *button;
|
|
GdkPixmap *pixmap;
|
|
GdkBitmap *mask;
|
|
GtkStyle *style;
|
|
|
|
/* Crée la fenêtre principale et attache le signal "delete_event" pour
|
|
* terminer l'application */
|
|
|
|
gtk_init( &argc, &argv );
|
|
window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
|
|
gtk_signal_connect( GTK_OBJECT (window), "delete_event",
|
|
GTK_SIGNAL_FUNC (close_application), NULL );
|
|
gtk_container_border_width( GTK_CONTAINER (window), 10 );
|
|
gtk_widget_show( window );
|
|
|
|
/* Utilisation de GDK pour créer le pixmap */
|
|
|
|
style = gtk_widget_get_style( window );
|
|
pixmap = gdk_pixmap_create_from_xpm_d( window->window, &mask,
|
|
&style->bg[GTK_STATE_NORMAL],
|
|
(gchar **)xpm_data );
|
|
|
|
/* Création d'un widget pixmap GTK pour contenir le pixmap GDK */
|
|
|
|
pixmapwid = gtk_pixmap_new( pixmap, mask );
|
|
gtk_widget_show( pixmapwid );
|
|
|
|
/* Cré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être */
|
|
gtk_main ();
|
|
|
|
return 0;
|
|
}
|
|
</verb></tscreen>
|
|
|
|
|
|
Pour charger un fichier à partir d'un fichier XPM appelé
|
|
<em/icon0.xpm/ se trouvant dans le répertoire courant, on aurait créé
|
|
le pixmap ainsi :
|
|
|
|
<tscreen><verb>
|
|
/* Charge un pixmap à partir d'un fichier */
|
|
|
|
pixmap = gdk_pixmap_create_from_xpm( window->window, &mask,
|
|
&style->bg[GTK_STATE_NORMAL],
|
|
"./icon0.xpm" );
|
|
pixmapwid = gtk_pixmap_new( pixmap, mask );
|
|
gtk_widget_show( pixmapwid );
|
|
gtk_container_add( GTK_CONTAINER(window), pixmapwid );
|
|
</verb></tscreen>
|
|
|
|
|
|
Utilisation des formes
|
|
<p>
|
|
Un désavantage de l'utilisation des pixmaps est que l'objet affiché
|
|
est toujours rectangulaire, quelle que soit l'image. On voudrait
|
|
pouvoir créer des bureaux et des applications avec des icônes ayant
|
|
des formes plus naturelles. Par exemple, pour une interface de jeu, on
|
|
aimerait avoir des boutons ronds à pousser. Pour faire cela, on doit
|
|
utiliser des fenêtres avec des formes.
|
|
|
|
Une fenêtre avec forme est simplement un pixmap dont les pixels du
|
|
fond sont transparents. Ainsi, lorsque l'image d'arrière-plan est
|
|
multicolore, on ne la cache pas avec le bord de notre icô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",
|
|
"& c #A69992489E79",
|
|
"* c #8E3886178E38",
|
|
"= c #104008200820",
|
|
"- c #596510401040",
|
|
"; c #C71B30C230C2",
|
|
": c #C71B9A699658",
|
|
"> c #618561856185",
|
|
", c #20811C712081",
|
|
"< c #104000000000",
|
|
"1 c #861720812081",
|
|
"2 c #DF7D4D344103",
|
|
"3 c #79E769A671C6",
|
|
"4 c #861782078617",
|
|
"5 c #41033CF34103",
|
|
"6 c #000000000000",
|
|
"7 c #49241C711040",
|
|
"8 c #492445144924",
|
|
"9 c #082008200820",
|
|
"0 c #69A618611861",
|
|
"q c #B6DA71C65144",
|
|
"w c #410330C238E3",
|
|
"e c #CF3CBAEAB6DA",
|
|
"r c #71C6451430C2",
|
|
"t c #EFBEDB6CD75C",
|
|
"y c #28A208200820",
|
|
"u c #186110401040",
|
|
"i c #596528A21861",
|
|
"p c #71C661855965",
|
|
"a c #A69996589658",
|
|
"s c #30C228A230C2",
|
|
"d c #BEFBA289AEBA",
|
|
"f c #596545145144",
|
|
"g c #30C230C230C2",
|
|
"h c #8E3882078617",
|
|
"j c #208118612081",
|
|
"k c #38E30C300820",
|
|
"l c #30C2208128A2",
|
|
"z c #38E328A238E3",
|
|
"x c #514438E34924",
|
|
"c c #618555555965",
|
|
"v c #30C2208130C2",
|
|
"b c #38E328A230C2",
|
|
"n c #28A228A228A2",
|
|
"m c #41032CB228A2",
|
|
"M c #104010401040",
|
|
"N c #492438E34103",
|
|
"B c #28A2208128A2",
|
|
"V c #A699596538E3",
|
|
"C c #30C21C711040",
|
|
"Z c #30C218611040",
|
|
"A c #965865955965",
|
|
"S c #618534D32081",
|
|
"D c #38E31C711040",
|
|
"F c #082000000820",
|
|
" ",
|
|
" .XoO ",
|
|
" +@#$%o& ",
|
|
" *=-;#::o+ ",
|
|
" >,<12#:34 ",
|
|
" 45671#:X3 ",
|
|
" +89<02qwo ",
|
|
"e* >,67;ro ",
|
|
"ty> 459@>+&& ",
|
|
"$2u+ ><ipas8* ",
|
|
"%$;=* *3:.Xa.dfg> ",
|
|
"Oh$;ya *3d.a8j,Xe.d3g8+ ",
|
|
" Oh$;ka *3d$a8lz,,xxc:.e3g54 ",
|
|
" Oh$;kO *pd$%svbzz,sxxxxfX..&wn> ",
|
|
" Oh$@mO *3dthwlsslszjzxxxxxxx3:td8M4 ",
|
|
" Oh$@g& *3d$XNlvvvlllm,mNwxxxxxxxfa.:,B* ",
|
|
" Oh$@,Od.czlllllzlmmqV@V#V@fxxxxxxxf:%j5& ",
|
|
" Oh$1hd5lllslllCCZrV#r#:#2AxxxxxxxxxcdwM* ",
|
|
" OXq6c.%8vvvllZZiqqApA:mq:Xxcpcxxxxxfdc9* ",
|
|
" 2r<6gde3bllZZrVi7S@SV77A::qApxxxxxxfdcM ",
|
|
" :,q-6MN.dfmZZrrSS:#riirDSAX@Af5xxxxxfevo",
|
|
" +A26jguXtAZZZC7iDiCCrVVii7Cmmmxxxxxx%3g",
|
|
" *#16jszN..3DZZZZrCVSA2rZrV7Dmmwxxxx&en",
|
|
" p2yFvzssXe:fCZZCiiD7iiZDiDSSZwwxx8e*>",
|
|
" OA1<jzxwwc:$d%NDZZZZCCCZCCZZCmxxfd.B ",
|
|
" 3206Bwxxszx%et.eaAp77m77mmmf3&eeeg* ",
|
|
" @26MvzxNzvlbwfpdettttttttttt.c,n& ",
|
|
" *;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&& ",
|
|
" &i0ycm6n4 ogk17,0<6666g ",
|
|
" N-k-<> >=01-kuu666> ",
|
|
" ,6ky& &46-10ul,66, ",
|
|
" Ou0<> o66y<ulw<66& ",
|
|
" *kk5 >66By7=xu664 ",
|
|
" <<M4 466lj<Mxu66o ",
|
|
" *>> +66uv,zN666* ",
|
|
" 566,xxj669 ",
|
|
" 4666FF666> ",
|
|
" >966666M ",
|
|
" oM6668+ ",
|
|
" *4 ",
|
|
" ",
|
|
" "};
|
|
|
|
|
|
/* Termine l'application lorsqu'elle est appelé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ée la fenêtre principale et attache le signal "delete_event"
|
|
* pour terminer l'application. On notera que la fenêtre principale
|
|
* n'a pas de barre de titre car nous la faisons surgir. */
|
|
|
|
gtk_init (&argc, &argv);
|
|
window = gtk_window_new( GTK_WINDOW_POPUP );
|
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|
GTK_SIGNAL_FUNC (close_application), NULL);
|
|
gtk_widget_show (window);
|
|
|
|
/* Cré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, &mask,
|
|
&style->bg[GTK_STATE_NORMAL],
|
|
WheelbarrowFull_xpm );
|
|
pixmap = gtk_pixmap_new( gdk_pixmap, mask );
|
|
gtk_widget_show( pixmap );
|
|
|
|
/* Pour afficher le pixmap, on utilise un widget fixe pour placer
|
|
* le pixmap */
|
|
|
|
fixed = gtk_fixed_new();
|
|
gtk_widget_set_usize( fixed, 200, 200 );
|
|
gtk_fixed_put( GTK_FIXED(fixed), pixmap, 0, 0 );
|
|
gtk_container_add( GTK_CONTAINER(window), fixed );
|
|
gtk_widget_show( fixed );
|
|
|
|
/* On masque tout sauf l'image elle-même */
|
|
|
|
gtk_widget_shape_combine_mask( window, mask, 0, 0 );
|
|
|
|
/* Affichage de la fenê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 à
|
|
un clic souris qui provoquera l'arrê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 « pages » qui se
|
|
chevauchent. Chaque page contient des informations
|
|
diffé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éparation de leur affichage.
|
|
|
|
Le premier appel de fonction que l'on doit connaître est, vous
|
|
l'aviez deviné, celui qui crée un widget bloc-notes.
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_notebook_new (void);
|
|
</verb></tscreen>
|
|
|
|
Lorsque le bloc-notes a été créé, il y a 12 fonctions permettant de
|
|
travailler sur les blocs-notes. Étudions-les séparément.
|
|
|
|
La première permet de positionner les indicateurs de pages. Ceux-ci
|
|
(désignés par le mot « tab » (signet)), peuvent se trouver en haut, en
|
|
bas, à gauche ou à 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 :
|
|
<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 à un bloc-notes. Il y
|
|
a trois façons d'ajouter des pages. Regardons les deux premières qui
|
|
sont trè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érant à la fin (<em/append/) ou au début
|
|
(<em/prepend/). <em/*child/ est le widget qui est placé dans la page
|
|
du bloc-notes, et <em/*tab_label/ est le label de la page qui est
|
|
ajoutée.
|
|
|
|
La troisième fonction ajoutant une page à un bloc-notes conserve
|
|
toutes les propriétés des deux précédentes, mais elle nous permet en
|
|
plus de spécifier la position où l'on désire insérer cette page.
|
|
|
|
<tscreen><verb>
|
|
void gtk_notebook_insert_page (GtkNotebook *notebook, GtkWidget *child,
|
|
GtkWidget *tab_label, gint position);
|
|
</verb></tscreen>
|
|
|
|
Les paramètres sont les mêmes que <em/_append_/ et <em/_prepend_/ sauf
|
|
qu'il y en a un de plus : <em/position/. Celui-ci sert à
|
|
spécifier l'endroit où cette page sera insérée.
|
|
|
|
Maintenant que nous savons insérer une page, voyons comment en supprimer une.
|
|
|
|
<tscreen><verb>
|
|
void gtk_notebook_remove_page (GtkNotebook *notebook, gint page_num);
|
|
</verb></tscreen>
|
|
|
|
Cette fonction ôte la page spécifiée par <em/page_num/ du widget
|
|
<em/*notebook/.
|
|
|
|
Pour connaître la page courante d'un bloc-notes, on dispose de la
|
|
fonction :
|
|
|
|
<tscreen><verb>
|
|
gint gtk_notebook_current_page (GtkNotebook *notebook);
|
|
</verb></tscreen>
|
|
|
|
Les deux fonctions suivantes permettent de passer à la page suivante
|
|
ou précédente d'un bloc-notes. Il suffit de faire l'appel de la
|
|
fonction adéquate avec le widget sur lequel on veut
|
|
opérer. Remarque : lorsqu'on est sur la dernière page du
|
|
bloc-notes et que l'on appelle <em/gtk_notebook_next_page/, on revient
|
|
à la première page. De même, si l'on est sur la première page et que
|
|
l'onappelle <em/gtk_notebook_prev_page/, on se retrouve sur sa
|
|
derniè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 « active ». Si vous
|
|
voulez ouvrir le bloc-notes à la page 5, par exemple, vous utiliserez
|
|
cette fonction. Sans elle, le bloc-notes s'ouvre sur sa premiè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 ô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é du code de <em/testgtk.c/ de
|
|
la distribution GTK et montre l'utilisation des 13 fonctions. Ce petit
|
|
programme crée une fenêtre contenant un bloc-notes et six boutons. Le
|
|
bloc-notes contient 11 pages, ajoutées par trois moyens
|
|
différents : à la fin, au milieu et au début. Les boutons
|
|
permettent de faire tourner les indicateurs de page, ajouter/ôter les
|
|
indicateurs et le contour, ôter une page, passer à la page suivante et
|
|
précé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îchir le widget --
|
|
* ce qui force le widget à se redessiner. */
|
|
|
|
gtk_widget_draw(GTK_WIDGET(notebook), NULL);
|
|
}
|
|
|
|
void delete (GtkWidget *widget, GdkEvent *event, gpointer *data)
|
|
{
|
|
gtk_main_quit ();
|
|
}
|
|
|
|
int main (int argc, char *argv[])
|
|
{
|
|
GtkWidget *window;
|
|
GtkWidget *button;
|
|
GtkWidget *table;
|
|
GtkWidget *notebook;
|
|
GtkWidget *frame;
|
|
GtkWidget *label;
|
|
GtkWidget *checkbutton;
|
|
int i;
|
|
char bufferf[32];
|
|
char bufferl[32];
|
|
|
|
gtk_init (&argc, &argv);
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
|
|
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
|
|
GTK_SIGNAL_FUNC (delete), NULL);
|
|
|
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|
|
|
table = gtk_table_new(2,6,TRUE);
|
|
gtk_container_add (GTK_CONTAINER (window), table);
|
|
|
|
/* Cré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 à la fin du bloc-notes. */
|
|
|
|
for (i=0; i < 5; i++) {
|
|
sprintf(bufferf, "Append Frame %d", i+1);
|
|
sprintf(bufferl, "Page %d", i+1);
|
|
|
|
frame = gtk_frame_new (bufferf);
|
|
gtk_container_border_width (GTK_CONTAINER (frame), 10);
|
|
gtk_widget_set_usize (frame, 100, 75);
|
|
gtk_widget_show (frame);
|
|
|
|
label = gtk_label_new (bufferf);
|
|
gtk_container_add (GTK_CONTAINER (frame), label);
|
|
gtk_widget_show (label);
|
|
|
|
label = gtk_label_new (bufferl);
|
|
gtk_notebook_append_page (GTK_NOTEBOOK (notebook), frame, label);
|
|
}
|
|
|
|
|
|
/* Ajoute une page à un endroit pré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é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écé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érant que ceci vous aide à créer des blocs-notes pour vos
|
|
applications GTK.
|
|
|
|
|
|
<sect1> Fenêtres avec barres de défilement
|
|
<p>
|
|
Les fenêtres avec barres de défilement servent à créer des zones
|
|
défilantes à l'intérieur d'une vraie fenêtre. On peut insérer
|
|
n'importe quel widget dans ces fenêtres, ils seront accessibles quelle
|
|
que soit leur taille en utilisant les barres de défilement.
|
|
|
|
|
|
La fonction suivante sert à créer une fenêtre avec barre de
|
|
défilement :
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_scrolled_window_new (GtkAdjustment *hadjustment,
|
|
GtkAdjustment *vadjustment);
|
|
</verb></tscreen>
|
|
<p>
|
|
Le premier paramètre est l'ajustement horizontal, et le second
|
|
l'ajustement vertical. Ils sont presque toujours positionnés à 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éfilement. Le premier paramètre est la fenêtre à défilement que l'on
|
|
veut modifier, le second configure le fonctionnement de la barre
|
|
horizontale et le troisième celui de la barre verticale.
|
|
|
|
Ce fonctionnement peut ê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être à défilement. Je n'ai commenté 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 (&argc, &argv);
|
|
|
|
/* Création d'une boîte de dialogue pour y placer la fenêtre à défilement.
|
|
* Une boîte de dialogue est une fenêtre comme les autres sauf qu'elle contient
|
|
* une vbox et un séparateur horizontal. Ce n'est qu'un raccourci pour cré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éation d'une fenêtre à 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ètre correspond à la barre horizontale,
|
|
* le second à la barre verticale. */
|
|
|
|
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
|
|
GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
|
|
|
|
/* Création d'une boîte de dialogue */
|
|
|
|
gtk_box_pack_start (GTK_BOX (GTK_DIALOG(window)->vbox), scrolled_window,
|
|
TRUE, TRUE, 0);
|
|
gtk_widget_show (scrolled_window);
|
|
|
|
/* Cré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être à défilement */
|
|
|
|
gtk_container_add (GTK_CONTAINER (scrolled_window), table);
|
|
gtk_widget_show (table);
|
|
|
|
/* Cré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 « Fermer » en bas de la boî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 « bouton par défaut ». */
|
|
|
|
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ère le bouton par défaut. Le fait de presser la touche « Entrée »
|
|
* 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être et faites attention aux
|
|
ré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ê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ède sa propre fenêtre pour recevoir les
|
|
événements et sa propre couleur de fond qui est habituellement
|
|
blanche. Comme il est directement dérivé de <em/GtkContainer/, il peut
|
|
être traité comme tel en utilisant la macro GTK_CONTAINER(List) :
|
|
voir le widget <em/GtkContainer/ pour en savoir plus.
|
|
|
|
On doit d'abord connaître l'utilisation des <em/GList/ et des
|
|
fonctions <em/g_list_*()/ qui leur sont liées pour pouvoir utiliser
|
|
pleinement le widget <em/GtkList/.
|
|
|
|
Un champ de la structure d'un widget <em/GtkList/ nous intéresse particulièrement :
|
|
|
|
<tscreen><verb>
|
|
struct _GtkList
|
|
{
|
|
...
|
|
GList *selection;
|
|
guint selection_mode;
|
|
...
|
|
};
|
|
</verb></tscreen>
|
|
|
|
Le champ <em/selection/ d'un <em/GtkList/ pointe sur une liste chaînée
|
|
de tous les items qui sont sélectionnés, ou vaut NULL si aucune
|
|
sélection n'est faite. Ainsi, pour connaî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é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/ :
|
|
|
|
<em/selection_mode/ peut avoir l'une des valeurs suivantes :
|
|
<itemize>
|
|
<item> GTK_SELECTION_SINGLE - <em/selection/ vaut NULL ou contient un
|
|
pointeur vers un seul item sélectionné.
|
|
|
|
<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é ou pointe vers le premier item sélectionné. Ce
|
|
dernier point à 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é à chaque fois que le champ sélection d'un
|
|
GtkList a changé. Cela arrive lorsqu'un fils d'un GtkList a été
|
|
sélectionné ou désélectionné.
|
|
|
|
<tscreen><verb>
|
|
void GtkList::select_child (GtkList *LIST, GtkWidget *CHILD)
|
|
</verb></tscreen>
|
|
|
|
Ce signal est invoqué lorsqu'un fils du GtkList va être
|
|
sélectionné. Ceci arrive principalement lors d'appels à
|
|
<em/gtk_list_select_item(), gtk_list_select_child()/ et lors d'appuis
|
|
de boutons. Quelques fois, il est indirectement déclenché lorsque des
|
|
fils sont ajoutés ou supprimés du GtkList.
|
|
|
|
<tscreen><verb>
|
|
void GtkList::unselect_child (GtkList *LIST, GtkWidget *CHILD)
|
|
</verb></tscreen>
|
|
|
|
Ce signal est invoqué lorsqu'un fils du GtkList va être
|
|
désélectionné. Cela arrive principalement lors d'appels à
|
|
<em/gtk_list_unselect_item(), gtk_list_unselect_child()/, et lors
|
|
d'appuis de boutons. Quelques fois, il est indirectement déclenché
|
|
lorsque des fils sont ajoutés ou supprimés du GtkList.
|
|
|
|
|
|
<sect1>Fonctions
|
|
<p>
|
|
<tscreen><verb>
|
|
guint gtk_list_get_type (void)
|
|
</verb></tscreen>
|
|
|
|
Retourne l'identificateur de type « GtkList ».
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_list_new (void)
|
|
</verb></tscreen>
|
|
|
|
Crée un nouvel objet « GtkList ». Le nouveau widget est retourné sous
|
|
la forme d'un pointeur vers un objet « GtkWidget ». NULL est retourné
|
|
en cas d'erreur.
|
|
|
|
<tscreen><verb>
|
|
void gtk_list_insert_items (GtkList *LIST, GList *ITEMS, gint POSITION)
|
|
</verb></tscreen>
|
|
|
|
Insère des items dans <em/LIST/, à partir de <em/POSITION/.
|
|
<em/ITEMS/ est une liste doublement chaîné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ère des items à 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è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>
|
|
|
|
Ôte des items de <em/LIST/. <em/ITEMS/ est une liste doublement
|
|
chaînée dont chaque noeud pointe vers un fils direct de <em/LIST/. Il
|
|
est de la responsabilité de l'appelant de faire un appel à
|
|
<em/g_list_free(ITEMS)/ aprè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>
|
|
|
|
Ôte et détruit des items de <em/LIST/. Un widget est concerné si sa
|
|
position courante dans <em/LIST/ est dans l'intervalle spécifié 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écifié 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écifié 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écifié.
|
|
|
|
<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écifié.
|
|
|
|
<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 ê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érique en « <\em GtkList*\ ». Voir
|
|
<em/Standard Macros::/, pour plus d'informations.
|
|
|
|
<tscreen><verb>
|
|
GtkListClass* GTK_LIST_CLASS (gpointer CLASS)
|
|
</verb></tscreen>
|
|
|
|
Convertit un pointeur générique en « GtkListClass* ». Voir
|
|
<em/Standard Macros::/, pour plus d'informations.
|
|
|
|
<tscreen><verb>
|
|
gint GTK_IS_LIST (gpointer OBJ)
|
|
</verb></tscreen>
|
|
|
|
Détermine si un pointeur générique référence un objet « GtkList ». 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'« emprisonner » des items en les
|
|
sé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îne pour stocker les donné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(&argc, &argv);
|
|
|
|
|
|
/* Création d'une fenêtre pour placer tous les widgets.
|
|
* Connexion de gtk_main_quit() à l'événement "destroy" de
|
|
* la fenêtre afin de prendre en charge les événements « fermeture d'une
|
|
* fenêtre » du gestionnaire de fenê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);
|
|
|
|
|
|
/* À l'intérieur de la fenêtre, on a besoin d'une boî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être à défilement pour placer le widget GtkList à l'inté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éation du widget GtkList
|
|
* Connexion du gestionnaire de signal sigh_print_selection() au signal
|
|
* "selection_changed" du GtkList pour afficher les items sélectionnés
|
|
* à 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éation d'une « Prison » pour y mettre un item. */
|
|
|
|
frame=gtk_frame_new("Prison");
|
|
gtk_widget_set_usize(frame, 200, 50);
|
|
gtk_container_border_width(GTK_CONTAINER(frame), 5);
|
|
gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_OUT);
|
|
gtk_container_add(GTK_CONTAINER(vbox), frame);
|
|
gtk_widget_show(frame);
|
|
|
|
/* Connexion du gestionnaire de signal sigh_button_event() au signal
|
|
* « mise au arrêts » des items du GtkList. */
|
|
|
|
gtk_signal_connect(GTK_OBJECT(gtklist),
|
|
"button_release_event",
|
|
GTK_SIGNAL_FUNC(sigh_button_event),
|
|
frame);
|
|
|
|
/* Création d'un séparateur. */
|
|
|
|
separator=gtk_hseparator_new();
|
|
gtk_container_add(GTK_CONTAINER(vbox), separator);
|
|
gtk_widget_show(separator);
|
|
|
|
/* Création d'un bouton et connexion de son signal "clicked" à la
|
|
* destruction de la fenê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é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 à chaque item. */
|
|
|
|
for (i=0; i<5; i++) {
|
|
GtkWidget *label;
|
|
gchar *string;
|
|
|
|
sprintf(buffer, "ListItemContainer avec Label #%d", i);
|
|
label=gtk_label_new(buffer);
|
|
list_item=gtk_list_item_new();
|
|
gtk_container_add(GTK_CONTAINER(list_item), label);
|
|
gtk_widget_show(label);
|
|
gtk_container_add(GTK_CONTAINER(gtklist), list_item);
|
|
gtk_widget_show(list_item);
|
|
gtk_label_get(GTK_LABEL(label), &string);
|
|
gtk_object_set_data(GTK_OBJECT(list_item),
|
|
list_item_data_key,
|
|
string);
|
|
}
|
|
/* Création de 5 autres labels. Cette fois-ci, on utilise
|
|
* gtk_list_item_new_with_label(). On ne peut interroger la chaî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îne de texte pour l'ajouter au items que l'on place dans une liste
|
|
* doublement chaînée (GList). On les ajoute alors par un simple appel à
|
|
* gtk_list_append_items().
|
|
* Comme on utilise g_list_prepend() pour mettre les items dans la liste
|
|
* doublement chaînée, leur ordre sera décroissant (au lieu d'être croissant si
|
|
* on utilisait g_list_append()). */
|
|
|
|
dlist=NULL;
|
|
for (; i<10; i++) {
|
|
sprintf(buffer, "Item avec le label %d", i);
|
|
list_item=gtk_list_item_new_with_label(buffer);
|
|
dlist=g_list_prepend(dlist, list_item);
|
|
gtk_widget_show(list_item);
|
|
gtk_object_set_data(GTK_OBJECT(list_item),
|
|
list_item_data_key,
|
|
"Item avec label intégré");
|
|
}
|
|
gtk_list_append_items(GTK_LIST(gtklist), dlist);
|
|
|
|
/* Enfin, on veut voir la fenêtre... */
|
|
|
|
gtk_widget_show(window);
|
|
|
|
/* Lancement de la boucle principale de gtk */
|
|
|
|
gtk_main();
|
|
|
|
/* On arrive ici après que gtk_main_quit() ait été appelée lorsque
|
|
* la fenêtre principale a été détruite. */
|
|
|
|
}
|
|
|
|
/* Gestionnaire de signal connecté aux événements boutons presser/relâcher
|
|
* du GtkList. */
|
|
|
|
void
|
|
sigh_button_event (GtkWidget *gtklist,
|
|
GdkEventButton *event,
|
|
GtkWidget *frame)
|
|
{
|
|
/* On ne fait quelque chose que si le troisième bouton (celui de droite) a été
|
|
* relâché. */
|
|
|
|
if (event->type==GDK_BUTTON_RELEASE &&
|
|
event->button==3) {
|
|
GList *dlist, *free_list;
|
|
GtkWidget *new_prisoner;
|
|
|
|
/* On recherche l'item sélectionné à ce moment pré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à prisonniers et on les
|
|
* remet dans la liste.
|
|
* Il ne faut pas oublier de libérer la liste doublement
|
|
* chaîné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'ôte du GtkList et on le place
|
|
* dans le cadre « Prison ». On doit désé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é lorsque le GtkList
|
|
* émet le signal "selection_changed". */
|
|
|
|
void
|
|
sigh_print_selection (GtkWidget *gtklist,
|
|
gpointer func_data)
|
|
{
|
|
GList *dlist;
|
|
|
|
/* Recherche dans la liste doublement chaînée des items sélectionnés
|
|
* du GtkList, à faire en lecture seulement ! */
|
|
|
|
dlist=GTK_LIST(gtklist)->selection;
|
|
|
|
/* S'il n'y a pas d'items sélectionné, il n'y a rien d'autre à faire que
|
|
* de le dire à l'utilisateur. */
|
|
|
|
if (!dlist) {
|
|
g_print("Sélection nettoyée\n");
|
|
return;
|
|
}
|
|
/* Ok, on a une sélection et on l'affiche. */
|
|
|
|
g_print("La sélection est ");
|
|
|
|
/* On récupère l'item dans la liste doublement chaînée
|
|
* puis on interroge la donnée associé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éselection exactement
|
|
comme le widget GtkList les demande pour ses fils.
|
|
|
|
Un <em/GtkListItem/ a sa propre fenêtre pour recevoir les événements et a
|
|
sa propre couleur de fond, habituellement blanche.
|
|
|
|
Comme il est directement dérivé d'un <em/GtkItem/, il peut être traité
|
|
comme tel en utilisant la macro GTK_ITEM(ListItem), reportez-vous à 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ée <em/gtk_list_item_new_with_label()/ est donc
|
|
fournie. Le même effet peut être obtenu en créant un <em/GtkLabel/ à
|
|
part, en configurant son alignement avec <em/xalign/=0 et
|
|
<em/yalign/=0.5 suivi d'un ajout ultérieur au <em/GtkListItem/.
|
|
|
|
Tout comme on n'est pas forcé d'ajouter un <em/GtkLabel/ à un
|
|
<em/GtkListItem/, on peut aussi ajouter un <em/GtkVBox/ ou un
|
|
<em/GtkArrow/ etc. à un <em/GtkListItem/.
|
|
|
|
<sect1>Signaux
|
|
<p>
|
|
Un <em/GtkListItem/ ne crée pas de nouveaux signaux par lui-même, mais
|
|
hé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 « GtkListItem ».
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_list_item_new (void)
|
|
</verb></tscreen>
|
|
|
|
Création d'un objet <em/GtkListItem/. Le nouveau widget est retourné
|
|
sous la forme d'un pointeur vers un objet <em/GtkWidget/. NULL est
|
|
retourné en cas d'erreur.
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_list_item_new_with_label (gchar *LABEL)
|
|
</verb></tscreen>
|
|
|
|
Création d'un objet <em/GtkListItem/ ayant un simple <em/GtkLabel/
|
|
comme seul fils. Le nouveau widget est retourné sous la forme d'un
|
|
pointeur vers un objet <em/GtkWidget/. NULL est retourné 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 é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 é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é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érique en <em/GtkListItemClass*/. Voir
|
|
<em/Standard Macros::/ pour plus d'informations.
|
|
|
|
<tscreen><verb>
|
|
gint GTK_IS_LIST_ITEM (gpointer OBJ)
|
|
</verb></tscreen>
|
|
|
|
Détermine si un pointeur générique se réfère à 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. É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î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éer une boîte de sélection de fichier, on utilise :
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_file_selection_new (gchar *title);
|
|
</verb></tscreen>
|
|
|
|
Pour configurer le nom de fichier, par exemple pour aller dans un
|
|
répertoire précis ou donner un nom de fichier par défaut, on utilise
|
|
la fonction :
|
|
|
|
<tscreen><verb>
|
|
void gtk_file_selection_set_filename (GtkFileSelection *filesel, gchar *filename);
|
|
</verb></tscreen>
|
|
|
|
Pour récupérer le texte que l'utilisateur a entré, ou sur lequel il a
|
|
cliqué, on utilisera la fonction :
|
|
|
|
<tscreen><verb>
|
|
gchar* gtk_file_selection_get_filename (GtkFileSelection *filesel);
|
|
</verb></tscreen>
|
|
|
|
Des pointeurs permettent d'accéder aux widgets contenus dans la widget
|
|
de sélection de fichiers. Ce sont :
|
|
|
|
<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éciser leurs utilisations.
|
|
|
|
Voici un exemple emprunté à <em/testgtk.c/ et modifié pour fonctionner
|
|
tout seul. Comme vous le verrez, il n'y a pas grand chose à faire pour
|
|
créer un wigdget de sélection de fichier. Cependant, dans cet exemple,
|
|
si le bouton Aide apparaît à l'écran, il ne fait rien car aucun signal
|
|
ne lui est attaché.
|
|
|
|
<tscreen><verb>
|
|
#include <gtk/gtk.h>
|
|
|
|
/* Récupère le nom de fichier sélectionné et l'affiche sur la console. */
|
|
|
|
void file_ok_sel (GtkWidget *w, GtkFileSelection *fs)
|
|
{
|
|
g_print ("%s\n", gtk_file_selection_get_filename (GTK_FILE_SELECTION (fs)));
|
|
}
|
|
|
|
void destroy (GtkWidget *widget, gpointer *data)
|
|
{
|
|
gtk_main_quit ();
|
|
}
|
|
|
|
int main (int argc, char *argv[])
|
|
{
|
|
GtkWidget *filew;
|
|
|
|
gtk_init (&argc, &argv);
|
|
|
|
/* Cré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, &filew);
|
|
|
|
/* Connexion de ok_button à la fonction file_ok_sel() */
|
|
|
|
gtk_signal_connect (GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button),
|
|
"clicked", (GtkSignalFunc) file_ok_sel, filew );
|
|
|
|
/* Connexion de cancel_button pour 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çons de créer des menus, la facile et la compliquée. Les
|
|
deux ont leur utilité, mais on peut généralement utiliser l'<em/usine
|
|
à menus/ (c'est la méthode facile...). La méthode « compliquée »
|
|
consiste à créer tous les menus en utilisant directement les
|
|
appels. La méthode facile consiste à utiliser les appels
|
|
<em/gtk_menu_factory/. C'est beaucoup plus simple, mais chaque
|
|
approche a ses avantages et inconvénients.
|
|
|
|
L'usine à menus est beaucoup plus facile à utiliser, elle facilite
|
|
aussi l'ajout d'autres menus. Par contre, écrire quelques fonctions
|
|
permettant de créer des menus en utilisant la méthode manuelle peut
|
|
être le début d'un long chemin avant une quelconque utilisation. Avec
|
|
l'usine à menus, il n'est pas possible d'ajouter des images ou des « /
|
|
» aux menus.
|
|
<p>
|
|
<sect1>Création manuelle de menus
|
|
<p>
|
|
Selon la tradition pédagogique, nous commencerons par le plus compliqué <tt/:)/
|
|
<p>
|
|
Regardons les fonctions utilisées pour créer les menus. La première sert à créer un nouveau menu.
|
|
|
|
<tscreen><verb>
|
|
GtkWidget *gtk_menu_bar_new()
|
|
</verb></tscreen>
|
|
|
|
Cette fonction crée une nouvelle barre de menu. On utilise la fonction
|
|
<em/gtk_container_add/ pour la placer dans une fenêtre, ou les
|
|
fonctions <em/box_pack/ pour la placer dans une boî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é (avec <em/gtk_widget_show/), il ne fait que contenir les
|
|
items du menu. Ceci deviendra plus clair lorsque nous étudierons
|
|
l'exemple ci-dessous.
|
|
<p>
|
|
Les deux appels suivants servent à créer des items de menu qui seront
|
|
placé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 à créer les menus qui doivent être affichés. On
|
|
doit bien faire la différence entre un « menu » qui est créé avec
|
|
<em/gtk_menu_new()/ et un « item de menu » créé avec les fonctions
|
|
<em/gtk_menu_item_new()/. L'item de menu sera un véritable bouton avec
|
|
une action associé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ès avoir étudié les
|
|
boutons. L'une crée un nouvel item de menu contenant déjà un label, et
|
|
l'autre ne fait que créer un item de menu vide.
|
|
<p>
|
|
Voici les étapes pour créer une barre de menu avec des menus attachés :
|
|
<itemize>
|
|
<item>Créer un nouveau menu avec <em/gtk_menu_new()/ <item>Cré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 à <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ès les autres. Cela crée une liste d'items de menu.
|
|
<item>Utiliser <em/gtk_menu_item_set_submenu()/ pour attacher les
|
|
items de menus nouvellement créés à l'item de menu racine (celui créé
|
|
à la seconde étape).
|
|
<item>Créer une nouvelle barre de menu avec
|
|
<em/gtk_menu_bar_new()/. Cette étape ne doit être faite qu'une fois
|
|
lorsque l'on cré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éation d'un menu surgissant est presque identique. La différence
|
|
est que le menu n'est pas posté « automatiquement » par une barre de
|
|
menu, mais explicitement en appelant la fonction <em/gtk_menu_popup()/
|
|
par un événement « bouton pressé ».
|
|
|
|
Suivez ces étapes
|
|
<itemize>
|
|
<item>Créer une fonction de gestion d'événement. Elle doit avoir le prototype
|
|
<tscreen>
|
|
static gint handler(GtkWidget *widget, GdkEvent *event);
|
|
</tscreen>
|
|
et elle utilisera l'événement <em/event/ pour savoir où faire surgir
|
|
le menu.
|
|
<item>Ce gestionnaire, si l'événement est un appui sur un
|
|
bouton souris, traite <em/event/ comme un événement bouton (ce qu'il
|
|
est) et l'utilise, de la façon indiquée dans le code d'exemple, pour
|
|
passer l'information à <em/gtk_menu_popup()/.
|
|
|
|
<item> Lier ce gestionnaire à un widget avec :
|
|
<tscreen>
|
|
gtk_signal_connect_object(GTK_OBJECT(widget), "event",
|
|
GTK_SIGNAL_FUNC (handler), GTK_OBJECT(menu));
|
|
</tscreen>
|
|
où <em/widget/ est le widget auquel vous le liez, <em/handler/ est
|
|
le gestionnaire, et <em/menu/ est un menu créé avec
|
|
<em/gtk_menu_new()/. Cela peut être un menu qui est aussi posté 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 (&argc, &argv);
|
|
|
|
/* Création d'un fenê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 « menu racine » de l'application. */
|
|
|
|
menu = gtk_menu_new();
|
|
|
|
/* Voici le menu racine dont le label sera le nom du menu affiché sur la barre
|
|
* de menu. Il n'a pas de gestionnaire de signal attaché car il ne fait
|
|
* qu'afficher le reste du menu lorsqu'il est pressé. */
|
|
|
|
root_menu = gtk_menu_item_new_with_label("Menu racine");
|
|
|
|
gtk_widget_show(root_menu);
|
|
|
|
/* Puis, on crée une petite boucle créant trois entrées pour « menu test »
|
|
* Notez l'appel à gtk_menu_append(). Ici, on ajoute une liste d'items à
|
|
* notre menu. Normalement, on devrait aussi capturer le signal "clicked"
|
|
* pour chacun des items et configurer une fonction de rappel pour lui,
|
|
* mais on l'a omis ici pour gagner de la place. */
|
|
|
|
for(i = 0; i < 3; i++)
|
|
{
|
|
/* Copie des noms dans buf. */
|
|
|
|
sprintf(buf, "Sous-menu Test - %d", i);
|
|
|
|
/* Cré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éressant lorsque l'item est
|
|
* sélectionné. */
|
|
|
|
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écifié que nous voulons que notre nouveau « menu »
|
|
* soit le menu du « menu racine ». */
|
|
|
|
gtk_menu_item_set_submenu(GTK_MENU_ITEM (root_menu), menu);
|
|
|
|
/* Cré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éation d'une barre de menus pour contenir les menus. Puis, on
|
|
* l'ajoute à notre fenê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é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 à la barre de menu --
|
|
* c'est l'item de menu racine sur lequel je me suis déchaîné ;-) */
|
|
|
|
gtk_menu_bar_append(GTK_MENU_BAR (menu_bar), root_menu);
|
|
|
|
/* Affichage de la fenêtre. */
|
|
|
|
gtk_widget_show(window);
|
|
|
|
gtk_main ();
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* On répond à un appui sur le bouton en postant un nouveau menu passé comme
|
|
* un widget.
|
|
*
|
|
* On remarque que le paramètre "widget" est le menu à poster, PAS le bouton
|
|
* qui a été pressé. */
|
|
|
|
|
|
static gint button_press (GtkWidget *widget, GdkEvent *event)
|
|
{
|
|
|
|
if (event->type == GDK_BUTTON_PRESS) {
|
|
GdkEventButton *bevent = (GdkEventButton *) event;
|
|
gtk_menu_popup (GTK_MENU(widget), NULL, NULL, NULL, NULL,
|
|
bevent->button, bevent->time);
|
|
|
|
/* On indique à l'appelant que l'on a géré cet événement. */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* On indique à l'appelant que l'on n'a pas géré cet événement. */
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/* Affiche une chaî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é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é la voie difficile, nous allons voir
|
|
l'utilisation des appels <em/gtk_menu_factory./
|
|
<p>
|
|
<sect1>Exemple d'usine à menu
|
|
<p>
|
|
Voici un exemple utilisant l'usine à menu de GTK. Le premier
|
|
fichier est <em/menus.h/. Nous séparerons <em/menus.c/ et <em/main.c/ à
|
|
cause des variables globales utilisé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/ :
|
|
|
|
<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ée pour créer les menus. Le premier champ
|
|
* est la chaîne de définition du menu. Le second, la touche de raccourci
|
|
* utilisée pour accéder à cette fonction du menu avec le clavier.
|
|
* Le troisième est la fonction de rappel à utiliser lorsque l'item de menu
|
|
* est choisi (par la touche de raccourci ou avec la souris). Le dernier
|
|
* élément est la donnée à passer à 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'élé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é d'un menu qui n'existe pas : %s", path);
|
|
}
|
|
|
|
</verb></tscreen>
|
|
<p>
|
|
Voici <em/main.h/ :
|
|
|
|
<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/ :
|
|
|
|
<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(&argc, &argv);
|
|
|
|
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
|
gtk_signal_connect(GTK_OBJECT(window), "destroy",
|
|
GTK_SIGNAL_FUNC(file_quit_cmd_callback),
|
|
"WM destroy");
|
|
gtk_window_set_title(GTK_WINDOW(window), "Usine à menu");
|
|
gtk_widget_set_usize(GTK_WIDGET(window), 300, 200);
|
|
|
|
main_vbox = gtk_vbox_new(FALSE, 1);
|
|
gtk_container_border_width(GTK_CONTAINER(main_vbox), 1);
|
|
gtk_container_add(GTK_CONTAINER(window), main_vbox);
|
|
gtk_widget_show(main_vbox);
|
|
|
|
get_main_menu(&menubar, &accel);
|
|
gtk_window_add_accelerator_table(GTK_WINDOW(window), accel);
|
|
gtk_box_pack_start(GTK_BOX(main_vbox), menubar, FALSE, TRUE, 0);
|
|
gtk_widget_show(menubar);
|
|
|
|
gtk_widget_show(window);
|
|
gtk_main();
|
|
|
|
return(0);
|
|
}
|
|
|
|
/* Juste une démonstration du fonctionnement des fonctions de rappel
|
|
* lorsqu'on utilise l'usine à menus. Souvent, on met tous les rappels
|
|
* des menus dans un fichier séparé, 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 à compiler :
|
|
|
|
<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égrés plus tard.
|
|
|
|
|
|
<sect>Widgets non documentés
|
|
<p>
|
|
On a besoin de leurs auteurs! :). Participez à notre didacticiel.
|
|
|
|
Si vous devez utiliser un de ces widgets non documenté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ès parlantes. Lorsque vous avez compris comment les choses fonctionnent, il n'est pas difficile de savoir comment utiliser un widget à partir des déclarations de ses fonctions. Cela, avec quelques exemples de codes pris ailleurs, devrait ne pas poser de problème.
|
|
|
|
Lorsque vous avez compris toutes les fonctions d'un nouveau widget non documenté, pensez à écrire un didacticiel pour que les autres puissent bénéficier du temps que vous y avez passé.
|
|
|
|
<sect1>Entrées de texte
|
|
<p>
|
|
|
|
<sect1>Sélections de couleurs
|
|
<p>
|
|
|
|
<sect1>Contrôle d'intervalle
|
|
<p>
|
|
|
|
<sect1>Règles
|
|
<p>
|
|
|
|
<sect1>Boîtes de texte
|
|
<p>
|
|
|
|
<sect1>Prévisualisation
|
|
<p>
|
|
(Ceci peut devoir être réécrit pour suivre le style du reste de ce
|
|
didacticiel).
|
|
|
|
<tscreen><verb>
|
|
|
|
Les prévisualisateurs servent à plusieurs choses dans GIMP/GTK. La
|
|
plus importante est celle-ci : les images de haute qualité peuvent
|
|
occuper des dizaines de mega-octets en mémoire - facilement ! Toute
|
|
opération sur une image aussi grosse implique un temps de traitement
|
|
élevé. Si cela vous prend 5 à 10 essais (i.e. 10 à 20 étapes puisque
|
|
vous devez recommencer lorsque vous avez fait une erreur) pour choisir
|
|
la bonne modification, cela prendra littéralement des heures pour
|
|
produire la bonne image - pour peu que vous ne manquiez pas de mémoire
|
|
avant. Ceux qui on passé des heures dans les chambres noires de
|
|
développement couleur connaissent cette sensation. Les
|
|
prévisualisations sont notre planche de salut !
|
|
|
|
L'aspect pénible de l'attente n'est pas le seul problème. souvent, il
|
|
est utile de comparer les versions « Avant » et « Après » côte à côte
|
|
ou, au pire l'une après l'autre. Si vous travaillez avec de grosses
|
|
images et des attentes de 10 secondes, l'obtention des versions «
|
|
Avant » et « Après » est, pour le moins, difficile. Pour des images de
|
|
30Mo (4"x6", 600dpi, 24 bits), la comparaison côte à côte est
|
|
impossible pour la plupart des gens, et la comparaison séquentielle
|
|
n'est guère mieux. Les prévisualisations sont notre planche de salut !
|
|
|
|
Mais il y a plus. Les prévisualisations permettent les
|
|
pré-prévisualisations côte à côte. En d'autres termes, vous écrivez un
|
|
plug-in (par exemple la simulation filterpack) qui aurait plusieurs
|
|
prévisualisations de ce-que-ce-serait-si-vous-faisiez-ceci. Une approche
|
|
comme celle ci agit comme une sorte de palette de prévisualisation et
|
|
est très pratique pour les petites modifications. Utilisons les
|
|
prévisualisations !
|
|
|
|
Encore plus : pour certains plug-ins une intervention humaine en
|
|
temps réel, spécifique aux images, peut s'avérer nécessaire. Dans le
|
|
plug-in SuperNova, par exemple, on demande à l'utilisateur d'entrer
|
|
les coordonnées du centre de la future supernova. La façon la plus
|
|
simple de faire cela, vraiment, est de présenter une prévisualisation
|
|
à l'utilisateur et de lui demander de choisir interactivement le
|
|
point. Utilisons les prévisualisations !
|
|
|
|
Enfin, quelques utilisations diverses : on peut utiliser les
|
|
pré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é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évisualisations pour des petits logos dans vos plug-ins et même pour
|
|
une photo de vous, l'Auteur. Utilisons les prévisualisations !
|
|
|
|
Quand ne pas utiliser les prévisualisations
|
|
|
|
N'utilisez pas les prévisualisations pour les graphes, les tracés,
|
|
etc. GDK est bien plus rapide pour ça. N'utilisez les que pour les
|
|
images !
|
|
|
|
Utilisons les prévisualisations !
|
|
|
|
Vous pouvez mettre une prévisualisation dans à peu prè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és autour d'elles. Les
|
|
pré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és fournissent les bordures nécessaires.
|
|
|
|
[Image][Image]
|
|
|
|
Les prévisualisations sont, à bien des égards, comme tous les autres
|
|
widgets de GTK (avec tout ce que cela implique) sauf qu'il disposent
|
|
d'une fonctionnalité supplémentaire : ils doivent être remplis
|
|
avec une image ! Nous traiterons d'abord exclusivement de l'aspect GTK
|
|
des prévisualisations, puis nous verrons comment les remplir.
|
|
|
|
|
|
/* Création d'un widget pré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évisualisations rendent mieux dans
|
|
des cadres :
|
|
|
|
GtkWidget *create_a_preview(int Width,
|
|
int Height,
|
|
int Colorfulness)
|
|
{
|
|
GtkWidget *preview;
|
|
GtkWidget *frame;
|
|
|
|
frame = gtk_frame_new(NULL);
|
|
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
|
|
gtk_container_border_width (GTK_CONTAINER(frame),0);
|
|
gtk_widget_show(frame);
|
|
|
|
preview=gtk_preview_new (Colorfulness?GTK_PREVIEW_COLOR
|
|
:GTK_PREVIEW_GRAYSCALE);
|
|
gtk_preview_size (GTK_PREVIEW (preview), Width, Height);
|
|
gtk_container_add(GTK_CONTAINER(frame),preview);
|
|
gtk_widget_show(preview);
|
|
|
|
my_preview_rendering_function(preview);
|
|
return frame;
|
|
}
|
|
|
|
Ceci est ma prévisualisation de base. Cette fonction retourne le cadre
|
|
« père », on peut ainsi le placer ailleurs dans notre interface. Bien
|
|
sûr, on peut passer le cadre « père » en paramètre à cette
|
|
fonction. Dans de nombreuses situations, toutefois, le contenu de la
|
|
prévisualisation est changée continuellement par notre application. En
|
|
ce cas, on peut passer un pointeur vers une prévisualisation à la
|
|
fonction <em/create_a_preview()/ et avoir ainsi un contrôle sur elle
|
|
plus tard.
|
|
|
|
Un point plus important qui pourra un jour vous faire économiser
|
|
beaucoup de temps. Quelques fois, il est souhaitable de mettre un
|
|
label à votre prévisualisation. Par exemple, on peut nommer la
|
|
prévisualisation contenant l'image originale « Original » et celle
|
|
contenant l'image modifiée « Moins Originale ». Il peut vous arriver
|
|
de placer la prévisualisation avec le label approprié dans une
|
|
vbox. L'effet inattendu est que si le label est plus large que la
|
|
prévisualisation (taille de cette dernière, taille de la fonte du
|
|
label, etc), le cadre s'élargit et ne convient plus à la
|
|
prévisualisation. Le même problème se passera probablement dans
|
|
d'autres situations aussi.
|
|
|
|
[Image]
|
|
|
|
La solution consiste à placer la prévisualisation et le label dans une
|
|
table de 2x2 en les attachant avec les paramètres suivants (c'est
|
|
l'une des possibilités, bien sûr. La clé consiste à ne pas mettre
|
|
GTK_FILL dans le second attachement)«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 :
|
|
|
|
[Image]
|
|
|
|
Divers
|
|
|
|
Rendre une prévisualisation cliquable se fait très facilement en la plaçant dans un bouton. Cela ajoute aussi une bordure agréable autour de la pré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évisualisation
|
|
|
|
Afin de nous familiariser avec les bases de ce remplissage, créons le motif suivant :
|
|
|
|
[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ées */
|
|
double r, alpha, x, y;
|
|
|
|
if (preview==NULL) return; /* J'ajoute généralement ceci quand je */
|
|
/* veux éviter des plantages stupides */
|
|
/* Vous devez vous assurer que tout a */
|
|
/* été correctement initialisé ! */
|
|
|
|
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ère "row" dans "preview" en partant du point de */
|
|
/* coordonnées (0,j) première colonne, j_ième ligne allant de SIZE */
|
|
/* pixels vers la droite */
|
|
}
|
|
|
|
free(row); /* on récupère un peu d'espace */
|
|
gtk_widget_draw(preview,NULL); /* qu'est-ce que ça fait ? */
|
|
gdk_flush(); /* et ça ? */
|
|
}
|
|
|
|
Ceux qui n'utilisent pas GIMP en ont suffisamment vu pour faire
|
|
déjà beaucoup de choses. Pour ceux qui l'utilisent, j'ai quelques
|
|
précisions à ajouter.
|
|
|
|
Prévisualisation d'image
|
|
|
|
Il est pratique de conserver une version réduite de l'image ayant
|
|
juste assez de pixels pour remplir la prévisualisation. Ceci est
|
|
possible en choisissant chaque énième pixel où n est le ratio de la
|
|
taille de l'image par rapport à la taille de la visualisation. Toutes
|
|
les opérations suivantes (y compris le remplissage des
|
|
prévisualisations) sont alors réalisées seulement sur le nombre réduit
|
|
de pixels. Ce qui suit est mon implantation de la réduction d'image
|
|
(Gardez à l'esprit que je n'ai que quelques notions de base en C !).
|
|
|
|
(ATTENTION : CODE NON TESTÉ !!!)
|
|
|
|
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 à la taille de prévisualisation choisie */
|
|
/* La taille de la prévisualisation est déterminé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 à 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ère */
|
|
|
|
gimp_drawable_mask_bounds (drawable->id, &x1, &y1, &x2, &y2);
|
|
width = x2-x1;
|
|
height = y2-y1;
|
|
/* S'il y a une SELECTION, on récupère ses frontières ! */
|
|
|
|
if (width != drawable->width && 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éera un masque réduit */
|
|
|
|
/* Si on veut prévisualiser l'image entiè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évisualiser une sélection avec une surface qui l'entoure, */
|
|
/* on doit l'agrandir un petit peu. Considérez ç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 à réduire. */
|
|
|
|
width = x2-x1;
|
|
height = y2-y1;
|
|
|
|
/* Les lignes ci-dessous déterminent la dimension qui sera le coté */
|
|
/* le plus long. Cette idée est empruntée au plug-in Supernova. */
|
|
/* Je soupçonne que j'aurais pu y penser moi-même, mais la vérité */
|
|
/* doit être dite. Le plagiat pue ! */
|
|
|
|
if (width>height) {
|
|
RW=LongerSize;
|
|
RH=(float) height * (float) LongerSize/ (float) width;
|
|
}
|
|
else {
|
|
RH=LongerSize;
|
|
RW=(float)width * (float) LongerSize/ (float) height;
|
|
}
|
|
|
|
/* L'image entière est réduite dans une chaîne ! */
|
|
|
|
tempRGB = (guchar *) malloc(RW*RH*bytes);
|
|
tempmask = (guchar *) malloc(RW*RH);
|
|
|
|
gimp_pixel_rgn_init (&srcPR, drawable, x1, y1, width, height, FALSE, FALSE);
|
|
gimp_pixel_rgn_init (&srcMask, mask, x1, y1, width, height, FALSE, FALSE);
|
|
|
|
/* 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 (&srcPR, src_row, x1, y1+whichrow, width);
|
|
gimp_pixel_rgn_get_row (&srcMask, src_mask_row, x1, y1+whichrow, width);
|
|
|
|
for (j=0; j < RW; j++) {
|
|
whichcol=(float)j*(float)width/(float)RW;
|
|
|
|
/* Pas de sélection = chaque point est complètement sélectionné ! */
|
|
|
|
if (NoSelectionMade)
|
|
tempmask[i*RW+j]=255;
|
|
else
|
|
tempmask[i*RW+j]=src_mask_row[whichcol];
|
|
|
|
/* Ajout de la ligne à la longue chaî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 à 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é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 :
|
|
|
|
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évisualisation«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ée */
|
|
void gtk_preview_uninit (void);
|
|
/* Aucune idé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évisualisation existante */
|
|
/* Apparamment, il y a un bug dans GTK qui rend ce traitement */
|
|
/* hasardeux. Une méthode pour corriger ce problème consiste à */
|
|
/* changer manuellement la taille de la fenêtre contenant la */
|
|
/* prévisualisation après avoir changé la taille de la */
|
|
/* pré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ée */
|
|
|
|
void gtk_preview_put_row (GtkPreview *preview,
|
|
guchar *src,
|
|
guchar *dest,
|
|
gint x,
|
|
gint y,
|
|
gint w);
|
|
/* Aucune idé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ée */
|
|
|
|
/* Aucune piste pour celles qui suivent mais devrait être */
|
|
/* un standard pour la plupart des widgets. */
|
|
|
|
void gtk_preview_set_gamma (double gamma);
|
|
void gtk_preview_set_color_cube (guint nred_shades,
|
|
guint ngreen_shades,
|
|
guint nblue_shades,
|
|
guint ngray_shades);
|
|
void gtk_preview_set_install_cmap (gint install_cmap);
|
|
void gtk_preview_set_reserved (gint nreserved);
|
|
GdkVisual* gtk_preview_get_visual (void);
|
|
GdkColormap* gtk_preview_get_cmap (void);
|
|
GtkPreviewInfo* gtk_preview_get_info (void);
|
|
|
|
That's all, folks!
|
|
|
|
</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érieures. <p> Certains widgets GTK n'ont pas de
|
|
fenêtre X associée, il se dessinent donc sur leurs parents. À cause de
|
|
cela, ils ne peuvent recevoir d'événements et, s'ils ont une taille
|
|
incorrecte, ils ne peuvent pas se mettre en place correctement : on peut
|
|
alors avoir des surimpressions douteuses, etc. Si vous avez besoin de
|
|
ces widgets, <em/EventBox/ est fait pour vous.
|
|
|
|
Au premier abord, le widget <em/EventBox/ peut apparaître comme
|
|
totalement dénué d'intérêt. Il ne dessine rien à l'écran et ne répond
|
|
à aucun évenement. Cependant, il joue un rôle - il fournit une fenêtre
|
|
X pour son widget fils. Ceci est important car de nombreux widgets GTK
|
|
n'ont pas de fenêtre X associée. Ne pas avoir de fenêtre permet
|
|
d'économiser de la mémoire mais a aussi quelques inconvénients. Un
|
|
widget sans fenêtre ne peut recevoir d'événement, et ne réalise aucune
|
|
mise en place de ce qu'il contient. Bien que le nom « EventBox »
|
|
insiste sur la fonction de gestion d'événement, le widget peut aussi
|
|
être utilisé pour la mise en place (et plus... voir l'exemple
|
|
ci-dessous).
|
|
|
|
<p>
|
|
Pour créer un widget EventBox, on utilise :
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_event_box_new (void);
|
|
</verb></tscreen>
|
|
|
|
<p>
|
|
Un widget fils peut alors être ajouté à cet <em/EventBox/ :
|
|
|
|
<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éé et mis en place sur une petite boîte, et configuré 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 (&argc, &argv);
|
|
|
|
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
|
|
|
gtk_window_set_title (GTK_WINDOW (window), "Event Box");
|
|
|
|
gtk_signal_connect (GTK_OBJECT (window), "destroy",
|
|
GTK_SIGNAL_FUNC (gtk_exit), NULL);
|
|
|
|
gtk_container_border_width (GTK_CONTAINER (window), 10);
|
|
|
|
/* Création d'un EventBox et ajout de celui-ci dans la fenêtre. */
|
|
|
|
event_box = gtk_event_box_new ();
|
|
gtk_container_add (GTK_CONTAINER(window), event_box);
|
|
gtk_widget_show (event_box);
|
|
|
|
/* Cré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é. */
|
|
|
|
gtk_widget_set_usize (label, 110, 20);
|
|
|
|
/* Attachement d'une action à celui-ci. */
|
|
|
|
gtk_widget_set_events (event_box, GDK_BUTTON_PRESS_MASK);
|
|
gtk_signal_connect (GTK_OBJECT(event_box), "button_press_event",
|
|
GTK_SIGNAL_FUNC (gtk_exit), NULL);
|
|
|
|
/* Encore une fois, vous avez besoin d'une fenê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érant sur les widgets. Elles
|
|
peuvent être utilisées pour configurer le style, l'espacement, la
|
|
taille, etc.
|
|
|
|
(Je devrais peut être faire une section uniquement consacré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éer une temporisation qui sera appelée tous les
|
|
<em/interval/ millisecondes.
|
|
|
|
<tscreen><verb>
|
|
gint gtk_timeout_add (guint32 interval,
|
|
GtkFunction function,
|
|
gpointer data);
|
|
</verb></tscreen>
|
|
|
|
Le premier paramètre est le nombre de millisecondes entre les appels à
|
|
notre fonction. Le deuxième est la fonction à appeler et le troisième
|
|
est la donnée passée à cette fonction de rappel. La valeur retournée
|
|
est un « marqueur » de type entier qui pourra être utilisé pour
|
|
arrêter la temporisation en appelant :
|
|
|
|
<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 à notre fonction de rappel. Évidemment, cela
|
|
veut dire que si vous voulez que votre fonction continue à être
|
|
appelée, elle doit retourner une valeur non nulle, ou TRUE.
|
|
|
|
La déclaration de votre fonction de rappel doit ressembler à ça :
|
|
|
|
<tscreen><verb>
|
|
gint timeout_callback (gpointer data);
|
|
</verb></tscreen>
|
|
|
|
<sect1>Surveillance des E/S
|
|
<p>
|
|
Une autre caractéristique intéressante du GTK est la possibilité de
|
|
vérifier les données d'un descripteur de fichier (celles retournées
|
|
par <em/open/(2) ou <em/socket/(2)). C'est particulièrement pratique pour les
|
|
applications réseau. La fonction suivante permet cette
|
|
vérification :
|
|
|
|
<tscreen><verb>
|
|
gint gdk_input_add (gint source,
|
|
GdkInputCondition condition,
|
|
GdkInputFunction function,
|
|
gpointer data);
|
|
</verb></tscreen>
|
|
|
|
Le premier paramètre est le descripteur de fichier que l'on veut
|
|
étudier, le second spécifie ce qu'on veut que le GDK recherche. Cela
|
|
peut être :
|
|
<p>
|
|
GDK_INPUT_READ - Appel <em/function/ lorsqu'il y a une donnée prête à
|
|
être lue dans le descripteur de fichier.
|
|
<p>
|
|
GDK_INPUT_WRITE - Appel de <em/function/ lorsque le descripteur de
|
|
fichier est prêt pour une écriture.
|
|
<p>
|
|
Je suis sûr que vous vous doutez, maintenant, que le troisième
|
|
paramètre est la fonction que l'on veut appeler lorsque les conditions
|
|
ci-dessus sont satisfaites. Le dernier paramètre est la donnée à
|
|
passer à cette fonction.
|
|
<p>
|
|
La valeur retournée est un marqueur qui pourra être utilisé pour dire
|
|
au GDK de cesser de surveiller ce descripteur à l'aide de la fonction :
|
|
<p>
|
|
<tscreen><verb>
|
|
void gdk_input_remove (gint tag);
|
|
</verb></tscreen>
|
|
<p>
|
|
La fonction de rappel doit être déclarée de la façon suivante :
|
|
<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 être appelée
|
|
lorsque rien d'autre ne se passe ? On utilise la fonction suivante qui force
|
|
GTK à appeler <em/function/ lorsqu'on est en phase d'inaction ;
|
|
|
|
<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ètres car ils
|
|
ressemblent beaucoup à ceux déjà vus ci-dessus. La fonction pointée
|
|
par le premier paramètre de <em/gtk_idle_add()/ sera appelée à chaque
|
|
occasion. Comme pour les autres, retourner FALSE empêchera la fonction
|
|
d'attente d'être appelée.
|
|
|
|
<sect>Gestion des sélections
|
|
|
|
<sect1>Introduction
|
|
<p>
|
|
Un type de communication inter-processus gérée par GTK est les
|
|
<em>sélections</em>. Une sélection identifie un morceau de données,
|
|
par exemple une portion de texte sélectionnée par l'utilisateur avec
|
|
la souris. Seule une application sur un écran (le <em/propriétaire/)
|
|
peut posséder une sélection particulière à un moment donné, ainsi
|
|
lorsqu'une sélection est réclamée par une application, le propriétaire
|
|
précédent doit indiquer à l'utilisateur que la sélection a été
|
|
abandonnée. Les autres applications peuvent demander le contenu d'une
|
|
sélection sous différentes formes appelé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ée de texte,
|
|
possèdent déjà la capacité 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é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ème bouton de la
|
|
souris). Cependant, il peut il y avoir des cas dans lesquels vous voulez donner
|
|
aux autres widgets la possibilité de fournir la sélection, ou vous désirez
|
|
récupérer des cibles non supportées par défaut.
|
|
|
|
<p>
|
|
Un concept fondamental dans la compréhension du fonctionnement des
|
|
sélections est celui d'<em/atome/. Un atome est un entier qui définit
|
|
de façon unique une chaîne (sur un affichage particulier). Certains
|
|
atomes sont prédéfinis par le serveur X et, dans certains cas, des
|
|
constantes définies dans <em/gtk.h/ correspondent à ces atomes. Par
|
|
exemple, la constante GDK_PRIMARY_SELECTION correspond à la chaîne
|
|
"PRIMARY". Dans d'autres cas, on doit utiliser les fonctions
|
|
<em/gdk_atom_intern()/, pour obtenir l'atome correspondant à une
|
|
chaîne, et <em/gdk_atom_name()/, pour obtenir le nom d'un atome. Les
|
|
sélections et les cibles sont identifiés par des atomes.
|
|
|
|
<sect1>Récupération de la sélection
|
|
<p>
|
|
La récupération de la sélection est un processus asynchrone. Pour démarrer le processus, on appelle :
|
|
|
|
<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écifiée par
|
|
<em/target/. Si tout est possible, le paramètre <em/time/ sera le
|
|
moment de l'événement qui a déclenché la sélection. Ceci aide à
|
|
s'assurer que les événements arrivent dans l'ordre où l'utilisateur
|
|
les a demandé. Cependant, si cela n'est pas possible (par exemple,
|
|
lorsque la conversion a été déclenchée par un signal "clicked"), alors
|
|
on peut utiliser la macro GDK_CURRENT_TIME.
|
|
|
|
<p>
|
|
Quand le propriétaire de la sélection répond à la requête, un signal
|
|
"selection_received" est envoyé à notre application. Le gestionnaire
|
|
de ce signal reçoit un pointeur vers une structure
|
|
<tt/GtkSelectionData/ définie ainsi :
|
|
|
|
<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é dans
|
|
notre appel <em/gtk_selection_convert()/. <em/type/ est un atome qui
|
|
identifie le type de données retourné par le propriétaire de la
|
|
sélection. Quelques valeurs possibles sont : "STRING", une chaîne
|
|
de caractè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és (les caractères, par
|
|
exemple) en bits. Habituellement, on ne se préoccupe pas de cela
|
|
lorsqu'on reçoit des données. <em/data/ est un pointeur vers la donnée
|
|
retournée et <em/length/ donne la longueur en octets de la donnée
|
|
retournée. Si <em/length/ est négative, cela indique qu'une erreur est
|
|
survenue et que la sélection ne peut être récupérée. Ceci peut arriver
|
|
si aucune application n'est propriétaire de la sélection, ou si vous
|
|
avez demandé une cible que l'application ne sait pas gérer. Le tampon
|
|
est garanti d'être un octet plus long que <em/length/ ; l'octet
|
|
supplémentaire sera toujours zéro, et il n'est donc pas nécessaire de
|
|
faire une copie de chaîne simplement pour qu'elle soit terminée par
|
|
zéro (comme doivent l'être toutes les chaînes C).
|
|
|
|
<p>
|
|
Dans l'exemple qui suit, on récupère la cible spéciale "TARGETS", qui
|
|
est une liste de toutes les cibles en lesquelles la sélection peut
|
|
être convertie.
|
|
|
|
<tscreen><verb>
|
|
#include <gtk/gtk.h>
|
|
|
|
void selection_received (GtkWidget *widget,
|
|
GtkSelectionData *selection_data,
|
|
gpointer data);
|
|
|
|
/* Gestionnaire de signal invoqué lorsque l'utilisateur clique sur
|
|
* le bouton « Obtenir les cibles ». */
|
|
|
|
void get_targets (GtkWidget *widget, gpointer data)
|
|
{
|
|
static GdkAtom targets_atom = GDK_NONE;
|
|
|
|
/* Obtention de l'atome correspondant à la chaî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é quand le propriétaire des sélections
|
|
* retourne la donné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ération s'est bien passée. */
|
|
|
|
if (selection_data->length < 0)
|
|
{
|
|
g_print ("Selection retrieval failed\n");
|
|
return;
|
|
}
|
|
|
|
/* On s'assure que l'on a obtenu la donnée sous la forme attendue. */
|
|
|
|
if (selection_data->type != GDK_SELECTION_TYPE_ATOM)
|
|
{
|
|
g_print ("La sélection \"TARGETS\" n'a pas été retournée sous la forme d'atomes !\n");
|
|
return;
|
|
}
|
|
|
|
/* Affichage des atomes reç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 (&argc, &argv);
|
|
|
|
/* Création de la fenê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é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é. On doit enregistrer
|
|
les gestionnaires qui seront appelés lorsque notre sélection est
|
|
demandée. Pour chaque paire sélection/cible que l'on gèrera, on fera
|
|
un appel à :
|
|
|
|
<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êtes
|
|
que ce gestionnaire gèrera. S'il ne vaut pas NULL, <em/remove_func/
|
|
sera appelé lorsque le gestionnaire de signal est supprimé. Ceci est
|
|
utile, par exemple, pour des langages interprétés qui doivent garder
|
|
une trace du nombre de références à <em/data/.
|
|
|
|
<p>
|
|
La fonction de rappel <em/function/ doit avoir la signature suivante :
|
|
|
|
<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ée doit
|
|
être échangée par octet ou non. Habituellement, ce sera 8 (un
|
|
caractère), ou 32 (un entier)). Cette initialisation est faite en
|
|
utilisant l'appel :
|
|
|
|
<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ées afin
|
|
que l'on n'ait pas à se soucier du reste. (On ne doit pas remplir ces
|
|
champs à la main).
|
|
|
|
<p>
|
|
Lorsque cela est demandé par l'utilisateur, on réclame la possession
|
|
de la sélection en appelant :
|
|
|
|
<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é de sélection à un bouton commutateur. Lorsque ce bouton
|
|
est appuyé, le programme réclame la sélection primaire. La seule cible
|
|
supportée (à part certaines cibles fournies par GTK lui-même, comme
|
|
« TARGETS ») est « STRING ». Lorsque celle-ci est demandée, on
|
|
retourne une représentation de l'heure sous forme de chaîne.
|
|
|
|
<tscreen><verb>
|
|
#include <gtk/gtk.h>
|
|
#include <time.h>
|
|
|
|
/* Fonction de rappel appelé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 é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étaire à NULL,
|
|
* on vérifie que nous sommes bien son proprié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é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(&current_time));
|
|
|
|
/* Lorsqu'on retourne une chaî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 (&argc, &argv);
|
|
|
|
/* Création de la fenê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é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), &have_selection);
|
|
gtk_signal_connect (GTK_OBJECT(selection_button), "selection_clear_event",
|
|
GTK_SIGNAL_FUNC (selection_clear), &have_selection);
|
|
|
|
gtk_selection_add_handler (selection_button, GDK_SELECTION_PRIMARY,
|
|
GDK_SELECTION_TYPE_STRING,
|
|
selection_handle, NULL, NULL);
|
|
|
|
gtk_widget_show (selection_button);
|
|
gtk_widget_show (window);
|
|
|
|
gtk_main ();
|
|
|
|
return 0;
|
|
}
|
|
</verb></tscreen>
|
|
|
|
|
|
|
|
<sect>glib<label id="sec_glib">
|
|
<p>
|
|
La <em/glib/ fournit de nombreuses fonctions et définitions utiles,
|
|
prêtes à être utilisées lorsqu'on crée des applications GDK et GTK. Je
|
|
les énumèrerais toutes avec une brève explication. Beaucoup sont des
|
|
répliques des fonctions standards de la <em/libc/, et je ne les
|
|
détaillerais donc pas trop. Ceci doit surtout servir de référence afin
|
|
de savoir ce qui est disponible pour être utilisé.
|
|
|
|
<sect1>Définitions
|
|
<p>
|
|
Les définitions pour les bornes de la plupart des types standards sont :
|
|
|
|
<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éfinitions de types. Celles qui ne sont pas
|
|
spécifiées sont configurées dynamiquement selon l'architecture. Évitez
|
|
surtout de compter sur la taille d'un pointeur si vous voulez un
|
|
programme portable ! Un pointeur sur un Alpha fait 8 octets, mais il
|
|
en fait 4 sur un Intel.
|
|
|
|
<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înées
|
|
<p>
|
|
Les fonctions suivantes servent à créer, gérer et détruire des listes
|
|
doublement chaînées. Je suppose que vous savez ce qu'est une liste
|
|
chaîné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ître pour une
|
|
utilisation géné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înées
|
|
<p>
|
|
La plupart des fonctions pour les listes simplement chaînées
|
|
ci-dessous sont identiques à celles vues plus haut. Voici une liste
|
|
complète :
|
|
|
|
<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 à la précédente, mais initialise la mémoire à zéro avant de
|
|
retourner un pointeur vers la zone réservée.
|
|
|
|
<tscreen><verb>
|
|
gpointer g_realloc (gpointer mem,
|
|
gulong size);
|
|
</verb></tscreen>
|
|
|
|
Réalloue <em/size/ octets de mémoire à partir de <em/mem/. Évidemment,
|
|
la mémoire doit avoir été allouée auparavant.
|
|
|
|
<tscreen><verb>
|
|
void g_free (gpointer mem);
|
|
</verb></tscreen>
|
|
|
|
Libère la mémoire. Facile.
|
|
|
|
<tscreen><verb>
|
|
void g_mem_profile (void);
|
|
</verb></tscreen>
|
|
|
|
Produit un profil de la mémoire utilisé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é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înes
|
|
<p>
|
|
Un ensemble complet de fonction de gestion des chaînes. Elles semblent
|
|
toutes très intéressantes et sont sûrement meilleures, à bien des
|
|
é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îne
|
|
d'origine dans la mémoire venant d'être alloué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 :
|
|
|
|
<tscreen><verb>
|
|
nom du programme:fonction qui a échoué:fichier ou autre descripteur:strerror
|
|
</verb></tscreen>
|
|
|
|
Voici un exemple d'appel utilisé dans le programme « Bonjour tout le monde ! » :
|
|
|
|
<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 « ** ERROR **: » au début du message et sort du programme. À
|
|
n'utiliser que pour les erreurs fatales.
|
|
|
|
<tscreen><verb>
|
|
void g_warning (gchar *format, ...);
|
|
</verb></tscreen>
|
|
|
|
Comme au dessus, mais ajoute « ** WARNING **: », et ne termine pas le
|
|
programme.
|
|
|
|
<tscreen><verb>
|
|
void g_message (gchar *format, ...);
|
|
</verb></tscreen>
|
|
Affiche « message: » avant la chaîne passée en paramètre.
|
|
|
|
<tscreen><verb>
|
|
void g_print (gchar *format, ...);
|
|
</verb></tscreen>
|
|
|
|
Remplace <em/printf()/.
|
|
|
|
Enfin la dernière fonction :
|
|
|
|
<tscreen><verb>
|
|
gchar* g_strsignal (gint signum);
|
|
</verb></tscreen>
|
|
|
|
Affiche le nom du signal système Unix correspondant au numéro de
|
|
signal. Utile pour les fonctions génériques de gestion de signaux.
|
|
|
|
Tout ce qui est ci-dessus est plus ou moins volé à <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 être
|
|
utilisé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 à :
|
|
<tscreen><verb>
|
|
void gtk_rc_parse (char *filename);
|
|
</verb></tscreen>
|
|
<p>
|
|
en lui passant le nom de votre fichier rc. Ceci forcera GTK à analyser
|
|
ce fichier et à utiliser les configurations de styles pour les types
|
|
de widgets qui y sont 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 à :
|
|
<tscreen><verb>
|
|
void gtk_widget_set_name (GtkWidget *widget,
|
|
gchar *name);
|
|
</verb></tscreen>
|
|
<p>
|
|
En lui passant comme premier paramètre le widget que vous avez créé,
|
|
et le nom que vous voulez lui donner comme second paramè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 :
|
|
|
|
<tscreen><verb>
|
|
button = gtk_button_new_with_label ("Bouton Spécial");
|
|
gtk_widget_set_name (button, "bouton special");
|
|
</verb></tscreen>
|
|
<p>
|
|
Ce bouton s'appelle « bouton special » et peut être accédé par son nom
|
|
dans le fichier rc en tant que « bouton special.GtkButton ». [<---
|
|
Vérifiez !]
|
|
<p>
|
|
Le fichier rc ci-dessous configure les propriétés de la fenêtre
|
|
principale et fait hériter tous les fils de celle-ci du style décrit
|
|
par « bouton_principal ». Le code utilisé dans l'application
|
|
est :
|
|
|
|
<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 :
|
|
|
|
<tscreen><verb>
|
|
widget "fenetre principale.*GtkButton*" style "bouton_principal"
|
|
</verb></tscreen>
|
|
<p>
|
|
Ce qui configure tous les widgets <em/GtkButton/ de « fenêtre
|
|
principale » avec le style « bouton_principal » défini dans le fichier
|
|
rc.
|
|
<p>
|
|
Ainsi que vous pouvez le voir, il s'agit d'un systè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é dans l'exemple suivant. Il
|
|
s'agit du fichier <em/testgtkrc/ de la distribution GTK mais j'ai
|
|
ajouté quelques commentaires et autres choses. Vous pouvez inclure
|
|
cette explication à votre application pour permettre à l'utilisateur
|
|
de 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ère plan d'un widget.
|
|
<item>bg_pixmap - configure l'arrière plan d'un widget avec un pixmap.
|
|
<item>font - configure la fonte à utiliser pour un widget.
|
|
</itemize>
|
|
<p>
|
|
De plus, un widget peut se trouver dans différents états et l'on peut
|
|
configurer des couleurs, pixmaps et fontes différentes pour chacun
|
|
d'eux. Ces états sont :
|
|
<itemize>
|
|
<item>NORMAL - L'état normal d'un widget, sans la souris au dessus de
|
|
lui, non pressé, etc.
|
|
<item>PRELIGHT - Lorsque la souris se trouve au dessus du widget, les
|
|
couleurs définies pour cet état sont actives.
|
|
<item>ACTIVE - Lorsque le widget est pressé ou cliqué, il devient
|
|
actif et les attributs associés à cet état sont appliqués.
|
|
<item>INSENSITIVE - Quand un widget est configuré pour être
|
|
insensible et qu'il ne peut être activé, il prend ces attributs.
|
|
<item>SELECTED - Lorsqu'un objet est choisi, il prend ces attributs.
|
|
</itemize>
|
|
<p>
|
|
Lorsqu'on utilise les mots-clés « <em/fg/ » et « <em/bg/ » pour
|
|
configurer les couleurs des widgets, le format est :
|
|
|
|
<tscreen><verb> fg[<STATE>] = { Red, Green, Blue } </verb></tscreen>
|
|
<p>
|
|
Où STATE est l'un des états vus plus haut (PRELIGHT, ACTIVE etc), et
|
|
où <em/Red/, <em/Green/ et <em/Blue/ sont des valeurs comprises entre
|
|
0 et 1.0. { 1.0, 1.0, 1.0 } représente la couleur blanche. Ces
|
|
valeurs doivent être de type réel ou elles seront considérées comme
|
|
valant 0, ainsi un simple « 1 » ne marchera pas, il faut mettre « 1.0
|
|
». Un « 0 » simple convient car ce n'est pas un problème s'il n'est
|
|
pas reconnu puisque toutes les valeurs non reconnues sont mises à 0.
|
|
<p>
|
|
<em/bg_pixmap/ est très similaire, sauf que les couleurs sont
|
|
remplacées par un nom de fichier.
|
|
|
|
<em/pixmap_path/ est une liste de chemins séparés par des « : ». Ces
|
|
chemins seront parcourus pour chaque pixmap que l'on spécifie.
|
|
|
|
<p>
|
|
La directive <em/font/ est simplement :
|
|
<tscreen><verb>
|
|
font = "<font name>"
|
|
</verb></tscreen>
|
|
<p>
|
|
Où la seule partie difficile est d'arriver à comprendre la chaîne
|
|
contenant le nom de la fonte. L'utilisation de <em/xfontsel/ ou d'un
|
|
autre utilitaire semblable peut aider.
|
|
<p>
|
|
« <em/widget_class/ » configure le style d'une classe de widgets. Ces
|
|
classes sont listées dans la section sur la hiérarchie des widgets.
|
|
<p>
|
|
La directive « <em/widget/ » configure un ensemble spécifique de
|
|
widgets selon un style donné, annulant tout style de configuration
|
|
pour la classe de widget donnée. Ces widgets sont enregistrés dans
|
|
l'application en utilisant l'appel <em/gtk_widget_set_name()/. Ceci
|
|
vous permet de spécifier les attributs d'un widget, widget par widget,
|
|
au lieu de configurer les attributs d'une classe entière de
|
|
widgets. Je vous demande instamment de documenter tous ces widgets
|
|
spéciaux pour que les utilisateurs puisse les adapter à leurs besoins.
|
|
<p>
|
|
Lorsque le mot-clé « <em/parent/ » est utilisé 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éjà défini à 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ée un nouveau style
|
|
"bouton_principal"en changeant simplement la fonte et la couleur de
|
|
fond pour l'état PRELIGHT.
|
|
<p>
|
|
Bien sûr, un bon nombre de ces attributs ne s'applique pas à tous les
|
|
widgets. C'est une question de bon sens. Tout ce qui peut s'appliquer
|
|
s'applique.
|
|
|
|
<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 états possibles. Remarquez que certains ne s'appliquent
|
|
# pas à certains widgets.
|
|
#
|
|
# NORMAL - L'état normal d'un widget, sans la souris au dessus de lui,
|
|
# non pressé, etc.
|
|
#
|
|
# PRELIGHT - Lorsque la souris se trouve au dessus du widget, les couleurs
|
|
# définies pour cet état sont actives.
|
|
#
|
|
# ACTIVE - Lorsque le widget est pressé ou cliqué, il devient actif et les
|
|
# attributs associés à cet état sont appliqués.
|
|
#
|
|
# INSENSITIVE - Quand un widget est configuré pour être insensible, et qu'il
|
|
# ne peut être activé, il prend ces attributs.
|
|
#
|
|
# SELECTED - Lorsqu'un objet est choisi, il prend ces attributs.
|
|
#
|
|
# Avec ces états, on peut configurer les attributs des widgets dans chacun
|
|
# de ces états en utilisant les directives suivantes.
|
|
#
|
|
# fg - configure la couleur de premier plan d'un widget.
|
|
# bg - configure la couleur d'arrière plan d'un widget.
|
|
# bg_pixmap - configure l'arrière plan d'un widget avec un pixmap.
|
|
# font - configure la fonte à utiliser pour un widget.
|
|
|
|
# Configuration d'un style appelé "button". Le nom n'est pas important
|
|
# car il est assigné aux widgets réels à la fin du fichier.
|
|
|
|
style "window"
|
|
{
|
|
#Configure l'espace autour de la fenêtre avec le pixmap spécifié.
|
|
#bg_pixmap[<STATE>] = "<pixmap filename>"
|
|
bg_pixmap[NORMAL] = "warning.xpm"
|
|
}
|
|
|
|
style "scale"
|
|
{
|
|
#Configure la couleur de premier plan (celle de la fonte) à rouge
|
|
#lorsqu'on est dans l'état "NORMAL".
|
|
|
|
fg[NORMAL] = { 1.0, 0, 0 }
|
|
|
|
#Configure le pixmap d'arrière plan de ce widget à celui de son parent.
|
|
bg_pixmap[NORMAL] = "<parent>"
|
|
}
|
|
|
|
style "button"
|
|
{
|
|
# Voici tous les états possibles pour un bouton. Le seul qui ne peut
|
|
# s'appliquer est l'é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
|
|
# écrase la fonte et la couleur de fond pour cré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 à 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és dans la hiérarchie des classes, mais
|
|
# peut probablement être listée dans ce document pour que l'utilisateur
|
|
# puisse s'y réfé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 être documenté pour en tirer profit.
|
|
widget "main window.*GtkButton*" style "main_button"
|
|
</verb></tscreen>
|
|
|
|
<sect>É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éer votre propre type de
|
|
widget. Comme GTK utilise l'héritage de widget de façon intensive et
|
|
qu'il y a déjà un widget ressemblant à celui que vous voulez, il est
|
|
souvent possible de cré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à
|
|
écrit. Ceci éviter la duplication des efforts et maintient au minimum
|
|
le nombre de widgets, ce qui permet de garder la cohérence du code et
|
|
de l'interface des différentes applications. Un effet de bord est que,
|
|
lorsque l'on a créé un nouveau widget, il faut l'annoncer afin que les
|
|
autres puissent en bénéficier. Le meilleur endroit pour faire cela
|
|
est, sans doute, la <tt>gtk-list</tt>.
|
|
|
|
<sect1>Anatomie d'un widget
|
|
|
|
<p>
|
|
Afin de créer un nouveau widget, il importe de comprendre comment
|
|
fonctionnent les objets GTK. Cette section ne se veut être qu'un
|
|
rapide survol. Consultez la documentation de référence pour plus de
|
|
détails.
|
|
|
|
<p>
|
|
Les widgets sont implantés selon une méthode orientée
|
|
objet. Cependant, ils sont écrits en C standard. Ceci améliore
|
|
beaucoup la portabilité et la stabilité, par contre cela signifie que
|
|
celui qui écrit des widgets doit faire attention à certains détails
|
|
d'implantation. Les informations communes à toutes les instances d'une
|
|
classe de widget (tous les widgets boutons, par exemple) sont stockées
|
|
dans la <em/structure de la classe/. Il n'y en a qu'une copie dans
|
|
laquelle sont stocké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
|
|
être une copie de la structure de classe du père. La déclaration de la
|
|
structure de classe de <em/GtkButton/ ressemble à ceci :
|
|
|
|
<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é comme un container (par exemple, lorsqu'il
|
|
change de taille), sa structure de classe peut être convertie en
|
|
<em/GtkContainerClass/ et les champs adéquats utilisés pour gérer les
|
|
signaux.
|
|
|
|
<p>
|
|
Il y a aussi une structure pour chaque widget créé sur une base
|
|
d'instance. Cette structure a des champs pour stocker les informations
|
|
qui sont différentes pour chaque instance du widget. Nous l'appelerons
|
|
<em/structure d'objet/. Pour la classe <em/Button/, elle ressemble
|
|
à :
|
|
|
|
<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 être
|
|
convertie dans la structure d'objet de la classe parente si besoin
|
|
est.
|
|
|
|
<sect1> Création d'un widget composé
|
|
|
|
<sect2> Introduction
|
|
|
|
<p>
|
|
Un type de widget qui peut être intéressant à créer est un widget qui
|
|
est simplement un agrégat d'autres widgets GTK. Ce type de widget ne
|
|
fait rien qui ne pourrait être fait sans créer de nouveaux widgets,
|
|
mais offre une méthode pratique pour empaqueter les élé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éer dans cette section créera un
|
|
widget <em/Tictactoe/, un tableau de 3x3 boutons commutateurs qui
|
|
déclenche un signal lorsque tous les boutons d'une ligne, d'une
|
|
colonne, ou d'une diagonale sont pressés.
|
|
|
|
<sect2> Choix d'une classe parent
|
|
|
|
<p>
|
|
La classe parent d'un widget composé est, typiquement, la classe
|
|
container contenant tous les éléments du widget composé. 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éation d'un widget
|
|
est divisé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épendamment des paramètre passés
|
|
à 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éés, ont besoin de connaître leue nombre de lignes et de
|
|
colonnes. Sauf à dupliquer la plupart des fonctionnalités de
|
|
<em/gtk_table_new()/ dans notre widget <em/Tictactoe/, nous ferions
|
|
mieux d'éviter de le dériver de <em/GtkTable/. Pour cette raison, nous
|
|
la dériverons plutôt de <em/GtkVBox/ et nous placerons notre table
|
|
dans la VBox.
|
|
|
|
<sect2> The header file
|
|
|
|
<p>
|
|
Chaque classe de widget possè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éristiques méritent d'être indiquées. Afin
|
|
d'éviter des définitions multiples, on enveloppe le fichier en-tête
|
|
avec :
|
|
|
|
<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 :
|
|
|
|
<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 :
|
|
|
|
<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ée pour la première fois, informe le
|
|
GTK de la classe et récupère un ID permettant d'identifier celle-ci de
|
|
faç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 (), &ttt_info);
|
|
}
|
|
|
|
return ttt_type;
|
|
}
|
|
</verb></tscreen>
|
|
|
|
<p>
|
|
La structure <em/GtkTypeInfo/ est définie de la façon suivante :
|
|
|
|
<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 : il a un rôle important
|
|
permettant aux options des widgets d'être correctement initialisées à
|
|
partir des langages interprétés, mais cette fonctionnalité est encore
|
|
très peu implantée. Lorsque GTK dispose d'une copie correctement
|
|
remplie de cette structure, il sait comment cré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 :
|
|
|
|
<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 : "tictactoe", invoqué lorsqu'une
|
|
ligne, une colonne ou une diagonale est complètement remplie. Tous les
|
|
widgets composés n'ont pas besoin de signaux. Si vous lisez ceci pour
|
|
la première fois, vous pouvez passer directement à 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ée un nouveau signal. Les paramètres sont :
|
|
|
|
<itemize>
|
|
<item> <em/name/ : Le nom du signal signal.
|
|
<item> <em/run_type/ : Indique si le gestionnaire par défaut doit être
|
|
lancé avant ou aprè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é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ée pour invoquer le
|
|
gestionnaire de signal. Pour les gestionnaires de signaux n'ayant pas
|
|
d'autres paramètres que l'objet émetteur et les données utilisateur,
|
|
on peut utiliser la fonction prédéfinie
|
|
<em/gtk_signal_default_marshaller()/.
|
|
|
|
<item> <em/return_val/ : Type de la valeur retournée.
|
|
|
|
<item> <em/nparams/ : Nombre de paramètres du gestionnaire de signal
|
|
(autres que les deux par défaut mentionnés plus haut).
|
|
|
|
<item> <em/.../ : Types des paramètres.
|
|
</itemize>
|
|
|
|
Lorsque l'on spécifie les types, on utilise l'énumération
|
|
<em/GtkArgType/ :
|
|
|
|
<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é
|
|
par une énumération (conventionnellement, les éléments de
|
|
l'énumé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/ à la place.
|
|
|
|
Après avoir créé nos signaux, nous devons demander à GTK d'associer
|
|
ceux-ci à 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" à
|
|
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ôt
|
|
limité, d'initialiser les champs de la structure avec des valeurs par
|
|
défaut. Cependant, pour les widgets composés, cette fonction cré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 être instanciés) à besoin
|
|
d'avoir -- celle que l'utilisateur appelle pour créer un objet de ce
|
|
type. Elle est conventionnellement appelée <em/WIDGETNAME_new()/. Pour
|
|
certains widgets, par pour ceux de Tictactoe, cette fonction prend des
|
|
paramètres et réalise certaines initialisations dépendantes des
|
|
paramètres. Les deux autres fonctions sont spécifiques au widget
|
|
Tictactoe.
|
|
|
|
<p>
|
|
<em/tictactoe_clear()/ est une fonction publique qui remet tous les
|
|
boutons du widget en position relâchée. Notez l'utilisation de
|
|
<em/gtk_signal_handler_block_by_data()/ pour empêcher notre
|
|
gestionnaire de signaux des boutons commutateurs d'être déclenché sans
|
|
besoin.
|
|
|
|
<p>
|
|
<em/tictactoe_toggle()/ est le gestionnaire de signal invoqué
|
|
lorsqu'on clique sur un bouton. Il vérifie s'il y a des combinaisons
|
|
gagnantes concernant le bouton qui vient d'être commuté et, si c'est
|
|
le cas, é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 &&
|
|
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 && found)
|
|
{
|
|
gtk_signal_emit (GTK_OBJECT (ttt),
|
|
tictactoe_signals[TICTACTOE_SIGNAL]);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
</verb></tscreen>
|
|
|
|
<p>
|
|
|
|
Enfin, un exemple de programme utilisant notre widget Tictactoe
|
|
|
|
<tscreen><verb>
|
|
#include <gtk/gtk.h>
|
|
#include "tictactoe.h"
|
|
|
|
/* Invoqué lorsqu'une ligne, une colonne ou une diagonale est complè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 (&argc, &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é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éation d'un widget à partir de zéro
|
|
|
|
<sect2> Introduction
|
|
|
|
<p>
|
|
|
|
Dans cette section, nous en apprendrons plus sur la façon dont les
|
|
widgets s'affichent eux-mêmes à l'écran et comment ils interagissent
|
|
avec les événements. Comme exemple, nous créerons un widget d'appel
|
|
télephonique interactif avec un pointeur que l'utilisateur pourra
|
|
déplacer pour initialiser la valeur.
|
|
|
|
<sect2>Afficher un widget à l'écran
|
|
<p>
|
|
Il y a plusieurs étapes mises en jeu lors de l'affichage. Lorsque le widget est
|
|
créé par l'appel <em/WIDGETNAME_new()/, plusieurs autres fonctions
|
|
supplémentaires sont requises.
|
|
|
|
<itemize>
|
|
<item> <em/WIDGETNAME_realize()/ s'occupe de créer une fenêtre X pour le
|
|
widget, s'il en a une.
|
|
<item> <em/WIDGETNAME_map()/ est invoquée après l'appel de
|
|
<em/gtk_widget_show()/. Elle s'assure que le widget est bien tracé à l'écran
|
|
(<em/mappé/). 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ée lorsque <em/gtk_widget_draw()/ est
|
|
appelé pour le widget ou l'un de ces ancêtres. Elle réalise les véritables
|
|
appels aux fonctions de dessin pour tracer le widget à l'é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 événements
|
|
d'exposition du widget. Il réalise les appels nécessaires aux fonctions de
|
|
dessin pour tracer la partie exposée à l'écran. Pour les widgets containers,
|
|
cette fonction doit générer les événements d'exposition pour ses widgets
|
|
enfants n'ayant pas leurs propres fenêtres (s'ils ont leurs propres fenêtres, X
|
|
génèrera les événements d'exposition nécessaires).
|
|
</itemize>
|
|
|
|
<p>
|
|
Vous avez pu noter que les deux dernières fonctions sont assez similaires --
|
|
chacune se charge de tracer le widget à l'écran. En fait, de nombreux types de
|
|
widgets ne se préoccupent pas vraiment de la différence entre les deux. La
|
|
fonction <em/draw()/ par défaut de la classe widget génère simplement un
|
|
événement d'exposition synthétique pour la zone à redessiner. Cependant,
|
|
certains types de widgets peuvent économiser du travail en faisant la
|
|
différence entre les deux fonctions. Par exemple, si un widget a plusieurs
|
|
fenêtres X et puisque les événements d'exposition identifient la fenêtre
|
|
exposée, il peut redessiner seulement la fenêtre concernée, ce qui n'est pas
|
|
possible avec des appels à <em/draw()/.
|
|
|
|
<p>
|
|
Les widgets container, même s'ils ne se soucient pas eux-mêmes de la
|
|
différence, ne peuvent pas utiliser simplement la fonction <em/draw()/ car
|
|
leurs widgets enfants tiennent compte de cette différence. Cependant, ce serait
|
|
du gaspillage de dupliquer le code de tracé pour les deux
|
|
fonctions. Conventionnellement, de tels widgets possèdent une fonction nommée
|
|
<em/WIDGETNAME_paint()/ qui réalise le véritable travail de tracé du widget et
|
|
qui est appelé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être, nous pouvons utiliser l'approche la plus simple :
|
|
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èrent hors de la boue, les widgets GTK sont des variantes
|
|
d'autres widgets, déjà écrits. Ainsi, bien que cette section s'appelle « créer
|
|
un widget à partir de zéro », le widget Dial commence réellement avec le code
|
|
source du widget Range. Celui-ci a été 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écialisés du widget Range. Par conséquent, bien que le code
|
|
source soit présenté ci-dessous sous une forme achevée, cela n'implique pas
|
|
qu'il a été é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éférable de les é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 :
|
|
|
|
<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 à jour
|
|
(GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
|
|
|
|
guint policy : 2;
|
|
|
|
/* Le bouton qui est pressé, 0 si aucun */
|
|
guint8 button;
|
|
|
|
/* Dimensions des composants de dial */
|
|
gint radius;
|
|
gint pointer_width;
|
|
|
|
/* ID du timer de mise à jour, 0 si aucun */
|
|
guint32 timer;
|
|
|
|
/* Angle courant*/
|
|
gfloat angle;
|
|
|
|
/* Anciennes valeurs d'ajustement stockées. On sait donc quand quelque
|
|
chose change */
|
|
gfloat old_value;
|
|
gfloat old_lower;
|
|
gfloat old_upper;
|
|
|
|
/* L'objet ajustment qui stocke les donné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 à faire avec ce widget par rapport à l'autre, nous
|
|
avons plus de champs dans la structure de données, mais à part ça, les choses
|
|
sont plutôt similaires.
|
|
|
|
<p>
|
|
|
|
Puis, après avoir inclus les fichiers en-tête et déclaré quelques constantes,
|
|
nous devons fournir quelques fonctions pour donner des informations sur le
|
|
widget et pour l'initialiser :
|
|
|
|
<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é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 (), &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é et que la fonction <em/new()/ en
|
|
fait plus car elle a maintenant un paramètre. Notez aussi que lorsque nous
|
|
stockons un pointeur vers l'objet Adjustement, nous incrémentons son nombre de
|
|
références (et nous le décrémentons lorsque nous ne l'utilisons plus) afin que
|
|
GTK puisse savoir quand il pourra être détruit sans danger.
|
|
|
|
<p>
|
|
Il y a aussi quelques fonctions pour manipuler les options du widget :
|
|
|
|
<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 à quelques nouveaux types de fonctions. D'abord, nous
|
|
avons une fonction qui réalise la création de la fenêtre X. Notez que l'on
|
|
passe un masque à la fonction <em/gdk_window_new()/ pour spécifier quels sont
|
|
les champs de la structure GdkWindowAttr qui contiennent des données (les
|
|
autres recevront des valeurs par défaut). Notez aussi la façon dont est créé le
|
|
masque d'événement du widget. On appelle <em/gtk_widget_get_events()/ pour
|
|
récupérer le masque d'événement que l'utilisateur a spécifié pour ce widget
|
|
(avec <em/gtk_widget_set_events()/) et ajouter les événements qui nous
|
|
intéressent.
|
|
|
|
<p>
|
|
Après avoir créé la fenêtre, nous configurons son style et son fond et mettons
|
|
un pointeur vers le widget dans le champ user de la GdkWindow. Cette dernière
|
|
étape permet à GTK de distribuer les événements pour cette fenê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, &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être contenant un widget et à chaque fois
|
|
que la forme de la fenêtre change, GTK demande à chaque widget fils la taille
|
|
qu'il désire avoir. Cette requête est géré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é une taille idéale, le forme de la fenêtre
|
|
est calculé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é la fenêtre, cette taille peut occasionnellement être plus petite
|
|
que la taille requise. La notification de la taille est géré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 à 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é plus haut, tout le dessin de ce widget est réalisé
|
|
dans le gestionnaire pour les événements d'exposition. Il n'y a pas grand chose
|
|
de plus à 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é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 événements
|
|
|
|
<p>
|
|
Le reste du code du widget gère différents types d'événements et n'est pas
|
|
trop différent de ce que l'on trouve dans la plupart des applications GTK. Deux
|
|
types d'évé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 à cause d'une circonstance extérieure.
|
|
|
|
<p>
|
|
Lorsque l'utilisateur clique sur le widget, on vérifie si le clic s'est bien
|
|
passé près du pointeur et si c'est le cas, on stocke alors le bouton avec
|
|
lequel l'utilisateur a cliqué dans le champ <em/button/ de la structure du
|
|
widget et on récupère tous les événements souris avec un appel à
|
|
<em/gtk_grab_add()/. Un déplacement ultérieur de la souris provoque le recalcul
|
|
de la valeur de contrôle (par la fonction <em/gtk_dial_update_mouse/). Selon la
|
|
politique qui a été choisie, les événements "value_changed" sont, soit générés
|
|
instantanément (<em/GTK_UPDATE_CONTINUOUS/), après un délai ajouté au timer
|
|
avec <em/gtk_timeout_add()/ (<em/GTK_UPDATE_DELAYED/), ou seulement lorsque le
|
|
bouton est relâché (<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é est dans la région du pointeur.
|
|
On fait cela en calculant les distances parallèle et perpendiculaire
|
|
du point où la souris a été pressée par rapport à 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, &x, &y, &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érieurs sont communiqués à
|
|
notre widget par les signaux "changed" et "value_changed". Les gestionnaires
|
|
pour ces fonctions appellent <em/gtk_dial_update()/ pour valider les
|
|
paramè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éliorations possibles
|
|
|
|
<p>
|
|
|
|
Le widget Dial décrit jusqu'à maintenant exécute à peu prè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 à la préparation. Cependant, certaines améliorations
|
|
peuvent être apportées à ce widget :
|
|
|
|
<itemize>
|
|
<item> Si vous testez ce widget, vous vous apercevrez qu'il y a un peu de
|
|
scintillement lorsque le pointeur est déplacé. Ceci est dû au fait que le
|
|
widget entier est effacé, puis redessiné à chaque mouvement du
|
|
pointeur. Souvent, la meilleure façon de gérer ce problème est de dessiner sur
|
|
un pixmap non affiché, puis de copier le résultat final sur l'écran en une
|
|
seule étape (le widget <em/ProgressBar/ se dessine de cette façon).
|
|
|
|
<item> L'utilisateur devrait pouvoir utiliser les flèches du curseur vers le
|
|
haut et vers le bas pour incrémenter et décré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ètent lorsqu'ils sont maintenus appuyés, comme font les
|
|
flè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 être fait dans un widget container avec un seul
|
|
widget fils positionnée en bas, entre les boutons mentionnés
|
|
ci-dessus. L'utilisateur pourrait alors ajouter au choix, un widget label ou
|
|
entré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éation des widgets a pu
|
|
être décrite. Si vous désirez é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 écrire : est-ce un widget container ? possède-t-il
|
|
sa propre fenêtre ? est-ce une modification d'un widget existant ? Puis,
|
|
trouvez un widget identique et commencez à faire les modifications. Bonne
|
|
chance !
|
|
|
|
<sect>Scribble, un programme simple de dessin
|
|
|
|
<sect1>Présentation
|
|
|
|
<p>
|
|
Dans cette section, nous construirons un programme simple de dessin. Ce
|
|
faisant, nous examinerons comment gérer les événements souris, comment dessiner
|
|
dans une fenêtre, et comment mieux dessiner en utilisant un pixmap en arrière
|
|
plan. Après avoir créé ce programme, nous l'étendrons en lui ajoutant le
|
|
support des périphériques <em/Xinput/, comme les tables de tracé. GTK dispose
|
|
de routines de support qui facilitent beaucoup l'obtention des informations
|
|
étendues (comme la pression et l'inclinaison du stylet) à partir de tels
|
|
périphériques.
|
|
|
|
<sect1>Gestion d'événement
|
|
|
|
<p>
|
|
Les signaux GTK que nous avons déjà 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î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 à ces
|
|
<em/événements/ bas niveau. Les gestionnaires de ces signaux ont un paramètre
|
|
supplémentaire qui est un pointeur vers une structure contenant des
|
|
informations sur l'événement. Par exemple, les gestionnaires des événements de
|
|
déplacement recoivent un paramètre vers une structure <em/GdkEventMotion/ qui
|
|
ressemble (en partie) à ceci :
|
|
|
|
<tscreen><verb>
|
|
struct _GdkEventMotion
|
|
{
|
|
GdkEventType type;
|
|
GdkWindow *window;
|
|
guint32 time;
|
|
gdouble x;
|
|
gdouble y;
|
|
...
|
|
guint state;
|
|
...
|
|
};
|
|
</verb></tscreen>
|
|
|
|
<em/type/ sera initialisé avec le type de l'événement, ici
|
|
<em/GDK_MOTION_NOTIFY/, <em/window/ est la fenêtre dans laquelle l'événement
|
|
est survenu. <em/x/ et <em/y/ donnent les coordonnées de l'événement et
|
|
<em/state/ spécifie l'état du modificateur lorsque l'événement s'est produit
|
|
(c'est-à-dire quelles sont les touches de modification et les boutons souris
|
|
qui ont été pressés). Il s'agit d'un OU bit à bit de l'une des valeurs
|
|
suivantes :
|
|
|
|
<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éterminer ce qui se passe lorsqu'un événement survient. Mais nous devons aussi
|
|
faire en sorte que GTK sache de quels événements nous voulons être
|
|
avertis. Pour ce faire, on appelle la fonction :
|
|
|
|
<tscreen><verb>
|
|
void gtk_widget_set_events (GtkWidget *widget,
|
|
gint events);
|
|
</verb></tscreen>
|
|
|
|
Le deuxième champ spécifie les événements qui nous intéressent. Il s'agit d'un
|
|
OU bit à bit de constantes qui indiquent différent types d'événements. Pour
|
|
référence ultérieure, les types d'événements sont :
|
|
|
|
<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 être observés lorsqu'on appelle
|
|
<em/gtk_widget_set_events()/. D'abord, elle doit être appelée avant que la
|
|
fenêtre X d'un widget GTK soit créée. En pratique, cela signifie que l'on doit
|
|
l'appeler immédiatement après avoir créé le widget. Ensuite, le widget doit
|
|
avoir une fenêtre X associée. Pour des raisons d'efficacité, de nombreux types
|
|
de widgets n'ont pas de fenêtre propre, mais se dessinent dans la fenêtre de
|
|
leur parent. Ces widgets sont :
|
|
|
|
<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 évé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é et quand la souris est déplacé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être, on indique donc
|
|
<em/GDK_EXPOSURE_MASK/. Bien que nous voulions être avertis via un événement
|
|
<em/Configure/ lorsque la taille de notre fenêtre change, on n'a pas besoin de
|
|
préciser le flag <em/GDK_STRUCTURE_MASK/ correspondant car il est
|
|
automatiquement spécifié pour chaque fenêtre.
|
|
|
|
<p>
|
|
Il arrive cependant qu'il puisse y avoir un problème en indiquant seulement
|
|
<em/GDK_POINTER_MOTION_MASK/. Cela fera que le serveur ajoutera un nouvel
|
|
événement de déplacement à la file des événements à chaque fois que
|
|
l'utilisateur déplace la souris. Si cela nous prend 0,1 seconde pour gérer un
|
|
événement de déplacement, si le serveur X n'ajoute un nouvel événement de
|
|
déplacement dans la queue que toutes les 0,05 secondes, nous serons vite à la
|
|
traîne de l'utilisateur. Si l'utilisateur dessine pendant 5 secondes, cela nous
|
|
prendra 5 secondes de plus pour le traiter après qu'il ait relâché le bouton de
|
|
la souris ! Ce que l'on voudrait, c'est ne récupérer qu'un événement de
|
|
déplacement pour chaque événement que l'on traite. Pour cela, il faut préciser
|
|
<em/GDK_POINTER_MOTION_HINT_MASK/.
|
|
|
|
<p>
|
|
Avec <em/GDK_POINTER_MOTION_HINT_MASK/, le serveur nous envoit un événement de
|
|
déplacement la première fois que la pointeur se déplace après être entré dans
|
|
la fenêtre, ou après un événement d'appui ou de relâchement d'un bouton. Les
|
|
événements de déplacement suivants seront supprimés jusqu'à ce que l'on demande
|
|
explicitement la position du pointeur en utilisant la fonction :
|
|
|
|
<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ède une
|
|
interface simple, mais n'est pas très utile car elle ne fait que récupérer la
|
|
position de la souris et ne se préoccupe pas de savoir si les boutons sont
|
|
pressés).
|
|
|
|
<p>
|
|
Le code pour configurer les événements pour notre fenêtre ressemble alors
|
|
à :
|
|
|
|
<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ès simples :
|
|
|
|
<tscreen><verb>
|
|
static gint
|
|
button_press_event (GtkWidget *widget, GdkEventButton *event)
|
|
{
|
|
if (event->button == 1 && 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, &x, &y, &state);
|
|
else
|
|
{
|
|
x = event->x;
|
|
y = event->y;
|
|
state = event->state;
|
|
}
|
|
|
|
if (state & GDK_BUTTON1_MASK && 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'écran. Le widget que l'on utilise pour
|
|
ceci est le widget <em/DrawingArea/. Un tel widget est essentiellement une
|
|
fenêtre X et rien de plus. Il s'agit d'une toile vide sur laquelle nous pouvons
|
|
dessiner ce que nous voulons.
|
|
|
|
Une zone de dessin est créée avec l'appel :
|
|
|
|
<tscreen><verb>
|
|
GtkWidget* gtk_drawing_area_new (void);
|
|
</verb></tscreen>
|
|
|
|
Une taille par défaut peut être donnée au widget par l'appel :
|
|
|
|
<tscreen><verb>
|
|
void gtk_drawing_area_size (GtkDrawingArea *darea,
|
|
gint width,
|
|
gint height);
|
|
</verb></tscreen>
|
|
|
|
Cette taille par défaut peu être surchargée en appelant
|
|
<em/gtk_widget_set_usize()/ et celle-ci, à son tour, peut être surchargée si
|
|
l'utilisateur modifie manuellement la taille de la fenêtre contenant la zone de
|
|
dessin.
|
|
|
|
<p>
|
|
Il faut noter que lorsque l'on crée un widget <em/DrawingArea/, nous sommes
|
|
<em>complètement</em> responsable du dessin du contenu. Si notre fenêtre est
|
|
cachée puis redécouverte, nous recevrons un événement d'exposition et devrons
|
|
redessiner ce qui a été caché auparavant.
|
|
|
|
<p>
|
|
Devoir se rappeler tout ce qui a été dessiné à l'écran pour pouvoir
|
|
correctement le redessiner peut s'avérer, c'est le moins que l'on puisse dire,
|
|
pénible. De plus, cela peut être visible si des portions de la fenêtre sont
|
|
effacées puis redessinées étape par étape. La solution à ce problème est
|
|
d'utiliser un <em/pixmap d'arrière-plan/ qui n'est pas sur l'écran. Au lieu de
|
|
dessiner directement à l'écran, on dessine sur une image stockée dans la
|
|
mémoire du serveur et qui n'est pas affichée, puis, lorsque l'image change ou
|
|
lorsque de nouvelles portions de l'image sont affichées, on copie les parties
|
|
adéquates sur l'écran.
|
|
|
|
<p>
|
|
Pour créer un pixmap mémoire, on appelle la fonction :
|
|
|
|
<tscreen><verb>
|
|
GdkPixmap* gdk_pixmap_new (GdkWindow *window,
|
|
gint width,
|
|
gint height,
|
|
gint depth);
|
|
</verb></tscreen>
|
|
|
|
Le paramètre <em/windows/ indique une fenêtre GTK de laquelle ce pixmap tire
|
|
certaines de ses propriétés. <em/width/ et <em/height/ précisent la taille du
|
|
pixmap. <em/depth/ précise la <em/profondeur de couleur/, c'est-à-dire le
|
|
nombre de bits par pixel, de la nouvelle fenêtre. Si cette profondeur vaut
|
|
<em/-1/, elle correspondra à celle de <em/window/.
|
|
|
|
<p>
|
|
Nous créons le pixmap dans notre gestionnaire "configure_event". Cet événement
|
|
est généré à chaque fois que la fenêtre change de taille, y compris lorsqu'elle
|
|
initialement créée.
|
|
|
|
<tscreen><verb>
|
|
/* Pixmap d'arrière-plan pour la zone de dessin */
|
|
static GdkPixmap *pixmap = NULL;
|
|
|
|
/* Création d'un nouveau pixmap d'arrière-plan de la taille voulue */
|
|
static gint
|
|
configure_event (GtkWidget *widget, GdkEventConfigure *event)
|
|
{
|
|
if (pixmap)
|
|
{
|
|
gdk_pixmap_destroy(pixmap);
|
|
}
|
|
pixmap = gdk_pixmap_new(widget->window,
|
|
widget->allocation.width,
|
|
widget->allocation.height,
|
|
-1);
|
|
gdk_draw_rectangle (pixmap,
|
|
widget->style->white_gc,
|
|
TRUE,
|
|
0, 0,
|
|
widget->allocation.width,
|
|
widget->allocation.height);
|
|
|
|
return TRUE;
|
|
}
|
|
</verb></tscreen>
|
|
|
|
L'appel à <em/gdk_draw_rectangle()/ remet le pixmap à blanc. Nous en dirons un
|
|
peu plus dans un moment.
|
|
|
|
<p>
|
|
Notre gestionnaire d'événement d'exposition copie alors simplement la partie
|
|
concernées du pixmap sur l'écran (on détermine la zone qu'il faut redessiner en
|
|
utilisant le champ <em/event->area/ de l'événement d'exposition) :
|
|
|
|
<tscreen><verb>
|
|
/* Remplit l'écran à partir du pixmap d'arrière-plan */
|
|
static gint
|
|
expose_event (GtkWidget *widget, GdkEventExpose *event)
|
|
{
|
|
gdk_draw_pixmap(widget->window,
|
|
widget->style->fg_gc[GTK_WIDGET_STATE (widget)],
|
|
pixmap,
|
|
event->area.x, event->area.y,
|
|
event->area.x, event->area.y,
|
|
event->area.width, event->area.height);
|
|
|
|
return FALSE;
|
|
}
|
|
</verb></tscreen>
|
|
|
|
Nous avons vu comment garder l'écran à jour avec notre pixmap, mais comment
|
|
dessine-t-on réellement ce que l'on veut dans le pixmap ? Il existe un grand
|
|
nombre d'appels dans la bibliothèque GDK de GTK pour dessiner sur des
|
|
<em>dessinables</em>. Un dessinable est simplement quelque chose sur lequel on
|
|
peut dessiner. Cela peut être une fenêtre, un pixmap, ou un bitmap (une image
|
|
en noir et blanc). Nous avons déjà vu plus haut deux de ces appels,
|
|
<em/gdk_draw_rectangle()/ et <em/gdk_draw_pixmap()/. La liste complète
|
|
est :
|
|
|
|
<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érence ou le fichier en-tête
|
|
<em/<gdk/gdk.h>/ pour plus de détails sur ces fonctions. Celles-ci
|
|
partagent toutes les mêmes deux paramêtres. Le premier est le dessinable sur
|
|
lequel dessiner, le second est un <em/contexte graphique/ (GC).
|
|
|
|
<p>
|
|
Un contexte graphique encapsule l'information sur des choses comme les couleurs
|
|
de premier et d'arrière plan et la largeur de ligne. GDK possède un ensemble
|
|
complet de fonctions pour créer et manipuler les contextes graphiques, mais,
|
|
pour simplifier, nous n'utiliserons que les contextes graphiques
|
|
prédéfinis. Chaque widget a un style associé (qui peut être modifié dans un
|
|
fichier gtkrc, voir la section sur les fichiers rc de GTK). Celui-ci, entre
|
|
autres choses, stocke plusieurs contextes graphiques. Quelques exemples d'accès
|
|
à ces contextes sont :
|
|
|
|
<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és par un
|
|
paramètre de type <em/GtkStateType/ qui peut prendre les valeurs :
|
|
|
|
<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éfaut est blanc et la couleur d'arrière plan par défaut est bleu foncé.
|
|
|
|
<p>
|
|
Notre fonction <em/draw_brush()/, qui réalise le dessin à l'écran est alors :
|
|
|
|
<tscreen><verb>
|
|
/* Dessine un rectangle à l'écran */
|
|
static void
|
|
draw_brush (GtkWidget *widget, gdouble x, gdouble y)
|
|
{
|
|
GdkRectangle update_rect;
|
|
|
|
update_rect.x = x - 5;
|
|
update_rect.y = y - 5;
|
|
update_rect.width = 10;
|
|
update_rect.height = 10;
|
|
gdk_draw_rectangle (pixmap,
|
|
widget->style->black_gc,
|
|
TRUE,
|
|
update_rect.x, update_rect.y,
|
|
update_rect.width, update_rect.height);
|
|
gtk_widget_draw (widget, &update_rect);
|
|
}
|
|
</verb></tscreen>
|
|
|
|
Après avoir dessiné le rectangle représentant la brosse sur le pixmap, nous
|
|
appelons la fonction :
|
|
|
|
<tscreen><verb>
|
|
void gtk_widget_draw (GtkWidget *widget,
|
|
GdkRectangle *area);
|
|
</verb></tscreen>
|
|
|
|
qui indique à X que la zone donnée par le paramètre <em/area/ a besoin d'être
|
|
mise à jour. X génèrera éventuellement un événement d'exposition (en combinant
|
|
peut-être les zones passés dans plusieurs appels à <em/gtk_widget_draw()/) ce
|
|
qui forcera notre gestionnaire d'événement d'exposition à copier les parties
|
|
adéquates à l'écran.
|
|
|
|
<p>
|
|
Nous avons maintenant couvert entièrement le programme de dessin, sauf quelques
|
|
détails banals comme la création de la fenêtre principale. Le code source
|
|
complet est disponible à l'endroit où vous avez obtenu ce didacticiel.
|
|
|
|
<sect1>Ajouter le support XInput
|
|
|
|
<p>
|
|
Il est maintenant possible d'acheter des périphériques bon marché, comme les
|
|
tablettes graphiques qui permettent d'exprimer beaucoup plus facilement son
|
|
talent qu'avec une souris. La façon la plus simple pour utiliser de tels
|
|
périphériques est simplement de le faire comme un remplacement de la souris,
|
|
mais cela ne tire pas partie des nombreux avantages de ces périphériques,
|
|
comme :
|
|
|
|
<itemize>
|
|
<item> Sensibilité à la pression ;
|
|
<item> rapport d'inclinaison ;
|
|
<item> positionnement au dessous du pixel ;
|
|
<item> entré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ète de, par exemple, la structure
|
|
<em/GdkEventMotion/, on voit qu'elle possède des champs pour supporter des
|
|
informations étendues sur les périphé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é d'inclinaison dans chaque direction, <em/source/ et
|
|
<em/deviceid/ précisent de deux façons différentes le périphérique pour lequel
|
|
l'événement est survenus. <em/source/ donne une simple information sur le type
|
|
du périphérique. Il peut prendre l'une des valeurs suivantes :
|
|
|
|
<tscreen><verb>
|
|
GDK_SOURCE_MOUSE
|
|
GDK_SOURCE_PEN
|
|
GDK_SOURCE_ERASER
|
|
GDK_SOURCE_CURSOR
|
|
</verb></tscreen>
|
|
|
|
<tt/deviceid/ précise un ID numérique unique pour le périphérique. Il peut être
|
|
utilisé pour trouver des informations supplémentaires sur le périphérique en
|
|
utilisant l'appel <em/gdk_input_list_devices()/ (voir ci-dessous). La valeur
|
|
spéciale <em/GDK_CORE_POINTER/ sert à désigner le périphérique de pointage
|
|
principal (habituellement la souris).
|
|
|
|
<sect2>Valider l'information supplémentaire sur un périphérique
|
|
|
|
<p>
|
|
Pour indiquer à GTK que l'on désire obtenir des informations supplémentaires
|
|
sur le périphérique, on a simplement besoin d'ajouter une ligne à nos
|
|
programmes.
|
|
|
|
<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ésirons les évé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é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 à la
|
|
situation par défaut.
|
|
|
|
<p>
|
|
Toutefois, nous ne sommes pas complètement à la fin de l'histoire. Par défaut,
|
|
aucun périphérique d'extension n'est autorisé. Nous avons besoin d'un mécanisme
|
|
pour que les utilisateurs puissent autoriser et configurer leur extensions. GTK
|
|
dispose du widget <em/InputDialog/ pour automatiser cette tâche. La procédure
|
|
suivante gère un widget InputDialog. Elle crée le dialogue s'il n'est pas
|
|
présent et le place au premier plan sinon.
|
|
|
|
<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, &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çon dont nous gérons ce dialogue. En le connectant
|
|
au signal "destroy", nous nous assurons que nous ne garderons pas un pointeur
|
|
sur le dialogue après l'avoir détruit -- cela pourrait provoquer une erreur de
|
|
segmentation).
|
|
|
|
|
|
<p>
|
|
InputDialog a deux boutons "Close" et "Save", qui n'ont pas d'actions qui leur
|
|
sont assignées par défaut. Dans la fonction ci-dessus, nous associons à "Close"
|
|
le masquage du dialogue et nous cachons le bouton "Save" car nous n'implantons
|
|
pas la sauvegarde des options XInput dans ce programme.
|
|
|
|
<sect2>Utiliser l'information supplémentaire d'un périphérique
|
|
|
|
<p>
|
|
Lorsque l'on a validé le périphérique, on peut simplement utiliser
|
|
l'information supplémentaire des champs des structures d'événement. En fait, il
|
|
est toujours prident d'utiliser ces informations car ces champs auront des
|
|
valeurs par défaut judicieuses même lorsque les événements supplémentaires ne
|
|
sont pas autorisés.
|
|
|
|
<p>
|
|
La seule modification consiste à 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é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éciser l'ID du périphérique
|
|
ainsi que la fenêtre. Habituellement, on aura obtenu cet ID par le champ
|
|
<em/deviceid/ d'une structure d'événement. Cette fonction retournera une valeur
|
|
cohérente lorsque les événements ne sont pas autorisés (dans ce cas,
|
|
<em/event->deviceid/ aura la valeur <em/GDK_CORE_POINTER/).
|
|
|
|
La structure de base des gestionnaires d'événements de déplacement et de bouton
|
|
pressé ne change donc pas trop -- il faut juste ajouter le code permettant de
|
|
traiter l'information supplémentaire.
|
|
|
|
<tscreen><verb>
|
|
static gint
|
|
button_press_event (GtkWidget *widget, GdkEventButton *event)
|
|
{
|
|
print_button_press (event->deviceid);
|
|
|
|
if (event->button == 1 && pixmap != NULL)
|
|
draw_brush (widget, event->source, event->x, event->y, event->pressure);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gint
|
|
motion_notify_event (GtkWidget *widget, GdkEventMotion *event)
|
|
{
|
|
gdouble x, y;
|
|
gdouble pressure;
|
|
GdkModifierType state;
|
|
|
|
if (event->is_hint)
|
|
gdk_input_window_get_pointer (event->window, event->deviceid,
|
|
&x, &y, &pressure, NULL, NULL, &state);
|
|
else
|
|
{
|
|
x = event->x;
|
|
y = event->y;
|
|
pressure = event->pressure;
|
|
state = event->state;
|
|
}
|
|
|
|
if (state & GDK_BUTTON1_MASK && 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érente pour chaque
|
|
<em/event->source/ et change la taille du pinceau selon la pression.
|
|
|
|
<tscreen><verb>
|
|
/* Dessine un rectangle à l'écran, la taille dépend de la pression,
|
|
et la couleur dépend du type de périphé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, &update_rect);
|
|
}
|
|
</verb></tscreen>
|
|
|
|
<sect2>En savoir plus sur un périphérique
|
|
|
|
<p>
|
|
Notre programme affichera le nom du périphérique qui a généré chaque appui de
|
|
bouton. Pour trouver le nom d'un périphérique, nous appelons la fonction :
|
|
|
|
<tscreen><verb>
|
|
GList *gdk_input_list_devices (void);
|
|
</verb></tscreen>
|
|
|
|
qui retourne une GList (un type de liste chaînée de la bibliothèque glib) de
|
|
structures GdkDeviceInfo. La structure GdkDeviceInfo est définie de la façon
|
|
suivante :
|
|
|
|
<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éresse est <em/name/ qui est, tout simplement, le nom que X donne
|
|
au périphé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écisé <em/GDK_EXTENSION_EVENTS_CURSOR/, nous
|
|
n'avons pas à nous en préoccuper.
|
|
|
|
<p>
|
|
Notre fonction <em/print_button_press()/ ne fait parcourir la liste retournée
|
|
jusqu'à trouver une correspondance, puis affiche le nom du périphé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érer après */
|
|
tmp_list = gdk_input_list_devices();
|
|
|
|
while (tmp_list)
|
|
{
|
|
GdkDeviceInfo *info = (GdkDeviceInfo *)tmp_list->data;
|
|
|
|
if (info->deviceid == deviceid)
|
|
{
|
|
printf("Bouton pressé sur le périphérique '%s'\n", info->name);
|
|
return;
|
|
}
|
|
|
|
tmp_list = tmp_list->next;
|
|
}
|
|
}
|
|
</verb></tscreen>
|
|
|
|
Ceci termine les modifications de notre programme « XInputize ». Comme pour la
|
|
première version, le code complet est disponible à l'endroit où vous avez
|
|
obtenu ce didacticiel.
|
|
|
|
<sect2>Sophistications supplémentaires<label id="sec_Further_Sophistications">
|
|
|
|
<p>
|
|
Bien que notre programme supporte maintenant XInput, il y manque des
|
|
caractéristiques que l'on souhaite trouver dans les applications
|
|
complètes. D'abord, l'utilisateur ne veut probablement pas avoir à configurer
|
|
ses périphériques à chaque fois qu'il lance le programme et nous devons donc
|
|
lui permettre de sauvegarder la configuration du périphérique. Ceci est réalisé
|
|
en parcourant ce que retourne <em/gdk_input_list_devices()/ et en écrivant la
|
|
configuration dans un fichier.
|
|
|
|
<p>
|
|
Pour restaurer un état au prochain démarrage du programme, GDK dispose de
|
|
fonctions pour changer la configuration des périphériques :
|
|
|
|
<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ée par <em/gdk_input_list_devices()/ ne doit pas être modifiée
|
|
directement). Un exemple est donné dans le programme de dessin <em/gsumi/
|
|
(disponible à 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 à un niveau légèrement supérieur à
|
|
GTK, peut-être dans la bibliothèque GNOME.
|
|
|
|
<p>
|
|
Une autre grosse omission que nous avons mentionnée plus haut est l'absence de
|
|
dessin du curseur. Les plates-formes autres qu'XFree86 n'autorisent pas encore
|
|
l'utilisation simultanée d'un périphé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 :
|
|
déterminer si le périphérique courant a besoin ou non d'un curseur dessiné et
|
|
déterminer si le périphérique courant est à proximité (si celui-ci est une
|
|
tablette de dessin, il est pratique de faire disparaître le curseur lorsque le
|
|
stylet est en dehors de la tablette. Lorsque le périphérique voit le stylet, on
|
|
dit qu'il est « à proximité »). La première vérification est faite en
|
|
recherchant dans la liste des périphériques, comme nous l'avons fait pour
|
|
obtenir le nom du périphérique. La deuxième est réalisée en choisissant des
|
|
événements "proximity_out". Une exemple de dessin d'un curseur personnel est
|
|
donné dans le programme <em/testinput/ de la distribution GTK.
|
|
|
|
|
|
<sect>Conseils pour l'écriture d'applications GTK
|
|
<p>
|
|
Cette section est simplement un regroupement de lignes de conduites générales
|
|
et sages, ainsi que d'astuces pour cré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ésenter brièvement ici.
|
|
|
|
<sect>Contributions
|
|
|
|
<p>
|
|
Ce document, comme beaucoup d'autres beaux logiciels, a été créé (NdT : et
|
|
traduit) par des
|
|
volontaires bénévoles. Si vous vous y connaissez sur certains aspects de GTK
|
|
qui ne sont pas encore documentés, soyez gentils de contribuer à ce document.
|
|
|
|
<p>
|
|
Si vous décidez de contribuer, envoyez-moi (Ian Main) votre texte à
|
|
<tt><htmlurl url="mailto:slow@intergate.bc.ca"
|
|
name="slow@intergate.bc.ca"></tt>. La totalité de ce document est libre et
|
|
tout ajout que vous pourriez y faire doit l'être également. Ainsi, tout le
|
|
monde peut utiliser n'importe quelle partie de vos exemples dans les
|
|
programmes, les copies de ce document peuvent être distribuées à volonté, etc.
|
|
<p>
|
|
Merci.
|
|
|
|
|
|
<sect>Remerciements
|
|
<p>
|
|
Je voudrai remercier les personnes suivantes pour leurs contributions à ce texte :
|
|
|
|
<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/ à la GTK, le placement des widgets et pour sa
|
|
sagesse. Il a aussi généreusement donné un abri à 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é à 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é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 à 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'écriture
|
|
de vos propres widgets GTK et de l'application exemple. Merci beaucoup à 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 été très pré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 à tous ceux d'entre vous qui ont commenté et aidé à amé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érale GNU telle qu'elle est
|
|
publiée par la Free Software Foundation ; soit la version 2 de la licence, ou
|
|
(comme vous voulez) toute version ultérieure.
|
|
<p>
|
|
Ce programme est distribué dans l'espoir qu'il sera utile, mais SANS AUCUNE
|
|
GARANTIE ; même sans la garantie de COMMERCIALITÉ ou d'ADÉQUATION A UN BUT
|
|
PARTICULIER. Voir la licence publique générale GNU pour plus de détails.
|
|
<p>
|
|
Vous devriez avoir reçu une copie de la licence publique générale GNU avec ce
|
|
programme ; si ce n'est pas le cas, écrivez à la Free Software
|
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
|
|
<sect1>Notes du traducteur
|
|
Ce document a été adapté en Français par <url url="mailto:jaco@dotcom.fr"
|
|
name="Éric Jacoboni">. Toute remarque sur cette adaptation sera la bienvenue.
|
|
<p>
|
|
Merci à <url url="mailto:kheops@linux-kheops.com" name="Joël Bernier"> pour
|
|
avoir initié cette adaptation, et à <url url="mailto:vincent@debian.org"
|
|
name="Vincent Renardias"> pour sa relecture.
|
|
</article>
|