Article publié dans Linux Magazine 63, juillet/août 2004.
Copyright © 2004 - David Elbaz.
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.
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 !
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+.
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.
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.
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.
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).
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
.
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.
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();
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.
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 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.
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 référence à une routine nommée.
$widget->signal_connect('signal', \&rappel, $param);
Une chaîne donnant le nom de la routine à appeler.
$widget->signal_connect('signal', 'rappel', $param);
Une référence à du code anonyme (coderef
).
$widget->signal_connect('signal', sub{do_something();}, $param);
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.
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.
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.
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 :
Un menu principal pour piloter l'application
Un tableau de valeurs pour présenter la base de données des adhérents
Des zones de saisie pour modifier les entrées du tableau de valeurs
Quelques boutons de-ci de-là notamment pour valider les données entrées dans les zones de saisie.
Une zone de saisie de texte pour taper le message à envoyer.
Un jeu de quelques autres widgets pour s'occuper de l'adjonction éventuelle d'une pièce jointe.
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
).
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);
$contenu
est le widget qui va être rangé.
$expand
est un booléen et va autoriser le widget à prendre le maximum
d'espace possible dans la boîte.
fill
, pris en compte uniquement si $expand
est vrai, va autoriser le
widget à occuper toute la place qui lui est allouée même si celle-ci est trop
grande.
Enfin, $padding
est l'espace en pixels placé tout autour du widget.
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.
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.
fill
va demander au widget de remplir tout l'espace qui lui est alloué.
shrink
va autoriser le widget à suivre toutes les modifications de taille
de la table.
expand
va autoriser la table à occuper l'espace maximum.
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.
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 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.
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.
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 :
accelerator
Il s'agit du raccourci clavier éventuellement associé à l'élément du menu. La chaîne à donner en argument est une combinaison des représentations des touches spéciales <ctrl>, <alt> et <shift>, et d'une lettre.
callback
C'est ici la fonction de rappel qui est renseignée.
callback_action
Ce paramètre attend un entier qui sera reçu en paramètre par la fonction de
rappel. Il peut être utilisé pour identifier quel Gtk2::MenuItem
a été activé
dans le cas (fréquent) d'une fonction de rappel commune à plusieurs éléments.
item_type
Ici va être spécifiée la nature de l'élément de menu. Il y a un certain nombre de possibilités mais, comme dit auparavant, nous ne rentrerons pas dans le détail.
Pour notre exemple, il suffira de connaître le paramètre <Branch>
qui
indique que l'élément en question est l'embranchement d'un sous-menu.
extra_data
Même les moins anglophones d'entre vous auront compris qu'il s'agit ici du
paramètre additionnel. Comme dans le cas de la méthode signal_connect()
, il
vous faudra recourir aux références pour passer plus d'un argument.
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).
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.
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.
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.
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.
GTK+ Drawing Kit. C'est une couche en dessous de GTK qui encapsule la Xlib.
Le nouveau site de référence de Gtk2-Perl
La documentation de référence
Le site de GTK+.
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.
Copyright © Les Mongueurs de Perl, 2001-2011
pour le site.
Les auteurs conservent le copyright de leurs articles.