Gtk


1. La boucle principale

Ce genre de bibliothèque exige une programmation essentiellement déclarative : on commence par dire à l'ordinateur de mettre des fenètres çà et là, de les peupler de telle ou telle manière ; puis on associe différentes actions (par exemple, quitter le programme, faire apparaitre une nouvelle fenètre, lancer de la musique, entreprendre un calcul, effectuer un dessin, etc.) à certains évènements (appuyer sur le bouton « quitter », bouger la souris, cliquer à un certain endroit, etc.) ; enfin, on informe l'ordinateur que cette partie descriptive est terminée et qu'il peut commencer à attendre les évènements. En ce qui concerne gtk, le programme minimal est le suivant.
  #include <gtk/gtk.h>
  main(int argc, char * argv[]){
    gtk_init(&argc,&argv);
    gtk_main();
  }
La fonction gtk_init se contente de regarder les options passées au programme, de prendre celles qui concernent gtk (par exemple, -display) et de contacter le serveur X. La fonction gtk_main est la boucle principale, dans laquelle on attend les évènements. Pour compiler ce programme, on peut utiliser la commande gtk-config qui connait les bonnes options à donner à gcc.
  gcc -o toto toto.c `gtk-config --cflags --libs`

2. Nos premiers widgets

On appelle widget tous les éléments graphiques d'une fenêtre : la fenêtre elle-même, les menus ou boutons qu'elle contient, le texte, les dessins, etc. Pour créer une fenètre, on procède ainsi.
  GtkWidget * w;
  w=gtk_window_new(GTK_WINDOW_TOPLEVEL);
  ...
  gtk_widget_show(w);
La commande gtk_widget_show demmande l'affichage d'un widget, quel qu'il soit. Si on l'oublie, rien n'apparaît.

On remarque que tous les widgets ont le même type, GtkWidget *. Néanmoins certaines commandes attendent un widget d'un type particulier. En C++, on ne se poserait pas de question, les mécanismes d'héritage s'occuperaient de tout. Mais nous sommes en C : il va falloir mettre des casts dans tous les sens. Gtk foirnit des macros à cet effet ; elles sont toutes semblables à l'exemple suivant.

  GTK_CONTAINER(w)
Il est maintenant temps de peupler notre fenêtre, disons, avec un bouton.
  GtkWidget * b;
  b=gtk_button_new_with_label("bouton 1");
  gtk_container_add(GTK_CONTAINER(w), b);
  gtk_widget_show(b);

3. D'autres widgets

Avant de passer à la suite (comment positionner les widgets les uns dans les autres aux endroites voulus, comment faire faire quelque chose à notre programme), nous présentons d'autres exemples de widgets, très simples.
  #include <gtk/gtk.h>

  main(int argc, char * argv[]){

    GtkWidget * w;
    GtkWidget * b;

    gtk_init(&argc, &argv);

    w=gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(w), "Coucou !");
    gtk_container_set_border_width(GTK_CONTAINER(w), 10);

    b=gtk_label_new("Essai");
    gtk_misc_set_alignment(GTK_MISC(b),0,0);
    gtk_container_add(GTK_CONTAINER(w), b);
    gtk_widget_show(b);

    b=gtk_hseparator_new();
    gtk_widget_set_usize(b,400,5);
    gtk_container_add(GTK_CONTAINER(w), b);
    gtk_widget_show(b);

    b=gtk_button_with_label("OK");
    gtk_container_add(GTK_CONTAINER(w), b);
    gtk_widget_show(b);

    gtk_widget_show(w);
    gtk_main();
  }

4. Positionnement des widgets

Dans les exemples précédents, l'ordinateur mettait les widgets les uns dans les autres un peu comme il pouvait, sans aucun contrôle de notre part. Pour imposer un agencement particulier, on peut utiliser des boites : dans une boite horizontale, les widgets vont se placer en ligne, de gauche à droite, dans une voite verticale, ils vont se placer en colonne, de haut en bas.
  GtkWidget * w;
  GtkWidget * box;
  GtkWidget * b;

  w=gtk_window_new(GTK_WINDOW_TOPLEVEL);

  box=gtk_hbox_new(FALSE,0);

  b=gtk_button_new_with_label("bouton 1");
  gtk_box_pack_start(GTK_BOX(box), b, TRUE, TRUE, 0);
  gtk_widget_show(b);

  b=gtk_button_new_with_label("bouton 2");
  gtk_box_pack_start(GTK_BOX(box), b, TRUE, TRUE, 0);
  gtk_widget_show(b);

  gtk_widget_show(box);
  gtk_widget_show(w);
  gtk_main();  
Le premier argument de la commande gtk_hbox_new dit si tous les widgets doivent avoir la même taille. Le second est l'espace entre deux widgets.

La commande gtk_box_pack_start met les widgets dans une boite (à gauche ou en haut, selon la nature de la boite). Son troisième argument (expand) dit s'il faut prendre toute la place disponible. Le quatrième argument (fill) dit s'il faut la prendre en augmentant la taille du widget. ou en rajoutant de l'espace sur ses côtés. Le dernier argument est l'espace à rajouter sur les côtés.

Il existe d'autres manières de positionner les widgets, en particulier les tables ; nous n'en parlerons pas ici.

5. Signaux et évèmenements

Jusqu'à présent, nos programmes ne faisaient absolument rien : s'il y avait des boutons, leur pression n'engendrait aucune action. Quand l'utilisateur fait quelque chose (par exemple, appuyer sur un bouton, cliquer n'importe où, bouger la souris, taper du texte, etc.), il déclenche un signal. on peut associer ce signal à une fonction, de la manière suivante.
  gtk_signal_connect( GTK_OBJECT(b),
                      "clicked",
                      GTK_SIGNAL_FUNC(affiche),
                      (gpointer) "Coucou !"
                    );
La fonction, quant à elle, sera défiunie ainsi.
  void affiche( GtkWidget * w, gpointer data ){
    printf("%s\n", (char *) data);
  }
Le second argument de la commande gtk_signal_connect est un signal envoyé par un widget. Le troisème argument est une fonction (convenablement castée) et le dernier argument sera passé en paramètre à la fonction (si on n'en a pas besoin, on peut mettre NULL).

Il est possible de desassocier une fonction du signal auquel elle a été préalablement associée en récupérant l'entier renvoyé par la commande gtk_signal_connect et en le donnat à la commande gtk_signal_disconnect.

  gint id;
  id = gtk_signal_connect(...);
  ...
  gtk_signal_disconnect( GTK_OBJECT(w), id );
Il est même possible de tout enlever.
  gtk_signal_handlers_destroy( GTK_OBJECT(w), id );

(Je déconseille de lire ce qui suit.)

Il y a une différence entre signal (envoyé par Gtk, spécifique à un widget, par exemple "cliquer" sur un bouton) et évènement (envoyé par le serveur X, d'un niveau plus bas). Les signaux correspondent à des évènements particuliers. Il est possible de remplacer le signal "clicked" dans l'exemple ci-dessus, mais la définition de la fonction affiche devra être changée.

  void affiche( GtkWidget * w,
                GdkEvent  * e,
                gpointer data
              )

il y a une autre commande, qui fait à peu près la même chose que gtk_signal_connect, mais qui appelle des fonctions avec un seul argument, le widget. [Je ne sais pas pourquoi il faut préciser le widget parent, w.]

  gtk_signal_connect_object( GTK_OBJECT(b),
                             "clicked",
                             GTK_SIGNAL_FUNC(affiche),
                             GTK_OBJECT(w)
                           );
  void affiche( GtkWidget * w ){
    printf("Coucou !\n");
  }

6. Remarques diverses, en vrac

Contrairement à d'autres bibliothèques (Tk), on ne gère pas séparément ce qui se passe horizontalement et verticalement (expand, fill, padding).

Il faut dire explicitement ce qu'il faut faire quand on reçoit le signel destry du gestionnaire de fenêtres.

C'est de la programmation orientée objets en C, l'héritage de fait avec des casts.

Il est (comme toujours) très pénible de mettre les widgets là où il faut. Y a-t-il un utilitaire graphique pour faire cela ?

Je n'ai pas parlé des fonctions gtk_main_quit() (pour quitter) et gtk_exit() (pour quitter violemment).

Je n'ai pas parlé des sélections.

Je n'ai pas parlé du fichier de configuration .gtkrc.

Pour les dessins : le widget drawing area est de beaucoup plus bas niveau que le canvas de Tk : par exemple, il faut s'occuper soi-même du double-buffering. De surcroit, il n'y a pas de documentation. J'ai l'impression qu'il n'y a pas de « tags » comme avec Tk.

Je n'ai pas regardé les interfaces en C++ et en Perl.

J'aurais pu parler des horloges. Pour appeler une fonction dans 300 ms :

  id=gtk_timeout_add(300, (GtkFunction)toto, NULL);
  gboolean toto(gpointer data){ ... return FALSE; }
pour l'appeler toutes les 300 ms :
  id=gtk_timeout_add(300, (GtkFunction)toto, NULL);
  gboolean toto(gpointer data){ ... return TRUE; }
pour desactiver une telle fonction :
  gtk_timeout_remove(id);

Vincent Zoonekynd (zoonek@math.jussieu.fr)
(Avril 1999) Dernière modification le 21 avril 1999.