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 gtk-list.
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.
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 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 GtkButton ressemble à ceci :
Lorsqu'un bouton est traité comme un container (par exemple, lorsqu'il
change de taille), sa structure de classe peut être convertie en
GtkContainerClass et les champs adéquats utilisés pour gérer les
signaux.
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
structure d'objet. Pour la classe Button, elle ressemble
à :
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.
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
FileSelection et ColorSelection de la distribution standard
sont des exemples de ce type de widget.
L'exemple de widget que nous allons créer dans cette section créera un
widget 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.
Choix d'une classe parent
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 FileSelection est la classe
Dialog. Comme nos boutons seront mis sous la forme d'un tableau,
il semble naturel d'utiliser la classe GtkTable comme
parent. Malheureusement, cela ne peut marcher. La création d'un widget
est divisée en deux fonctions -- WIDGETNAME_new() que
l'utilisateur appelle, et WIDGETNAME_init() qui réalise le
travail d'initialisation du widget indépendamment des paramètre passés
à la fonction _new(). Les widgets fils n'appellent que la
fonction _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
gtk_table_new() dans notre widget Tictactoe, nous ferions
mieux d'éviter de le dériver de GtkTable. Pour cette raison, nous
la dériverons plutôt de GtkVBox et nous placerons notre table
dans la VBox.
The header file
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 :
En plus des fonctions et structures, nous déclarons trois macros
standard, TICTACTOE(obj), TICTACTOE_CLASS(class), et
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.
Continuons maintenant avec l'implantation de notre widget. La fonction
centrale pour chaque widget est 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.
Les champs de cette structure s'expliquent d'eux-mêmes. Nous
ignorerons le champ 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.
La fonction _class_init()
La fonction 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 :
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
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 GTK_RUN_FIRST, ou GTK_RUN_LAST, bien qu'il
y ait d'autres possibilités.
object_type : L'ID de l'objet auquel s'applique ce signal
(il s'appliquera aussi au descendants de l'objet).
function_offset : L'offset d'un pointeur vers le
gestionnaire par défaut dans la structure de classe.
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
gtk_signal_default_marshaller().
return_val : Type de la valeur retournée.
nparams : Nombre de paramètres du gestionnaire de signal
(autres que les deux par défaut mentionnés plus haut).
... : Types des paramètres.
Lorsque l'on spécifie les types, on utilise l'énumération
GtkArgType :
gtk_signal_new() retourne un identificateur entier pour le
signal, que l'on stocke dans le tableau 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 TICTACTOE(), nous l'appellerons
donc 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
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.
La fonction _init()
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.
Il reste une fonction que chaque widget (sauf pour les types widget de
base, comme 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 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.
tictactoe_clear() est une fonction publique qui remet tous les
boutons du widget en position relâchée. Notez l'utilisation de
gtk_signal_handler_block_by_data() pour empêcher notre
gestionnaire de signaux des boutons commutateurs d'être déclenché sans
besoin.
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".
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.
Afficher un widget à l'écran
Il y a plusieurs étapes mises en jeu lors de l'affichage. Lorsque le widget est
créé par l'appel WIDGETNAME_new(), plusieurs autres fonctions
supplémentaires sont requises.
WIDGETNAME_realize() s'occupe de créer une fenêtre X pour le
widget, s'il en a une.
WIDGETNAME_map() est invoquée après l'appel de
gtk_widget_show(). Elle s'assure que le widget est bien tracé à l'écran
(mappé). Dans le cas d'une classe container, elle doit aussi appeler des
fonctions map()> pour chaque widget fils.
WIDGETNAME_draw() est invoquée lorsque 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 gtk_widget_draw() pour ses
widgets fils.
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).
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 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 à draw().
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 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
WIDGETNAME_paint() qui réalise le véritable travail de tracé du widget et
qui est appelée par les fonctions draw() et expose().
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 draw() par défaut et n'implanter que la fonction
expose().
Origines du widget Dial
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 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.
Les bases
Un petite partie de notre widget devrait ressembler au widget Tictactoe. Nous
avons d'abord le fichier en-tête :
/* 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__ */
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.
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 :
Notez que cette fonction init() fait moins de choses que pour le widget
Tictactoe car ce n'est pas un widget composé et que la fonction 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.
Il y a aussi quelques fonctions pour manipuler les options du widget :
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 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 gtk_widget_get_events() pour
récupérer le masque d'événement que l'utilisateur a spécifié pour ce widget
(avec gtk_widget_set_events()) et ajouter les événements qui nous
intéressent.
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.
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
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.
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
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.
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
gtk_draw_polygon pour dessiner le pointeur avec une forme en trois
dimensions selon les couleurs stockées dans le style du widget.
style.
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.
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 button de la structure du
widget et on récupère tous les événements souris avec un appel à
gtk_grab_add(). Un déplacement ultérieur de la souris provoque le recalcul
de la valeur de contrôle (par la fonction gtk_dial_update_mouse). Selon la
politique qui a été choisie, les événements "value_changed" sont, soit générés
instantanément (GTK_UPDATE_CONTINUOUS), après un délai ajouté au timer
avec gtk_timeout_add() (GTK_UPDATE_DELAYED), ou seulement lorsque le
bouton est relâché (GTK_UPDATE_DISCONTINUOUS).
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);
}
}
}
}
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 gtk_dial_update() pour valider les
paramètres, calculer le nouvel angle du pointeur et redessiner le widget (en
appelant gtk_widget_draw()).
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 :
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 ProgressBar se dessine de cette façon).
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.
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 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 GtkRange.
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.
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 !