source: trunk/third/gtk/docs/html/gtk_tut_fr-19.html @ 14482

Revision 14482, 46.3 KB checked in by ghudson, 25 years ago (diff)
This commit was generated by cvs2svn to compensate for changes in r14481, which included commits to RCS files with non-trunk default branches.
Line 
1<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
2<HTML>
3<HEAD>
4 <META NAME="GENERATOR" CONTENT="SGML-Tools 1.0.9">
5 <TITLE>Didacticiel: Écriture de vos propres widgets </TITLE>
6 <LINK HREF="gtk_tut_fr-20.html" REL=next>
7 <LINK HREF="gtk_tut_fr-18.html" REL=previous>
8 <LINK HREF="gtk_tut_fr.html#toc19" REL=contents>
9</HEAD>
10<BODY BGCOLOR="#FFFFFF">
11<A HREF="gtk_tut_fr-20.html">Page suivante</A>
12<A HREF="gtk_tut_fr-18.html">Page précédente</A>
13<A HREF="gtk_tut_fr.html#toc19">Table des matières</A>
14<HR NOSHADE>
15<H2><A NAME="s19">19. Écriture de vos propres widgets </A></H2>
16
17<P>
18<H2><A NAME="ss19.1">19.1 Vue d'ensemble</A>
19</H2>
20
21<P>Bien que la distribution GTK fournisse de nombreux types de widgets
22qui devraient couvrir la plupart des besoins de base, il peut arriver
23un moment où vous aurez besoin de créer votre propre type de
24widget. Comme GTK utilise l'héritage de widget de façon intensive et
25qu'il y a déjà un widget ressemblant à celui que vous voulez, il est
26souvent possible de créer un nouveau type de widget en seulement
27quelques lignes de code. Mais, avant de travailler sur un nouveau
28widget, il faut vérifier d'abord que quelqu'un ne l'a pas déjà
29écrit. Ceci éviter la duplication des efforts et maintient au minimum
30le nombre de widgets, ce qui permet de garder la cohérence du code et
31de l'interface des différentes applications. Un effet de bord est que,
32lorsque l'on a créé un nouveau widget, il faut l'annoncer afin que les
33autres puissent en bénéficier. Le meilleur endroit pour faire cela
34est, sans doute, la <CODE>gtk-list</CODE>.
35<P>
36<H2><A NAME="ss19.2">19.2 Anatomie d'un widget</A>
37</H2>
38
39<P>Afin de créer un nouveau widget, il importe de comprendre comment
40fonctionnent les objets GTK. Cette section ne se veut être qu'un
41rapide survol. Consultez la documentation de référence pour plus de
42détails.
43<P>
44<P>Les widgets sont implantés selon une méthode orientée
45objet. Cependant, ils sont écrits en C standard. Ceci améliore
46beaucoup la portabilité et la stabilité, par contre cela signifie que
47celui qui écrit des widgets doit faire attention à certains détails
48d'implantation. Les informations communes à toutes les instances d'une
49classe de widget (tous les widgets boutons, par exemple) sont stockées
50dans la <EM>structure de la classe</EM>.  Il n'y en a qu'une copie dans
51laquelle sont stockées les informations sur les signaux de la
52classe (fonctionnement identique aux fonctions virtuelles en C). Pour
53permettre l'héritage, le premier champ de la structure de classe doit
54être une copie de la structure de classe du père. La déclaration de la
55structure de classe de <EM>GtkButton</EM> ressemble à ceci&nbsp;:
56<P>
57<BLOCKQUOTE><CODE>
58<PRE>
59struct _GtkButtonClass
60{
61  GtkContainerClass parent_class;
62
63  void (* pressed)  (GtkButton *button);
64  void (* released) (GtkButton *button);
65  void (* clicked)  (GtkButton *button);
66  void (* enter)    (GtkButton *button);
67  void (* leave)    (GtkButton *button);
68};
69</PRE>
70</CODE></BLOCKQUOTE>
71<P>
72<P>Lorsqu'un bouton est traité comme un container (par exemple, lorsqu'il
73change de taille), sa structure de classe peut être convertie en
74<EM>GtkContainerClass</EM> et les champs adéquats utilisés pour gérer les
75signaux.
76<P>
77<P>Il y a aussi une structure pour chaque widget créé sur une base
78d'instance. Cette structure a des champs pour stocker les informations
79qui sont différentes pour chaque instance du widget. Nous l'appelerons
80<EM>structure d'objet</EM>. Pour la classe <EM>Button</EM>, elle ressemble
81à&nbsp;:
82<P>
83<BLOCKQUOTE><CODE>
84<PRE>
85struct _GtkButton
86{
87  GtkContainer container;
88
89  GtkWidget *child;
90
91  guint in_button : 1;
92  guint button_down : 1;
93};
94</PRE>
95</CODE></BLOCKQUOTE>
96<P>
97<P>Notez que, comme pour la structure de classe, le premier champ est la
98structure d'objet de la classe parente, cette structure peut donc être
99convertie dans la structure d'objet de la classe parente si besoin
100est.
101<P>
102<H2><A NAME="ss19.3">19.3 Création d'un widget composé</A>
103</H2>
104
105<H3>Introduction</H3>
106
107<P>Un type de widget qui peut être intéressant à créer est un widget qui
108est simplement un agrégat d'autres widgets GTK. Ce type de widget ne
109fait rien qui ne pourrait être fait sans créer de nouveaux widgets,
110mais offre une méthode pratique pour empaqueter les éléments d'une
111interface utilisateur afin de la réutiliser facilement. Les widgets
112<EM>FileSelection</EM> et <EM>ColorSelection</EM> de la distribution standard
113sont des exemples de ce type de widget.
114<P>
115<P>L'exemple de widget que nous allons créer dans cette section créera un
116widget <EM>Tictactoe</EM>, un tableau de 3x3 boutons commutateurs qui
117déclenche un signal lorsque tous les boutons d'une ligne, d'une
118colonne, ou d'une diagonale sont pressés.
119<P>
120<H3>Choix d'une classe parent</H3>
121
122<P>La classe parent d'un widget composé est, typiquement, la classe
123container contenant tous les éléments du widget composé. Par exemple,
124la classe parent du widget <EM>FileSelection</EM> est la classe
125<EM>Dialog</EM>. Comme nos boutons seront mis sous la forme d'un tableau,
126il semble naturel d'utiliser la classe <EM>GtkTable</EM> comme
127parent. Malheureusement, cela ne peut marcher. La création d'un widget
128est divisée en deux fonctions -- <EM>WIDGETNAME_new()</EM> que
129l'utilisateur appelle, et <EM>WIDGETNAME_init()</EM> qui réalise le
130travail d'initialisation du widget indépendamment des paramètre passés
131à la fonction <CODE>_new()</CODE>. Les widgets fils n'appellent que la
132fonction <EM>_init</EM> de leur widget parent. Mais cette division du
133travail ne fonctionne pas bien avec les tableaux qui, lorsqu'ils sont
134créés, ont besoin de connaître leue nombre de lignes et de
135colonnes. Sauf à dupliquer la plupart des fonctionnalités de
136<EM>gtk_table_new()</EM> dans notre widget <EM>Tictactoe</EM>, nous ferions
137mieux d'éviter de le dériver de <EM>GtkTable</EM>. Pour cette raison, nous
138la dériverons plutôt de <EM>GtkVBox</EM> et nous placerons notre table
139dans la VBox.
140<P>
141<H3>The header file</H3>
142
143<P>Chaque classe de widget possède un fichier en-tête qui déclare les
144structures d'objet et de classe pour ce widget, en plus de fonctions
145publiques. Quelques caractéristiques méritent d'être indiquées. Afin
146d'éviter des définitions multiples, on enveloppe le fichier en-tête
147avec&nbsp;:
148<P>
149<BLOCKQUOTE><CODE>
150<PRE>
151#ifndef __TICTACTOE_H__
152#define __TICTACTOE_H__
153.
154.
155.
156#endif /* __TICTACTOE_H__ */
157</PRE>
158</CODE></BLOCKQUOTE>
159<P>Et, pour faire plaisir aux programmes C++ qui inclueront ce fichier, on l'enveloppe aussi dans&nbsp;:
160<P>
161<BLOCKQUOTE><CODE>
162<PRE>
163#ifdef __cplusplus
164extern "C" {
165#endif /* __cplusplus */
166.
167.
168.
169#ifdef __cplusplus
170}
171#endif /* __cplusplus */
172</PRE>
173</CODE></BLOCKQUOTE>
174<P>En plus des fonctions et structures, nous déclarons trois macros
175standard, <CODE>TICTACTOE(obj)</CODE>, <CODE>TICTACTOE_CLASS(class)</CODE>, et
176<CODE>IS_TICTACTOE(obj)</CODE>, qui, respectivement, convertissent un pointeur
177en un pointeur vers une structure d'objet ou de classe, et vérifient
178si un objet est un widget Tictactoe.
179<P>
180<P>Voici le fichier en-tête complet&nbsp;:
181<P>
182<BLOCKQUOTE><CODE>
183<PRE>
184
185#ifndef __TICTACTOE_H__
186#define __TICTACTOE_H__
187
188#include &lt;gdk/gdk.h>
189#include &lt;gtk/gtkvbox.h>
190
191#ifdef __cplusplus
192extern "C" {
193#endif /* __cplusplus */
194
195#define TICTACTOE(obj)          GTK_CHECK_CAST (obj, tictactoe_get_type (), Tictactoe)
196#define TICTACTOE_CLASS(klass)  GTK_CHECK_CLASS_CAST (klass, tictactoe_get_type (), TictactoeClass)
197#define IS_TICTACTOE(obj)       GTK_CHECK_TYPE (obj, tictactoe_get_type ())
198
199
200typedef struct _Tictactoe       Tictactoe;
201typedef struct _TictactoeClass  TictactoeClass;
202
203struct _Tictactoe
204{
205  GtkVBox vbox;
206 
207  GtkWidget *buttons[3][3];
208};
209
210struct _TictactoeClass
211{
212  GtkVBoxClass parent_class;
213
214  void (* tictactoe) (Tictactoe *ttt);
215};
216
217guint          tictactoe_get_type        (void);
218GtkWidget*     tictactoe_new             (void);
219void           tictactoe_clear           (Tictactoe *ttt);
220
221#ifdef __cplusplus
222}
223#endif /* __cplusplus */
224
225#endif /* __TICTACTOE_H__ */
226</PRE>
227</CODE></BLOCKQUOTE>
228<P>
229<H3>La fonction  <CODE>_get_type()</CODE></H3>
230
231<P>Continuons maintenant avec l'implantation de notre widget. La fonction
232centrale pour chaque widget est <EM>WIDGETNAME_get_type()</EM>. Cette
233fonction, lorsqu'elle est appelée pour la première fois, informe le
234GTK de la classe et récupère un ID permettant d'identifier celle-ci de
235façon unique. Lors des appels suivants, elle ne fait que retourner cet
236ID.
237<P>
238<BLOCKQUOTE><CODE>
239<PRE>
240guint
241tictactoe_get_type ()
242{
243  static guint ttt_type = 0;
244
245  if (!ttt_type)
246    {
247      GtkTypeInfo ttt_info =
248      {
249        "Tictactoe",
250        sizeof (Tictactoe),
251        sizeof (TictactoeClass),
252        (GtkClassInitFunc) tictactoe_class_init,
253        (GtkObjectInitFunc) tictactoe_init,
254        (GtkArgFunc) NULL,
255      };
256
257      ttt_type = gtk_type_unique (gtk_vbox_get_type (), &amp;ttt_info);
258    }
259
260  return ttt_type;
261}
262</PRE>
263</CODE></BLOCKQUOTE>
264<P>
265<P>La structure <EM>GtkTypeInfo</EM> est définie de la façon suivante&nbsp;:
266<P>
267<BLOCKQUOTE><CODE>
268<PRE>
269struct _GtkTypeInfo
270{
271  gchar *type_name;
272  guint object_size;
273  guint class_size;
274  GtkClassInitFunc class_init_func;
275  GtkObjectInitFunc object_init_func;
276  GtkArgFunc arg_func;
277};
278</PRE>
279</CODE></BLOCKQUOTE>
280<P>
281<P>Les champs de cette structure s'expliquent d'eux-mêmes. Nous
282ignorerons le champ <EM>arg_func</EM> ici&nbsp;: il a un rôle important
283permettant aux options des widgets d'être correctement initialisées à
284partir des langages interprétés, mais cette fonctionnalité est encore
285très peu implantée. Lorsque GTK dispose d'une copie correctement
286remplie de cette structure, il sait comment créer des objets d'un type
287particulier de widget.
288<P>
289<H3>La fonction <EM>_class_init()</EM></H3>
290
291<P>La fonction <EM>WIDGETNAME_class_init()</EM> initialise les champs de la
292structure de classe du widget et configure tous les signaux de cette
293classe. Pour notre widget Tictactoe, cet appel est&nbsp;:
294<P>
295<BLOCKQUOTE><CODE>
296<PRE>
297
298enum {
299  TICTACTOE_SIGNAL,
300  LAST_SIGNAL
301};
302
303static gint tictactoe_signals[LAST_SIGNAL] = { 0 };
304
305static void
306tictactoe_class_init (TictactoeClass *class)
307{
308  GtkObjectClass *object_class;
309
310  object_class = (GtkObjectClass*) class;
311 
312  tictactoe_signals[TICTACTOE_SIGNAL] = gtk_signal_new ("tictactoe",
313                                         GTK_RUN_FIRST,
314                                         object_class->type,
315                                         GTK_SIGNAL_OFFSET (TictactoeClass, tictactoe),
316                                         gtk_signal_default_marshaller, GTK_ARG_NONE, 0);
317
318
319  gtk_object_class_add_signals (object_class, tictactoe_signals, LAST_SIGNAL);
320
321  class->tictactoe = NULL;
322}
323</PRE>
324</CODE></BLOCKQUOTE>
325<P>
326<P>Notre widget n'a qu'un signal&nbsp;: "tictactoe", invoqué lorsqu'une
327ligne, une colonne ou une diagonale est complètement remplie. Tous les
328widgets composés n'ont pas besoin de signaux. Si vous lisez ceci pour
329la première fois, vous pouvez passer directement à la section suivante
330car les choses vont se compliquer un peu
331<P>La fonction :
332<P>
333<BLOCKQUOTE><CODE>
334<PRE>
335gint   gtk_signal_new                     (gchar               *name,
336                                           GtkSignalRunType     run_type,
337                                           gint                 object_type,
338                                           gint                 function_offset,
339                                           GtkSignalMarshaller  marshaller,
340                                           GtkArgType           return_val,
341                                           gint                 nparams,
342                                           ...);
343</PRE>
344</CODE></BLOCKQUOTE>
345<P>crée un nouveau signal. Les paramètres sont :
346<P>
347<UL>
348<LI> <EM>name</EM> : Le nom du signal signal.</LI>
349<LI> <EM>run_type</EM> : Indique si le gestionnaire par défaut doit être
350lancé avant ou après le gestionnaire de l'utilisateur. Le plus
351souvent, ce sera <CODE>GTK_RUN_FIRST</CODE>, ou <CODE>GTK_RUN_LAST</CODE>, bien qu'il
352y ait d'autres possibilités.
353</LI>
354<LI> <EM>object_type</EM> : L'ID de l'objet auquel s'applique ce signal
355(il s'appliquera aussi au descendants de l'objet).
356</LI>
357<LI> <EM>function_offset</EM> : L'offset d'un pointeur vers le
358gestionnaire par défaut dans la structure de classe.
359</LI>
360<LI> <EM>marshaller</EM> : Fonction utilisée pour invoquer le
361gestionnaire de signal. Pour les gestionnaires de signaux n'ayant pas
362d'autres paramètres que l'objet émetteur et les données utilisateur,
363on peut utiliser la fonction prédéfinie
364<EM>gtk_signal_default_marshaller()</EM>.
365</LI>
366<LI> <EM>return_val</EM> : Type de la valeur retournée.
367</LI>
368<LI> <EM>nparams</EM> : Nombre de paramètres du gestionnaire de signal
369(autres que les deux par défaut mentionnés plus haut).
370</LI>
371<LI> <EM>...</EM> : Types des paramètres.</LI>
372</UL>
373<P>Lorsque l'on spécifie les types, on utilise l'énumération
374<EM>GtkArgType</EM>&nbsp;:
375<P>
376<BLOCKQUOTE><CODE>
377<PRE>
378typedef enum
379{
380  GTK_ARG_INVALID,
381  GTK_ARG_NONE,
382  GTK_ARG_CHAR,
383  GTK_ARG_SHORT,
384  GTK_ARG_INT,
385  GTK_ARG_LONG,
386  GTK_ARG_POINTER,
387  GTK_ARG_OBJECT,
388  GTK_ARG_FUNCTION,
389  GTK_ARG_SIGNAL
390} GtkArgType;
391</PRE>
392</CODE></BLOCKQUOTE>
393<P>
394<P><EM>gtk_signal_new()</EM> retourne un identificateur entier pour le
395signal, que l'on stocke dans le tableau <EM>tictactoe_signals</EM>, indicé
396par une énumération (conventionnellement, les éléments de
397l'énumération sont le nom du signal, en majuscules, mais, ici, il y
398aurait un conflit avec la macro <CODE>TICTACTOE()</CODE>, nous l'appellerons
399donc <CODE>TICTACTOE_SIGNAL</CODE> à la place.
400<P>Après avoir créé nos signaux, nous devons demander à GTK d'associer
401ceux-ci à la classe Tictactoe. Ceci est fait en appelant
402<EM>gtk_object_class_add_signals()</EM>. Puis nous configurons le pointeur
403qui pointe sur le gestionnaire par défaut du signal "tictactoe" à
404NULL, pour indiquer qu'il n'y a pas d'action par défaut.
405<P>
406<H3>La fonction <EM>_init()</EM></H3>
407
408<P>
409<P>Chaque classe de widget a aussi besoin d'une fonction pour initialiser
410la structure d'objet. Habituellement, cette fonction a le rôle, plutôt
411limité, d'initialiser les champs de la structure avec des valeurs par
412défaut. Cependant, pour les widgets composés, cette fonction crée
413aussi les widgets composants.
414<P>
415<BLOCKQUOTE><CODE>
416<PRE>
417
418static void
419tictactoe_init (Tictactoe *ttt)
420{
421  GtkWidget *table;
422  gint i,j;
423 
424  table = gtk_table_new (3, 3, TRUE);
425  gtk_container_add (GTK_CONTAINER(ttt), table);
426  gtk_widget_show (table);
427
428  for (i=0;i&lt;3; i++)
429    for (j=0;j&lt;3; j++)
430      {
431        ttt->buttons[i][j] = gtk_toggle_button_new ();
432        gtk_table_attach_defaults (GTK_TABLE(table), ttt->buttons[i][j],
433                                   i, i+1, j, j+1);
434        gtk_signal_connect (GTK_OBJECT (ttt->buttons[i][j]), "toggled",
435                            GTK_SIGNAL_FUNC (tictactoe_toggle), ttt);
436        gtk_widget_set_usize (ttt->buttons[i][j], 20, 20);
437        gtk_widget_show (ttt->buttons[i][j]);
438      }
439}
440</PRE>
441</CODE></BLOCKQUOTE>
442<P>
443<H3>Et le reste...</H3>
444
445<P>
446<P>Il reste une fonction que chaque widget (sauf pour les types widget de
447base, comme <EM>GtkBin</EM>, qui ne peuvent être instanciés) à besoin
448d'avoir -- celle que l'utilisateur appelle pour créer un objet de ce
449type. Elle est conventionnellement appelée <EM>WIDGETNAME_new()</EM>. Pour
450certains widgets, par pour ceux de Tictactoe, cette fonction prend des
451paramètres et réalise certaines initialisations dépendantes des
452paramètres. Les deux autres fonctions sont spécifiques au widget
453Tictactoe.
454<P>
455<P><EM>tictactoe_clear()</EM> est une fonction publique qui remet tous les
456boutons du widget en position relâchée. Notez l'utilisation de
457<EM>gtk_signal_handler_block_by_data()</EM> pour empêcher notre
458gestionnaire de signaux des boutons commutateurs d'être déclenché sans
459besoin.
460<P>
461<P><EM>tictactoe_toggle()</EM> est le gestionnaire de signal invoqué
462lorsqu'on clique sur un bouton. Il vérifie s'il y a des combinaisons
463gagnantes concernant le bouton qui vient d'être commuté et, si c'est
464le cas, émet le signal "tictactoe".
465<P>
466<BLOCKQUOTE><CODE>
467<PRE>
468 
469GtkWidget*
470tictactoe_new ()
471{
472  return GTK_WIDGET ( gtk_type_new (tictactoe_get_type ()));
473}
474
475void           
476tictactoe_clear (Tictactoe *ttt)
477{
478  int i,j;
479
480  for (i=0;i&lt;3;i++)
481    for (j=0;j&lt;3;j++)
482      {
483        gtk_signal_handler_block_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
484        gtk_toggle_button_set_state (GTK_TOGGLE_BUTTON (ttt->buttons[i][j]),
485                                     FALSE);
486        gtk_signal_handler_unblock_by_data (GTK_OBJECT(ttt->buttons[i][j]), ttt);
487      }
488}
489
490static void
491tictactoe_toggle (GtkWidget *widget, Tictactoe *ttt)
492{
493  int i,k;
494
495  static int rwins[8][3] = { { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
496                             { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
497                             { 0, 1, 2 }, { 0, 1, 2 } };
498  static int cwins[8][3] = { { 0, 1, 2 }, { 0, 1, 2 }, { 0, 1, 2 },
499                             { 0, 0, 0 }, { 1, 1, 1 }, { 2, 2, 2 },
500                             { 0, 1, 2 }, { 2, 1, 0 } };
501
502  int success, found;
503
504  for (k=0; k&lt;8; k++)
505    {
506      success = TRUE;
507      found = FALSE;
508
509      for (i=0;i&lt;3;i++)
510        {
511          success = success &amp;&amp;
512            GTK_TOGGLE_BUTTON(ttt->buttons[rwins[k][i]][cwins[k][i]])->active;
513          found = found ||
514            ttt->buttons[rwins[k][i]][cwins[k][i]] == widget;
515        }
516     
517      if (success &amp;&amp; found)
518        {
519          gtk_signal_emit (GTK_OBJECT (ttt),
520                           tictactoe_signals[TICTACTOE_SIGNAL]);
521          break;
522        }
523    }
524}
525</PRE>
526</CODE></BLOCKQUOTE>
527<P>
528<P>
529<P>Enfin, un exemple de programme utilisant notre widget Tictactoe&nbsp;
530<P>
531<BLOCKQUOTE><CODE>
532<PRE>
533#include &lt;gtk/gtk.h>
534#include "tictactoe.h"
535
536/* Invoqué lorsqu'une ligne, une colonne ou une diagonale est complète */
537
538void win (GtkWidget *widget, gpointer data)
539{
540  g_print ("Ouais !\n");
541  tictactoe_clear (TICTACTOE (widget));
542}
543
544int main (int argc, char *argv[])
545{
546  GtkWidget *window;
547  GtkWidget *ttt;
548 
549  gtk_init (&amp;argc, &amp;argv);
550
551  window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
552 
553  gtk_window_set_title (GTK_WINDOW (window), "Aspect Frame");
554 
555  gtk_signal_connect (GTK_OBJECT (window), "destroy",
556                      GTK_SIGNAL_FUNC (gtk_exit), NULL);
557 
558  gtk_container_border_width (GTK_CONTAINER (window), 10);
559
560  /* Création d'un widget Tictactoe */
561  ttt = tictactoe_new ();
562  gtk_container_add (GTK_CONTAINER (window), ttt);
563  gtk_widget_show (ttt);
564
565  /* On lui attache le signal "tictactoe" */
566  gtk_signal_connect (GTK_OBJECT (ttt), "tictactoe",
567                      GTK_SIGNAL_FUNC (win), NULL);
568
569  gtk_widget_show (window);
570 
571  gtk_main ();
572 
573  return 0;
574}
575</PRE>
576</CODE></BLOCKQUOTE>
577<P>
578<H2><A NAME="ss19.4">19.4 Création d'un widget à partir de zéro</A>
579</H2>
580
581<H3>Introduction</H3>
582
583<P>
584<P>Dans cette section, nous en apprendrons plus sur la façon dont les
585widgets s'affichent eux-mêmes à l'écran et comment ils interagissent
586avec les événements. Comme exemple, nous créerons un widget d'appel
587télephonique interactif avec un pointeur que l'utilisateur pourra
588déplacer pour initialiser la valeur.
589<P>
590<H3>Afficher un widget à l'écran</H3>
591
592<P>Il y a plusieurs étapes mises en jeu lors de l'affichage. Lorsque le widget est
593créé par l'appel <EM>WIDGETNAME_new()</EM>, plusieurs autres fonctions
594supplémentaires sont requises.
595<P>
596<UL>
597<LI> <EM>WIDGETNAME_realize()</EM> s'occupe de créer une fenêtre X pour le
598widget, s'il en a une.</LI>
599<LI> <EM>WIDGETNAME_map()</EM> est invoquée après l'appel de
600<EM>gtk_widget_show()</EM>. Elle s'assure que le widget est bien tracé à l'écran
601(<EM>mappé</EM>). Dans le cas d'une classe container, elle doit aussi appeler des
602fonctions <EM>map()</EM>> pour chaque widget fils.</LI>
603<LI> <EM>WIDGETNAME_draw()</EM> est invoquée lorsque <EM>gtk_widget_draw()</EM> est
604appelé pour le widget ou l'un de ces ancêtres. Elle réalise les véritables
605appels aux fonctions de dessin pour tracer le widget à l'écran. Pour les
606widgets containers, cette fonction doit appeler <EM>gtk_widget_draw()</EM> pour ses
607widgets fils.</LI>
608<LI> <EM>WIDGETNAME_expose()</EM> est un gestionnaire pour les événements
609d'exposition du widget. Il réalise les appels nécessaires aux fonctions de
610dessin pour tracer la partie exposée à l'écran. Pour les widgets containers,
611cette fonction doit générer les événements d'exposition pour ses widgets
612enfants n'ayant pas leurs propres fenêtres (s'ils ont leurs propres fenêtres, X
613génèrera les événements d'exposition nécessaires).</LI>
614</UL>
615<P>
616<P>Vous avez pu noter que les deux dernières fonctions sont assez similaires --
617chacune se charge de tracer le widget à l'écran. En fait, de nombreux types de
618widgets ne se préoccupent pas vraiment de la différence entre les deux. La
619fonction <EM>draw()</EM> par défaut de la classe widget génère simplement un
620événement d'exposition synthétique pour la zone à redessiner. Cependant,
621certains types de widgets peuvent économiser du travail en faisant la
622différence entre les deux fonctions. Par exemple, si un widget a plusieurs
623fenêtres X et puisque les événements d'exposition identifient la fenêtre
624exposée, il peut redessiner seulement la fenêtre concernée, ce qui n'est pas
625possible avec des appels à <EM>draw()</EM>.
626<P>
627<P>Les widgets container, même s'ils ne se soucient pas eux-mêmes de la
628différence, ne peuvent pas utiliser simplement la fonction <EM>draw()</EM> car
629leurs widgets enfants tiennent compte de cette différence. Cependant, ce serait
630du gaspillage de dupliquer le code de tracé pour les deux
631fonctions. Conventionnellement, de tels widgets possèdent une fonction nommée
632<EM>WIDGETNAME_paint()</EM> qui réalise le véritable travail de tracé du widget et
633qui est appelée par les fonctions <CODE>draw()</CODE> et <CODE>expose()</CODE>.
634<P>
635<P>Dans notre exemple, comme le widget d'appel n'est pas un widget container et
636n'a qu'une fenêtre, nous pouvons utiliser l'approche la plus simple&nbsp;:
637utiliser la fonction <EM>draw()</EM> par défaut et n'implanter que la fonction
638<EM>expose()</EM>.
639<P>
640<H3>Origines du widget Dial</H3>
641
642<P>Exactement comme les animaux terrestres ne sont que des variantes des premiers
643amphibiens qui rampèrent hors de la boue, les widgets GTK sont des variantes
644d'autres widgets, déjà écrits. Ainsi, bien que cette section s'appelle « créer
645un widget à partir de zéro », le widget Dial commence réellement avec le code
646source du widget Range. Celui-ci a été pris comme point de départ car ce serait
647bien que notre Dial ait la même interface que les widgets Scale qui ne sont que
648des descendants spécialisés du widget Range. Par conséquent, bien que le code
649source soit présenté ci-dessous sous une forme achevée, cela n'implique pas
650qu'il a été écrit <EM>deus ex machina</EM>. De plus, si vous ne savez pas comment
651fonctionnent les widgets Scale du point de vue du programmeur de l'application,
652il est préférable de les étudier avant de continuer.
653<P>
654<H3>Les bases</H3>
655
656<P>Un petite partie de notre widget devrait ressembler au widget Tictactoe. Nous
657avons d'abord le fichier en-tête&nbsp;:
658<P>
659<BLOCKQUOTE><CODE>
660<PRE>
661/* GTK - The GIMP Toolkit
662 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
663 *
664 * This library is free software; you can redistribute it and/or
665 * modify it under the terms of the GNU Library General Public
666 * License as published by the Free Software Foundation; either
667 * version 2 of the License, or (at your option) any later version.
668 *
669 * This library is distributed in the hope that it will be useful,
670 * but WITHOUT ANY WARRANTY; without even the implied warranty of
671 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
672 * Library General Public License for more details.
673 *
674 * You should have received a copy of the GNU Library General Public
675 * License along with this library; if not, write to the Free
676 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
677 */
678
679#ifndef __GTK_DIAL_H__
680#define __GTK_DIAL_H__
681
682#include &lt;gdk/gdk.h>
683#include &lt;gtk/gtkadjustment.h>
684#include &lt;gtk/gtkwidget.h>
685
686
687#ifdef __cplusplus
688extern "C" {
689#endif /* __cplusplus */
690
691
692#define GTK_DIAL(obj)          GTK_CHECK_CAST (obj, gtk_dial_get_type (), GtkDial)
693#define GTK_DIAL_CLASS(klass)  GTK_CHECK_CLASS_CAST (klass, gtk_dial_get_type (), GtkDialClass)
694#define GTK_IS_DIAL(obj)       GTK_CHECK_TYPE (obj, gtk_dial_get_type ())
695
696
697typedef struct _GtkDial        GtkDial;
698typedef struct _GtkDialClass   GtkDialClass;
699
700struct _GtkDial
701{
702  GtkWidget widget;
703
704  /* politique de mise à jour 
705     (GTK_UPDATE_[CONTINUOUS/DELAYED/DISCONTINUOUS]) */
706
707  guint policy : 2;
708
709  /* Le bouton qui est pressé, 0 si aucun */
710  guint8 button;
711
712  /* Dimensions des composants de dial */
713  gint radius;
714  gint pointer_width;
715
716  /* ID du timer de mise à jour, 0 si aucun */
717  guint32 timer;
718
719  /* Angle courant*/
720  gfloat angle;
721
722  /* Anciennes valeurs d'ajustement stockées. On sait donc quand quelque
723     chose change */
724  gfloat old_value;
725  gfloat old_lower;
726  gfloat old_upper;
727
728  /* L'objet ajustment qui stocke les données de cet appel */
729  GtkAdjustment *adjustment;
730};
731
732struct _GtkDialClass
733{
734  GtkWidgetClass parent_class;
735};
736
737
738GtkWidget*     gtk_dial_new                    (GtkAdjustment *adjustment);
739guint          gtk_dial_get_type               (void);
740GtkAdjustment* gtk_dial_get_adjustment         (GtkDial      *dial);
741void           gtk_dial_set_update_policy      (GtkDial      *dial,
742                                                GtkUpdateType  policy);
743
744void           gtk_dial_set_adjustment         (GtkDial      *dial,
745                                                GtkAdjustment *adjustment);
746#ifdef __cplusplus
747}
748#endif /* __cplusplus */
749
750
751#endif /* __GTK_DIAL_H__ */
752</PRE>
753</CODE></BLOCKQUOTE>
754<P>Comme il y a plus de choses à faire avec ce widget par rapport à l'autre, nous
755avons plus de champs dans la structure de données, mais à part ça, les choses
756sont plutôt similaires.
757<P>
758<P>
759<P>Puis, après avoir inclus les fichiers en-tête et déclaré quelques constantes,
760nous devons fournir quelques fonctions pour donner des informations sur le
761widget et pour l'initialiser&nbsp;:
762<P>
763<BLOCKQUOTE><CODE>
764<PRE>
765#include &lt;math.h>
766#include &lt;stdio.h>
767#include &lt;gtk/gtkmain.h>
768#include &lt;gtk/gtksignal.h>
769
770#include "gtkdial.h"
771
772#define SCROLL_DELAY_LENGTH  300
773#define DIAL_DEFAULT_SIZE 100
774
775/* Déclararations des prototypes */
776
777[ omis pour gagner de la place ]
778
779/* Données locales */
780
781static GtkWidgetClass *parent_class = NULL;
782
783guint
784gtk_dial_get_type ()
785{
786  static guint dial_type = 0;
787
788  if (!dial_type)
789    {
790      GtkTypeInfo dial_info =
791      {
792        "GtkDial",
793        sizeof (GtkDial),
794        sizeof (GtkDialClass),
795        (GtkClassInitFunc) gtk_dial_class_init,
796        (GtkObjectInitFunc) gtk_dial_init,
797        (GtkArgFunc) NULL,
798      };
799
800      dial_type = gtk_type_unique (gtk_widget_get_type (), &amp;dial_info);
801    }
802
803  return dial_type;
804}
805
806static void
807gtk_dial_class_init (GtkDialClass *class)
808{
809  GtkObjectClass *object_class;
810  GtkWidgetClass *widget_class;
811
812  object_class = (GtkObjectClass*) class;
813  widget_class = (GtkWidgetClass*) class;
814
815  parent_class = gtk_type_class (gtk_widget_get_type ());
816
817  object_class->destroy = gtk_dial_destroy;
818
819  widget_class->realize = gtk_dial_realize;
820  widget_class->expose_event = gtk_dial_expose;
821  widget_class->size_request = gtk_dial_size_request;
822  widget_class->size_allocate = gtk_dial_size_allocate;
823  widget_class->button_press_event = gtk_dial_button_press;
824  widget_class->button_release_event = gtk_dial_button_release;
825  widget_class->motion_notify_event = gtk_dial_motion_notify;
826}
827
828static void
829gtk_dial_init (GtkDial *dial)
830{
831  dial->button = 0;
832  dial->policy = GTK_UPDATE_CONTINUOUS;
833  dial->timer = 0;
834  dial->radius = 0;
835  dial->pointer_width = 0;
836  dial->angle = 0.0;
837  dial->old_value = 0.0;
838  dial->old_lower = 0.0;
839  dial->old_upper = 0.0;
840  dial->adjustment = NULL;
841}
842
843GtkWidget*
844gtk_dial_new (GtkAdjustment *adjustment)
845{
846  GtkDial *dial;
847
848  dial = gtk_type_new (gtk_dial_get_type ());
849
850  if (!adjustment)
851    adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
852
853  gtk_dial_set_adjustment (dial, adjustment);
854
855  return GTK_WIDGET (dial);
856}
857
858static void
859gtk_dial_destroy (GtkObject *object)
860{
861  GtkDial *dial;
862
863  g_return_if_fail (object != NULL);
864  g_return_if_fail (GTK_IS_DIAL (object));
865
866  dial = GTK_DIAL (object);
867
868  if (dial->adjustment)
869    gtk_object_unref (GTK_OBJECT (dial->adjustment));
870
871  if (GTK_OBJECT_CLASS (parent_class)->destroy)
872    (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
873}
874</PRE>
875</CODE></BLOCKQUOTE>
876<P>Notez que cette fonction <EM>init()</EM> fait moins de choses que pour le widget
877Tictactoe car ce n'est pas un widget composé et que la fonction <EM>new()</EM> en
878fait plus car elle a maintenant un paramètre. Notez aussi que lorsque nous
879stockons un pointeur vers l'objet Adjustement, nous incrémentons son nombre de
880références (et nous le décrémentons lorsque nous ne l'utilisons plus) afin que
881GTK puisse savoir quand il pourra être détruit sans danger.
882<P>
883<P>Il y a aussi quelques fonctions pour manipuler les options du widget&nbsp;:
884<P>
885<BLOCKQUOTE><CODE>
886<PRE>
887GtkAdjustment*
888gtk_dial_get_adjustment (GtkDial *dial)
889{
890  g_return_val_if_fail (dial != NULL, NULL);
891  g_return_val_if_fail (GTK_IS_DIAL (dial), NULL);
892
893  return dial->adjustment;
894}
895
896void
897gtk_dial_set_update_policy (GtkDial      *dial,
898                             GtkUpdateType  policy)
899{
900  g_return_if_fail (dial != NULL);
901  g_return_if_fail (GTK_IS_DIAL (dial));
902
903  dial->policy = policy;
904}
905
906void
907gtk_dial_set_adjustment (GtkDial      *dial,
908                          GtkAdjustment *adjustment)
909{
910  g_return_if_fail (dial != NULL);
911  g_return_if_fail (GTK_IS_DIAL (dial));
912
913  if (dial->adjustment)
914    {
915      gtk_signal_disconnect_by_data (GTK_OBJECT (dial->adjustment), (gpointer) dial);
916      gtk_object_unref (GTK_OBJECT (dial->adjustment));
917    }
918
919  dial->adjustment = adjustment;
920  gtk_object_ref (GTK_OBJECT (dial->adjustment));
921
922  gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
923                      (GtkSignalFunc) gtk_dial_adjustment_changed,
924                      (gpointer) dial);
925  gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
926                      (GtkSignalFunc) gtk_dial_adjustment_value_changed,
927                      (gpointer) dial);
928
929  dial->old_value = adjustment->value;
930  dial->old_lower = adjustment->lower;
931  dial->old_upper = adjustment->upper;
932
933  gtk_dial_update (dial);
934}
935</PRE>
936</CODE></BLOCKQUOTE>
937<P>
938<H3><EM>gtk_dial_realize()</EM></H3>
939
940<P>Nous arrivons maintenant à quelques nouveaux types de fonctions. D'abord, nous
941avons une fonction qui réalise la création de la fenêtre X. Notez que l'on
942passe un masque à la fonction <EM>gdk_window_new()</EM> pour spécifier quels sont
943les champs de la structure GdkWindowAttr qui contiennent des données (les
944autres recevront des valeurs par défaut). Notez aussi la façon dont est créé le
945masque d'événement du widget. On appelle <EM>gtk_widget_get_events()</EM> pour
946récupérer le masque d'événement que l'utilisateur a spécifié pour ce widget
947(avec <EM>gtk_widget_set_events()</EM>) et ajouter les événements qui nous
948intéressent.
949<P>
950<P>Après avoir créé la fenêtre, nous configurons son style et son fond et mettons
951un pointeur vers le widget dans le champ user de la GdkWindow. Cette dernière
952étape permet à GTK de distribuer les événements pour cette fenêtre au widget
953correct.
954<P>
955<BLOCKQUOTE><CODE>
956<PRE>
957static void
958gtk_dial_realize (GtkWidget *widget)
959{
960  GtkDial *dial;
961  GdkWindowAttr attributes;
962  gint attributes_mask;
963
964  g_return_if_fail (widget != NULL);
965  g_return_if_fail (GTK_IS_DIAL (widget));
966
967  GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
968  dial = GTK_DIAL (widget);
969
970  attributes.x = widget->allocation.x;
971  attributes.y = widget->allocation.y;
972  attributes.width = widget->allocation.width;
973  attributes.height = widget->allocation.height;
974  attributes.wclass = GDK_INPUT_OUTPUT;
975  attributes.window_type = GDK_WINDOW_CHILD;
976  attributes.event_mask = gtk_widget_get_events (widget) |
977    GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
978    GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK |
979    GDK_POINTER_MOTION_HINT_MASK;
980  attributes.visual = gtk_widget_get_visual (widget);
981  attributes.colormap = gtk_widget_get_colormap (widget);
982
983  attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
984  widget->window = gdk_window_new (widget->parent->window, &amp;attributes, attributes_mask);
985
986  widget->style = gtk_style_attach (widget->style, widget->window);
987
988  gdk_window_set_user_data (widget->window, widget);
989
990  gtk_style_set_background (widget->style, widget->window, GTK_STATE_ACTIVE);
991}
992</PRE>
993</CODE></BLOCKQUOTE>
994<P>
995<H3>Négotiation de la taille</H3>
996
997<P>Avant le premier affichage de la fenêtre contenant un widget et à chaque fois
998que la forme de la fenêtre change, GTK demande à chaque widget fils la taille
999qu'il désire avoir. Cette requête est gérée par la fonction
1000<EM>gtk_dial_size_request()</EM>. Comme notre widget n'est pas un widget container,
1001et n'a pas de contraintes réelles sur sa taille, nous ne faisons que retourner
1002une valeur raisonnable par défaut.
1003<P>
1004<BLOCKQUOTE><CODE>
1005<PRE>
1006static void
1007gtk_dial_size_request (GtkWidget      *widget,
1008                       GtkRequisition *requisition)
1009{
1010  requisition->width = DIAL_DEFAULT_SIZE;
1011  requisition->height = DIAL_DEFAULT_SIZE;
1012}
1013</PRE>
1014</CODE></BLOCKQUOTE>
1015<P>
1016<P>Lorsque tous les widgets on demandé une taille idéale, le forme de la fenêtre
1017est calculée et chaque widget fils est averti de sa taille. Habituellement, ce
1018sera autant que la taille requise, mais si, par exemple, l'utilisateur a
1019redimensionné la fenêtre, cette taille peut occasionnellement être plus petite
1020que la taille requise. La notification de la taille est gérée par la fonction
1021<EM>gtk_dial_size_allocate()</EM>. Notez qu'en même temps qu'elle calcule les
1022tailles de certains composants pour une utilisation future, cette routine fait
1023aussi le travail de base consistant à déplacer les widgets X Window dans leur
1024nouvelles positions et tailles.
1025<P>
1026<BLOCKQUOTE><CODE>
1027<PRE>
1028static void
1029gtk_dial_size_allocate (GtkWidget     *widget,
1030                        GtkAllocation *allocation)
1031{
1032  GtkDial *dial;
1033
1034  g_return_if_fail (widget != NULL);
1035  g_return_if_fail (GTK_IS_DIAL (widget));
1036  g_return_if_fail (allocation != NULL);
1037
1038  widget->allocation = *allocation;
1039  if (GTK_WIDGET_REALIZED (widget))
1040    {
1041      dial = GTK_DIAL (widget);
1042
1043      gdk_window_move_resize (widget->window,
1044                              allocation->x, allocation->y,
1045                              allocation->width, allocation->height);
1046
1047      dial->radius = MAX(allocation->width,allocation->height) * 0.45;
1048      dial->pointer_width = dial->radius / 5;
1049    }
1050}
1051</PRE>
1052</CODE></BLOCKQUOTE>
1053.
1054<P>
1055<H3><EM>gtk_dial_expose()</EM></H3>
1056
1057<P>Comme cela est mentionné plus haut, tout le dessin de ce widget est réalisé
1058dans le gestionnaire pour les événements d'exposition. Il n'y a pas grand chose
1059de plus à dire là dessus, sauf constater l'utilisation de la fonction
1060<EM>gtk_draw_polygon</EM> pour dessiner le pointeur avec une forme en trois
1061dimensions selon les couleurs stockées dans le style du widget.
1062style.
1063<P>
1064<BLOCKQUOTE><CODE>
1065<PRE>
1066static gint
1067gtk_dial_expose (GtkWidget      *widget,
1068                 GdkEventExpose *event)
1069{
1070  GtkDial *dial;
1071  GdkPoint points[3];
1072  gdouble s,c;
1073  gdouble theta;
1074  gint xc, yc;
1075  gint tick_length;
1076  gint i;
1077
1078  g_return_val_if_fail (widget != NULL, FALSE);
1079  g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
1080  g_return_val_if_fail (event != NULL, FALSE);
1081
1082  if (event->count > 0)
1083    return FALSE;
1084 
1085  dial = GTK_DIAL (widget);
1086
1087  gdk_window_clear_area (widget->window,
1088                         0, 0,
1089                         widget->allocation.width,
1090                         widget->allocation.height);
1091
1092  xc = widget->allocation.width/2;
1093  yc = widget->allocation.height/2;
1094
1095  /* Draw ticks */
1096
1097  for (i=0; i&lt;25; i++)
1098    {
1099      theta = (i*M_PI/18. - M_PI/6.);
1100      s = sin(theta);
1101      c = cos(theta);
1102
1103      tick_length = (i%6 == 0) ? dial->pointer_width : dial->pointer_width/2;
1104     
1105      gdk_draw_line (widget->window,
1106                     widget->style->fg_gc[widget->state],
1107                     xc + c*(dial->radius - tick_length),
1108                     yc - s*(dial->radius - tick_length),
1109                     xc + c*dial->radius,
1110                     yc - s*dial->radius);
1111    }
1112
1113  /* Draw pointer */
1114
1115  s = sin(dial->angle);
1116  c = cos(dial->angle);
1117
1118
1119  points[0].x = xc + s*dial->pointer_width/2;
1120  points[0].y = yc + c*dial->pointer_width/2;
1121  points[1].x = xc + c*dial->radius;
1122  points[1].y = yc - s*dial->radius;
1123  points[2].x = xc - s*dial->pointer_width/2;
1124  points[2].y = yc - c*dial->pointer_width/2;
1125
1126  gtk_draw_polygon (widget->style,
1127                    widget->window,
1128                    GTK_STATE_NORMAL,
1129                    GTK_SHADOW_OUT,
1130                    points, 3,
1131                    TRUE);
1132 
1133  return FALSE;
1134}
1135</PRE>
1136</CODE></BLOCKQUOTE>
1137<P>
1138<H3>Gestion des événements</H3>
1139
1140<P>Le reste du code du widget gère différents types d'événements et n'est pas
1141trop différent de ce que l'on trouve dans la plupart des applications GTK. Deux
1142types d'événements peuvent survenir -- l'utilisateur peut cliquer sur le widget
1143avec la souris et faire glisser pour déplacer le pointeur, ou bien la valeur de
1144l'objet Adjustment peut changer à cause d'une circonstance extérieure.
1145<P>
1146<P>Lorsque l'utilisateur clique sur le widget, on vérifie si le clic s'est bien
1147passé près du pointeur et si c'est le cas, on stocke alors le bouton avec
1148lequel l'utilisateur a cliqué dans le champ <EM>button</EM> de la structure du
1149widget et on récupère tous les événements souris avec un appel à
1150<EM>gtk_grab_add()</EM>. Un déplacement ultérieur de la souris provoque le recalcul
1151de la valeur de contrôle (par la fonction <EM>gtk_dial_update_mouse</EM>). Selon la
1152politique qui a été choisie, les événements "value_changed" sont, soit générés
1153instantanément (<EM>GTK_UPDATE_CONTINUOUS</EM>), après un délai ajouté au timer
1154avec <EM>gtk_timeout_add()</EM> (<EM>GTK_UPDATE_DELAYED</EM>), ou seulement lorsque le
1155bouton est relâché (<EM>GTK_UPDATE_DISCONTINUOUS</EM>).
1156<P>
1157<BLOCKQUOTE><CODE>
1158<PRE>
1159static gint
1160gtk_dial_button_press (GtkWidget      *widget,
1161                       GdkEventButton *event)
1162{
1163  GtkDial *dial;
1164  gint dx, dy;
1165  double s, c;
1166  double d_parallel;
1167  double d_perpendicular;
1168
1169  g_return_val_if_fail (widget != NULL, FALSE);
1170  g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
1171  g_return_val_if_fail (event != NULL, FALSE);
1172
1173  dial = GTK_DIAL (widget);
1174
1175  /* Détermine si le bouton pressé est dans la région du pointeur.
1176     On fait cela en calculant les distances parallèle et perpendiculaire
1177     du point où la souris a été pressée par rapport à la ligne passant
1178     par le pointeur */
1179 
1180  dx = event->x - widget->allocation.width / 2;
1181  dy = widget->allocation.height / 2 - event->y;
1182 
1183  s = sin(dial->angle);
1184  c = cos(dial->angle);
1185 
1186  d_parallel = s*dy + c*dx;
1187  d_perpendicular = fabs(s*dx - c*dy);
1188 
1189  if (!dial->button &amp;&amp;
1190      (d_perpendicular &lt; dial->pointer_width/2) &amp;&amp;
1191      (d_parallel > - dial->pointer_width))
1192    {
1193      gtk_grab_add (widget);
1194
1195      dial->button = event->button;
1196
1197      gtk_dial_update_mouse (dial, event->x, event->y);
1198    }
1199
1200  return FALSE;
1201}
1202
1203static gint
1204gtk_dial_button_release (GtkWidget      *widget,
1205                          GdkEventButton *event)
1206{
1207  GtkDial *dial;
1208
1209  g_return_val_if_fail (widget != NULL, FALSE);
1210  g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
1211  g_return_val_if_fail (event != NULL, FALSE);
1212
1213  dial = GTK_DIAL (widget);
1214
1215  if (dial->button == event->button)
1216    {
1217      gtk_grab_remove (widget);
1218
1219      dial->button = 0;
1220
1221      if (dial->policy == GTK_UPDATE_DELAYED)
1222        gtk_timeout_remove (dial->timer);
1223     
1224      if ((dial->policy != GTK_UPDATE_CONTINUOUS) &amp;&amp;
1225          (dial->old_value != dial->adjustment->value))
1226        gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
1227    }
1228
1229  return FALSE;
1230}
1231
1232static gint
1233gtk_dial_motion_notify (GtkWidget      *widget,
1234                         GdkEventMotion *event)
1235{
1236  GtkDial *dial;
1237  GdkModifierType mods;
1238  gint x, y, mask;
1239
1240  g_return_val_if_fail (widget != NULL, FALSE);
1241  g_return_val_if_fail (GTK_IS_DIAL (widget), FALSE);
1242  g_return_val_if_fail (event != NULL, FALSE);
1243
1244  dial = GTK_DIAL (widget);
1245
1246  if (dial->button != 0)
1247    {
1248      x = event->x;
1249      y = event->y;
1250
1251      if (event->is_hint || (event->window != widget->window))
1252        gdk_window_get_pointer (widget->window, &amp;x, &amp;y, &amp;mods);
1253
1254      switch (dial->button)
1255        {
1256        case 1:
1257          mask = GDK_BUTTON1_MASK;
1258          break;
1259        case 2:
1260          mask = GDK_BUTTON2_MASK;
1261          break;
1262        case 3:
1263          mask = GDK_BUTTON3_MASK;
1264          break;
1265        default:
1266          mask = 0;
1267          break;
1268        }
1269
1270      if (mods &amp; mask)
1271        gtk_dial_update_mouse (dial, x,y);
1272    }
1273
1274  return FALSE;
1275}
1276
1277static gint
1278gtk_dial_timer (GtkDial *dial)
1279{
1280  g_return_val_if_fail (dial != NULL, FALSE);
1281  g_return_val_if_fail (GTK_IS_DIAL (dial), FALSE);
1282
1283  if (dial->policy == GTK_UPDATE_DELAYED)
1284    gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
1285
1286  return FALSE;
1287}
1288
1289static void
1290gtk_dial_update_mouse (GtkDial *dial, gint x, gint y)
1291{
1292  gint xc, yc;
1293  gfloat old_value;
1294
1295  g_return_if_fail (dial != NULL);
1296  g_return_if_fail (GTK_IS_DIAL (dial));
1297
1298  xc = GTK_WIDGET(dial)->allocation.width / 2;
1299  yc = GTK_WIDGET(dial)->allocation.height / 2;
1300
1301  old_value = dial->adjustment->value;
1302  dial->angle = atan2(yc-y, x-xc);
1303
1304  if (dial->angle &lt; -M_PI/2.)
1305    dial->angle += 2*M_PI;
1306
1307  if (dial->angle &lt; -M_PI/6)
1308    dial->angle = -M_PI/6;
1309
1310  if (dial->angle > 7.*M_PI/6.)
1311    dial->angle = 7.*M_PI/6.;
1312
1313  dial->adjustment->value = dial->adjustment->lower + (7.*M_PI/6 - dial->angle) *
1314    (dial->adjustment->upper - dial->adjustment->lower) / (4.*M_PI/3.);
1315
1316  if (dial->adjustment->value != old_value)
1317    {
1318      if (dial->policy == GTK_UPDATE_CONTINUOUS)
1319        {
1320          gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
1321        }
1322      else
1323        {
1324          gtk_widget_draw (GTK_WIDGET(dial), NULL);
1325
1326          if (dial->policy == GTK_UPDATE_DELAYED)
1327            {
1328              if (dial->timer)
1329                gtk_timeout_remove (dial->timer);
1330
1331              dial->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH,
1332                                             (GtkFunction) gtk_dial_timer,
1333                                             (gpointer) dial);
1334            }
1335        }
1336    }
1337}
1338</PRE>
1339</CODE></BLOCKQUOTE>
1340<P>
1341<P>Les changements de l'Adjustement par des moyens extérieurs sont communiqués à
1342notre widget par les signaux "changed" et "value_changed". Les gestionnaires
1343pour ces fonctions appellent <EM>gtk_dial_update()</EM> pour valider les
1344paramètres, calculer le nouvel angle du pointeur et redessiner le widget (en
1345appelant <EM>gtk_widget_draw()</EM>).
1346<P>
1347<BLOCKQUOTE><CODE>
1348<PRE>
1349static void
1350gtk_dial_update (GtkDial *dial)
1351{
1352  gfloat new_value;
1353 
1354  g_return_if_fail (dial != NULL);
1355  g_return_if_fail (GTK_IS_DIAL (dial));
1356
1357  new_value = dial->adjustment->value;
1358 
1359  if (new_value &lt; dial->adjustment->lower)
1360    new_value = dial->adjustment->lower;
1361
1362  if (new_value > dial->adjustment->upper)
1363    new_value = dial->adjustment->upper;
1364
1365  if (new_value != dial->adjustment->value)
1366    {
1367      dial->adjustment->value = new_value;
1368      gtk_signal_emit_by_name (GTK_OBJECT (dial->adjustment), "value_changed");
1369    }
1370
1371  dial->angle = 7.*M_PI/6. - (new_value - dial->adjustment->lower) * 4.*M_PI/3. /
1372    (dial->adjustment->upper - dial->adjustment->lower);
1373
1374  gtk_widget_draw (GTK_WIDGET(dial), NULL);
1375}
1376
1377static void
1378gtk_dial_adjustment_changed (GtkAdjustment *adjustment,
1379                              gpointer       data)
1380{
1381  GtkDial *dial;
1382
1383  g_return_if_fail (adjustment != NULL);
1384  g_return_if_fail (data != NULL);
1385
1386  dial = GTK_DIAL (data);
1387
1388  if ((dial->old_value != adjustment->value) ||
1389      (dial->old_lower != adjustment->lower) ||
1390      (dial->old_upper != adjustment->upper))
1391    {
1392      gtk_dial_update (dial);
1393
1394      dial->old_value = adjustment->value;
1395      dial->old_lower = adjustment->lower;
1396      dial->old_upper = adjustment->upper;
1397    }
1398}
1399
1400static void
1401gtk_dial_adjustment_value_changed (GtkAdjustment *adjustment,
1402                                    gpointer       data)
1403{
1404  GtkDial *dial;
1405
1406  g_return_if_fail (adjustment != NULL);
1407  g_return_if_fail (data != NULL);
1408
1409  dial = GTK_DIAL (data);
1410
1411  if (dial->old_value != adjustment->value)
1412    {
1413      gtk_dial_update (dial);
1414
1415      dial->old_value = adjustment->value;
1416    }
1417}
1418</PRE>
1419</CODE></BLOCKQUOTE>
1420<P>
1421<H3>Améliorations possibles</H3>
1422
1423<P>
1424<P>Le widget Dial décrit jusqu'à maintenant exécute à peu près 670 lignes de
1425code. Bien que cela puisse sembler beaucoup, nous en avons vraiment fait
1426beaucoup avec ce code, notamment parce que la majeure partie de cette longueur
1427est due aux en-têtes et à la préparation. Cependant, certaines améliorations
1428peuvent être apportées à ce widget&nbsp;:
1429<P>
1430<UL>
1431<LI> Si vous testez ce widget, vous vous apercevrez qu'il y a un peu de
1432scintillement lorsque le pointeur est déplacé. Ceci est dû au fait que le
1433widget entier est effacé, puis redessiné  à chaque mouvement du
1434pointeur. Souvent, la meilleure façon de gérer ce problème est de dessiner sur
1435un pixmap non affiché, puis de copier le résultat final sur l'écran en une
1436seule étape (le widget <EM>ProgressBar</EM> se dessine de cette façon).
1437</LI>
1438<LI> L'utilisateur devrait pouvoir utiliser les flèches du curseur vers le
1439haut et vers le bas pour incrémenter et décrémenter la valeur.
1440</LI>
1441<LI> Ce serait bien si le widget avait des boutons pour augmenter et diminuer
1442la valeur dans de petites ou de grosses proportions. Bien qu'il serait possible
1443d'utiliser les widgets <EM>Button</EM> pour cela, nous voudrions aussi que les
1444boutons s'auto-répètent lorsqu'ils sont maintenus appuyés, comme font les
1445flèches d'une barre de défilement. La majeure partie du code pour implanter ce
1446type de comportement peut se trouver dans le widget <EM>GtkRange</EM>.
1447</LI>
1448<LI> Le widget Dial pourrait être fait dans un widget container avec un seul
1449widget fils positionnée en bas, entre les boutons mentionnés
1450ci-dessus. L'utilisateur pourrait alors ajouter au choix, un widget label ou
1451entrée pour afficher la valeur courante de l'appel.
1452</LI>
1453</UL>
1454<P>
1455<H2><A NAME="ss19.5">19.5 En savoir plus</A>
1456</H2>
1457
1458<P>Seule une petite partie des nombreux détails de la création des widgets a pu
1459être décrite. Si vous désirez écrire vos propres widgets, la meilleure source
1460d'exemples est le source de GTK lui-même. Posez-vous quelques questions sur les
1461widgets que vous voulez écrire&nbsp;: est-ce un widget container ? possède-t-il
1462sa propre fenêtre ? est-ce une modification d'un widget existant ? Puis,
1463trouvez un widget identique et commencez à faire les modifications. Bonne
1464chance !
1465<P>
1466<HR NOSHADE>
1467<A HREF="gtk_tut_fr-20.html">Page suivante</A>
1468<A HREF="gtk_tut_fr-18.html">Page précédente</A>
1469<A HREF="gtk_tut_fr.html#toc19">Table des matières</A>
1470</BODY>
1471</HTML>
Note: See TracBrowser for help on using the repository browser.