[couverture de Linux Magazine 63]

Création d'interface utilisateur avec Perl et GTK (1)

Article publié dans Linux Magazine 63, juillet/août 2004.

Copyright © 2004 - David Elbaz.

[+ del.icio.us] [+ Developers Zone] [+ Bookmarks.fr] [Digg this] [+ My Yahoo!]

Chapeau

Même après l'article précédent sur Tk, même après la longue série d'articles des mongueurs déjà derrière nous, je vois d'ici la réaction de certains, toujours empreints de leur première impression sur Perl, se dire : « Et bien ! Tout ça rien que pour faire du CGI ?! ».

Non, non et re-non ;-). Perl ne sert pas qu'à faire du web (même s'il le fait très bien comme nous vous le montrerons), il a tout à fait sa place dans un grand nombre de champs d'application de l'informatique et notamment dans celui de l'interface utilisateur.

Ainsi pour enfoncer le clou, après une présentation du vénérable Tk, nous vous proposons de jeter un œil du côté du toolkit utilisé dans (à peu près) la moitié des environnements de bureau sous Linux.

Cette présentation s'étendra sur deux articles afin de fluidifier la lecture et de faciliter la compréhension.

Introduction

GTK+, et plus précisément le binding[1] Gtk-Perl, est un sujet bien trop vaste pour ambitionner de vous le présenter de façon complète. Cette bibliothèque est à l'image de certains logiciels avancés (libres ou non) où l'utilisateur moyen n'utilise que 10% des fonctionnalités. Cependant, d'une part GTK+ est essentiellement basé, du point de vue de l'utilisateur, sur deux principes : la façon dont sont empaquetés les widgets dans l'interface, et les signaux GTK+ (et événements X). D'autre part, il y a (nécessairement) une certaine redondance dans la nature des composants d'une telle bibliothèque, a fortiori dans sa documentation et autres éléments de compréhension. Il est donc possible de vous fournir des règles de base pour la maîtrise de cet outil, des bases solides qui vous permettront de progresser rapidement au rythme de vos besoins.

Vous avez l'eau à la bouche ? Alors, à table !

Présentation de GTK+

Note: GTK+ est une version plus avancée de la boîte à outils initiale GTK. Pour des questions de convenances et parce qu'aujourd'hui tout le monde fait ainsi, lorsque GTK sera mentionné dans cet article, il s'agira en fait, sauf note explicite, de GTK+.

Mouvement perpétuel

GTK est un triple acronyme récursif, l'acronyme le plus profond étant récursif à l'infini.

GTK signifie GIMP Tool Kit (la boite à outils de GIMP) et GIMP signifie GNU Image Manipulation Program (le programme de manipulation d'image GNU). Pour finir, GNU (je vous fais l'affront de vous expliquer que cela) signifie GNU's Not Unix (GNU n'est pas Unix).

Ce dernier bouclant à l'infini sur le G, la boucle est bouclée.

Histoire de famille

Vous l'avez compris, GTK fait donc partie du projet GNU et lui a offert une contribution de taille, puisque l'environnement de bureau (desktop environment) GNOME est un rejeton de GTK (et donc lui aussi un acronyme triplement récursif ;-)) : GNU Network Object Model Environment (je ne vous traduis pas celui-là, tout le monde s'accorde sur le fait qu'il ne veut rien dire...).

GTK a été créé en 1996 lorsque Peter Mattis et Spencer Kimball (deux étudiants de Berkeley) en ont eu assez d'utiliser Motif pour leur programme de manipulation d'image (GIMP) et (excusez du peu) ont décidé de créer leur propre toolkit.

Autant que le programme GIMP, le toolkit connut un franc succès et prit de l'ampleur. Son développement fut dissocié de celui du GIMP, la somme de travail nécessitant deux équipes distinctes. L'intérêt pour ces projets était tel que lorsque les initiateurs, Mattis et Kimball, partirent sans organiser leur succession, les projets restèrent vivaces et purent repartir de plus belle après une phase de réorganisation.

Aujourd'hui, GTK est l'œuvre de beaucoup de gens, même si on peut citer Owen Taylor et Tim Janik pour leur rôle prépondérant.

Portée et portabilité

GTK a été initialement développé par des étudiants de Berkeley pour leur projet de fin d'étude, inutile de vous préciser sur quels systèmes la boîte à outils a été développée et était censée tourner. Son API est orientée objet mais son langage natif est le C. Cette combinaison peu banale de technologies a facilité le développement de nombreux bindings dans toutes sortes de langages (Ada, C++, Java, PHP, Python).

GTK est arrivé à maturité assez rapidement et a été presqu'immédiatement utilisé par de nombreux acteurs majeurs du monde Linux. Citons en particulier MandrakeSoft qui a choisi GTK (et même Gtk-Perl !) notamment pour la création d'outils dit user-friendly de configuration et d'administration, ainsi que pour son installation graphique.

Cependant, le toolkit est resté cantonné pendant longtemps au monde Unix. C'était probablement son défaut majeur.

La levée de cette limitation était un des buts que s'était fixés l'équipe de développement de GTK 2.0 et c'est chose faite.

Certes, son utilisation, de l'avis de tous, n'est pas encore des plus aisées, mais elle est possible et des distributions sont disponibles.

A noter qu'auparavant, GTK était utilisable sous Windows par l'intermédiaire de Cygwin, mais cela ne pouvait être qu'une solution de remplacement.

Gtk-Perl

C'est Kenneth Albanowski qui le premier a eu la bonne idée d'associer Perl et GTK en 1997. Il lance le projet et le maintient jusqu'en 2000, date où Paolo Molaro le reprend. Paolo ouvre un site web http://www.gtkperl.org, une liste de diffusion, il complète le portage et l'amène jusqu'à la version 0.7008. Et puis plus rien... Plus rien pendant plus d'un an, pas même un signe de vie, encore à ce jour. Là aussi, comme dans l'histoire de GTK, c'est la réactivité de notre (belle) communauté qui a relevé à bout de bras un projet condamné puisqu'il n'évoluait plus.

Deux volontés se sont manifestées presqu'en même temps, celle de Goran Thyni et celle de MandrakeSoft (qui avait besoin de faire évoluer ses outils de configuration) en la personne de Guillaume Cottenceau. Il y avait quelques divergences mais le bon esprit a prévalu et tous les deux ont concouru très efficacement à la production d'une version stable et complète du binding. Après une succession de chefs de projet et de collaborateurs, ce sont muppet et Ross MacFarland qui maintiennent le projet, poursuivent le développement et la production de documentation. A noter l'excellente tenue de la documentation pour cette version du binding, disponible en ligne ou sous forme de page de man.

Au final, la Gtk2-Perl Team nous a produit un bien bel outil qui nous ouvre enfin la voie du développement d'applications portables avec GTK (allié à Perl notamment).

Gtk2-Perl

La nouvelle version du binding s'appelle en fait Gtk2-Perl et non pas Gtk-Perl 2.0. C'est d'abord pour suivre les conventions de nommage de GTK lui-même, mais il y a une autre raison. La version 2.0 du binding a été développée alors que la version antérieure était encore utilisée et surtout alors qu'on ne pouvait joindre le dernier chef de projet de Gtk-Perl, Paolo Molaro. Sans son accord, il était difficile pour une équipe qui s'était montée spontanément sur la liste de diffusion de déclarer obsolète la version en cours de Gtk-Perl. C'est au final la simple existence de Gtk2-Perl en tant qu'outil complet et fonctionnel qui a rendu Gtk-Perl obsolète.

La distribution Gtk2-Perl comprend un certain nombre de modules : le module Gtk2 qui fournit le gros des fonctionnalités de la boîte à outils, mais il y a aussi des utilitaires et des interfaces aux composants de plus bas niveau de la distribution Gtk2-Perl. Il y aussi des petites bibliothèques de plus haut niveau (que Gtk2) qui implémentent des approches simplifiées à des constructions, parfois complexes, comme les tableaux de valeurs.

Mais du point de vue de l'utilisateur de Gtk2-Perl, il convient d'abord (et surtout) de maîtriser les concepts de base du module Gtk2.

Et roulez jeunesse !

A présent, assez de mots, place au code et aux explications :). Les parties théoriques et les parties plus pratiques de la présentation de Gtk2 s'entremêlant de façon inextricable, nous allons traiter tout cela de front. Les explications viendront au fur et à mesure des besoins et de l'utilisation des fonctionnalités de Gtk2-Perl.

Ces besoins vont être ceux d'un projet en constante évolution depuis Linux Mag n° 59 ;-)... Nos lecteurs assidus se souviendront de mon association qui a pour but de restaurer un castel gascon (Linux Mag n°60 et 61). Dans le cadre de cette activité associative, avait été développé un script qui permettait d'envoyer un courriel personnalisé aux adhérents. Le président de l'association, dans la continuité de cet outil, aurait besoin d'un programme qui lui permettrait d'envoyer des courriels à plusieurs personnes mais de façon plus sélective (pour un envoi de factures, de récépissés ou tout autre document, à certaines personnes uniquement).

Nous allons donc commencer par voir dans cet article comment jeter les bases de cette interface d'administration. La présentation des données dans un tableau ainsi que le module d'envoi de courriel viendront plus tard.

Premier pas

Sous Linux, lorsque vous voulez coder une application graphique, la première chose à faire est d'obtenir de votre gestionnaire de fenêtre qu'il vous alloue une fenêtre. Rassurez-vous, vous n'aurez pas à faire cela vous-mêmes, la chose est tout à fait transparente avec l'utilisation de GTK (qui elle-même s'en remet à la bibliothèque GDK[2] pour ce genre de choses).

En C, le langage natif de GTK, la chose se ferait ainsi :

    #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;
     }

Alors qu'en Perl, on aurait :

    #!/usr/bin/perl -w

    use Gtk2 '-init';

    $window = Gtk2::Window->new('toplevel');
    $window->show();

    Gtk2->main();

[Figure 1]

Le premier point à évoquer à propos de cet embryon d'application vient de la comparaison avec le code en C. Vous vous attendez à ce que je vous dise que Perl est plus joli, concis, explicite. Oui, je le dis ;-) D'autant plus qu'il ne s'agit là que de quelques lignes, imaginez les dizaines de milliers de lignes des outils de configuration de MandrakeSoft...

Mais le plus important à noter, plus que les différences, ce sont les similitudes.

La Gtk2-Perl team a vraiment fait un travail remarquable : la syntaxe de Gtk-Perl est on ne peut plus perlienne mais reste malgré tout proche du C. Il est assez aisé de reconnaître la fonction C utilisée à partir d'un code en Perl. Ainsi, la documentation GTK, qui reste la référence, est tout à fait utilisable, en plus de celle de Gtk2-Perl.

Mais, assez parlé du C, voyons plutôt ce que Perl fait avec ce petit bout de code.

La première ligne est classique, elle appelle l'interpréteur Perl avec l'option « avertissement » (-w pour warnings). La ligne suivante charge le module Gtk2 et importe la méthode init() (qui initialise les différentes bibliothèques, c'est indispensable). A noter que les personnes familières avec Gtk-Perl (la précédente version) se rappelleront qu'il était d'usage d'appeler Gtk seul et de rajouter la ligne

    Gtk->init();  # ou encore 'init Gtk;'

Les deux façons de procéder étaient équivalentes avec le module Gtk et le sont toujours avec Gtk2 (mais c'est cette syntaxe qui est préférée par les auteurs de Gtk2).

La ligne suivante crée une fenêtre avec l'option toplevel qui indique qu'elle n'est la fille d'aucun autre widget. Cette ligne est évidente pour les personnes familières avec la programmation orientée objet : on ne fait qu'y appeler le constructeur (la méthode new()) du package Gtk2::Window. La méthode nous renvoie une référence à un hachage qui représentera la fenêtre du point de vue programmatique et nous permettra d'appeler les méthodes ad hoc par la suite.

C'est de cette façon que seront créés tous les widgets, chacun avec ses options spécifiques le cas échéant, mais le cadre générique reste le même.

Par exemple, pour des bulles d'aide :

    my $tip = Gtk2::Tooltips->new();

Ou encore pour une fenêtre de dialogue :

    my $dialog = Gtk2::Dialog->new();

La troisième ligne du bout de code présenté plus haut est indispensable : vous pourrez créer la plus parfaite des interfaces graphiques, si vous ne lui dites pas de s'afficher à l'écran, elle existera du point de vue de votre programme mais restera invisible. A ce sujet, il est conseillé de faire appeler la méthode show() par tous les widgets de l'interface et de ne le faire avec la fenêtre principale de l'application qu'au tout dernier moment. L'interface apparaît alors d'un coup, le rendu est plus homogène. Si cet effet ne vous importe pas et qu'au contraire vous souhaitez vous éviter la tâche redondante d'appeler la méthode show() pour chacun des widgets, il est possible de faire autrement.

La méthode show_all() vous permet de montrer (show en anglais) tous les widgets contenus dans celui qui appelle la méthode au moment où elle est appelée. Autrement dit, appeler cette méthode en tout dernier dans votre programme (en fait juste avant la quatrième ligne du code montré plus haut) par la fenêtre principale revient à le faire pour tous les widgets de cette fenêtre (et donc souvent de l'application tout entière).

A noter que la méthode show() est une méthode du package Gtk2::Widget. De cette façon, tous les widgets Gtk2 (amenés à être visibles) héritent de cette classe et par là-même de cette méthode.

Enfin, la dernière ligne de ce premier script d'exemple l'amène dans la boucle GTK. C'est « à cet endroit » que GTK va attendre que l'utilisateur se manifeste. Cette ligne est bien entendu obligatoire mais l'oublier ne provoquera pas d'erreurs : le programme se terminera simplement. De la même façon, il faut que le script puisse, au cours de son exécution, revenir à cette boucle. Si dans le cadre de la gestion d'une action de l'utilisateur, la réponse du programme (erreur, défaut de conception) est trop longue, le programme ne répondra plus et l'interface ne se rafraîchira plus.

Les événements X et signaux GTK

Nous avons donc un embryon d'interface qui pour le moment se borne à afficher une fenêtre vide de 200 pixels de côté (taille par défaut) et à attendre que l'utilisateur se manifeste. En théorie... Car non seulement cette interface n'est pas très utile en l'état, mais elle est complètement incapable de réagir si justement quelque chose se passe. Y compris même si la chose qui se produit est la fermeture de la fenêtre principale (avec un clic sur la croix en haut à droite de la fenêtre, généralement).

En effet, en l'état actuel des choses, même avec la disparition de la fenêtre principale (signe pour l'utilisateur que l'application est fermée), l'application continue son exécution et retourne à la boucle principale GTK (GTK main (loop)). D'évidence, en l'absence de tout widget à l'écran, il est impossible désormais d'interagir avec l'application. Mais le fait que l'application retourne à la boucle principale, inutilement donc, est votre problème (à vous développeur), pas celui de GTK.

Il faut donc expliquer noir sur blanc dans votre programme ce que vous voulez qu'il se passe lorsque l'utilisateur clique sur (généralement) la croix en haut à droite pour fermer l'application. C'est évidemment le cas pour quelque action de l'utilisateur que ce soit : un clic sur un bouton, un choix dans un menu d'option, la sélection d'un rang dans un tableau de valeur. Tout doit être prévu et décrit.

Pour ce faire, GTK a implémenté son propre mécanisme de signaux qui vient compléter celui des événements X (quand, sous Linux notamment, c'est la Xlib qui est en dessous du gestionnaire de fenêtre). Ce mécanisme va associer un widget à un signal et une fonction de rappel (et éventuellement un paramètre additionnel).

L'association est faite simplement par l'appel d'une méthode accessible à tous les widgets : la méthode signal_connect().

    $widget->signal_connect('signal', 'rappel', $param);

Le signal

Le paramètre signal est le signal ou l'événement X dont on attend l'émission. Les signaux sont très nombreux, certains sont communs à tous les widgets, beaucoup d'autres sont spécifiques à des widgets ou des familles de widgets. Référez-vous à la documentation de chaque widget (et celle de ses parents), vous obtiendrez là une liste exhaustive des signaux disponibles pour chaque widget. GTK a même prévu de pouvoir émettre les signaux les plus courants par le biais de méthodes, là aussi communes ou particulières à chaque widget. Nous avons déjà utilisé une telle méthode dans notre script puisque la méthode show() est en fait l'envoi d'un signal au widget lui disant d'apparaître à l'écran. Il en va de même avec activate() pour un élément de menu ou clicked pour un bouton. Les événements X sont en fait des signaux envoyés par le gestionnaire de fenêtre à l'application. Le contour d'une application, sa barre de titre, les boutons pour tuer la fenêtre ou l'icônifier ne font pas partie de l'application mais bien de l'environnement du gestionnaire de fenêtres. Ainsi toute action de l'utilisateur sur l'application, via un de ces boutons, sera transmise à cette dernière via les événements X.

La fonction de rappel

Le paramètre rappel du synopsis indique l'action à effectuer lorsque le bon signal est reçu par le bon widget. Cette action est une routine et elle peut être désignée de trois façons.

La donnée additionnelle

Le paramètre $param est tout simplement l'éventuelle donnée additionnelle passée en argument de la fonction de rappel. Gtk2-Perl ne permet (à la différence de Gtk-Perl) de ne passer qu'un seul argument. Autrement dit, si vous voulez passer plus d'un argument à la fonction de rappel, il vous faut recourir aux références.

Différence entre signal et événement

La connexion d'un signal ou d'un événement à une fonction de rappel se fera pareillement avec la méthode signal_connect(). La différence ne se verra qu'au sein de la fonction de rappel qui recevra des paramètres différents suivant qu'il s'agit de l'un ou de l'autre.

Dans le cas d'un signal, la routine recevra le widget et la donnée additionnelle alors que dans le cas d'un événement X, sera intercalée entre le widget et la donnée additionnelle une référence à un hachage.

Les éléments de cette structure de données (le hachage), possiblement différents d'un événement à l'autre, permettront de définir l'événement en question. À noter le champ type qui renseigne sur le type (hé oui ;-) de l'événement auquel est rattaché cette structure de données.

Concrètement

Dans le cadre de notre application, il faut que la fenêtre principale attende, au minimum, l'événement delete_event émis lors de la fermeture de la fenêtre.

    $window->signal_connect('delete_event', sub{Gtk2->main_quit();});

Le comportement attendu de l'application en cas de fermeture de la fenêtre est suffisamment simple pour ne pas avoir à recourir à une fonction nommée. C'est ici la méthode de classe main_quit() qui est appelée car c'est la seule à même de sortir proprement de la boucle principale GTK.

Architecture de l'interface

Nous en sommes à présent à la phase où nous voulons rajouter des choses dans la fenêtre pour avancer dans le développement de l'interface et des fonctionnalités attendues.

Une rapide étude de nos besoins nous montre qu'il nous faut :

Une interface assez simple, somme toute, mais beaucoup trop complexe pour notre pauvre petite fenêtre.

En effet, une fenêtre GTK ne peut contenir qu'un seul autre widget. La fenêtre GTK hérite bien de la classe Gtk::Container qui l'autorise à contenir d'autres widgets, mais elle n'en hérite pas directement.

Elle est définie par une classe intermédiaire Gtk2::Bin qui limite le nombre de widgets enfants à un. C'est aussi le cas des boutons (Gtk2::Button) par exemple, qui sont aussi des conteneurs puisqu'ils contiennent un widget Gtk2::Label, et qui ne peuvent contenir qu'un widget enfant.

L'architecture et le rendu de l'interface de l'application sera donc le fait de widgets spécialisés dotés d'options de rangement accrues à la différence de la fenêtre GTK. En effet, même si celle-ci peut intégrer un autre widget, à l'instar de n'importe quel Gtk2::Container, avec la méthode add() (prosaïque), ceci est loin d'être suffisant.

    $widget_parent->add($widget_enfant);

Ces widgets sont les boîtes (Gtk2::HBox et Gtk2::VBox) et les tableaux (Gtk2::Table).

Les boîtes

L'idée de base avec les boîtes GTK est assez simple. Afin d'obtenir le résultat de rendu escompté, on va utiliser des widgets invisibles dans lesquels on va juxtaposer horizontalement et verticalement d'autres widgets. Il est bien sûr possible d'imbriquer ce boîtes les unes dans les autres pour que, par exemple, au sein d'un empilement vertical de widgets, on puisse en arranger d'autres horizontalement.

Pour ajouter des widgets au sein de ces conteneurs, il y a deux options. La première est d'utiliser add() comme pour la fenêtre GTK puisque c'est une méthode de leur ancêtre commun. Vous perdez la finesse que vous offrent les boîtes mais vous pouvez ajouter malgré tout des widgets à l'infini de cette façon.

La seconde méthode est en fait double : pack_start() et pack_end().

Mais avant d'empaqueter les widgets dans les boîtes, il faut les créer. Ça, nous savons déjà le faire :

Pour un empilement vertical des widgets :

    my $vbox = Gtk2::VBox->new($homogene, $espace);

Ou pour un rangement horizontal :

    my $hbox = Gtk2::HBox->new($homogene, $espace);

Le paramètre $homogene est un booléen. S'il est vrai, tous les widgets rangés dans la boîte occuperont un espace équivalent. S'il est faux, ils occuperont juste la place dont ils ont besoin. Le paramètre $espace indique évidemment l'espace, en pixels, entre chaque widget rangé dans la boîte.

En complément de ces options de rangements, viennent la méthode de rangement (pack_start() et pack_end()) et ses propres options (identiques pour les deux méthodes). Vous aurez compris que ces deux méthodes vont permettre suivant les boîtes qui les appellent de ranger de haut en bas ou de bas en haut, et de gauche à droite ou de droite à gauche.

Enfin, les options de ces méthodes viennent étoffer encore les possibilités d'arrangements des widgets :

    $conteneur->pack_start($contenu, $expand, $fill, $padding);
    $conteneur->pack_end($contenu, $expand, $fill, $padding);

A noter qu'il existe des méthodes qui sont des raccourcis de ces deux dernières. pack_start_defaults et pack_end_defaults vont toujours avoir les widgets conteneur comme appelant et contenu comme paramètre mais positionneront d'office les paramètres $expand, $fill et $padding à vrai, vrai et 0.

Les tableaux

A ne pas confondre avec les tableaux HTML... Il s'agit là plutôt d'une grille. Un tableau va être composé d'un nombre de colonnes et de rangées, définies à sa création, qui vont déterminer un nombre de cellules composant le tableau.

Le placement des widgets au sein du tableau consistera à indiquer combien de cellules il couvrira.

Avant d'y ajouter quoi que ce soit, le tableau se crée de cette façon :

    $table = Gtk2::Table->new($lignes, $colonnes, $homogene);

$lignes et $colonnes vont indiquer combien de lignes et de colonnes vont composer ce tableau. $homogene va intervenir dans la taille des cellules. Si ce booléen est vrai, toutes les cellules seront de taille égale. Autrement, elles seront ajustées en fonction du plus gros widget de la rangée ou de la ligne.

Les widgets sont ensuite attachés au tableau en indiquant des coordonnées. Pour cela, prenez le tableau pour un repère orthogonal, ayant son origine en haut à gauche, dont les abscisses iraient vers la droite (uniquement) et les ordonnées vers le bas (uniquement).

    $table = Gtk2::Table(2,2,0);

serait figuré ainsi :

     0          1          2
    0+----------+----------+
     |          |          |
    1+----------+----------+
     |          |          |
    2+----------+----------+

C'est la méthode attach() qui est utilisée, la voici avec ses nombreux arguments.

    $table->attach(
                   $widget,
                   $x_debut, $x_fin,
                   $y_debut, $y_fin,
                   $x_options, $y_options,
                   $x_padding, $y_options
                   );

$widget est d'évidence le widget à attacher. Les quatre paramètres suivants sont les coordonnées de début et de fin, en abscisses et en ordonnées.

$x_options et $y_options sont les options de remplissage des cellules par le widget.

Si vous souhaitez passer plus d'une option, il vous faudra employer les références.

Finalement, les paramètres $x_padding et $y_padding sont les espaces, en pixels, placés tout autour du widget.

Notre architecture

Nous allons donc placer une Gtk2::VBox dans la fenêtre. Cette dernière contiendra le menu et un tableau. Seront placés dans ce tableau, un tableau de valeurs sur les quatre cinquièmes et des zones de saisie sur le reste.

Nous allons commencer par créer la boîte. Le tableau étant à placer en dessous du menu (que nous voyons dans le chapitre qui suit), il (le tableau) sera créé et ajouté un peu plus tard (dans le prochain article en fait).

(Il eut été bien sûr possible d'ajouter le tableau avant le menu et s'arranger par la suite pour les retrouver dans l'ordre que vous souhaitez, mais restons modestes ;-)

Voici le code qui reprend ce qui a été fait jusqu'à présent (rajoutant use strict pour les conventions de code, et le hachage %W où je regroupe à toutes fins utiles presque tous les widgets de l'interface).

    #!/usr/bin/perl -w

    use Gtk2 '-init';
    use Gtk2::SimpleMenu;
    use strict;

    my %W;

    my $window = Gtk2::Window->new('toplevel');
    $window->signal_connect('delete_event', sub{Gtk2->main_quit();});
    $W{main} = $window;

    my $vbox = Gtk2::VBox->new();
    $vbox->show();
    $window->add($vbox);
    $W{vbox} = $vbox;

    $window->show();

    Gtk2->main();

Les menus

Les menus sont des éléments importants de l'interface et comme ils peuvent être déroulants, contextuels, d'options, il fallait prévoir une API qui puisse être très polyvalente. S'en tenant à la ligne de cet article, nous n'allons pas rentrer dans les détails tout en essayant de vous donner de solides bases de compréhension, entre théorie et pratique.

Pas à pas

La base du menu principal, classiquement placé en haut de l'application, se compose d'un Gtk2::Menubar (le conteneur) et de Gtk2::MenuItem (les éléments toujours visibles de la barre de menu). Chacun d'eux va donner accès à un Gtk2::Menu (le conteneur secondaire), qui va contenir d'autres Gtk2::MenuItem. Ces derniers peuvent de nouveau donner accès à d'autres Gtk2::Menu pour créer d'autres embranchements (et ainsi de suite). Les éléments d'un menu peuvent aussi être autre chose que des Gtk2::MenuItem, comme nous ne le verrons pas plus tard (reportez-vous à la documentation pour cela ;-).

Voilà comment créer une barre de menu avec un menu Fichier comprenant les options Créer et Sauver.

    # le conteneur des éléments du menu Fichier
    $menu = Gtk2::Menu->new();

    # un élément que nous ajoutons avec la méthode 'append()'
    # sans oublier de le "montrer"
    $sauver = Gtk2::MenuItem->new('Sauver');
    $menu->append($sauver);
    $sauver->show();

    # un autre élément que nous ajoutons avec la méthode 'append()'
    # sans oublier de le "montrer"
    $creer = Gtk2::MenuItem->new('Créer');
    $menu->append($creer);
    $creer->show();

    # le titre du menu auquel nous attachons le menu lui-même
    # et que nous "montrons"
    $fichier = Gtk2::MenuItem->new('Fichier');
    $fichier->set_submenu($menu);
    $fichier->show();

    # le conteneur MenuBar auquel nous ajoutons le titre de menu 'Fichier'
    # et que nous "montrons"
    $menubar = Gtk2::MenuBar->new();
    $menubar->append($fichier);
    $menubar->show();

Il ne reste plus qu'à intégrer le Gtk2::MenuBar dans un autre conteneur (une boîte, un tableau, ce que vous voulez...) avec une des méthodes vues précédemment.

Dans les différentes documentations, ce qui vient de vous être expliqué est appelé the hard way. Vous pouvez cependant vous rendre compte qu'il n'y a rien de difficile dans cette construction. Il est probable que les auteurs voulaient dire « long » plutôt que « dur »... Effectivement, si vous imaginez une flopée de menus et de sous-menus comme dans GIMP, ce peut être long d'autant plus que ne sont traitées dans cet exemple que la création et l'association des widgets. Les fonctions de rappel de chaque élément de menu, les raccourcis claviers et les options spéciales ne sont absolument pas traités.

La moulinette

En conséquence, il a été créé une easy way avec le Gtk2::ItemFactory. Les auteurs de Gtk2-Perl ont même créé un module de plus haut niveau encore, qui simplifie encore la simplification : Gtk2::SimpleMenu (qui hérite de Gtk2::ItemFactory). A noter que ce widget ne sert qu'à faire des menus principaux (alors que le Gtk2::ItemFactory peut servir à faire toutes les sortes de menus).

Comme nous n'avons pour lors besoin que d'un seul menu principal, Gtk2::SimpleMenu nous suffira donc.

Le constructeur du Gtk2::SimpleMenu attend plusieurs paramètres et notamment une référence à une structure de données modélisant le menu. Les éléments de cette structure peuvent être un ou plusieurs parmi les suivants :

C'est dans la façon de présenter ces données que vont se différencier Gtk2::ItemFactory et Gtk2::SimpleMenu. Ce dernier a été développé pour tirer parti de la capacité naturelle de Perl à présenter clairement des structures de données imbriquées.

En effet avec Gtk2::ItemFactory, les données sont présentées sous la forme d'une liste de listes qui contiennent les informations attendues pour chaque élément de menu. Le menu est alors modélisé sous la forme d'une arborescence où, par exemple, l'item Quitter du menu Fichier sera identifié comme /Fichier/Quitter.

On peut alors retrouver un élément du menu grâce à la méthode get_widget() en lui fournissant le chemin du widget dans cette arborescence fictive.

    $widget = $menu->get_widget('/Fichier/Quitter');

Gtk2::SimpleMenu héritant de Gtk2::ItemFactory, cette méthode qui est bien pratique est aussi disponible pour ce widget (alors même que nous n'allons pas spécifier ce paramètre nous-mêmes, Gtk2::SimpleMenu fera le travail pour nous).

Notre menu

En ce qui nous concerne, nous avons besoin (besoin qui peut évoluer) d'un menu qui se décomposerait comme suit :

    Fichier
      ->Quitter

    Édition
      ->Tout sélectionner

Ce menu enfin créé, il nous sera possible d'ajouter le tableau après lui pour y placer par la suite les autres widgets de l'interface.

Le code (menu et ajout du tableau) se poursuivrait donc ainsi :

    my $menu_model = [
            _File => {
                item_type => '<Branch>',
                children => [
                    Quit => {
                        callback => sub{Gtk2->main_quit()},
                        callback_action => 1,
                        accelerator => '<ctrl>Q'
                    }
                ]
            },
            _Edition => {
                item_type => '<Branch>',
                children => [
                    'Tout Sélectionner' => {
                        callback => \&select_all,
                        callback_action => 2,
                        accelerator => '<ctrl>A'
                    }
                ]
            }
    ];

    my $menu = Gtk2::SimpleMenu->new(
            menu_tree => $menu_model,
            user_data => \%W
    );

    my $menubar = $menu->{widget};
    $W{menubar} = $menubar;
    $vbox->pack_start($menubar, 0, 0, 1);
    $menubar->show();
    $W{menu} = $menu;

    $window->add_accel_group($menu->{accel_group});

    my $table = Gtk2::Table->new(5, 1, 1);
    $vbox->pack_start($table, 1, 1, 1);
    $table->show();

    # on montre la fenêtre princpale en dernier
    # de même pour l'entrée dans la boucle principale GTK
    # Ces deux lignes seront déplacés à chaque ajout de code
    # pour les maintenir à cette position
    $window->show();

    Gtk2->main();

Après ce que je vous ai déjà expliqué, les quelques lignes précédentes devraient être aisées à comprendre. Pour ce qui est de la structure de données, on retrouve au premier niveau le nom des menus.

[Figure 2]

Vous avez remarqué que les titres sont précédés par un underscore. Nous ne nous étendrons pas là-dessus non plus mais sachez que cela s'appelle en GTK un mnemonic et qu'il crée un raccourci clavier qui sera une combinaison de la touche <Alt> et de lettre qui suit l'underscore. Ce raccourci est créé automatiquement par la simple présence de l'underscore.

Le deuxième niveau de la structure de données est là pour définir des paramètres du titre de menu. Le niveau suivant égrène dans une liste les différents éléments du menu. Il est bien sûr possible de créer des embranchements.

Le deuxième paramètre du constructeur (méthode new()) du Gtk2::SimpleMenu est la donnée additionnelle par défaut. Les arguments dans le descriptif de chaque élément de menu peuvent prendre le pas sur cet argument. L'argument est en l'occurrence notre petit catalogue qui contient (presque) tous les widgets de l'interface.

Après avoir créé l'objet Gtk2::SimpleMenu, il est possible de récupérer le conteneur du menu (un Gtk2::MenuBar) aisément car c'est un des attributs de l'objet. Nous pouvons par la suite le manipuler (l'intégrer à un autre widget, le montrer ou pas) comme si nous l'avions créé nous-mêmes.

On ajoute donc le menu à la boîte principale (qui va aussi contenir le tableau) avec les options qui obligent le menu à n'occuper que sa place (faites varier les options pour voir ce que cela donne...).

Dernière chose à expliciter, la ligne qui parle d'accel_group. Le Gtk2::SimpleMenu a créé de lui-même des raccourcis clavier sous la forme de mnemonics comme mentionné plus haut, mais aussi d'autres appelés accelerators (paramètre accelerator dans la modélisation du menu).

De la même façon, nous ne pouvons nous étendre sur ce sujet. Sachez simplement qu'ils sont regroupés en Gtk2::AccelGroup et que ce widget doit être ajouté à la fenêtre principale puisque c'est elle qui reçoit le signal. Retrouver le groupe d'accelerators généré automatiquement par le Gtk2::SimpleMenu est simple puisque c'est un de ses attribut.

Le reste du code concerne le tableau : tout devrait être compréhensible.

Conclusion

Afin de vous laisser reprendre des forces pour la suite, nous arrêterons la présentation de Gtk2-Perl ici. Mais comme vous connaissez déjà les bases de ce binding, que vous connaissez aussi le but du second article, les plus impatients d'entre vous peuvent essayer d'avancer un peu par eux-mêmes.

Installez Gtk2-Perl, regardez la documentation et développez la suite de l'interface vous-mêmes.

Vous la comparerez à ce qui vous sera présenté dans le prochain article.

Notes de l'article

  1. Littéralement un lien. C'est une couche logicielle qui permet de faire le lien entre un langage et une bibliothèque logicielle dans un autre langage.

  2. GTK+ Drawing Kit. C'est une couche en dessous de GTK qui encapsule la Xlib.

Références de l'article

Auteur

David Elbaz est membre de groupe Paris.pm, de l'association les mongueurs de Perl, du groupe de travail 'Article'. Il aime particulièrement la programmation d'interfaces graphiques car c'est un trait d'union possible entre les développeurs et les utilisateurs, entre les développeurs d'aujourd'hui et ceux de demain.

Il remercie vivement les membres du groupe de relecture pour leurs conseils toujours aussi avisés.

[IE7, par Dean Edwards] [Validation du HTML] [Validation du CSS]