I. Introduction

I-A. À propos de GTK+

GTK+ (le GIMP Toolkit) est une bibliothèque pour créer des interfaces graphiques. Son nom provient d'ailleurs du fait qu'elle a été initialement écrite pour le fameux GNU Image Manipulation Project (GIMP). Elle est placée sous la licence LPGL, ainsi vous pouvez développer des logiciels libres, ou même des logiciels commerciaux payants sans avoir à dépenser quoi que ce soit en licences ou en royalties. GTK+ est elle-même basée sur d'autres bibliothèques qui sont :

  • GDK : un encapsulateur de la Xlib ;
  • gtk-pixbuf : pour manipuler les images ;
  • Pango : pour l'internationalisation ;
  • gobject : qui définit le système de type sur lequel est basé GTK;
  • gmodule : qui est utilisé pour charger des extensions à la volée ;
  • Glib : une bibliothèque d'utilitaires indispensables ;
  • Xlib : sur laquel est basée GDK;
  • Xext : qui contient du code pour les pixmaps à mémoire partagée et autres extensions X;
  • math : utilisée par GTK à plusieurs occasions.

L'un des avantages d'avoir une bibliothèque écrite en C est que la plupart des langages sont capables d'avoir une interface avec les bibliothèques C, rendant GTK disponible pour plusieurs langages, comme C-, Perl, Python, Eiffel et beaucoup d'autres. Les créateurs de GTK+ sont :

  • Peter Mattis ;
  • Spencer Kimball ;
  • Josh MacDonald. Actuellement, GTK+ est maintenu par :

  • Owen Taylor ;
  • Tim Janik.

Le site officiel de GTK+ est http://www.gtk.org.

I-B. À propos de Gtk2-Perl

Gtk2-Perl est donc l'encapsulateur qui permet d'utiliser la librairie GTK+, version 2, en Perl. Gtk2-Perl est sous la license LGPL, ce qui signifie qu'il possède les mêmes restrictions et les mêmes libertés que Perl.

Bien qu'écrite en C, GTK+ est implémentée selon le modèle de programmation objet, ce qui implique beaucoup de rigueur de la part des développeurs. En effet, pour mettre en place la notion d'héritage et d'objet, il a été développé un système de type et une méthodologie précise dont on a pas à se préoccuper avec un langage orienté objet.

Et c'est là que le travail de la Gtk2-Perl TEAM est magique ! Tout le côté désagréable de gestion de type est effectué par le module et non par le programmeur. Ainsi écrire un programme avec le module Gtk2-Perl se fait de la même manière que n'importe quel programme Perl écrit selon le modèle de programmation objet. La Gtk2-Perl Team que l'on peut contacter sur gtk-Perl-list@lists.gnome.org :

  • muppet scott at asofyet dot org ;
  • Ross McFarland rwmcfa1 at neces dot com ;
  • Torsten Schoenfeld kaffeetisch at web dot de ;
  • Marc Lehmann pcg at goof dot com ;
  • Goran Thyni goran at kirra dot net ;
  • Joern Reder joern at zyn dot de ;
  • Chas Owens alas at wilma dot widomaker dot com ;
  • Guillaume Cottenceau gc at mandrakesoft dot com.

Ont également participé en envoyant des patchs :

  • René Seindal rene at seindal dot dk ;
  • Tom Hargreaves hex at freezone dot co dot uk ;
  • Thierry Vignaud tvignaud at mandrakesoft dot com ;
  • James Curbo hannibal at adtrw dot org ;
  • Dr. Michael Langner langner at fiz-chemie dot de ;
  • Anuradha Ratnaweera Aratnaweera at virtusa dot com.

Le site officiel de GTK+ est http://gtk2-perl.sourceforge.net/.

I-C. À propos de ce tutoriel

Il est vrai que le module est fidèle à la bibliothèque C donc la documentation C est valable à 95 %. Mais il faut bien penser à ceux qui ne maîtrisent pas le langage C et encore moins la langue de Shakespeare ! Ce tutoriel ne se veut qu'une introduction en français mais en aucun cas un manuel de référence. Votre trouverez d'ailleurs ce dernier au format pod sur le site officiel http://www.gtk2-Perl.sourceforge.net. à mon avis, une fois que l'on a assimilé quelques bases en Gtk2-Perl, la consultation de la documentation C est naturelle. Ce tutoriel se concentre sur les liens entre Perl et GTK c'est pourquoi une connaissance approfondie de Perl n'est pas nécessaire, mais est préférable. Je prendrais rarement le temps d'expliquer des détails sur Perl donc si vous n'êtes pas sûr de vos compétences en Perl, ayez à portée de main votre guide de référence Perl préféré. Si quelqu'un veut aider à l'élaboration de ce tutoriel, qu'il se fasse connaître en me contactant :

J'espère que mes exemples seront lisibles et compréhensibles pour les novices en Perl, les experts et n'importe qui entre les deux. Ce document est « un travail en cours ». J'aimerais beaucoup savoir si vous avez eu des difficultés à apprendre Gtk2-Perl à partir de ce document et j'apprécierais également toute suggestion qui vise à améliorer le document. Si vous êtes un programmeur expérimenté en Gtk2-Perl, ce qui n'est pas mon cas, faîtes-moi savoir si j'ai commis des erreurs, s'il est des éléments que j'ai délaissés ou mal expliqués. Les principales sources de ce document sont :

  • l'excellent GTK-Tutoriel : http://www.gtk.org/documentation.php ;
  • les exemples qui sont fournis avec les sources du module.
  • le site http://gtk2-Perl.sourceforge.net où vous trouverez tout sur Gtk2-Perl donc incontournable… mais il vaut mieux parler anglais. La coloration syntaxique a été réalisée à l'aide du programme highlight.

Une version pdf de ce document est disponible ici.

I-D. Pour bien démarrer

La première chose bien évidemment est d'avoir une installation de Gtk2 qui fonctionne, une version récente est recommandée. Ensuite , il faut télécharger et installer dans l'ordre suivant les sources de :

  • Perl-ExtUtils-Depends ;
  • Perl-ExtUtils-PkgConfig ;
  • Perl-Glib ;
  • Perl-Gtk2 .

Vous pouvez toujours avoir les dernières versions à partir du site http://gtk2-perl.sourceforge.net/.

L'installation ne devrait pas poser de problème. Pour chacun des modules, il suffit de faire :

 
Sélectionnez
Perl Makefile.PL
make
make install

II. À la découverte de Gtk2-Perl

Tous les programmes se trouvent dans le répertoire exemples.

Pour découvrir Gtk2-Perl, nous allons écrire le programme le plus court et le plus simple possible. Celui-ci créera une fenêtre de 200 pixels sur 200 que l'on ne pourra pas fermer si ce n'est en la « tuant » à partir du shell.

Image non disponible
 
Sélectionnez
#!/usr/bin/Perl -w
use strict;
use Gtk2 '-init';
my $fenetre = Gtk2::Window->new('toplevel');
$fenetre->show;
Gtk2->main;

Pour lancer le programme, on peut écrire perl minimum.pl. La fenêtre qui apparaît peut ne pas ressembler exactement à l'image ci-dessus. GTK+ est, parmi d'autres choses, « thémable ». C'est-à-dire que l'utilisateur décide du style des widgets, des couleurs et icônes à utiliser, les polices, etc. L'exécution ci-dessus utilisait le thème par défaut. Quoi qu'il en soit, la barre de titre dépend du gestionnaire de fenêtres choisi ou de l'OS.

Nous avons donc ici la structure minimale d'une application écrite en Gtk2-Perl :

  1. use Gtk2 '-init' : au début du programme, qui charge le module Gtk2-Perl et initialise les bibliothèques C adéquates ;
  2. Gtk2->main; : à la fin du programme, qui lance la boucle principale. Quand le contrôle atteindra ce point, Gtk+ se mettra en sommeil et attendra les événements X, les notifications d'entrées sorties, les dépassements de temps impartis qui peuvent se produire. Dans notre exemple, nous n'avons pas pris en compte les événements, c'est pourquoi il nous faut recourir au bon vieux Ctrl C pour fermer la fenêtre. Entre ces deux lignes, nous placerons tout ce qui compose notre application. Ici, nous avons juste créé puis affiché une fenêtre en écrivant :
 
Sélectionnez
my $fenetre = Gtk2::Window->new('toplevel');
$fenetre->show;

L'argument 'toplevel' signifie que nous voulons une fenêtre qui reprenne le placement et les décorations du gestionnaire de fenêtre. Plutôt que de créer un fenêtre de taille 0 × 0, une fenêtre sans enfant est réglée à la taille de 200 × 200 par défaut ainsi on peut la manipuler.

II-A. Hello world à la Gtk2-Perl

Image non disponible
 
Sélectionnez
#!/usr/bin/Perl -w
use strict;    # une bonne idée pour tout script Perl non-trivial

# Charge le module Gtk2 et lance une procédure d'initialisation de
# la bibliothèque C
use Gtk2 '-init';

# Variables convenables pour vrai et faux
use constant TRUE  => 1;
use constant FALSE => 0;

# Création d'une fenêtre
my $window = Gtk2::Window->new('toplevel');

# Quand on attribue le signal "delete event" à une fenêtre (ce qui est
# attribué par le gestionnaire de fenêtre, soit par l'option "fermer" soit
# par la barre de titre), on demande à celle-ci d'appeler la fonction
# CloseWindow définie plus loin.
$window->signal_connect( 'delete event', \&CloseWindow, 'coucou' );

# Ici, on connecte l'événement "destroy" à un gestionnaire de signal.
# Cet événement se produit quand on appelle la fonction Gtk2::widget destroy
# sur la fenêtre ou si la fonction de rappel liée au "delete event" retourne
# FALSE.
$window->signal_connect( 'destroy', \&DestroyWindow );

# On déclare les attributs de la fenêtre. Il s'agit ici d'une bande de 15 pixels
# disposée sur le contour de la fénêtre afin que celle-ci ne soit pas trop
# "rabougrie" !
$window->set_border_width(15);

# Création d'un bouton
my $button = Gtk2::Button->new('Hello World');

# Quand le bouton reçoit le signal 'clicked', il appelle la fonction
# Hello définie plus loin...
$button->signal_connect( 'clicked', \&Hello );

# Ensuite, il déclenchera la destruction de la fenêtre en appelant la
# fonction Gtk2::widget destroy (). Encore une fois, le signal "destroy"
# peut provenir d'ici ou du gestionnaire de fenêtre.
$button->signal_connect( 'clicked', sub { $window->destroy } );

# On place le bouton dans la fenêtre.
$window->add($button);

# On montre le bouton quand on a défini tous ses attributs.
$button->show();

# Idem pour la fenêtre.
$window->show();

# Toute application en Gtk2-Perl doit posséder la ligne suivante qui
# lance la boucle principale.
Gtk2->main;
### La fonction de rappel qui est appelée quand on lique sur le bouton.
sub Hello {
	print("Salut à tous ! !\n");
}
### La fonction de rappel appelée par l'événement "delete event".
sub CloseWindow {

	# Si vous retournez FALSE dans le gestionnaire de l'événement
	# "delete event", alors le signal "destroy" sera emis.
	# Si vous retournez TRUE, c'est que vous ne voulez pas que la
	# fenêtre soit détruite.
	# C'est utile si on veut demander une confirmation du style
	# " voulez-vous vraiment quitter ?" dans une boîte de dialogue.
	# Changez TRUE en FALSE et la fenêtre principale sera détruite.
	return TRUE;
}
### La fonction de rappel pour fermer la fenêtre
sub DestroyWindow {
	Gtk2->main_quit;
	return FALSE;
}

II-B. Cheminons à travers Hello world

Maintenant, voyons si nous ne pouvons pas cheminer à travers le programme et lui donner du sens.

use Gtk2 '-init'; doit être inclus dans tout programme Gtk2-Perl. Cela permet à Perl d'utiliser les variables, les fonctions, les structures, etc. définies par GTK. Dans le même temps, cela initialise le module Gtk2 et met en place quelques éléments comme l'aspect par défaut, la couleur et les déclencheurs de signaux par défaut. Cela peut-être facilement combiné avec d'autres directives use comme use strict ;.

Les variables TRUE et FALSE correspondent à des valeurs booléennes. Elles sont définies pour rendre le code plus lisible.

Gtk::Window->new() crée une nouvelle fenêtre. Le window manager décide comment la décorer (avec des choses comme la barre de titre) et où la placer. Les autres widgets de l'application seront placés à l'intérieur. Vous pouvez avoir plusieurs fenêtres par programme. L'argument indique à X le type de fenêtre dont il s'agit. Les fenêtres toplevel n'ont pas de fenêtre parent et ne peuvent être contenues par aucune fenêtre. Vous pouvez également créer des fenêtres de dialogue avec new Gtk::Dialog().

La fonction set_border_width() prendra un conteneur (comme une fenêtre) et placera de l'espace autour du contenu. Cela évitera à votre application d'être trop « tassée ». Le nombre passé en argument est la largeur (en pixel) de la bordure créée. Essayez différentes valeurs.

new Gtk::Button crée un nouveau bouton. Ici, nous en avons créé un avec un label texte. Que ce serait-il passé si nous n'avions pas spécifié de texte à mettre sur le bouton ? Essayez et voyez par vous-même. Remarquez la taille du bouton et de la fenêtre quand vous le fait ? GTK redimensionnera automatiquement les widgets sauf si vous lui dîtes de ne pas le faire (cela inclus la fenêtre). Pour cette raison, il ne sert à rien de s'inquiéter du style ou de la taille de la police que l'utilisateur peut avoir définis par défaut. Donnez juste la disposition à GTK et laissez le faire le reste. Pour plus d'informations sur les boutons voir la section les concernant.

signal_connect relie un événement ou un signal à une routine. Un exemple serait d'appuyer sur le bouton de la souris. En termes de profane, quand un événement ou un signal est généré, GTK regarde si cet événement possède une routine enregistrée pour ce widget, si c'est le cas, il exécute cette routine. Si aucune routine n'est enregistrée, il ne se passe rien.

Dans notre exemple, le premier argument de signal_connect est une chaîne indiquant l'événement que nous voulons enregistrer et le second argument est une référence à la routine à appeler si l'événement se produit. Certains événements peuvent nécessiter différents arguments.

La fonction add() ajoute un widget dans un conteneur (ici, la fenêtre). Si nous n'avions pas fait cela, nous n'aurions pas vu le bouton parce qu'il n'aurait pas été dans la fenêtre.

La fonction show() rend la fenêtre visible. N'exécutez pas show() tant que toutes les propriétés du widget ne sont pas définies. Vous devriez toujours montrer les widgets enfants avant les widgets parents (ici, rendre visible le bouton avant la fenêtre qui le contient). La manière dont la fenêtre apparaît tout d'un coup rend le programme plus professionnel.

Gtk2->main déclenche le processus de contrôle des événements GTK. Il s'agit d'une ligne que vous verrez dans toutes les applications Gtk2-Perl. Quand le contrôle atteint ce point, GTK se mettra en sommeil, attendant les événements X (comme les boutons ou les touches pressés), les délais ou les notifications de fichier E/S qui peuvent se produire. Imaginez cela comme une boucle infinie dont on sort par l'appel Gtk2->main_quit().

II-C. Théorie des signaux et des rappels

Une interface graphique est composée d'éléments (fenêtre, cadres, boutons…) appelés widgets. Ceux-ci peuvent émettre des signaux. Ce ne sont pas les mêmes que les signaux d'un système Unix et ils ne sont pas implémentés en les utilisant bien que la terminologie soit presque identique. Quand on clique sur un bouton, il émet le signal 'clicked'. Gtk en prend note en le plaçant dans une liste. Si on veut que ce clic déclenche une action, il faut relier ce signal à une fonction qui sera appelée dès que le signal sera émis. En simplifiant, il faut considérer le programme comme un régulateur qui centralise tous les événements qui se produisent et à qui on peut demander de nous prévenir si tel ou tel événement a eu lieu.

Pour relier un signal à une fonction, on procède ainsi :

 
Sélectionnez
$object->signal_connect( 'signal_name', \&signal_func );
$object->signal_connect( signal_name => \&signal_func );
$object->signal_connect( 'signal_name', \&signal_func, $donnee_optionnelle ... );
$object->signal_connect( 'signal_name', \&signal_func, @donnees_optionnelles );

En Perl, les deux premières formes sont identiques ainsi que les deux suivantes, car Perl envoie tous les arguments sous la forme d'une seule liste de scalaires. Bien sûr, vous pouvez envoyer autant d'éléments par liste que vous le désirez.

La variable à gauche du signal_connect() est le widget qui émettra le signal. Le premier argument est une chaîne de caractères représentant le signal dont vous aimeriez enregistrer le rappel de signal. Le second argument est la routine que vous voulez appeler. Cette routine est appelée rappel de signal ou fonction de rappel. Si vous passez des arguments à la routine, vous les précisez en les ajoutant à la fin de la liste d'arguments. Tout ce qui est après le second argument est passé en tant que liste, c'est pourquoi vous pouvez vous permettre de passer autant d'arguments que vous le souhaitez.

Si vous ne comprenez pas les références Perl, souvenez-vous que les routines sont précédées par un \& et que vous ne pouvez pas placer des parenthèses après le nom de la routine.

Pour les rappels de signaux associés avec uniquement un signal et composés de quelques lignes, il est courant de voir quelque chose comme ceci :

 
Sélectionnez
$object->signal_connect( "signal_name", sub { faire_quelque_chose; } );

Une routine est habituellement définie par :

 
Sélectionnez
fonction_de_rappel {
	my ( $widget, $donnee ) = @_;
	...;
}

Le premier argument envoyé à un rappel de signal sera toujours le widget émetteur et le reste des arguments sont des données optionnelles envoyées par la fonction signal_connect(). Souvenez-vous que Perl ne vous oblige pas à déclarer ou à utiliser les arguments envoyés à une routine.

II-D. Les événements

E n plus du mécanisme des signaux décrit précédemment, il y a un ensemble d'événements qui reflètent les mécanismes des événements X. Par exemple, si l'utilisateur ferme la fenêtre principale, c'est le gestionnaire de fenêtres qui enverra un message à l'application.

Les événements suivants peuvent également être reliés à une fonction de rappel :

event selection_clear_event
button_press_event selection_request_event
button_release_event selection_notify_event
scroll_event proximity_in_event
motion_notify_event proximity_out_event
delete_event visibility_notify_event
destroy_event client_event
expose_event no_expose_event
key_press_event window_state_event
key_release_event selection_received
enter_notify_event selection_get
leave_notify_event drag_begin_event
configure_event drag_end_event
focus_in_event drag_data_delete
focus_out_event drag_motion
map_event drag_drop
unmap_event drag_data_get
property_notify_event drag_data_received

Afin de connecter une fonction de rappel à l'un de ces événements, on utilise signal_connect() de la même manière que pour connecter un signal, sauf que l'on écrit le nom de l'événement à la place du nom du signal. Le dernier argument passé au rappel de signal est une structure d'événement ainsi au début de votre fonction, vous devriez dire :

 
Sélectionnez
my ( $widget, $evenement, $donnee ) = @_;

Ou si vous voulez passer un ensemble de données :

 
Sélectionnez
my ( $widget, @data ) = @_;
my $event = shift(@data);

Un événement est une structure qui comporte plusieurs champs. Il y a en particulier le champ type qui est une chaîne de caractères qui identifie l'événement et qui appartient à la liste suivante :

'nothing' 'property_notify'
'delete' 'selection_clear'
'destroy' 'selection_request'
'expose' 'selection_notify'
'motion_notify' 'proximity_in'
'button_press' 'proximity_out'
'2button_press' 'drag_enter'
'3button_press' 'drag_leave'
'button_release' 'drag_motion'
'key_press' 'drag_status'
'key_release' 'drop_start'
'enter_notify' 'drop_finished'
'leave_notify' 'client_event'
'focus_change' 'visibility_notify'
'configure' 'no_expose'
'map' 'window_state'
'unmap' 'setting'

Il y a également le champ button qui contient un nombre indiquant le bouton pressé (typiquement 1, 2 ou 3) et le champ key qui indique la touche pressée (s'il y en a une). Cela permet de déterminer facilement la cause d'un événement. Par exemple, cela fut-il causé par un bouton pressé ? si oui, lequel ?

 
Sélectionnez
sub some_event {
	my ( $widget, @donnees ) = @_;
	my $evenement = shift(@donnees);
	if (    ( defined( $evenement->type ) )
		and ( $evenement->type eq 'button_press' ) )
	{
		if ( $evenement->button == 3 ) {

			#click_droit
		}
		else {
			# pas_un_click_droit
		}
	}
}

Gardez à l'esprit que le code précédent ne fonctionne que si le rappel est lié à l'un des événements mentionnés ci-dessus. Les signaux n'envoient pas une structure d'événement ainsi, à moins que vous ne connaissiez exactement le nombre d'arguments envoyés, vous n'avez pas besoin de l'information sur la structure d'un événement. Je milite pour ne pas connecter un signal et un événement au même retour.

II-E. Plus sur les gestionnaires de signaux

La valeur de retour de la fonction signal_connect() est un « tag » qui identifie la fonction de rappel. Vous pouvez avoir autant de rappels par signal ou par objet que vous le souhaitez et ils seront exécutés les uns après les autres dans l'ordre ou ils ont été attaché.

II-E-1. Émettre un signal

Si vous voulez émettre un signal spécifique, vous pouvez le faire en appelant l'une des fonctions suivantes :

 
Sélectionnez
$widget->signal_emit($id);
$widget->signal_emit_by_name($nom_du_signal);

L'argument de la première forme est le « id tag » qui est retourné par signal_connect(). L'argument de la seconde forme est une chaîne identifiant le nom du signal. Ainsi beaucoup de widgets ont des méthodes qui émettent des signaux les plus courants. Par exemple, la méthode destroy() émettra le signal 'destroy' et la méthode activate() le signal 'activate'.

II-E-2. Supprimer des retours

Ce « id tag » vous permet également de supprimer un retour de la liste en utilisant signal_disconnect() comme ceci :

 
Sélectionnez
$widget->signal_disconnect($id);

Si vous voulez ôter tous les gestionnaires d'un widget, vous pouvez appeler la fonction :

 
Sélectionnez
$widget->signal_handlers_destroy();

Cet appel se passe de commentaires. Il enlève simplement tous les gestionnaires de l'objet passé en tant que premier argument.

II-E-3. Désactiver temporairement des gestionnaires

Vous pouvez débrancher temporairement des gestionnaires avec :

 
Sélectionnez
$widget->signal_handler_block($callback_id);
$widget->signal_handler_block_by_func( \&callback, $donnee );
$widget->signal_handler_block_by_data($donnee);
$widget->signal_handler_unblock($callback_id);
$widget->signal_handler_unblock_by_func( \&callback, $donnee );
$widget->signal_handler_unblock_by_data($donnee);

II-E-4. Connecter ou déconnecter des gestionnaires

Voici un aperçu des fonctions utilisées pour connecter ou déconnecter un gestionnaire pour chacun des signal_connect disponibles. Vous trouverez plus de détails dans la documentation Gtk.

 
Sélectionnez
$id = $object->signal_connect( $nom_du_signal, \&function, @optional_data );
$id = $object->signal_connect_after( $nom_du_signal, \&function, @optional_data );
$id = $object->signal_connect_object( $nom_du_signal, \&function, $slot_object );
$id = $object->signal_connect_object_after( $nom_du_signal, \&function, slot_object );

#Je ne suis pas sûr de celle-là
$id =
  $object->signal_connect_full( $name, \&function, $callback_marshal, @optional_data, \&destroy_function,
	$object_signal, $after );

#Je ne suis pas sûr de celles-là non plus
$id = $object->signal_connect_object_while_alive( $signal, \&function, $alive_object );
$id = $object->signal_connect_while_alive( $signal, \&function, @optional_data, $alive_object );
$object->signal_disconnect($id);
$object->signal_disconnect_by_func( \&function, @optional_data );

II-F. Un hello world un peu plus évolué

Image non disponible
 
Sélectionnez
#!/usr/bin/Perl -w
use strict;
use Gtk2 '-init';

# Variables convenables pour vrai et faux
use constant TRUE  => 1;
use constant FALSE => 0;

# Création d'une fenêtre
my $window = Gtk2::Window->new('toplevel');

# On définit une bordure de 15 pixels
$window->set_border_width(15);

# On déclare le titre de la fenêtre
$window->set_title('Salut les boutons !');

# On relie l'événement delete event à une fonction de rappel à qui on
# passe comme argument une chaîne de caractère
$window->signal_connect( 'delete event', \&CloseWindow, "Puisqu'il faut partir..." );

# Le signal "destroy" sera émis parce que la fonction de rappel "CloseWindow"
# renvoie FALSE.
$window->signal_connect( 'destroy', \&DestroyWindow );

# Création d'une boîte dans laquelle on placera les boutons. Nous verrons
# en détail les comportements des boîtes dans le chapître sur les conteneurs.
# Une boîte n'est pas visible. Elle est juste utile pour ranger les widgets
# qu'elle contient.
my $box = Gtk2::HBox->new( FALSE, 0 );

# On place la boîte dans la fenêtre.
$window->add($box);

# Création d'un bouton qui s'appellera 'bouton 1'
my $button1 = Gtk2::Button->new("Bouton 1");

# On relie le signal "clicked" à la fonction de rappel "rappel"
# à qui on passe comme argument la chaîne de caractère "bouton 1".
$button1->signal_connect( 'clicked', \&rappel, "bouton 1" );

# On place le bouton 1 dans notre boîte
$box->pack_start( $button1, TRUE, TRUE, 0 );

# On montre le bouton
$button1->show;

# On refait la même chose pour placer un second bouton dans la boîte
my $button2 = Gtk2::Button->new("Bouton 2");
$button2->signal_connect( 'clicked', \&rappel, "bouton 2" );
$box->pack_start( $button2, TRUE, TRUE, 0 );
$button2->show;

# On montre la boîte
$box->show;

# On montre la fenêtre
$window->show();

# On lance la boucle principale.
Gtk2->main;
### La fonction de rappel qui est appelé quand on a cliqué sur un bouton.
sub rappel {
	my ( $widget, $pressed_button ) = @_;
	print("Vous avez pressé le $pressed_button ! !\n");
}
### La fonction de rappel appelé par l'événement "delete event".
sub CloseWindow {
	my ( $widget, $event, $message ) = @_;

	# On récupère le nom de l'événement
	my $name = $event->type;
	print "L'événement $name s'est produit !\n";
	print "$message \n";
	return FALSE;
}
### La fonction de rappel pour fermer la fenêtre
sub DestroyWindow {
	Gtk2->main_quit;
	return FALSE;
}

Un bon exercice serait d'ajouter un troisième bouton « quitter ». Vous pourriez également jouer avec les options de le méthode pack_start() quand vouz aurez lu le chapître suivant. Essayez de redimensionner la fenêtre et observez son comportement.

III. Les boîtes de regroupement

Notre premier hello world n'était composé que d'un bouton placé dans une fenêtre. La fenêtre fait partie de ces widgets qui ne peuvent contenir qu'un seul widget. Pour placer ce dernier à l'intérieur, on utilise l'appel $window->add(). évidemment, il y peu de chance que l'application que vous voulez créer ne soit composée que d'un unique widget. Heureusement, il existe des conteneurs qui peuvent contenir (c'est mieux !) plus d'un widget. Les boîtes de regroupement que nous avons déjà dans notre hello world un peu plus évolué en font partie.

III-A. Théorie des boîtes de regroupement

Le rangement est fait en créant des boîtes puis en regroupant les widgets à l'intérieur. Ces boîtes sont des conteneurs invisibles dans lesquelles on peut regrouper les widgets. Il en existe deux sortes : la boite horizontale et la boîte verticale. En rangeant les widgets dans une boîte horizontale, les objets sont insérés de la gauche vers la droite ou de la droite vers la gauche en fonction de l'appel utilisé. Pour une boîte verticale, le rangement se fait de bas en haut ou vice-versa. Vous pouvez utiliser des combinaisons en rangeant des boîtes à l'intérieur d'autres boîtes, ou à côté afin de produire l'effet désiré.

Pour créer une nouvelle boîte horizontale ou verticale :

 
Sélectionnez
$box = Gtk2::HBox->new( $homogeneous, $spacing );
$box = Gtk2::VBox->new( $homogeneous, $spacing );

Si $homogeneous est une valeur vraie alors tous les widgets rangés à l'intérieur disposeront du même espace. $spacing est l'espace en pixels qu'il faut laisser entre les widgets.

Les fonctions suivantes sont utilisées pour placer les objets dans les boîtes :

 
Sélectionnez
$box->pack_start( $child, $expand, $fill, $padding );
$box->pack_end( $child, $expand, $fill, $padding );

La fonction pack_start() commence à remplir une VBox par le haut et continue vers le bas. Pour une HBox, elle commence par la gauche et continue vers la droite. pack_end() fera le contraire. Ces fonctions nous permettent juste de justifier à droite ou à gauche la position des widgets et peuvent être mélangées pour obtenir l'effet désiré. L'objet ajouté peut-être ou bien un conteneur ou bien un widget. En fait, de nombreux widgets sont eux-même des conteneurs (les boutons par exemple, mais nous n'utilisons en général qu'un label ou un icône à l'intérieur des boutons). Comme vous l'avez peut-être deviné, l'argument $child est le widget à placer dans la boîte. Enfin $padding est un espace donné en pixel qui entourera l'enfant.

Si l'argument $expand est une valeur vraie alors les widgets seront disposés dans la boîte pour remplir l'espace qui leurs est alloué. Déclarer $expand comme valeur fausse vous permettra de faire des justifications à droite et à gauche de vos widgets. Noter que déclarer $expand vraie pour une boîte revient à déclarer $expand vraie pour chaque widget.

Si l'argument $fill est une valeur vraie alors tout espace vide est alloué aux objets eux-même. Autrement l'espace vide est un emballage dans la boîte autour des objets. Cela a de l'effet uniquement si l'argument $expand est vrai.

En utilisant ces appels, GTK sait où vous voulez placer vos widgets et peut ainsi les redimensionner ou faire d'autres choses sympathiques. Comme vous l'imaginez, cette méthode nous donne une certaine flexibilité quand on crée et place des widgets.

III-B. Cas des boîtes homogènes

Vous avez créé une boîte avec une valeur vraie pour l'argument $homogeneous. Par exemple :

 
Sélectionnez
$box = new Gtk2::HBox( TRUE, 0 );
$box = new Gtk2::VBox( TRUE, 0 );

Dans ce cas, tous les widgets contenus dans la boîte seront dimensionnés en fonction du plus grand et l'argument $expand$ n'a pas d'intérêt.

Si $fill est une valeur vraie alors tous les widgets contenus ont la même taille et remplissent l'espace qui leur est alloué.

Image non disponible

Si $fill est une valeur fausse alors les widgets gardent leurs tailles respectives et sont également répartis dans le conteneur.

Image non disponible

III-C. Cas des boîtes non homogènes

Vous avez créé une boîte avec une valeur vraie pour l'argument $homogeneous. Par exemple :

 
Sélectionnez
$box = new Gtk2::HBox( FALSE, 0 );
$box = new Gtk2::VBox( FALSE, 0 );

Si $expand est une valeur fausse alors la valeur de $fill n'a aucun intérêt. Les widgets auront alors leur propre taille et ne rempliront pas l'espace.

Image non disponible

Si $expand est une valeur vraie alors les widgets seront étirés dans la boîte. Si $fill est fausse, alors les widgets gardent leur taille naturelle.

Image non disponible

En revanche, si $fill est une valeur vraie alors les widgets remplissent la boîte.

Image non disponible

III-D. Détails sur les boîtes

Le rangement dans les boîtes peut être déroutant au début. Il y a beaucoup d'options et il n'est pas immédiatement évident de les arranger entre elles. En fait, il y a cinq styles de base différents. Ces cinq styles sont ceux qui sont illustrés ci-dessus.

III-E. Commentaire à propos de l'exemple

Chaque ligne de l'exemple contient une boîte horizontale avec plusieurs boutons. L'appel pour ranger est un raccourci pour regrouper chaque bouton dans une boîte. Chaque bouton est placé dans la boîte de la même manière (les arguments sont passées à la fonction pack_start()). C'est une forme raccourcie de pack_start() et de pack_end() qui déclare $expand vraie, $fill vraie et $padding=0.

Ces fonctions sont :

 
Sélectionnez
$box->pack_start_defaults($widget);
$box->pack_end_defaults($widget);

La valeur $homogeneous de la boîte peut être modifiée en utilisant la fonction :

 
Sélectionnez
$box->set_homogeneous($homogeneous);

De même pour la valeur $spacing :

 
Sélectionnez
$box->set_spacing($spacing);

Si vous voulez déplacer un enfant, utilisez :

 
Sélectionnez
$box->reorder_child($child,$position);

$child est le widget à déplacer et $position est la position à changer en partant de 0. Si vous voulez connaître l'ordre actuel, regardez la liste fournie par l'appel à la fonction sur les conteneurs children().

Si vous voulez changer un regroupement d'enfants, vous pouvez utiliser :

 
Sélectionnez
$box>set_child_packing($widget,$expand,$fill,$padding,$pack_type);

Les arguments sont les mêmes que pour les fonctions pack_start() et pack_end() à l'exception de $pack_type qui est soit 'start' soit 'end'.

Quelle est la différence entre l'espace (spacing), déclaré quand la boîte est créée, et l'emballage (padding), déclaré quand les éléments sont rangés ? L'espace est ajouté entre les objets et l'emballage est ajouté de chaque côté de l'objet.

IV. Les tables

Découvrons une autre manière de ranger : les tables. Elles peuvent être extrêmement utiles dans certaines situations. Utiliser des tables consiste à créer une grille dans laquelle nous plaçons les widgets. Les widgets peuvent prendre autant de cases que nous le souhaitons.

IV-A. Créer un table

Tout d'abord, la fonction qui permet de créer un table :

 
Sélectionnez
$table = Gtk2::Table->new($nb_lignes, $nb_colonnes, $homogeneous);

Le premier argument est le nombre de lignes et le second, évidemment le nombre de colonnes. L'argument $homogeneous sert à déterminer la manière dont les cases de la table sont dimensionnées. Si $homogeneous est une valeur vraie, les cases sont dimensionnées en fonction de la plus grande ? Si $homogeneous est une valeur fausse, la taille de la case est dictée par le plus petit widget de la ligne et le plus large de la colonne.

Les colonnes et les lignes sont disposées de 0 à n ou n est le nombre spécifié à l'appel new Gtk2::Table(). Ainsi si vous spécifiez 3 lignes et 2 colonnes, la disposition ressemblera à ceci :

 
Sélectionnez
 0          1          2
0+----------+----------+
 |          |          |
1+----------+----------+
 |          |          |
2+----------+----------+
 |          |          |
3+----------+----------+

Remarquez que l'origine du repère est en haut à gauche.

IV-B. Lier les widgets à la table

Pour placer un widget dans une case, on utilise :

 
Sélectionnez
$table->attach( $child, $left_attach, $right_attach, $top_attach, $bottom_attach,
	$xoptions, $yoptions, $xpadding, $ypadding );

Sur la gauche de l'appel de fonction, la table que vous avez créée et le premier argument est le widget que vous souhaitez placer dans la table.

Les arguments $right_attach et $left_attach déterminent où placer le widget et combien de cases utiliser. Si vous voulez un bouton dans la case droite du bas de notre table 2×2 et voulez remplir cette entrée uniquement, alors :

 
Sélectionnez
$left_attach   = 1;
$right_attach  = 2;
$top_attach    = 1;
$bottom_attach = 2;

Maintenant si vous voulez qu'un widget occupe toute la ligne supérieure de notre table, vous devrez définir :

 
Sélectionnez
$left_attach   = 0;
$right_attach  = 2;
$top_attach    = 0;
$bottom_attach = 1;

$xoptions et $yoptions servent à spécifier les options de rangements et peuvent être combinées pour permettre multiples options.

Ces options sont :

  • 'fill' si la case est plus grande que le widget et que 'fill' est spécifié, le widget s'étendra pour remplir tout l'espace vide ;
  • 'shrink' : si on a alloué moins d'espace que requis pour le widget (habituellement quand l'utilisateur redimensionne la fenêtre) alors le widget devrait normalement dépasser du fond de la fenêtre et ne plus être visible. Si 'shrink' est spécifié, les widgets seront réduits avec la table ;
  • 'expand' obligera la table à occuper tout l'espace restant dans la fenêtre.

Si vous souhaitez spécifier une de ces options, vous l'encadrez entre apostrophe comme ceci : 'option'. Mais si vous voulez combiner ces options, vous devrez les placer dans une liste anonyme comme ceci :

 
Sélectionnez
['option1', 'option2']

padding comme pour les boîtes, crée une zone claire autour du widget en pixel.

Une variation de attach() est attach_defaults() qui permet de vous dispenser des arguments $xoptions, $yoptions, $xpadding et $ypadding. Les valeurs par défaut de $xoptions et $yoptions sont ['fill', 'expand'] et celles de $xpadding et $ypadding sont initialisées à 0.

IV-C. Espacement entre lignes et colonnes

Nous avons aussi les fonctions set_row_spacing() et set_col_spacing() qui placent des espaces entre les lignes (et les colonnes), ou entre certaines lignes et certaines colonnes.

 
Sélectionnez
$table->set_row_spacing($row, $spacing);
$table->set_col_spacing($column, $spacing);

Notez que pour les colonnes, l'espace est placé à la droite de la colonne et pour les lignes, l'espace est placé dessous.

Vous pouvez aussi déclarer un espace consistant pour toutes les colonnes et les lignes avec :

 
Sélectionnez
$table->set_row_spacings($spacing);
$table->set_col_spacings($spacing);

Noter qu'avec ces appels, la dernière ligne et la dernière colonne ne reçoivent pas d'espace.

IV-D. Redimensionner une table

Si vous voulez redimensionner une table après l'avoir créée, vous pouvez utiliser :

 
Sélectionnez
$table->resize($rows, $columns);

IV-E. Déclarer la propriété homogeneous

Si vous voulez changer la valeur de la propriété homogeneous sur une table existante, vous pouvez utiliser :

 
Sélectionnez
$table->set_homogeneous($homogeneous);

L'argument $homogeneous est une valeur vraie ou fausse.

IV-F. Exemple

Ici, nous créons une fenêtre avec 3 boutons dans une table 2×2. Les deux premiers sont placés dans la ligne du haut. Un troisième, le bouton « quitter » est placé dans la ligne inférieure, occupant les deux colonnes. Cela doit ressembler à :

Image non disponible
 
Sélectionnez
#!/usr/bin/Perl -w
use Gtk2 '-init';
use constant TRUE  => 1;
use constant FALSE => 0;

# Création de la fenêtre
my $window = Gtk2::Window->new('toplevel');
$window->signal_connect( 'delete event', sub { Gtk2->main_quit; } );
$window->set_title('Table');
$window->set_border_width(20);

# Création de la table
my $table = Gtk2::Table->new( 2, 2, TRUE );
$window->add($table);

# Création du premier bouton
$button1 = Gtk2::Button->new('bouton 1');
$button1->signal_connect( 'clicked', \&ButtonClicked, 'bouton 1' );

# Insère le bouton 1 dans la case en haut à gauche de la table
$table->attach_defaults( $button1, 0, 1, 0, 1 );
$button1->show();

# Création d' un second bouton
my $button2 = Gtk2::Button->new('bouton 2');
$button2->signal_connect( 'clicked', \&ButtonClicked, 'bouton 2' );

# Insère le bouton2 dans la case en haut à droite de la table
$table->attach_defaults( $button2, 1, 2, 0, 1 );
$button2->show();

# Crée le bouton 'Quit'
my $button3 = Gtk2::Button->new('Quitter');
$button3->signal_connect( 'clicked', sub { Gtk2->main_quit; } );

# Insère le bouton''quit'' dans les deux cases du bas
$table->attach_defaults( $button3, 0, 2, 1, 2 );

# Montre les divers éléments
$button1->show();
$button2->show();
$button3->show();
$table->show();
$window->show();

# On lance la boucle principale
Gtk2->main;
### onction de rappel
sub ButtonClicked {
	my ( $button, $text ) = @_;
	print("Vous avez pressé le $text \n");
}

V. Les fenêtres

Il s'agit de présenter ici quelques méthodes supplémentaires concernant les fenêtres.

V-A. Donner un nom à la fenêtre

 
Sélectionnez
$window->set_title($title);

$title contient le nom de la fenêtre.

V-B. Déclarer le widget par défaut et choix d'un widget

Chaque fenêtre peut avoir un widget qui a « le focus », c'est-à-dire que tous les événements clavier seront redirigés vers ce widget. En général, le widget qui a le focus est entouré d'un mince cadre noir. On peut changer le widget qui a le focus en appuyant sur la touche tab.

 
Sélectionnez
$window->set_focus($widget);

Les widgets qui ont le focus peuvent être activés en pressant sur la barre d'espace.

Le widget par défaut pour une fenêtre peut être déclaré par :

 
Sélectionnez
$window->set_default($widget);

Le widget par défaut peut être activé par la touche entrée.

V-C. Déclarer l'attitude de la fenêtre

L'attitude de la fenêtre détermine comment elle s'accommode des requêtes de redimensionnement.

 
Sélectionnez
$window->set_policy($allow_shrink, $allow_grow, $auto_shrink);

L'argument $allow_shrink est une valeur vraie ou fausse déterminant si l'utilisateur peut réduire une fenêtre en deçà de la taille requise.

L'argument $allow_grow est une valeur vraie ou fausse qui détermine si l'utilisateur peut agrandir le fenêtre au delà de la taille requise.

L'argument $auto_shrink est une valeur vraie ou fausse spécifiant la fenêtre revient automatiquement à la taille précédente le changement si c'est une demande plus grande.

 
Sélectionnez
# la fenêtre est redimensionnable par l'utilisateur
$window->set_policy( FALSE, TRUE, FALSE );

# la taille de la fenêtre est contrôlée par le programme
$window->set_policy( FALSE, FALSE, TRUE );

V-D. Faire des fenêtres modales

Une fenêtre modale attire l'attention sur elle-même et reste au premier plan de telle sorte que l'utilisateur ne peut utiliser aucune autre fenêtre jusqu'à sa disparition. Les seuls événements que l'application tolère sont ceux qui concernent la fenêtre modale. Les fenêtres sans mode ne gèlent pas le reste de l'application. Les fenêtres modales servent habituellement de boites de dialogues. Pour déclarer une fenêtre modale :

 
Sélectionnez
$window->set_modal($modal);

$modal est une valeur vraie ou fausse.

V-E. Taille et position de la fenêtre

Vous pouvez déclarer la taille par défaut :

 
Sélectionnez
$window->set_default_size($width, $height);

Ou $width est la largeur et $height la hauteur de la fenêtre en pixels.

La position de la fenêtre peut être définie par :

 
Sélectionnez
$window->set_position($position);

Les différentes possibilités pour l'argument $position sont :

  • 'none' aucune influence sur le placement. Le window manager du système place la fenêtre où il veut. C'est la valeur par défaut ;
  • 'center' la fenêtre sera placée au centre de l'écran ;
  • 'mouse' la fenêtre sera placée à l'endroit où se trouve la souris.

VI. Les boutons

VI-A. Les boutons normaux

VI-A-1. Créer un bouton

Les fonctions suivantes créent un bouton :

 
Sélectionnez
$button = Gtk2::Button->new();
$button = Gtk2::Button->new($label);
$button = Gtk2::Button->new_with_label($label);
$button = Gtk2::Button->new_with_mnemonic($label);
$button = Gtk2::Button->new_from_stock($id);

La première des fonctions ci-dessus crée un bouton vide. Les deux suivantes créent un bouton avec un label. La seconde est en fait une forme raccourcie de la troisième. Si vous créez un bouton avec un label, vous pouvez utiliser

$button->child pour accéder au widget enfant. Par exemple, pour changer le texte d'un label, vous pourrez utiliser :

 
Sélectionnez
$button->child->set("new label");

La quatrième fonction ajoute à cela une nouvelle fonctionnalité. Elle permet, en plus d'afficher un label, de faire réagir le bouton à l'aide d'un raccourci clavier. La touche servant de raccourci est spécifiée dans le paramètre label. Il suffit de mettre " " devant la lettre souhaitée pour que la combinaison Alt +Touche active le bouton. Par exemple pour l'application de ce chapitre, le texte à afficher sera « Quitter » et si nous voulons que la combinaison de touches Alt + Q permette de quitter l'application, le paramètre label devra être « Quitter ».

La cinquième fonction permet de créer un bouton avec un label, un raccourci clavier et une image. Cependant, pour faire cela, GTK+ utilise-les GtkStockItem qui est une structure contenant les informations sur le label et l'image à afficher. GTK+ comporte déjà beaucoup de GtkStockItem prédéfinis (en tout cas les plus courant). Le paramètre $id est donc une chaîne identifiant le GtkStockItem à utiliser. Pour notre exemple, l'identifiant est 'gtk-close'.

VI-A-2. Les signaux des boutons

Les boutons possèdent les signaux suivants :

  • pressed : émis quand on presse le bouton de la souris alors que le pointeur est à l'intérieur du bouton ou quand la fonction $button->pressed() est appelée ;
  • released : émis quand on relâche le bouton de la souris alors que le pointeur est à l'intérieur du bouton ou quand la fonction $button->released() est appelée ;
  • clicked : émis quand le bouton de la souris est pressé puis relâché à l'intérieur du bouton ou quand la fonction $button->clicked() est appelée ;
  • enter : émis quand le pointeur de la souris entre sur le bouton ou quand la fonction $button->enter() est appelée ;
  • leave : émis quand le pointeur de la souris sort du bouton ou quand la fonction $button->leave() est appelée.

VI-A-3. Styles de relief

Les styles de relief des boutons peuvent être : 'normal', 'half', 'none'. Il est évident que le style par défaut est : 'normal'. Pour obtenir ou déclarer le style de relief, vous devrez utiliser les fonctions suivantes :

 
Sélectionnez
$button->set_relief($relief_style);
$button->get_relief();

VI-A-4. Exemple

Dans le programme suivant, je crée d'abord un bouton avec label auquel j'attache un gestionnaire de signal. à chaque fois que l'on clique sur le bouton, celui-ci est détruit et remplacé par un autre. Le but est de vous montrer les différentes possibilités.

Image non disponible Image non disponible
Image non disponible Image non disponible
 
Sélectionnez
#!/usr/bin/Perl -w
use Gtk2 '-init';
use constant TRUE  => 1;
use constant FALSE => 0;

# Nombre de click sur le bouton
my $numclicked = 0;

# Création de la fenêtre
my $window = Gtk2::Window->new("toplevel");
$window->signal_connect( "delete event", sub { Gtk2->main_quit; } );
$window->set_border_width(15);

# Création du bouton avec un texte qui est en réalité
# un label anonyme.
my $button = Gtk2::Button->new("Texte initial");
$button->signal_connect( "clicked", \&ClickedButtonEvent );
$window->add($button);
$button->show();

# montre la fenêtre
$window->show();

# boucle d'événement Gtk2
Gtk2->main;
### Routines
# fonction appelée quand le bouton est clické
sub ClickedButtonEvent {
	if ( $numclicked == 0 ) {

		# Pour avoir accès au label contenu dans le bouton,
		# on doit appeler la méthode child héritée de GtkBin
		# qui renvoie le widget contenu, ici un label.
		$button->child->set_text("Texte changé ! !");
		$numclicked++;
	}
	elsif ( $numclicked == 1 ) {

		# Au second click, on ôte le bouton présent. On en recrée
		# un que l'on peut activer grâce à la combinaison de touches
		# Alt + Q
		$window->remove($button);
		$button = Gtk2::Button->new_with_mnemonic("Appuyer sur Alt+\ Q");
		$button->signal_connect( "clicked", \&ClickedButtonEvent );
		$button->show();
		$window->add($button);
		$numclicked++;
	}
	elsif ( $numclicked == 2 ) {

		# On recommence la manipulation précédente sauf que l'on crée
		# un bouton fermer standard ! !
		$window->remove($button);
		$button = Gtk2::Button->new_from_stock('gtk-close');
		$button->signal_connect( "clicked", \&ClickedButtonEvent );
		$button->show();
		$window->add($button);
		$numclicked++;
	}
	else {
		Gtk2->main_quit;
	}
}

VI-B. Les boutons Toggle ou interrupteurs

Les boutons toggle sont dérivés des boutons normaux et sont très similaires sauf qu'ils n'ont que deux états possibles et qui change à l'aide d'un clic. Ils peuvent être remontés et quand vous cliquez, ils redescendent. Vous recliquez et ils remontent.

Les boutons toggle servent de base aux cases à cocher et aux boutons radios, au point que ces derniers héritent de beaucoup des appels utilisés pour les boutons toggle. Nous vous indiquerons lesquels quand nous les rencontreront.

VI-B-1. Créer un bouton toggle

 
Sélectionnez
$button = Gtk2::ToggleButton->new();
$button = Gtk2::ToggleButton->new_with_label($label);
$button = Gtk2::ToggleButton->new_with_mnemonic($label);

Comme vous pouvez l'imaginer, cela fonctionne exactement comme pour les boutons normaux et toutes les fonctions du widget GtkButton sont utilisables avec ce type de bouton.

Pour retrouver l'état d'un widget toggle, incluant les boutons radios et les cases à cocher, nous utilisons une construction illustrée dans l'exemple ci-dessous. Cela teste l'état du toggle en accédant au domaine actif de la structure du widget toggle. Le signal qui nous intéresse émis par un bouton toggle (les boutons toggle, radio et les cases à cocher) est le signal toggled. Pour contrôler l'état de ces boutons, déclarez un gestionnaire de signal pour attraper les signaux toggled et accéder à la structure afin de déterminer son état. Le rappel ressemblera à :

 
Sélectionnez
sub toggle_button_callback {
	$togglebutton = $_[0];
	if ( $togglebutton->get_active ) {
		# Si le contrôle arrive là, le bouton est baissé
	}
	else {
		# Si le contrôle arrive là, le bouton est levé
	}
}

VI-B-2. Changer l'état du bouton

Pour forcer l'état d'un bouton toggle, et ses enfants, les boutons radios et les cases à cocher, utilisez :

 
Sélectionnez
$togglebutton->set_active($state);

Passer une valeur vraie ou fausse comme argument précise s'il doit être en bas (pressé) ou en haut (relâché). Le défaut est en haut ou faux. Notez que si vous utilisez la fonction set_active() et que l'état est en fait changé, cela déclenchera l'émission du signal clicked par le bouton (et non toggled comme beaucoup de personnes le pensent).

Pour obtenir l'état d'un bouton, vous pouvez utiliser :

 
Sélectionnez
$togglebutton->get_active();

Plutôt que de changer la valeur d'un bouton toggle en contrôlant son état actif et en déclarant la valeur opposée, utiliser tout simplement la fonction :

 
Sélectionnez
$togglebutton->toggled();

Cette fonction émettra aussi le signal toggled.

Il existe cependant un troisième état qui n'est pas accessible en cliquant sur le bouton mais par une fonction spécifique. Ce troisième état, vous le connaissez sûrement. Le meilleur exemple est-celui des éditeurs de texte : vous avez une phrase dans laquelle il y a du texte normal et du texte en gras. Si vous sélectionnez le texte en gras, le bouton permettant justement de le mettre en gras s'enfonce (en général B). Par contre si vous sélectionnez le texte normal, ce même bouton repasse à son état relâché. Venons en au troisième état, qui apparaît lorsque vous sélectionnez la phrase entière. Le bouton ne change pas d'état mais seulement d'aspect, il donne l'impression d'être inactif.

Ce changement d'aspect doit se faire manuellement, et s'effectue avec cette fonction :

 
Sélectionnez
$bouton->set_inconsistent($setting);

Il suffit de mettre le paramètre setting à TRUE pour donner au bouton l'aspect inactif.

Et pour connaître l'aspect du bouton, il y a tout naturellement la fonction :

 
Sélectionnez
$bouton->get_inconsistent();

VI-B-3. Exemple

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use Gtk2 '-init';
use constant TRUE  => 1;
use constant FALSE => 0;

my $window = Gtk2::Window->new("toplevel");
$window->signal_connect( "delete event", sub { Gtk2->main_quit; } );
$window->set_title("Les Interrupteurs");
$window->set_border_width(0);

my $box1 = Gtk2::VBox->new( FALSE, 0 );
$box1->show();
$window->add($box1);

# On crée le bouton toggle
my $button1 = Gtk2::ToggleButton->new("Etat relâché - Aspect normal");
$box1->pack_start( $button1, FALSE, FALSE, 0 );
$button1->signal_connect( "toggled", \&OnToggled );
$button1->show();

# On crée un bouton Etat
my $button2 = Gtk2::Button->new_with_label("Changer l'état");
$box1->pack_start( $button2, TRUE, TRUE, 0 );
$button2->signal_connect( "clicked", \&OnEtatClicked, $button1 );
$button2->show();

# On crée un bouton Aspect
my $button3 = Gtk2::Button->new_with_label("Changer l'aspect");
$box1->pack_start( $button3, TRUE, TRUE, 0 );
$button3->signal_connect( "clicked", \&OnAspectClicked, $button1 );
$button3->show();

$window->show();
Gtk2->main;

sub OnEtatClicked {
	my ( $widget, $button ) = @_;

	# On récupère l'état du bouton puis on le modifie
	my $etat = $button->get_active();
	$button->set_active( $etat, TRUE );
}

sub OnAspectClicked {
	my ( $widget, $button ) = @_;

	# On récupère l'aspect du bouton puis on le modifie
	my $binconsistent = $button->get_inconsistent();
	$button->set_inconsistent( $binconsistent, TRUE );

	# On émet le signal toggled pour changer le label du bouton
	$button->toggled();
}

sub OnToggled {
	my ($widget)      = @_;
	my $binconsistent = $widget->get_inconsistent();
	my $etat          = $widget->get_active();

	# On change le texte du label
	my $label =
	  sprintf( "Etat : %s - Aspect : %s", $etat ? "enfoncé" : "relâché", $binconsistent ? "modifié" : "normal" );
	$widget->set_label($label);
}

VI-C. Les cases à cocher

Les cases à cocher héritent de beaucoup de propriétés et de fonctions des boutons toggle, mais ils ont une allure différente. Plutôt que d'être des boutons avec du texte à l'intérieur, ce sont des petites cases avec le texte à leur droite. Elles sont souvent utilisées pour choisir des options oui ou non dans une application.

Les deux fonctions de création sont les mêmes que pour un bouton normal.

 
Sélectionnez
$button = Gtk2::CheckButton->new();
$button = Gtk2::CheckButton->new($label);

Passer une chaîne en argument crée une case à cocher avec un label à son côté. Le contrôle de l'état de la case se fait de la même manière que pour le bouton toggle.

VI-D. Les boutons radio

Nous passons maintenant au widget GtkRadioButton qui se différencie des autres boutons par la possibilité d'en grouper plusieurs. De ce fait, lors que par exemple nous avons un groupe de trois boutons, il n'y en a qu'un seul qui peut être actif. On pourrait très bien faire cela avec le widget GtkCheckButton mais cela serait beaucoup plus long à programmer. Dans la hiérarchie des widgets, GtkRadioButton dérive de GtkCheckButton.

VI-D-1. Création d'un groupe de radio.

Afin de grouper les boutons radio, Gtk2-Perl utilise un tableau. Pour créer le premier bouton radio du groupe, il faut obligatoirement passer par une de ces fonctions :

 
Sélectionnez
$button = Gtk2::RadioButton->new($group,$label);
$button = Gtk2::RadioButton->new_with_mnemonic($group,$label);
$button = Gtk2::RadioButton->new_with_label_from_widget($button,$label);

$group est un référence à un tableau. Au moment de la création, le bouton radio est automatiquement ajouté au tableau.

Ensuite pour rajouter les autres boutons au groupe, il y a plusieurs possibilités. La première est d'utiliser une des trois fonctions précédentes mais ce n'est pas tout, car autant pour le premier bouton du groupe, il n'est pas nécessaire d'avoir un groupe, autant pour les autres boutons cela devient obligatoire. Pour cela, GTK+ nous fournit une fonction qui permet d'obtenir le groupe auquel les boutons du groupe sont ajoutés :

 
Sélectionnez
$group = $button->get_group();

Avec cette fonction, nous pouvons donc connaître le groupe auquel appartient le bouton, ce qui va nous permettre d'ajouter de nouveau bouton au groupe. La troisième méthode de création permet de créer un bouton radio sans avoir à se préoccuper de connaître le groupe des boutons précédents. Il suffit de connaître un des boutons du groupe.

VI-D-2. Exemple

L'exemple suivant crée un groupe de trois boutons radio et affiche celui qui est actif quand on clique sur le bouton Valider.

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use Gtk2 '-init' ;

use constant TRUE => 1 ;
use constant FALSE => 0 ;

my $window = Gtk2::Window->new("toplevel") ;
$window->signal_connect("delete event" , sub { Gtk2->main_quit ; }) ;
$window->set_title("Radio Buttons") ;
$window->set_border_width(0) ;

my $box1 = Gtk2::VBox->new(FALSE, 0) ;
$box1->show() ;

my $box2 = Gtk2::VBox->new(FALSE, 10) ;
$box2->set_border_width(10) ;
$box1->pack_start($box2, FALSE, FALSE, 0) ;
$box2->show() ;
$window->add($box1) ;

# On crée un premier bouton radio que l'on place
# dans la boîte.
my $button1 = Gtk2::RadioButton->new(undef, "bouton 1") ;
$box2->pack_start($button1, FALSE, FALSE, 0) ;
$button1->show() ;

# On récupère le groupe auquel appartient le premier bouton.
my $group = $button1->get_group() ;

# On crée un autre bouton radio que l'on rattache au groupe.
my $button2 = Gtk2::RadioButton->new_with_mnemonic($group, "bouton 2") ;

# On veut que ce bouton soit le bouton actif par défaut.
$button2->set_active(TRUE) ;
$box2->pack_start($button2, TRUE, TRUE, 0) ;
$button2->show() ;

# Même chose mais sans passer par la variable $groupe.
my $button3 = Gtk2::RadioButton->new_with_label_from_widget($button1,"bouton 3") ;
$box2->pack_start($button3, TRUE, TRUE, 0) ;
$button3->show() ;

# Un séparateur pour l'esthétique
my $separator = Gtk2::HSeparator->new() ;
$box1->pack_start($separator, FALSE, FALSE, 0) ;
$separator->show() ;

## Le bouton valider qui lance une routine dont l'objectif
# est de nous indiquer lequel des boutons est actif.
my $button_close = Gtk2::Button->new_from_stock('gtk-ok') ;
$button_close->signal_connect("clicked" ,\&valider,$button1) ;
$box1->pack_start($button_close, TRUE, TRUE, 0) ;
$button_close->can_default(TRUE) ;
$button_close->grab_default() ;
$button_close->show() ;
$window->show() ;

Gtk2->main ;

sub valider{
	my ($widget,$button)=@_ ;

	# On récupère la référence du groupe auquel appartient le bouton
	my $group = $button->get_group() ;
	my $label ;
	# On parcours le tableau des boutons et on affiche le bouton qui est actif.
	my $but ;
	foreach $but (@$group) {
		if ($but->get_active) {
			# On récupère le label du bouton actif
			$label = $but->get_label() ;
		}
	}
	# On affiche le tout dans une petite fenêtre de dialogue. Il faut tout d'abord
	# remonter jusqu'à la fenêtre mère.
	my $window = $widget->get_toplevel() ;
	
	# On peut alors créer notre fenêtre de dialogue. Nous verrons son fonctionnement plus tard.
	my $dialog = Gtk2::MessageDialog->new ($window,'modal' ,'info' ,'ok' , sprintf "Le %s est actif !" ,$label) ;
	$dialog->run ;
	$dialog->destroy ;
}

VII. Les labels

VII-A. Ce qui n'a pas changé !

Dans toute cette section, nous verrons les fonctions qui existaient déjà dans GtkPerl, puis dans la section suivante, nous découvrirons les apports de Pango.

Les labels sont très utilisés avec GTK et sont relativement simples. Les labels n'émettent aucun signal puisqu'ils ne sont pas associés à une fenêtre X. Si vous avez besoin d'attraper des signaux, placez les dans une boîte à événement ou dans un bouton.

Pour créer un nouveau label, utilisez :

 
Sélectionnez
$label = Gtk2::Label->new ($string);
$label = Gtk2::Label->new_with_mnemonic ($string);

Le seul argument est la chaîne que vous voulez afficher.

Pour changer le texte du label après sa création, utilisez la fonction :

 
Sélectionnez
$label->set_text($string);

Encore une fois, l'argument est la nouvelle chaîne.

L'espace utilisé pour la nouvelle chaîne sera ajusté automatiquement si nécessaire. Vous pouvez produire des labels multi-lignes en plaçant des caractères de fin de ligne dans la chaîne.

Pour retrouver la chaîne courante, utilisez :

 
Sélectionnez
$label->get();

Le texte du label peut être justifié avec :

 
Sélectionnez
$label->set_justify($jtype);

Les valeurs de $jtype sont :

 
Sélectionnez
'left'
'right'
'center' (default)
'fill'

Le widget label est également capable de faire automatiquement les césures du texte en fin de ligne. Cela peut être activé par :

 
Sélectionnez
$label->set_line_wrap($wrap);

L'argument $wrap est une valeur vraie ou fausse.

Si vous voulez un label souligné, vous pouvez déclarer un patron pour le label :

 
Sélectionnez
$label->set_pattern($pattern);

L'argument pattern indique comment souligner le texte. Cela consiste en une chaîne de caractère et d'espace. Le caractère indique les caractères du label à souligner. Par exemple , la chaîne « _ _ _ _ _ _ » soulignera le premier, le troisième, le cinquième, le septième et le neuvième caractères. Notez toutefois, qu'il ne faut pas confondre ce caractère de soulignement avec celui créé par la fonction new_with_mnemonic().

VII-A-1. Exemple

Voici un court exemple qui illustre ces fonctions. Il utilise le widget cadre pour mieux mettre en valeur les styles de labels. Vous pouvez l'ignorer pour le moment car le widget cadre sera expliqué plus tard.

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use Gtk2 '-init' ;

use constant TRUE => 1 ;
use constant FALSE => 0 ;

use strict ;

my $window = Gtk2::Window->new("toplevel") ;
$window->signal_connect("destroy" , sub { Gtk2->main_quit ; }) ;
$window->set_title("Les labels") ;
$window->set_border_width(5) ;

my $hbox = Gtk2::HBox->new(FALSE, 5) ;
$window->add($hbox) ;

my $vbox1 = Gtk2::VBox->new(FALSE, 5) ;
$hbox->pack_start($vbox1, FALSE, FALSE, 0) ;

my $label1 = Gtk2::Label->new("Ceci est un label normal") ;
my $frame1 = Gtk2::Frame->new("Label normal") ;
$frame1->add($label1) ;
$vbox1->pack_start($frame1, FALSE, FALSE, 0) ;

my $label2 = Gtk2::Label->new("Ceci est un label multi-lignes.\n Seconde ligne\n"
  . "Troisième Line") ;
my $frame2 = Gtk2::Frame->new(" Label multi-lignes ") ;
$frame2->add($label2) ;
$vbox1->pack_start($frame2, FALSE, FALSE, 0) ;

my $label3 = Gtk2::Label->new("Ceci est un label justifié à gauche. \n"
  ."Multi-lignes\n"
  ."Troisième ligne") ;
$label3->set_justify('left') ;

my $frame3 = Gtk2::Frame->new("Label justifié à gauche") ;
$frame3->add($label3) ;
$vbox1->pack_start($frame3, FALSE, FALSE, 0) ;

my $label4 = Gtk2::Label->new("Ceci est un label justifié à droite.\n "
  . "Multi-lignes.\n"
  . "Troisième ligne.") ;
$label4->set_justify('right') ;
my $frame4 = Gtk2::Frame->new("Label justifié à droite") ;
$frame4->add($label4) ;
$vbox1->pack_start($frame4, FALSE, FALSE, 0) ;

my $vbox2 = Gtk2::VBox->new(FALSE, 5) ;
$hbox->pack_start($vbox2, FALSE, FALSE, 0) ;
my $frame5 = Gtk2::Frame->new("Label avec l'option line wrap") ;
my $label5 = Gtk2::Label->new("Ceci est un exemple de césure de ligne. "
  . "Cela ne devrait pas prendre la largeur totale "
  . "allouée mais fait passer les mots à la ligne "
  . "automatiquement. "
  . " Cela supporte correctement plusieurs "
  . "paragraphes et ajoute également "
  . "plusieurs espaces. ") ;
$label5->set_line_wrap(TRUE) ;
$frame5->add($label5) ;
$vbox2->pack_start($frame5, FALSE, FALSE, 0) ;

my $frame6 = Gtk2::Frame->new("Label avec l'opton line wrap et fill") ;
my $label6 = Gtk2::Label->new(" Ceci est un exemple de césure de ligne.L'option fill, "
  . "fait que le texte prend "
  . "toute la largeur disponible. "
  . "Voici une phrase qui le prouve. "
  . "Et en voici une autre. "
  . "Here comes the sun, do de do de do.\n"
  . " Ceci est un nouveau paragraphe\n"
  . " Un autre nouveau , plus long, meilleur. "
  . "On arrive à la fin, "
  . "malheureusement.") ;
$label6->set_justify('fill') ;
$label6->set_line_wrap(TRUE) ;
$frame6->add($label6) ;
$vbox2->pack_start($frame6, FALSE, FALSE, 0) ;

my $frame7 = Gtk2::Frame->new("Label souligné") ;
my $label7 = Gtk2::Label->new("Voici un label souligné !\n"
  . "Celui- est souligné à la mode funky ") ;
$label7->set_justify('left') ;
$label7->set_pattern(" "
  . " ") ;
$frame7->add($label7) ;
$vbox2->pack_start($frame7, FALSE, FALSE, 0) ;

$window->show_all() ;

Gtk2->main ;

VII-B. L'apport de Pango

La gestion des caractères est, en C, l'œuvre de la bibliothèque Pango. En Perl, à quelques exceptions prêtes, nous n'avons pas à nous préoccuper de cette librairie, Gtk2-Perl fait le travail pour nous.

VII-B-1. Formatage de texte

Nous allons maintenant voir comment modifier l'apparence de notre texte (police, couleur, …). Pour notre application test, nous allons afficher une ligne en Courier gras taille 10, une ligne en Times New Roman italique bleu taille 12, et une ligne en Verdana souligné taille 16. Et tout cela centré (bien sûr).

VII-B-2. Définition du format

Pour définir le format du texte, il suffit d'insérer des balises à l'intérieur même de notre texte.

VII-B-3. Les balises rapides

Les balises rapides servent à mettre le texte en gras, italique ou autre de manière très simple. Voici l'intégralité de ces balises :

  • <b> met le texte en gras ;
  • <big> augmente légèrement la taille du texte ;
  • <i> met le texte en italique ;
  • <s> barre le texte ;
  • <sub> met le texteen indice ;
  • <sup> met le texteen exposant ;
  • <small> diminue légèrement la taille du texte ;
  • <tt> met le texte en télétype ;
  • <u> souligne le texte.

Pour utiliser ces balises, il suffit d'encadrer le texte à modifier des balises de formatage. Par exemple, pour mettre le texte en gras, il faudra entrer : <b>Gras</b>. Pour que le label prenne en compte la mise en forme vous devez utiliser :

 
Sélectionnez
$label->set_markup($texte_avec_balises);

VII-B-4. La balise <span>

Cette fois-ci, nous allons étudier la balise <span> en détail. Avec celle-ci, nous allons pouvoir changer la police de caractères, la couleur du texte et bien d'autres choses.

Cette balise s'utilise comme les précédentes si ce n'est qu'elle accepte des paramètres. La liste ci-dessous présente tous les paramètres et les effets sur le texte.

  • font_family pour définir la police à utiliser. Si la valeur donnée à ce paramètre est incorrecte, la police par défaut (Sans Serif) sera utilisée ;
  • face identique à font_family
  • size pour définir la taille du texte (par défaut 10) ;
  • style pour définir le style du texte. Trois valeurs possibles : normal, oblique, italic. Exemple : <span style='oblique'>Oblique</span> ;
  • font_desc permet de combiner les paramètres précédents en un seul ;
  • weight permet de définir l'épaisseur des lettres. Les valeurs peuvent être ultralight, light, normal, bold, utrabold, heavy ou encore une valeur numérique. (Vous remarquerez qu'il n'y a que peu ou pas de différence) ;
  • variant pour définir si l'on veut du texte normal (normal) ou en petite majuscule smallcaps) ;
  • stretch permet d'étirer le texte. La valeur de ce paramètre peut être : ultracondensed, extracondensed, condensed, semicondensed, normal, semiexpanded, expanded, extraexpanded, ultraexpanded ;
  • foreground pour définir la couleur du texte. Il faut donner la valeur hexadécimale de la palette RVB ;
  • background pour définir la couleur du fond. Il faut donner la valeur hexadécimale de la palette RVB ;
  • underline pour souligner le texte ;
  • rise pour déplacer le texte verticalement ;
  • strikethrough pour barrer le texte, par exemple : <span strikethrough='true'>Striketrough = "true"</span> ;
  • lang pour indiquer la langue du texte.

Tous ces paramètres peuvent être mis à l'intérieur d'une seule balise <span>.

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use strict ;
use Gtk2 '-init' ;

# Variables convenables pour vrai et faux
use constant TRUE => 1 ;
use constant FALSE => 0 ;

my $window = Gtk2::Window->new("toplevel") ;
$window->signal_connect("destroy" , sub { Gtk2->main_quit ; }) ;
$window->set_title("Les labels avec style") ;
$window->set_border_width(5) ;
$window->set_default_size(320 ,100) ;

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

my $label1 = Gtk2::Label->new() ;
$label1->set_markup("<span face='Courier New'><b>Courier New 10 Gras</b></span>\n") ;

my $label2 = Gtk2::Label->new() ;
$label2->set_markup("<span font desc='Times New Roman italic 12' foreground='#0000FF'>Times New Roman
12 Italique</span>\n") ;

my $label3 = Gtk2::Label->new() ;
$label3->set_markup("<span font desc='Sans 16' foreground='#00FF00'><u>Sans 16 Souligné</u></span>") ;

$vbox->pack_start($label1, FALSE, FALSE, 0) ;
$vbox->pack_start($label2, FALSE, FALSE, 0) ;
$vbox->pack_start($label3, FALSE, FALSE, 0) ;
$label1->show() ;
$label2->show() ;
$label3->show() ;

$window->show() ;

Gtk2->main ;

VIII. Les widgets de réglage

VIII-A. Les ajustements

GTK possède plusieurs widgets qui peuvent être ajustés visuellement par l'utilisateur à l'aide de la souris ou du clavier, comme le widget range. Il y a aussi quelques widgets qui mettent à disposition des portions ajustables pour une plus grande surface de données comme les widgets texte et viewport.

Évidemment, une application a besoin d'être capable de réagir aux changements que l'utilisateur. Une manière de le faire serait que chaque widget émette son propre type de signal quand son réglage change et même passe la nouvelle valeur au gestionnaire de signal, ou lui demande de regarder à l'intérieur des données du widget afin de s'informer de la valeur. Vous pouvez aussi vouloir connecter les réglages de plusieurs widgets ensembles afin que les réglages de l'un règle les autres. L'exemple le plus simple de ce phénomène est de connecter une barre de défilement à une zone de texte ou à un « panning viewport ». Si chaque widget possède son propre moyen de déclarer ou de récupérer la valeur du réglage alors le programmeur doit écrire leurs propres gestionnaires de signal pour faire la traduction entre la sortie du signal d'un widget et l'entrée d'une autre fonction déclarant les réglages.

GTK résout ce problème en utilisant l'objet Adjustment qui n'est pas vraiment un widget mais un moyen pour les widgets de stocker et de passer des informations de réglages sous une forme concise et flexible. L'usage le plus évident de l'ajustement est de stocker les paramètres de configuration et les valeurs des widgets « Range » comme les barres de défilement et les contrôles d'échelle. Quoi qu'il en soit, puisque les Adjustments sont dérivés de la classe Objet, ils possèdent des pouvoirs spéciaux supérieurs à ceux des structures de données normales. Plus important, ils peuvent émettre des signaux tout comme les widgets, et ces signaux peuvent être utilisés non seulement pour permettre à vos programmes de réagir aux entrées utilisateurs sur certains widgets réglables mais également pour propager les valeurs des réglages de manière transparente entre les widgets réglables. Malheureusement, les réglages peuvent être difficiles à comprendre, c'est pourquoi il est possible que vous ne voyiez pas bien le rôle des réglages avant d'avoir vu les widgets qui les utilisent comme les barres de progression, les viewport, les barres de défilement.

VIII-A-1. Créer des Ajustements

Beaucoup de widgets qui utilisent les objets de réglage le font automatiquement mais dans certains cas que nous verrons plus loin, vous devrez les créer vous-même avec :

 
Sélectionnez
Gtk2::Adjustment->new($value,$lower,$upper,$step_increment,$page_increment,$page_size);

L'argument $value est la valeur initiale que vos voulez donner à vos réglages, correspondant habituellement au point le plus haut et le plus à gauche du widget réglable.

L'argument lower spécifie la valeur la plus basse que le réglage peut prendre.

L'argument $step_increment spécifie le plus petit des deux incréments avec lequel l'utilisateur peut changer la valeur alors que page_increment est le plus grand.

L'argument $page_size correspond habituellement à la zone visible d'un widget.

L'argument $upper est utilisé pour représenter les coordonnées du point le plus au fond et le plus à droite dans l'enfant du widget « contenant ». Par conséquent, ce n'est pas toujours la plus grande valeur que $value puisse prendre puisque $page_size est en général non nul.

VIII-A-2. Utiliser les ajustements : la manière facile

Les widgets réglables peuvent être grossièrement divisés entre ceux qui utilisent et nécessitent des unités spécifiques pour ces valeurs et ceux qui les traitent comme des nombres arbitraires. Le second groupe inclut les widgets range (les barres de défilements, les échelles, les barres de progression et les boutons spins). Ces widgets sont tous typiquement ajustés directement par l'utilisateur à l'aide du clavier ou de la souris. Ils traiteront les valeurs lower et upper du réglage comme un intervalle à l'intérieur duquel on peut manipuler la valeur du réglage. Par défaut, ils ne modifieront que la valeur du réglage.

L'autre groupe comprend les widgets texte, viewport, listes composées et la fenêtre défilable. Ce sont des widgets typiquement ajustés « indirectement » à l'aide de barres de défilement. Alors que les widgets qui utilisent des réglages peuvent ou bien créer les leurs, ou bien utiliser ceux fournis, vous voudrez en général laisser cette catégorie de widget créer ses propres réglages. Habituellement, ils refuseront finalement toutes les valeurs, sauf la $value elle-même, que vous leur donner mais les résultats sont en général indéfinis (ce qui signifie que vous aurez à lire les codes sources et ils peuvent être différents de widget à widget).

VIII-A-3. Les ajustements internes

OK, vous vous dîtes, c'est bien ! Mais que se passe-t-il si vous voulez créer votre propre gestionnaire pour répondre quand l'utilisateur règle un widget range ou un bouton spin ? Et comment on obtient-on la valeur de réglage pour ces gestionnaires ? En Gtk2-Perl, vous utiliserez :

 
Sélectionnez
get_adjustment->adjustment_name ;

Ou adjustment_name est au choix :

 
Sélectionnez
value
lower
upper
step_increment
page_increment
page_size

Puisque, quand vous déclarez la valeur d'un réglage, vous voulez généralement que ce changement soit répété à tous les widgets qui utilisent ce réglage, GTK fournit la fonction suivante :

 
Sélectionnez
$adjustment->set_value($value);

Comme mentionné précédemment, les réglages sont capables d'émettre des signaux. C'est bien sûr la raison pour laquelle les mises à jour se produisent automatiquement quand vous partagez un objet Adjustment entre une barre de défilement et tout autre widget réglable ; tous les widgets réglables connectent des gestionnaires de signaux à leur signal d'ajustement 'value_changed', autant que le peut votre programme.

Les divers widgets qui utilisent l'objet Adjustment émettront ce signal sur un réglage à partir du moment ou il change sa valeur. Cela se produit à la fois quand l'entrée utilisateur oblige le curseur à glisser sur le widget range, aussi bien que quand le programme change explicitement la valeur par un set_value(). Ainsi, par exemple, si vous utilisez un widget échelle et que vous voulez changer l'angle de rotation d'une image, quels que soient ces changements de valeurs, vous créerez un rappel comme celui-ci :

 
Sélectionnez
sub cb_rotation_image
{
	($adjustment, $image)=@_;
	$image->rotation($adjustment->get_adjustment->value);
	...
}

Et vous le connectez au réglage du widget échelle comme ceci :

 
Sélectionnez
$adjustment->signal_connect("value_changed",\&cb_rotation_image,$image);

Que se passe-t-il quand un widget reconfigure les champs upper et lower de ses réglages, comme, par exemple, quand l'utilisateur ajoute du texte à un widget texte ? Dans ce cas, il émet le signal 'changed'.

Les widgets Range connectent typiquement un gestionnaire pour ce signal ce qui change leur apparence pour refléter ce changement, par exemple la taille du curseur dans la barre de défilement grandira ou rétrécira en proportion inverse de la différence entre les valeurs upper et lower de ses réglages. Vous n'aurez probablement jamais besoin d'attacher un gestionnaire à ce signal sauf si vous écrivez un nouveau type de widget range. Quoi qu'il en soit, si vous changez les valeurs d'un réglage directement, vous devrez émettre ce signal sur lui pour reconfigurer n'importe quel widget qui l'utilise, comme ceci :

 
Sélectionnez
$adjustment->signal_emit("changed");

VIII-B. Les widgets Range

La catégorie des widgets Range inclut la barre de défilement et la moins commune glissière. Bien que ces deux widgets soient généralement utilisés dans des buts différents, ils sont presque similaires en fonction et en implémentation. Tous les widgets Range partagent un ensemble d'éléments graphiques, chacun possédant sa propre fenêtre X et recevant les événements. Ils contiennent tous une glissière et un curseur (parfois appelé « thumbwheel » dans d'autres environnements GUI). Prendre le curseur avec le pointeur de la souris le promène le long de la glissière alors que cliquer dans la glissière avance le curseur en direction du lieu du clic, soit complètement soit d'une quantité fixée dépendant du bouton de la souris utilisé.

Comme mentionné précédemment, tous les widgets Range sont associés à un objet Adjustment à partir duquel ils calculent la longueur du curseur et sa position dans la glissière. Quand l'utilisateur manipule le curseur, le widget range changera la valeur de l'ajustement.

VIII-B-1. Les fonctions communes

Le widget Range a une gestion interne plutôt compliquée mais comme tous les widgets de la « classe de base », cette complexité n'est intéressante que si vous voulez la programmer. Ainsi, presque toutes les fonctions et les signaux qu'il définit, ne sont réellement utilisés qu'en écrivant des widgets dérivés.

La politique de mise à jour d'un widget Range définie à quel point, durant l'interaction de l'utilisateur, il changera le $value de son réglage et émettra le signal $value_changed de ce réglage. Les politiques de mises à jour sont :

  • 'continuous' : c'est le défaut. Le signal 'value_changed' est émis continuellement même si le curseur n'est bougé que d'une valeur minuscule.
  • 'discontinuous' : le signal 'value_changed' est émis uniquement une fois que le curseur ne bouge plus et que l'utilisateur a relâché le bouton de la souris.
  • 'delayed' : le signal 'value_changed' est émis quand l'utilisateur relâche le bouton de la souris ou si le curseur s'arrête pendant une courte période de temps.

Ces politiques de mise à jour peuvent être déclarées à l'aide de la fonction :

 
Sélectionnez
$range->set_update_policy($policy);

Obtenir et déclarer l'ajustement « au vol » se fait par :

 
Sélectionnez
$range->get_adjustment();
$range->set_adjustment($adjustment);

$range->get_adjustment() retourne l'ajustement auquel $range est attaché.

set_adjustment() ne fait absolument rien si vous lui passez l'ajustement que Range utilise déjà, sans regarder si vous avez changé l'un des champs ou non. Si vous lui passez un nouvel ajustement, il ôtera la référence à l'ancien, s'il existe (peut-être en le détruisant), connectera les signaux appropriés au nouveau et appellera la fonction privée adjustment_changed qui recalculera (ou du moins est supposée recalculer) la taille et/ou la position du curseur et le redessinera si nécessaire. Comme mentionné dans la section sur les réglages, si vous souhaitez réutiliser le même ajustement, il vous faudra émettre le signal 'changed' quand vous modifiez sa valeur, à l'aide de :

 
Sélectionnez
$adjustment->signal_emit ("changed");

VIII-C. Souris et clavier

Tous les widgets Range de GTK réagissent aux clics de souris plus ou moins de la même manière. Cliquer avec le bouton 1 dans la glissière fera que le page_increment de l'ajustement sera ajouté ou soustrait à sa value. et que le curseur sera bougé en conséquence. Cliquer sur le bouton 2 dans la glissière fera sauter le curseur jusqu'à l'endroit du clic. Cliquer sur l'une des flèches de la barre de défilement provoquera un changement de la valeur du réglage égal au step_increment. Cela peut prendre un certain temps à maîtriser, mais par défaut, les barres de défilement, tout comme les widgets échelles, peuvent être manipulés au clavier (état « focus ») en GTK. Si vous pensez que l'utilisateur sera dérouté, vous pouvez toujours rendre cette option indisponible en invalidant le flag « focus » de la barre de défilement.

 
Sélectionnez
$scrollbar->unset_flags('focus');

Les comportements claviers (qui ne sont bien sûr disponible que si le flag « focus » est actif) sont, pour des raisons évidentes, légèrement différents en fonction des widgets Range horizontaux et verticaux. Ils ne sont d'ailleurs pas tout à fait les mêmes selon qu'il s'agisse d'un widget échelle ou d'une barre de défilement, pour des raisons un peu moins évidentes (peut-être pour éviter la confusion entre les touches pour la barre horizontale et ceux pour la barre verticale dans les fenêtres défilables, qui, chacune, opèrent dans le même domaine).

Tous les widgets Range verticaux peuvent être modifiés à l'aide des flèches vers le haut et vers le bas du clavier, aussi bien qu'avec les touches Page up et Page down. Les flèches montent et descendent le curseur selon le step_increment alors que Page up et Page down le modifie selon le page_increment. L'utilisateur peut aussi déplacer le curseur tout en haut ou tout en bas de la glissière à l'aide du clavier. Avec le widget VScale, c'est fait avec les touches Home et End alors qu'avec VScrollbar c'est fait en tapant Control Page up et Control Page down.

Les flèches droite et gauche agissent comme on peut s'y attendre sur le widget Range en bougeant le curseur selon le step_increment. Les touches Home et End bougent le curseur au début ou à la fin de la glissière. Pour le HScale widget, bouger le curseur selon le page_increment se fait à l'aide des touches Control Right et Control Left alors que pour le HScrollbar c'est Control Home et Control End.

VIII-D. Exemple

Cet exemple crée une fenêtre avec trois widgets Range, tous connectés au même réglage, et un couple de contrôle pour régler quelques-uns des paramètres mentionnés ci-dessus dans la section sur les réglages. Ainsi, vous pourrez voir comment ils affectent la manière dont ces widgets fonctionnent pour l'utilisateur.

Image non disponible
 
Sélectionnez
#!/usr/bin/Perl -w

use strict ;

use Gtk2 '-init' ;

use constant TRUE => 1 ;
use constant FALSE => 0 ;

# Création standard de la fenêtre
my $window = Gtk2::Window->new("toplevel") ;
$window->set_title("Les ajustements, les glissières et les barres de défilement") ;
$window->signal_connect("destroy" , sub { Gtk2->main_quit ; }) ;

# Une première boîte verticale
my $box1 = Gtk2::VBox->new(FALSE, 0) ;
$window->add($box1) ;
$box1->show() ;

# Une nouvelle boîte horizontale
my $box2 = Gtk2::HBox->new(TRUE, 10) ;
$box2->set_border_width(10) ;
$box1->pack_start($box2, TRUE, TRUE, 0) ;
$box2->show() ;

# Création du ajustement avec les arguments
# value, lower, upper, step increment, page_increment, page_size
# Notez que la valeur de page_size fait seulement une différence pour
# les widgets scrollbar , et la plus haute valeur que nous obtiendrons est
# (upper - page_size).
my $adj1 = Gtk2::Adjustment->new(0.0 , 0.0 , 100.0 , 0.1 , 1.0 , 1.0) ;

# Une glissière verticale
my $vscale1 = Gtk2::VScale->new($adj1) ;
$box2->pack_start($vscale1, TRUE, TRUE, 0) ;
$vscale1->show() ;

# Une troisième boîte verticale
my $box3 = Gtk2::VBox->new(FALSE, 10) ;
$box2->pack_start($box3, TRUE, TRUE, 0) ;
$box3->show() ;

# Réutilise le même ajustement
my $hscale1 = Gtk2::HScale->new($adj1) ;
$hscale1->set_size_request(200 , 100) ;
$box3->pack_start($hscale1, TRUE, TRUE, 0) ;
$hscale1->show() ;

# Réutilise encore le même ajustement pour une barre de défilement
my $scrollbar = Gtk2::HScrollBar->new($adj1) ;

# Notez que cela implique que les échelles seront mises à jour
# continuellement quand la barre de défilement est bougée
$scrollbar->set_update_policy('continuous') ;
$box3->pack_start($scrollbar, TRUE, TRUE, 0) ;
$scrollbar->show() ;

# Une nouvelle boîte
my $box4 = Gtk2::HBox->new(FALSE, 10) ;
$box4->set_border_width(10) ;
$box1->pack_start($box4, TRUE, TRUE, 0) ;
$box4->show() ;

# Une case à cocher pour contrôler si la valeur des glissières
# est affichée ou non
my $button1 = Gtk2::CheckButton->new("Affichage de la valeur des widgets range") ;
$button1->set_active(TRUE) ;
$button1->signal_connect("toggled" , \&cb_draw_value) ;
$box4->pack_start($button1, TRUE, TRUE, 0) ;
$button1->show() ;

# Une nouvelle boite
my $box5 = Gtk2::HBox->new(FALSE, 10) ;
$box5->set_border_width(10) ;
# Une option du menu pour changer la position de la valeur.
my $label = Gtk2::Label->new("Position de la valeur numérique :") ;
$box5->pack_start($label, FALSE, FALSE, 0) ;
$label->show() ;
# Un menu à option que nous documenterons plus tard
my $opt = Gtk2::OptionMenu->new() ;
my $menu = Gtk2::Menu->new() ;
# Premier élément du menu
my $item = make_menu_item("en haut" , \&cb_pos_menu_select, 'top') ;
$menu->append($item) ;
# Second élément du menu
$item = make_menu_item("en bas" , \&cb_pos_menu_select, 'bottom') ;
$menu->append($item) ;
# Troisième élément du menu
$item = make_menu_item("à gauche" , \&cb_pos_menu_select, 'left') ;
$menu->append($item) ;
# Quatrième élément du menu
$item = make_menu_item("à droite" , \&cb_pos_menu_select, 'right') ;
$menu->append($item) ;
# On place de tout dans le menu puis dans la boîte
$opt->set_menu($menu) ;
$box5->pack_start($opt, TRUE, TRUE, 0) ;
$opt->show() ;

$box1->pack_start($box5, TRUE, TRUE, 0) ;
$box5->show() ;

# Une nouvelle boîte
my $box6 = Gtk2::HBox->new(FALSE, 10) ;
$box6->set_border_width(10) ;
# Une autre option du menu, cette fois-ci pour changer la politique
# de mise à jour des widgets échelles
my $label2 = Gtk2::Label->new("Option de mise à jour :") ;
$box6->pack_start($label2, FALSE, FALSE, 0) ;
$label2->show() ;
# Encore un menu à option
my $opt2 = Gtk2::OptionMenu->new() ;
my $menu2 = Gtk2::Menu->new() ;
# Premier élément du menu
$item = make_menu_item ("Continu" , \&cb_update_menu_select, 'continuous') ;
$menu2->append($item) ;
# Second élément du menu
$item = make_menu_item ("Discontinu" , \&cb_update_menu_select, 'discontinuous') ;
$menu2->append($item) ;
# Troisième élément du menu
$item = make_menu_item ("Retardé" , \&cb_update_menu_select, 'delayed') ;
$menu2->append($item) ;
# On place le tout dans le menu puis dans la boîte
$opt2->set_menu($menu2) ;
$box6->pack_start($opt2, TRUE, TRUE, 0) ;
$opt2->show() ;
$box1->pack_start($box6, TRUE, TRUE, 0) ;
$box6->show() ;

# Une nouvelle boîte
my $box7 = Gtk2::HBox->new(FALSE, 10) ;
$box7->set_border_width(10) ;

# Un widget HScale pour ajuster le nombre de digits
# sur les exemple d'échelle.
my $label3 = Gtk2::Label->new("Nombre de décimales :") ;
$box7->pack_start($label3, FALSE, FALSE, 0) ;
$label3->show() ;

# On crée un ajustement pour le nombre de décimales
my $adj2 = Gtk2::Adjustment->new(1.0 , 0.0 , 5.0 , 1.0 , 1.0 , 0.0) ;
$adj2->signal_connect("value changed" , \&cb_digits_scale) ;
# puis on l'utilise avec le widget HScale
my $scale2 = Gtk2::HScale->new($adj2) ;
$scale2->set_digits(0) ;
$box7->pack_start($scale2, TRUE, TRUE, 0) ;
$scale2->show() ;
$box1->pack_start($box7, TRUE, TRUE, 0) ;
$box7->show() ;

# Une nouvelle boîte horizontale
my $box8 = Gtk2::HBox->new(FALSE, 10) ;
$box8->set_border_width(10) ;

# Et un dernier widget HScale pour ajuster la taille du curseur de la scrollbar
my $label4 = Gtk2::Label->new("Taille du curseur de la barre de défilement :") ;
$box8->pack_start($label4, FALSE, FALSE, 0) ;
$label4->show() ;

# Un troisième objet ajustement pour la taille du curseur
my $adj3 = Gtk2::Adjustment->new(1.0 , 1.0 , 100.0 , 1.0 , 1.0 , 0.0) ;
$adj3->signal_connect("value changed" , \&cb_page_size, $adj1) ;
# puis on l'utilise avec le widget HScale
my $scale3 = Gtk2::HScale->new($adj3) ;
$scale3->set_digits(0) ;
$box8->pack_start($scale3, TRUE, TRUE, 0) ;
$scale3->show() ;
$box1->pack_start($box8, TRUE, TRUE, 0) ;
$box8->show() ;

my $separator = Gtk2::HSeparator->new() ;

$box1->pack_start($separator, FALSE, TRUE, 0) ;
$separator->show() ;

# Le bouton pour quitter
my $button = Gtk2::Button->new_from_stock("gtk-quit") ;
$button->signal_connect("clicked" , sub { Gtk2->main_quit ; }) ;
$box1->pack_start($button, TRUE, TRUE, 0) ;
$button->can_default(TRUE) ;
$button->grab_default() ;
$button->show() ;
$window->show() ;


Gtk2->main ;

### Fonctions de rappels
sub cb_pos_menu_select
{
	my ($item, $pos) = @_ ;
	# Déclare le valeur de la position pour chaque widget scale
	$hscale1->set_value_pos($pos) ;
	$vscale1->set_value_pos($pos) ;
}

sub cb_update_menu_select
{
	my ($item, $policy) = @_ ;
	# Déclare la politique de mise à jour pour chaque widget scale
	$hscale1->set_update_policy($policy) ;
	$vscale1->set_update_policy($policy) ;
}

sub cb_digits_scale
{
	my ($adj) = @_ ;
	# Déclare le nombre de décimale pour adj->value
	$hscale1->set_digits($adj->value) ;
	$vscale1->set_digits($adj->value) ;
}

sub cb_page_size
{
	my ($get, $set) = @_ ;

	# Déclare le page_size and page_increment size pour l'ajustement
	# à la valeur spéficiée ar la "Page Size" de l'échelle
	$set->page_size($get->value) ;
	$set->page_increment($get->value) ;

	# émet maintenant le signal "changed" pour reconfigurer tous les widgets
	# qui sont liés à ce réglage
	$set->signal_emit("changed") ;
}

sub cb_draw_value
{
	my ($button) = @_ ;
	# ranche la valeur de l'affichage, débranche les widgets échelle
	# selon l'état de la cases à cocher
	$hscale1->set_draw_value($button->get_active) ;
	$vscale1->set_draw_value($button->get_active) ;
}

# Fonctions pratiques
sub make_menu_item
{
	my ($name, $callback, $data) = @_ ;
	my $item ;
	$item = Gtk2::MenuItem->new($name) ;
	$item->signal_connect("activate" , $callback, $data) ;
	$item->show() ;
	return $item ;
}

sub scale_set_default_values
{
	my ($scale) = @_ ;
	$scale->set_update_policy('continuous') ;
	$scale->set_digits(1) ;
	$scale->set_value_pos('top') ;
	$scale->set_draw_value(TRUE) ;
}

IX. Les menus

Nous allons cette fois voir quelles sont les solutions que Gtk2-Perl met à notre disposition pour ajouter un menu à une fenêtre. Nous parlons de « solutions » au pluriel, car il existe en fait deux méthodes possibles à la création d'un menu, l'une étant plus rapide que l'autre.

Ce chapitre est composé de trois parties distinctes :

  • une première partie traitant de la création d'un menu classique par la méthode la plus longue ;
  • une deuxième partie dans laquelle nous verrons des éléments spéciaux d'un menu ;
  • la troisième partie abordera la création d'un menu par la méthode rapide.

IX-A. Le menu - Méthode longue.

Pour construire un tel menu, nous allons utiliser pas moins de trois widgets différents qui serviront tous à différentes étapes de la création. Ces widgets sont MenuBar, Menu, MenuItem.

IX-A-1. Les éléments d'un menu.

Dans un premier temps, le plus simple est de voir comment seront disposés ces trois widgets dans notre futur menu.

Image non disponible

Nous avons tout d'abord le widget MenuBar (en rouge) qui est l'élément principal de notre menu. Il contient des éléments de type GtkMenuItem (en bleu) qui peuvent ouvrir des éléments GtkMenu (en vert) contenant aussi des GtkMenuItem.

Du point de vue du fonctionnement, les éléments GtkMenuItem peuvent soit ouvrir un sous-menu (élément Gtk-Menu) soit exécuter l'action qui lui est associée par l'intermédiaire d'une fonction de rappel. Il ne reste maintenant plus qu'à créer notre menu.

IX-A-2. Création d'un menu

La création d'un menu doit passer par au moins six étapes différentes :

  1. Création de l'élément MenuBar qui sera la barre de menu ;
  2. Création d'un élément Menu qui sera un menu ;
  3. Création des éléments MenuItem à insérer dans le menu ;
  4. Création de l'élément MenuItem qui ira dans l'élément MenuBar ;
  5. Association de cet élément avec le menu créé précédemment ;
  6. Ajout de l'élément MenuItem dans la barre MenuBar.

Si, par la suite, vous souhaitez ajouter d'autres menus, il suffit de recommencer à partir de la seconde étape. Étudions maintenant les fonctions qui vont nous permettre de coder le menu.

IX-A-3. Création de l'élément MenuBar

Cette étape, rapide et simple, se résume en l'utilisation d'une seule fonction :

 
Sélectionnez
$menubar = Gtk2::MenuBar->new ();

IX-A-4. Création d'un élément Menu qui sera un menu

Cette fois aussi, une seule fonction est utilisée :

 
Sélectionnez
$menu = Gtk2::Menu->new ();

IX-A-5. Création des éléments MenuItem à insérer dans le menu

Dans un premier temps, il faut créer le MenuItem grâce à l'une de ces trois fonctions :

 
Sélectionnez
$menuitem = Gtk2::MenuItem->new ();
$menuitem = Gtk2::MenuItem->new_with_label ($label);
$menuitem = Gtk2::MenuItem->new_with_mnemonic ($label);

Nous reconnaissons la syntaxe de ces constructeurs qui est semblable à celles des boutons. La première fonction créer un élément vide, la deuxième un élément avec un texte (label), et la troisième un élément avec un texte associé à un raccourci.

Maintenant que l'élément est créé, il faut l'insérer dans le menu. Pour cela, il existe plusieurs fonctions possibles, mais nous n'allons en voir que deux :

 
Sélectionnez
$menu->append($menuitem);
$menu->prepend($menuitem);

Ces fonctions ne font pas partie du widget Menu mais de MenuShell qui est le widget dont Menu dérive. La première fonction ajoute les éléments de haut en bas alors que la seconde les ajoute de bas en haut. Cette étape peut s'avérer longue à coder, car il faudra la répéter pour chaque élément que nous voulons ajouter au menu.

IX-A-6. Création de l'élément MenuItem qui ira dans l'élément MenuBar

Il suffit d'utiliser une des trois fonctions de création du widget MenuItem.

IX-A-7. Association de cet élément avec le menu créer précédemment

Il faut maintenant que lorsque l'utilisateur clique sur le MenuItem qui est dans MenuBar, le menu s'ouvre. C'est pourquoi il ne faut oublier :

 
Sélectionnez
$menuitem_avec_sous_menu->set_submenu($menu);

Cette fonction va donc associer le MenuItem $menuitem_avec_sous_menu au Menu $menu.

IX-A-8. Ajout de l'élément MenuItem dans la barre MenuBar

MenuBar dérivant aussi de MenuShell, on utilise la même méthode que pour ajouter le MenuItem au Menu.

IX-B. Exemple

Notre exemple comportera deux menus. Le premier « Fichier », proposera des fonctions inactives (« Nouveau », « Ouvrir », « Enregistrer », « Fermer ») et une fonction active (« Quitter »), alors que le second «  ? » proposera la fonction « A propos de… ».

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use strict ;
use Gtk2 '-init' ;
use constant TRUE => 1 ;
use constant FALSE => 0 ;

# Création de la fenêtre
my $window = Gtk2::Window->new("toplevel") ;
$window->set_title ("Les menus (1)") ;
$window->signal_connect (destroy => sub { Gtk2->main_quit }) ;
$window->set_default_size(320 ,200) ;

# Création de la GtkVBox
my $vbox = Gtk2::VBox->new(FALSE, 0) ;
$window->add($vbox) ;

###### réation du menu #####

### Étape 1
my $menubar = Gtk2::MenuBar->new() ;

### Premier sous-menu
### Étape 2
my $menu1= Gtk2::Menu->new() ;

### Étape 3
my $menuitem1 = Gtk2::MenuItem->new_with_label("Nouveau") ;
$menu1->append($menuitem1) ;

my $menuitem2 = Gtk2::MenuItem->new_with_label("Ouvrir") ;
$menu1->append($menuitem2) ;

my $menuitem3 = Gtk2::MenuItem->new_with_label("Enregistrer") ;
$menu1->append($menuitem3) ;

my $menuitem4 = Gtk2::MenuItem->new_with_label("Fermer") ;
$menu1->append($menuitem4) ;

my $menuitem5 = Gtk2::MenuItem->new_with_label("Quitter") ;
$menu1->append($menuitem5) ;
$menuitem5->signal_connect("activate" ,\&on_quitter,$window) ;

### Étape 4
my $menuitem6 = Gtk2::MenuItem->new_with_label("Fichier") ;

### Étape 5
$menuitem6->set_submenu($menu1) ;

### Étape 6
$menubar->append($menuitem6) ;

### Second sous-menu **/
### Étape 2
my $menu2 = Gtk2::Menu->new() ;

### Étape 3
my $menuitem7 = Gtk2::MenuItem->new_with_label("A propos de ...") ;
$menu2->append($menuitem7) ;
$menuitem7->signal_connect("activate" ,\&on_about,$window) ;

### Étape 4
my $menuitem8 = Gtk2::MenuItem->new_with_label(" ?") ;

### Étape 5
$menuitem8->set_submenu($menu2) ;

### Étape 6
$menubar->append($menuitem8) ;

### Ajout du menu a la fenetre
$vbox->pack_start($menubar,FALSE,FALSE,0) ;

$window->show_all() ;
Gtk2->main() ;

### Les fonctions de rappels
### Ici on demande une consfirmation pour quitter l'application
sub on_quitter{
	my ($widget,$window) = @_ ;
	my $dialog = Gtk2::MessageDialog->new ($window,
	  [qw/modal destroy-with-parent/],
	  'question' ,
	  'yes no' ,
	  "Voulez-vous vraiment \n quitter le programme ?") ;
	my $reponse = $dialog->run ;
	if ($reponse eq "yes") {
		Gtk2->main_quit ;
	}
	else {
		$dialog->destroy() ;
	}
}

sub on_about {
	my ($widget,$window) = @_ ;
	
	my $dialog = Gtk2::MessageDialog->new ($window,
	  [qw/modal destroy-with-parent/],
	  'info' ,
	  'ok' ,
	  "Gtk2 Perl tutoriel") ;
	$dialog->run ;
	$dialog->destroy() ;
}

IX-C. Éléments « avancés » de menu.

En plus des MenuItem, GTK+ offre cinq éléments de menu additionnels prêts à l'emploi : ImageMenuItem, Radio-MenuItem, CheckMenuItem, SeparatorMenuItem et TearoffMenuItem. Bien entendu toutes les fonctions s'appliquant sur des MenuItems s'appliquent aussi à ces items (héritage oblige).

  • ImageMenuItem est un widget qui permet de créer une entrée de menu textuelle avec une icône juste devant ;
  • RadioMenuItem est un widget qui fonctionne selon les mêmes principes que les boutons radios ;
  • CheckMenuItem est un widget à trois états que l'on peut cocher ;
  • SeparatorMenuItem est un widget qui sert simplement à séparer des parties de menu grâce à une ligne horizontale. Leur but est uniquement décoratif ;
  • TearoffMenuItem est un widget qui permet de détacher le menu de sa barre et d'en faire une fenêtre à part entière.

IX-C-1. Les ImageMenuItems

Pour créer un ImageMenuItem, on a à disposition quatre fonctions :

 
Sélectionnez
$menuitem = Gtk2::ImageMenuItem->new();
$menuitem = Gtk2::ImageMenuItem->new_from_stock($stock_id,$accel);
$menuitem = Gtk2::ImageMenuItem->new_with_label($label);
$menuitem = Gtk2::ImageMenuItem->new_with_mnemonic($label);

On retrouve encore les mêmes types de fonctions de création, aussi je passe sur l'explication des paramètres. La seule nouveauté est peut-être $accel de Gtk2::ImageMenuItem->new_from_stock, qui sert à ajouter l'entrée à un AccelGroup précédemment créé en utilisant le raccourci par défaut de l'icône stock.

Maintenant, il s'agit de définir une icône pour notre entrée, pour cela, on a :

 
Sélectionnez
$menuitem->set_image($image);

Cette fonction permet de définir l'icône (généralement on utilisera un GtkImage) qui sera affichée par l'entrée passée en paramètre. Cette fonction peut aussi servir à remplacer l'icône que Gtk2::ImageMenuItem->new_from_stock définit pour l'entrée lors de sa création.

La dernière fonction spécifique des ImageMenuItems est :

 
Sélectionnez
$image = $menuitem->get_image();

Elle sert, comme vous l'aurez deviné, à récupérer ce qui sert d'icône à l'entrée.

IX-C-2. Les CheckMenuItems

Les CheckMenuItems émettent un signal lorsqu'on les (dé)coche, il s'agit du signal « toggled ». Les fonctions de création d'un CheckMenuItem sont :

 
Sélectionnez
$menuitem = Gtk2::CheckMenuItem->new();
$menuitem = Gtk2::CheckMenuItem->new_with_label($label);
$menuitem = Gtk2::CheckMenuItem->new_with_mnemonic($label);

Rien de nouveau à l'horizon, alors nous continuons.

Nous avons plusieurs fonctions qui permettent de changer l'état de notre CheckMenuItem par programme :

 
Sélectionnez
$menuitem->set_active($is_active);
$menuitem->set_inconsistent($setting);
$menuitem->toggled();

La première fonction permet de passer le CheckMenuItem dans l'état « coché » si le paramètre $is_active est TRUE ou dans l'état « non coché » si $is_active est FALSE. Cette fonction ne permet pas de mettre notre CheckMenuItem dans le troisième état « demi coché », pour cela, il faut utiliser la deuxième fonction, et grâce au paramètre $setting, on active ou non cet état. La troisième fonction, elle, permet d'alterner entre état « coché » et « non coché », car elle émet en fait le signal « toggled », ce qui a pour effet d'inverser l'état du CheckMenuItem.

Nous disposons également des fonctions associées pour récupérer l'état d'un CheckMenuItem :

 
Sélectionnez
$etat = $menuitem->get_active();
$etat = $menuitem->get_inconsistent();

La première permet de connaître l'état d'un CheckMenuItem, elle renvoie TRUE s'il est « coché » ou FALSE sinon. La deuxième permet juste de savoir si le CheckMenuItem est dans le troisième état « demi coché ».

IX-C-3. Les RadioMenuItems

Les RadioMenuItems héritent des CheckMenuItems, donc tout ce qui a été dit juste avant s'applique aussi ici. Pour créer un RadioMenuItem, nous pouvons nous servir de :

 
Sélectionnez
$menuitem = Gtk2::RadioMenuItem->new($group);
$menuitem = Gtk2::RadioMenuItem->new_with_label($group,$label);
$menuitem = Gtk2::RadioMenuItem->new_with_mnemonic($group,$label);

Ce widget fonctionne de la même manière que le widget RadioButton, et donc le paramètre group de ces fonctions, sert à dire au RadioMenuItem à quel groupe il va appartenir. Le premier RadioMenuItem d'un groupe prendra toujours undef comme paramètre $group, de cette façon il va en créer un nouveau. Ensuite, il suffira de récupérer ce paramètre $group grâce à la fonction suivante et de le passer à un autre RadioMenuItem pour que celui-ci fasse partie du même groupe que le premier.

 
Sélectionnez
$menuitem->get_group();

Tout comme pour le widget RadioButton, quand on crée un groupe de RadioMenuItem, il faut toujours récupérer le groupe du RadioMenuItem précédemment créé (sauf pour le premier bien entendu).

On peut aussi définir ou remplacer le groupe d'un RadioMenuItem après coup grâce à :

 
Sélectionnez
$menuitem->set_group($group);

IX-C-4. Les SeparatorMenuItems

Ah, en voilà un widget qu'il est bien ! Une seule fonction pour sa création, et c'est tout :

 
Sélectionnez
$menuitem = Gtk2::SeparatorMenuItem->new();

IX-C-5. Les TearoffMenuItems

Pour le créer, faites :

 
Sélectionnez
$menuitem = Gtk2::TearoffMenuItem->new();

Et c'est tout ! Ensuite, un premier clic sur l'élément TearoffMenuItem créera une copie du menu dans une fenêtre, et un second clic sur l'élément (que cela soit dans le menu ou dans la fenêtre) supprimera la fenêtre créée.

Pour savoir si le menu est attaché ou détaché, il faut utiliser la fonction :

 
Sélectionnez
$etat = $menu->get_tearoff_state();

Cette dernière renverra TRUE si le menu est détaché et FALSE sinon. Lorsque nous fermons la fenêtre du menu détaché à la main (c'est-à-dire lorsque nous cliquons sur la croix), le menu est automatiquement rattaché, $menu->get_tearoff_state renverra tout de même TRUE.

IX-C-6. Programme exemple

Nous avons repris l'exemple précédent en modifiant le menu pour y mettre nos nouveaux items et en y ajoutant trois labels pour indiquer l'état des différents items. Il y a aussi trois fonctions de rappels supplémentaires pour la mise à jour de nos labels.

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use strict ;
use Gtk2 '-init' ;
use constant TRUE => 1 ;
use constant FALSE => 0 ;

# Création de la fenêtre
my $window = Gtk2::Window->new("toplevel") ;
$window->set_title ("Les menus (2)") ;
$window->signal_connect (destroy => sub { Gtk2->main_quit }) ;
$window->set_default_size(320 ,200) ;

# Création de la GtkVBox
my $vbox = Gtk2::VBox->new(FALSE, 0) ;
$window->add($vbox) ;

my $accel = Gtk2::AccelGroup->new() ;

###### réation du menu #####
### Étape 1
my $menubar = Gtk2::MenuBar->new() ;
### Premier sous-menu
### Étape 2
my $menu1= Gtk2::Menu->new() ;
### Étape 3
my $menuitem1 = Gtk2::TearoffMenuItem->new() ;
$menu1->append($menuitem1) ;
$menuitem1->signal_connect("activate" ,\&on_tearoff,$menu1) ;

my $menuitem2 = Gtk2::ImageMenuItem->new_from_stock("gtk-new" ,$accel) ;
$menu1->append($menuitem2) ;

my $menuitem3 = Gtk2::ImageMenuItem->new_from_stock("gtk-open" ,$accel) ;
$menu1->append($menuitem3) ;

my $menuitem4 = Gtk2::ImageMenuItem->new_from_stock("gtk-save" ,$accel) ;
$menu1->append($menuitem4) ;

my $menuitem5 = Gtk2::ImageMenuItem->new_from_stock("gtk-close" ,$accel) ;
$menu1->append($menuitem5) ;

my $menuitem6 = Gtk2::SeparatorMenuItem->new() ;
$menu1->append($menuitem6) ;

my $menuitem7 = Gtk2::RadioMenuItem->new_with_label(undef,"Radio1") ;
$menu1->append($menuitem7) ;

my $group = $menuitem7->get_group() ;

### Il est inutile ici d'utiliser le signal "toggled"
$menuitem7->signal_connect("activate" ,\&on_radio) ;

##### Les boutons radio dans le menu !
my $menuitem8 = Gtk2::RadioMenuItem->new_with_label($group,"Radio2") ;
$menu1->append($menuitem8) ;
$menuitem8->signal_connect("activate" ,\&on_radio) ;

my $menuitem9 = Gtk2::RadioMenuItem->new_with_label($group,"Radio3") ;
$menu1->append($menuitem9) ;
$menuitem9->signal_connect("activate" ,\&on_radio) ;

my $menuitem10 = Gtk2::SeparatorMenuItem->new() ;
$menu1->append($menuitem10) ;

#### Les cases à cocher aussi !
my $menuitem11 = Gtk2::CheckMenuItem->new_with_label("Check") ;
$menu1->append($menuitem11) ;
$menuitem11->signal_connect("toggled" ,\&on_check) ;

my $menuitem12 = Gtk2::SeparatorMenuItem->new() ;
$menu1->append($menuitem12) ;

my $menuitem13 = Gtk2::MenuItem->new_with_label("Quitter") ;
$menu1->append($menuitem13) ;
$menuitem13->signal_connect("activate" ,\&on_quitter,$window) ;

### Étape 4
my $menuitem14 = Gtk2::MenuItem->new_with_label("Fichier") ;

### Étape 5
$menuitem14->set_submenu($menu1) ;

### Étape 6
$menubar->append($menuitem14) ;

### Second sous-menu **/
### Étape 2
my $menu2 = Gtk2::Menu->new() ;

### Étape 3
my $menuitem15 = Gtk2::MenuItem->new_with_label("A propos de ...") ;
$menu2->append($menuitem15) ;
$menuitem15->signal_connect("activate" ,\&on_about,$window) ;

### Étape 4
my $menuitem16 = Gtk2::MenuItem->new_with_label(" ?") ;

### Étape 5
$menuitem16->set_submenu($menu2) ;

### Étape 6
$menubar->append($menuitem16) ;

### Creation de la deuxieme GtkVBox (pour les labels) */
my $vbox2 = Gtk2::VBox->new(FALSE, 0) ;

my $RadioLabel = Gtk2::Label->new("Radio 1 est actif") ;
$vbox2->pack_start($RadioLabel, TRUE, TRUE, 0) ;

my $CheckLabel = Gtk2::Label->new("Check est décoché") ;
$vbox2->pack_start($CheckLabel, TRUE, TRUE, 0) ;

my $TearoffLabel = Gtk2::Label->new("Menu attaché") ;
$vbox2->pack_start($TearoffLabel, TRUE, TRUE, 0) ;

### Ajout du menu à la fenetre
$vbox->pack_start($menubar, FALSE, FALSE, 0) ;

### Ajout des labels à la fenêtre
$vbox->pack_start($vbox2, TRUE, TRUE, 0) ;
$window->show_all() ;

Gtk2->main() ;

#### Les fonstions de rappels
sub on_radio {
	my $widget = shift ;
	### Récupérer le label du bouton radio actif
	my $RadioName = $widget->child->get_label() ;
	$RadioLabel->set_label(sprintf"%s est actif" ,$RadioName) ;
}
sub on_check{

	my $widget = shift ;
	my $Label ;
	### Savoir si le GtkCheckMenuItem est coché ou non
	my $Coche = $widget->get_active() ;
	if ($Coche) {
		$Label = "Check est coché" ;
	}
	else {
		$Label ="Check est décoché" ;
	}
	$CheckLabel->set_label($Label) ;
}

sub on_tearoff {
	my ($widget,$menu) = @_ ;
	my $Label ;
	### Savoir si le menu est détaché ou non */
	my $Detache = $menu->get_tearoff_state() ;
	if ($Detache) {
		$Label = "Menu détaché" ;
	}
	else {
		$Label = "Menu attaché" ;
	}
	$TearoffLabel->set_label($Label) ;
}

sub on_quitter{
	my ($widget,$window) = @_ ;
	my $dialog = Gtk2::MessageDialog->new ($window,
	  [qw/modal destroy-with-parent/],
	  'question' ,
	  'yes no' ,
	  "Voulez-vous vraiment \n quitter le programme ?") ;
	my $reponse = $dialog->run ;
	if ($reponse eq "yes") {
		Gtk2->main_quit ;
	}
	else {
		$dialog->destroy() ;
	}
}

sub on_about {
	my ($widget,$window) = @_ ;
	
	my $dialog = Gtk2::MessageDialog->new ($window,
	  [qw/modal destroy-with-parent/],
	  'info' ,
	  'ok' ,
	  "Gtk2 Perl tutoriel") ;
	$dialog->run ;
	$dialog->destroy() ;
}

IX-D. Le menu - Méthode rapide

Grâce aux deux premières parties du chapitre, nous avons appris à créer un menu. Vous avez pu remarquer que cette méthode peut s'avérer très fastidieuse si le menu comporte beaucoup d'éléments. Nous allons maintenant voir comment simplifier tout cela pour le biais de l'objet ItemFactory.

IX-D-1. Fonctionnement de ItemFactory

L'objet ItemFactory n'est en fait qu'une grosse machine nous fournissant les fonctions nécessaires à la création d'un menu. Pour cela, ItemFactory utilise la structure ItemFactoryEntry qui elle nous permet de définir les différents éléments qui composent le menu ainsi que les actions qui leur sont associés. En Perl, on utilise un tableau :

 
Sélectionnez
[$path,$accelerator,$callback,$callback_action,$item_type,$extra_data]

IX-D-2. Création des éléments du menu

Pour cela, la seule à faire est de déclarer et de remplir un tableau de ItemFactoryEntry. Nous allons donc maintenant étudier comment remplir ce tableau d'après la définition de la structure ItemFactoryEntry.

Le paramètre $path correspond au chemin de l'élément dans l'arborescence du menu. Il faut pour cela, voir l'arborescence d'un menu comme celle des fichiers, c'est-à-dire organisée en répertoire (les branches du menu) et en fichier (les éléments du menu). Ce paramètre permet aussi de définir le texte de l'élément. Par exemple, nous avons un menu « Fichier » qui possède un élément « Nouveau », alors le path de la branche « Fichier » sera « /Fichier » et celui de l'élément « Nouveau » sera « /Fichier/Nouveau ». Pour souligner une lettre (pour indiquer un raccourci clavier), il faut précéder cette lettre de _ comme pour les mnémoniques. Cependant, cela ne crée pas un raccourci, il s'agit juste d'une indication.

Le second paramètre $accelerator définit la combinaison de touche qui activera l'élément du menu, et cela, même si le menu n'est pas ouvert. La combinaison de touche allouée à l'élément est affichée après le label de l'élément du menu. La syntaxe de ce paramètre est <touche-speciale> Lettre, touche-spéciale pouvant être Ctrl, Alt ou Shift. Il est bien sûr possible d'utiliser une combinaison de ces touches (exemple : <Ctrl><SHIFT>O).

Le paramètre $callback est la fonction de rappel qui sera appelée lorsque l'élément sera activé à l'aide de la souris ou d'un raccourci clavier.

Le quatrième paramètre, $callback_action est un entier qui sera passé en paramètre à la fonction de rappel, car celle-ci doit avoir le prototype suivant :

 
Sélectionnez
sub fonction_callback
my ($window,$callback_action, $widget) = @_;
...
}

Dans ce cas, $window est la fenêtre contenant le menu, $callback_action est identique à la valeur donnée à l'élément et $widget est l'élément lui-même.

Le cinquième paramètre, $item_type, définit de quoi est constitué l'élément du menu. Ce paramètre doit être une des valeurs suivantes :

  • <Branch> : élément ouvrant un autre menu ;
  • undef, «  », <Item> : élément classique avec du texte ;
  • <StockItem> : élément de type StockItem;
  • <ImageItem> : élément de type ImageMenuItem ;
  • <CheckItem> :élément de type CheckMenuItem;
  • <ToggleItem> : élément de type ToggleItem ;
  • <RadioItem> : élément de type RadioMenuItem;
  • path : path correspond au path de l'élément principal du groupe de RadioMenuItem ;
  • <Title> : affiche seulement le texte (grisé) de l'élément. Ce dernier ne peut être sélectionné ;
  • <Separator> : une ligne de séparation ;
  • <Tearoff> : élément pour détacher une partie du menu ;
  • <LastBranch> : élément ouvrant un autre menu. Met l'élément à droite de la barre de menu. La seule subtilité ici, est pour les éléments de type RadioMenuItem. Le premier bouton radio du groupe (ex : path = /Choix/Pour) aura $item_type égal à <RadioItem> alors que les autres auront $item_type égal à /Choix/Pour.

    Enfin pour terminer, le dernier paramètre $extra_data devra être défini dans deux cas :

  • l'élément est de type <StockItem>, alors $extra_data sera l'identifiant du StockItem à afficher dans le menu ;
  • l'élément est de type <ImageItem>, alors $extra_data sera un pointeur sur le GdkPixmap (pas encore étudié à ce stade du cours) à afficher.

Nous savons donc maintenant comment déclarer le menu et il ne reste plus qu'à le créer.

IX-D-3. Création du menu.

La première chose à faire est de créer un objet pour récupérer les raccourcis clavier du menu. Pour cela nous allons utiliser l'objet AccelGroup dont nous n'allons pas parler ici. La création de cet objet se fait à l'aide de cette fonction :

 
Sélectionnez
$accel = Gtk2::AccelGroup->new();

Ensuite, il faut créer l'objet ItemFactory avec cette fonction :

 
Sélectionnez
$item_factory = Gtk2::ItemFactory->new($container_type,$path, $accel);

Le premier paramètre $container_type, définit le type de menu que nous voulons créer. Il existe trois valeurs possibles :

  • "Gtk2::MenuBar" qui créera une barre de menu tout ce qu'il y a de plus normal ;
  • "Gtk2::Menu" qui créera un menu qui servira pour faire un menu popup ;
  • "Gtk2::OptionMenu" qui créera une sorte de bouton qui ouvrira le menu une fois cliqué.

Le paramètre $path est le nom que nous donnons au menu. Il représente la racine du menu et doit avoir la syntaxe suivante <nom_du_menu>.

Le dernier paramètre permet de récupérer les raccourcis clavier dans l'AccelGroup. Ce paramètre peut être égal à undef.

Une fois le menu créé, il faut y insérer tous ses éléments avec la fonction :

 
Sélectionnez
$item_factory->create_items($window,@menu_items);

Le paramètre $window est la fenêtre contenant le menu et @menu_items est bien entendu le tableau préalablement créé décrivant les menus.

Il faut ensuite récupérer de tout cela pour pouvoir l'ajouter à notre fenêtre. Pour cela, il faut utiliser cette fonction :

 
Sélectionnez
$menubar = $item_factory->get_widget($path);

Et pour terminer tout cela, il faut associer les raccourcis à la fenêtre contenant le menu. Cela est possible par le biais d'une fonction du widget Window :

 
Sélectionnez
$window->add_accel_group($accel);

Et voila, le menu est terminé.

IX-D-4. Exemple

Nous allons reprendre l'exemple de la première partie, mais cette en utilisant des éléments de menu de type <StockItem>.

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use strict ;
use Gtk2 '-init' ;
use constant TRUE => 1 ;
use constant FALSE => 0 ;

# Création de la fenêtre
my $window = Gtk2::Window->new("toplevel") ;
$window->set_title ("Les menus faciles") ;
$window->signal_connect (destroy => sub { Gtk2->main_quit }) ;
$window->set_default_size(320 ,200) ;

# Création de la GtkVBox
my $vbox = Gtk2::VBox->new(FALSE, 0) ;
$window->add($vbox) ;

my @menu_items = (
  [ "/ Fichier" ,undef,0 ,0 ,"<Branch>" ],
  [ "/Fichier/Nouveau" ,"<control>N" ,\&menuitem_cb,0 ,"<StockItem>" ,"gtk-new" ,"coucou" ],
  [ "/Fichier/Ouvrir" ,"<control>O" ,\&menuitem_cb,0 ,"<StockItem>" , 'gtk-open' ],
  [ "/Fichier/Enregistrer" ,"<control>S" ,\&menuitem_cb,0 ,"<StockItem>" ,'gtk-save' ],
  [ "/Fichier/Enregistrer sous..." , undef,\&menuitem_cb,0 ,"<StockItem>" ,'gtk-save' ],
  [ "/Fichier/sep1" ,undef,\&menuitem_cb,0 ,"<Separator>" ],
  [ "/Fichier/Quitter" ,"<control>Q" ,\&on_quitter,0 ,"<StockItem>" ,'gtk-quit' ],
  [ "/ Preferences" ,undef,0 ,0 ,"<Branch>" ],
  [ "/ Preferences/Couleur" ,undef,0 ,0 ,"<Branch>" ],
  [ "/ Preferences/Couleur/Rouge" ,undef,\&menuitem_cb,0 ,"<RadioItem>" ],
  [ "/ Preferences/Couleur/Vert" ,undef,\&menuitem_cb,0 ,"/Preferences/Couleur/Rouge" ],
  [ "/ Preferences/Couleur/Bleu" ,undef,\&menuitem_cb,0 ,"/Preferences/Couleur/Vert" ],
  [ "/ Preferences/Forme" ,undef,0 ,0 ,"<Branch>" ],
  [ "/ Preferences/Forme/Carre" ,undef,\&menuitem_cb,0 ,"<RadioItem>" ],
  [ "/ Preferences/Forme/Rectangle" ,undef,\&menuitem_cb,0 ,"/Preferences/Forme/Carre" ],
  [ "/ Preferences/Forme/Ovale" ,undef,\&menuitem_cb,0 ,"/Preferences/Forme/Rectangle" ],

  # Si vous aviez voulu justifier le menu suivant à droite, il aurait fallu utiliser :
  # <LastBranch> et non <Branch>. Les menus d'aide justifié à droite sont de nos
  # jours considérés comme une mauvaise idée.
  [ "/ ?" , undef, 0 , 0 , "<Branch>" ],
  [ "/ ?/ A propos" , undef, \&on_about, 0 ],
) ;

### Groupe de raccourci clavier
my $accel = Gtk2::AccelGroup->new() ;

### Création du menu
my $item_factory = Gtk2::ItemFactory->new("Gtk2::MenuBar" ,"<main>" , $accel) ;
### Récuperation des éléments du menu
$item_factory->create_items($window,@menu_items) ;
### Récuperation du widget pour l'affichage du menu
my $menubar = $item_factory->get_widget("<main>") ;
### Ajout du menu en haut de la fenêtre
$vbox->pack_start($menubar, FALSE, FALSE, 0) ;
### Association des raccourcis avec la fenetre
$window->add_accel_group($accel) ;

$window->show_all() ;
Gtk2->main() ;

################ Fonctions de rappels

sub on_quitter{
	my ($widget,$window) = @_ ;
	my $dialog = Gtk2::MessageDialog->new ($window,
	  [qw/modal destroy-with-parent/],
	  'question' ,
	  'yes no' ,
	  "Voulez-vous vraiment \n quitter le programme ?") ;
	  
	my $reponse = $dialog->run ;
	if ($reponse eq "yes") {
		Gtk2->main_quit ;
	}
	else {
		$dialog->destroy() ;
	}
}

sub on_about {
	my ($widget,$window) = @_ ;
	my $dialog = Gtk2::MessageDialog->new ($window,
	  [qw/modal destroy-with-parent/],
	  'info' ,
	  'ok' ,
	  "Gtk2 Perl tutoriel") ;
	$dialog->run ;
	$dialog->destroy() ;
}

sub menuitem_cb {
	my ($window, $callback_action, $widget) = @_ ;
	my $dialog = Gtk2::MessageDialog->new ($window,
	  'destroy-with-parent' ,
	  'info' ,
	  'close' ,
	  sprintf "Vous avez activé ou cliqué sur le menu item : \" %s\"" ,
	  Gtk2::ItemFactory->path_from_widget ($widget)) ;
	  
	# Ferme la boîte de dialogue après la réponse de l'utilisateur
	$dialog->signal_connect (response => sub { $dialog->destroy ; 1 }) ;
	$dialog->show ;
}

X. La barre d'outils

La barre d'outils est très utile pour placer en dessous du menu les commandes les plus utilisées comme faire/défaire, nouveau, ouvrir, sauvegarde, etc. Un widge Gtk2::Toolbar est indispensable pour à toute bonne application.

X-A. Utiliser une barre d'outils

Comme pour tous les autres widgets, c'est très simple :

 
Sélectionnez
$toolbar = Gtk2::Toolbar->new();

GtkToolbar étant un container, après l'avoir créée, il va falloir y ajouter des widgets. Cela se divise en 2 catégories :

  • les widgets habituels comme les GtkButton, GtkToggleButton, GtkRadio pour lesquels GtkToolbar fournit des fonctions qui s'occupent de la création de ces derniers ;
  • les autres widgets que nous pouvons ajouter mais c'est à nous de fournir les widgets donc il faudra les créer auparavant.

Voici les premières fonctions qui permettent d'ajouter un bouton avec du texte et (ou) un icône :

 
Sélectionnez
$toolbar->append_item ($text, $tooltip_text, $tooltip_private_text,
                             $icon, $callback, $user_data);
$toolbar->prepend_item ($text, $tooltip_text, $tooltip_private_text,
                             $icon, $callback, $user_data);
$toolbar->insert_item ($text,$tooltip_text,$tooltip_private_text,
                             $icon, $callback, $user_data, $position);

La première fonction ajoute un bouton à la suite des autres boutons, la seconde l'ajoute en première position et la dernière à une position spécifique. Le paramètre $text n'est autre que le label du bouton. Le troisième paramètre, $tooltip_text, est le texte de la bulle d'aide. Le paramètre suivant, tooltip_private_text, n'est plus utilisé, car la partie qui gère ce paramètre est obsolète. Il faut donc mettre ce paramètre à undef. Le paramètre $icon est là pour définir l'image qui sera associée au bouton. Les deux paramètres servent à connecter une fonction de rappel au clic sur le bouton. Le paramètre callback est en fait le nom de la fonction de rappel et $user_data est la donnée supplémentaire à passer à la fonction de rappel. Et pour terminer, la fonction insert_item possède un paramètre supplémentaire position qui détermine à quelle position le bouton doit être ajouté. Si cette valeur est soit trop grande, soit négative, cette fonction aura le même effet que append_item.

Ensuite il est possible de créer des éléments selon un type de widget prédéfini, comme Gtk2:::RadioButton, Gtk2::ToggleButton, Gtk2::Button :

 
Sélectionnez
$toolbar->append_element ($type, $widget, $text,
                         $tooltip_text, $tooltip_private_text, $icon, $callback, $user_data);
$toolbar->prepend_element ($type, $widget, $text,
                         $tooltip_text, $tooltip_private_text, $icon, $callback, $user_data);
$toolbar->insert_element ($type, $widget, $text,
                         $tooltip_text, $tooltip_private_text, $icon, $callback, $user_data,$position);

La majorité des paramètres de ces fonctions ont été détaillés précédemment, nous n'allons donc étudier que les nouveaux. Tout d'abord, le paramètre $type permet de définir le type de widget que nous allons ajouter, et peut prendre une de ces valeurs :

  • 'space' pour ajouter un espace ;
  • 'button' pour ajouter un bouton ;
  • 'togglebutton' pour ajouter un bouton toggle ;
  • 'radiobutton' pour ajouter un bouton radio ;
  • 'widget' pour ajouter un widget quelconque.

Il y a ensuite le paramètre $widget qui doit être utilisé dans deux cas. Le premier cas est bien entendu, si nous ajoutons un élément de type 'widget'. Alors $widget sera en fait le widget que nous voulons ajouter.

Le deuxième cas est si nous ajoutons un élément de type 'radiobutton'. Nous avons vu que les boutons radiofonctionnaient par groupe, alors dans ce cas, pour grouper les boutons radios le paramètre $widget doit être un bouton radio ajouté précédemment pour que Gtk2-Perl puisse les grouper. Bien sûr, s'il s'agit du premier bouton radio, il faut mettre widget à undef. Dans tous les autres cas, le paramètre widget doit obligatoirement être undef.

Et maintenant, voila les fonctions pour ajouter n'importe quel type de widget :

 
Sélectionnez
$toolbar->append_widget ($widget, $tooltip_text, $tooltip_private_text);
$toolbar->prepend_widget ($widget, $tooltip_text, $tooltip_private_text);
$toolbar->insert_widget ($widget, $tooltip_text, $tooltip_private_text, $position);

Cette fois, tout est simple, le paramètre $widget est simplement le widget que nous voulons ajouter. Il faudra tout de même connecter manuellement les signaux du widget ajouté.

Pour finir, il est possible d'utiliser les StockItem pour créer un bouton. Voici la fonction qui permet cela :

 
Sélectionnez
$toolbar->insert_stock ($stock_id, $tooltip_text, $tooltip_private_text,
$callback, $user_data, $position);

Le paramètre $stock_id est tout simplement l'identifiant du GtkStockItem qui figurera sur le bouton.

X-A-1. Les espaces

Nous avons déjà vu que les fonctions de type *_element permettaient d'ajouter un espace pour rendre plus claire la barre d'outils. Il existe en plus de cela trois autres fonctions :

 
Sélectionnez
$toolbar->append_space();
$toolbar->prepend_space();
$toolbar->insert_space($position);

Comme d'habitude la première fonction ajoute un espace à la suite des autres éléments, la deuxième au tout début de l a barre d'outils et la troisième à une position particulière.

Pour supprimer un espace de la barre d'outils, il existe cette fonction :

 
Sélectionnez
$toolbar->remove_space($position);

X-A-2. Orientation de la barre d'outils.

La barre d'outils peut être orienté verticalement ou horizontalement et cela à n'importe quel moment a l'aide de cette fonction :

 
Sélectionnez
$toolbar->set_orientation($orientation) ;

Le paramètre $orientation peut prendre deux valeurs :

  • 'horizontal' ;
  • 'vertical'.

Et, cette fonction permet de connaître l'orientation de la barre d'outils :

 
Sélectionnez
$toolbar->get_orientation();

X-A-3. Les styles

Il est possible de changer la façon d'afficher certains éléments de la barre d'outils : afficher que le texte, seulement les icônes ou les deux. Voila la fonction qui permet de contrôler cela :

 
Sélectionnez
$toolbar->set_style($style) ;

Le paramètre style peut prendre quatre valeurs différentes :

  • 'icons' pour n'afficher que l'icône ;
  • 'text' pour n'afficher que le texte ;
  • 'both' pour afficher le texte en dessous de l'icône (valeur par défaut) ;
  • 'both-horiz' pour afficher le texte à côté de l'icône.

Et pour finir, cette fonction permet de connaître le style de la barre d'outils :

 
Sélectionnez
$toolbar->get_style();

X-A-4. La taille des icônes

Enfin pour terminer, Gtk2-Perl nous offre la possibilité de modifier la taille des icônes de la barre d'outils avec cette fonction :

 
Sélectionnez
$toolbar->set_icon_size($icon_size);

Le paramètre $icon_size est du type GtkIconSize que nous avons déjà rencontré dans le chapitre sur les images.

Nous allons tout de même rappeler les différentes valeurs possibles qui sont les suivantes :

  • 'menu' ;
  • 'small-toolbar' ;
  • 'large-toolbar' ;
  • 'button' ;
  • 'dnd' ;
  • 'dialog'.

Et bien sûr, pour connaître la taille des icônes, nous avons la fonction :

 
Sélectionnez
$toolbar->get_icon_size();

X-A-5. Exemple

Dans cet exemple, nous allons juste créer une barre d'outils dans laquelle nous allons ajouter quelques boutons dont deux d'entre eux permettront de changer l'orientation de la barre d'outils, une image et une zone de saisie. Vous remarquerez que le texte associé à l'icône n'est pas défini par l'utilisateur ce qui fait que le texte affiché est « suivant » alors que la flèche sert à placer la barre horizontalement. Un jour, je prendrais peut-être le temps de voir comment faire cela !

La méthode pour insérer l'image n'est-certainement pas la plus pratique. Vous pouvez voir une autre approche en analysant le code de appwindow.pl fourni parmi les exemples de Gtk2-Perl.

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w
use strict ;
use Gtk2 '-init' ;
use constant TRUE => 1 ;
use constant FALSE => 0 ;
# l'image que l'on veut insérer dans la barre au format xpm
my @gtk_xpm = ('32 39 5 1' ,
				'. c none' ,
				'+ c black' ,
				'@ c #3070E0' ,
				'# c #F05050' ,
				'$ c #35E035' ,
				'................+...............' ,
				'..............+++++.............' ,
				'............+++++@@++...........' ,
				'..........+++++@@@@@@++.........' ,
				'........++++@@@@@@@@@@++........' ,
				'......++++@@++++++++@@@++.......' ,
				'.....+++@@@+++++++++++@@@++.....' ,
				'...+++@@@@+++@@@@@@++++@@@@+....' ,
				'..+++@@@@+++@@@@@@@@+++@@@@@++..' ,
				'.++@@@@@@+++@@@@@@@@@@@@@@@@@@++' ,
				'.+#+@@@@@@++@@@@+++@@@@@@@@@@@@+' ,
				'.+##++@@@@+++@@@+++++@@@@@@@@$@.' ,
				'.+###++@@@@+++@@@+++@@@@@++$$$@.' ,
				'.+####+++@@@+++++++@@@@@+@$$$$@.' ,
				'.+#####+++@@@@+++@@@@++@$$$$$$+.' ,
				'.+######++++@@@@@@@++@$$$$$$$$+.' ,
				'.+#######+##+@@@@+++$$$$$$@@$$+.' ,
				'.+###+++##+##+@@++@$$$$$$++$$$+.' ,
				'.+###++++##+##+@@$$$$$$$@+@$$@+.' ,
				'.+###++++++#+++@$$@+@$$@++$$$@+.' ,
				'.+####+++++++#++$$@+@$$++$$$$+..' ,
				'.++####++++++#++$$@+@$++@$$$$+..' ,
				'.+#####+++++##++$$++@+++$$$$$+..' ,
				'.++####+++##+#++$$+++++@$$$$$+..' ,
				'.++####+++####++$$++++++@$$$@+..' ,
				'.+#####++#####++$$+++@++++@$@+..' ,
				'.+#####++#####++$$++@$$@+++$@@..' ,
				'.++####++#####++$$++$$$$$+@$@++.' ,
				'.++####++#####++$$++$$$$$$$$+++.' ,
				'.+++####+#####++$$++$$$$$$$@+++.' ,
				'..+++#########+@$$+@$$$$$$+++...' ,
				'...+++########+@$$$$$$$$@+++....' ,
				'.....+++######+@$$$$$$$+++......' ,
				'......+++#####+@$$$$$@++........' ,
				'.......+++####+@$$$$+++.........' ,
				'.........++###+$$$@++...........' ,
				'..........++##+$@+++............' ,
				'...........+++++++..............' ,
				'.............++++...............') ;

my $item ;

# la fenêtre principale
my $window = Gtk2::Window->new('toplevel') ;
$window->signal_connect("destroy" , sub { Gtk2->main_quit() ; }) ;
$window->set_title("La barre d'outils") ;
$window->set_default_size(200 ,80) ;

my $vbox = Gtk2::VBox->new(FALSE,0) ;
$window->add($vbox) ;

#Création de la barre d'outils
my $toolbar = Gtk2::Toolbar->new() ;
$vbox->pack_start($toolbar,FALSE,FALSE,0) ;

$toolbar->insert_stock('gtk-new' ,'Nouveau' ,undef,\&on_clicked,'Nouveau' ,-1) ;
$toolbar->insert_stock('gtk-open' ,'Ouvrir' ,undef,\&on_clicked,'Ouvrir' ,-1) ;
$toolbar->insert_stock('gtk-save' ,'Enregistrer' ,undef,\&on_clicked,'Enregistrer' ,-1) ;
$toolbar->insert_stock('gtk-quit' ,'Quitter' ,undef,\&quitter,undef,-1) ;

# Insertion d'un espace
$toolbar->insert_space(10) ;

# On veut maintenant insérer une image dans la barre d'outils.
# On crée d'abord un objet Gtk2::Gdk::Pixbuf à partir du fichier xpm
# présent plus haut.
my $pixbuf = Gtk2::Gdk::Pixbuf->new_from_xpm_data(@gtk_xpm) ;

# Nous devons ensuite créer une image car il faut que l'objet à ajouter
# à la barre soit un héritier de Gtk2::Widget.
my $image = Gtk2::Image->new_from_pixbuf($pixbuf) ;
my $icon_button = $toolbar->append_item ("Image" , "Une petite image" , undef,
$image, \&on_clicked, 'Image') ;

# Insertion d'un espace
$toolbar->insert_space(10) ;

# Création à partir de stock. Ces deux icônes permettent de changer
# l'orientation de la barre
$toolbar->insert_stock('gtk-go-forward' ,'horizontale' ,undef,\&on_orientation_change,'horizontal' ,-1) ;
$toolbar->insert_stock('gtk-go-down' ,'verticale' ,undef,\&on_orientation_change,'vertical' ,-1) ;

# Insertion d'un espace
$toolbar->insert_space(10) ;

# Insérer une zone de saisie se fait très facilement.
my $entry = Gtk2::Entry->new() ;
$toolbar->append_widget($entry, "Une petite zone de saisie" , undef) ;


# On règle la taille des icônes
$toolbar->set_icon_size('button') ;

# Affichage uniquement des icônes
$toolbar->set_style('icons') ;

##### Un menu à options pour changer la taille des icônes
my $opt1 = new Gtk2::OptionMenu() ;
my $menu1 = new Gtk2::Menu() ;
my $hbox1 = Gtk2::HBox->new(TRUE,0) ;
$vbox->pack_start($hbox1,FALSE,FALSE,0) ;
my $label1 = Gtk2::Label->new ("Taille des icônes") ;
$hbox1->pack_start($label1,FALSE,FALSE,0) ;
	
# On automatise le création des options
my @choix_taille=('menu' ,'small-toolbar' ,'large-toolbar' ,'button' ,'dnd' ,'dialog') ;
foreach (@choix_taille) {
	$item = make_menu_item($_ , \&change_taille_icone,$_) ;
	$menu1->append($item) ;
}
# On place le tout dans le menu puis dans la boîte
$opt1->set_menu($menu1) ;
$hbox1->pack_start($opt1, FALSE, FALSE, 0) ;

##### Un menu options pour changer le style de la barre
my $opt2 = new Gtk2::OptionMenu() ;
my $menu2 = new Gtk2::Menu() ;
my $hbox2 = Gtk2::HBox->new(TRUE,0) ;
$vbox->pack_start($hbox2,FALSE,FALSE,0) ;
my $label2 = Gtk2::Label->new ("Style") ;
$hbox2->pack_start($label2,FALSE,FALSE,0) ;

my @choix_style=('icons' ,'text' ,'both' ,'both-horiz') ;
foreach (@choix_style) {
	$item = make_menu_item($_, \&change_style, $_) ;
	$menu2->append($item) ;
}
# On place le tout dans le menu puis dans la boîte
$opt2->set_menu($menu2) ;
$hbox2->pack_start($opt2, FALSE, FALSE, 0) ;
	
$window->show_all() ;

Gtk2->main() ;

sub on_orientation_change {
	my ($self,$orientation) = @_ ;
	$toolbar->set_orientation($orientation) ;
}

# Fonctions pratiques
sub make_menu_item {
	my ($name, $callback, $data) = @_ ;
	my $item ;
	$item = new Gtk2::MenuItem($name) ;
	$item->signal_connect("activate" , $callback, $data) ;
	$item->show() ;
	return $item ;
}

sub change_taille_icone {
	my ($item, $taille) = @_ ;
	$toolbar->set_icon_size($taille) ;
}

sub change_style {
	my ($item, $style) = @_ ;
	$toolbar->set_style($style) ;
}

sub on_clicked {
	my ($item, $message) = @_ ;
	print "Vous avez clické sur $message !\n" ;
}

sub quitter{
	Gtk2->main_quit() ;
}

XI. Les barres d'état

Les barres d'état sont de simples widgets utilisés pour afficher un message texte. Ils gardent une pile des messages qu'on leur donne ainsi, quand on enlève le message courant, le message précédent réapparaît.

Afin de permettre aux différentes parties du programme d'utiliser la même barre d'état, le widget barre d'état possède un identificateur de contexte qui, comme son nom l'indique, identifie les utilisateurs. Le message sur le haut de la pile est-celui qui est affiché, sans s'occuper de son contexte. Les messages sont gérés dans la pile selon le principe du dernier entré - premier sorti et non en fonction de l'ordre des identificateurs de contexte.

Une barre d'état est créée grâce à l'appel :

 
Sélectionnez
$statusbar = Gtk::Statusbar->new();

Un nouvel identificateur de contexte est requis pour utiliser la fonction suivante ainsi qu'une brève description du contexte.

 
Sélectionnez
$statusbar->get_context_id($context_description);

Les trois fonctions suivantes peuvent opérer sur les barres d'état :

 
Sélectionnez
$statusbar->push($context_id, $text);
$statusbar->pop($context_id);
$statusbar->remove($context_id, $message_id);

La première, push(), est utilisée pour ajouter un nouveau message dans la barre d'état. Elle retourne un identificateur de Message, qui peut être passé plus tard à la fonction $status->remove() pour enlever le message avec le Message donné et l'identificateur de contexte de la pile de la barre d'état.

La fonction pop() enlève-le plus haut message de la pile avec l'identificateur de contexte donné.

XI-A. Exemple

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use strict ;
use Gtk2 '-init' ;
use constant TRUE => 1 ;
use constant FALSE => 0 ;

my $count = 1 ;

my $window = Gtk2::Window->new("toplevel") ;
$window->set_default_size(200 , 100) ;
$window->set_title("Barre d'état") ;
$window->signal_connect("delete event" , sub { Gtk2->main_quit ; }) ;

my $vbox = Gtk2::VBox->new(FALSE, 1) ;
$window->add($vbox) ;
$vbox->show() ;

my $statusbar = Gtk2::Statusbar->new() ;
$vbox->pack_start($statusbar, TRUE, TRUE, 0) ;
$statusbar->show() ;

my $context_id = $statusbar->get_context_id("Exemple de barre d'etat") ;

my $button = Gtk2::Button->new("Rajouter un élément") ;
$button->signal_connect("clicked" , \&push_item, $context_id) ;
$vbox->pack_start($button, TRUE, TRUE, 2) ;
$button->show() ;

my $button2 = Gtk2::Button->new("Retirer le dernier élément") ;
$button2->signal_connect("clicked" , \&pop_item, $context_id) ;
$vbox->pack_start($button2, TRUE, TRUE, 2) ;
$button2->show() ;

$window->show() ;

Gtk2->main() ;

### Rappels
sub push_item {
	my ($widget, $context_id) = @_ ;
	my $buff = ("Elément " . $count++) ;
	$statusbar->push($context_id, $buff) ;
}

sub pop_item {
	my ($widget, $context_id) = @_ ;
	$statusbar->pop($context_id) ;
}

XII. Les bulles d'aide

Ce sont des petits textes qui apparaissent quand vous laissez votre pointeur immobile pendant quelques secondes sur un bouton ou un autre widget.

Les widgets qui ne reçoivent pas d'événements (ceux qui n'ont pas leur propre fenêtre) ne fonctionneront pas avec les bulles d'aide.

Le premier appel que vous utilisez crée une bulle d'aide. Vous avez juste besoin de le faire une fois pour un ensemble de bulles d'aide. Cette fonction peut donc être utilisée pour créer plusieurs bulles d'aide.

 
Sélectionnez
$tooltips = Gtk2::Tooltips->new();

Une fois que vous avez créé une nouvelle bulle d'aide ainsi que le widget sur lequel vous voulez l'utiliser, il vous suffit d'appeler :

 
Sélectionnez
$tooltips->set_tip ($widget , $tip_text , $tip_private);

Le premier argument est le widget sur lequel vous voulez que la bulle d'aide apparaisse et le suivant le texte de la bulle d'aide. Le dernier argument est une chaîne de caractères qui peut être utilisée comme un identificateur quand on utilise le widget GTK TipsQuery pour implémenter l'aide sensitive du contexte. Pour le moment, vous pouvez le laisser blanc.

Voici un court exemple :

 
Sélectionnez
$button = Gtk::Button->new("Bouton");
$tooltips = Gtk::Tooltips->new();
$tooltips->set_tip ($button , "C'est un bouton" , "") ;

Vous pouvez utiliser d'autres appels avec les bulles d'aide. Je vais juste les lister avec une brève description de ce qu'ils peuvent faire. Pour rendre disponible un ensemble de bulles d'aide indisponibles :

 
Sélectionnez
$tooltips->enable();

Pour rendre indisponible un ensemble de bulles d'aide disponibles :

 
Sélectionnez
$tooltips->disable();

Pour déclarer le délai en millisecondes, avant que n'apparaisse la bulle d'aide.

 
Sélectionnez
$tooltips->set_delay ($delay);

La valeur par défaut est 500 millisecondes (une demie seconde).

On a ainsi vu toutes les fonctions associées aux bulles d'aide. Bien plus que ce vous vouliez savoir :-)

XIII. Les cadres

Les cadres sont utilisés pour enfermer un ou un groupe de widgets dans une boîte qui peut optionnellement avoir un titre. La position du titre et le style de la boîte peuvent être changés pour convenir à vos souhaits.

Un cadre est créé à l'aide de :

 
Sélectionnez
$frame = Gtk2::Frame->new($label);

Le label est par défaut placé en haut à gauche du cadre. Une chaîne vide en tant qu'argument $label aura pour conséquence qu'aucun label ne sera affiché. Le texte du label peut être changé avec :

 
Sélectionnez
$frame->set_label($label);

La position du label peut être modifiée :

 
Sélectionnez
$frame->set_label_align($xalign, $yalign);

$xalign et $yalign prennent des valeurs comprises entre 0.0 et 1.0. $xalign indique la position du label le long du bord horizontal supérieur du cadre. $yalign n'est pour l'instant pas utilisé. La valeur par défaut de $xalign est 0.0 ce qui place le label sur la gauche du cadre.

La fonction suivante change le style de la boîte qui est utilisé pour matérialiser le cadre :

 
Sélectionnez
frame->set_shadow_type($type);

L'argument $type peut prendre l'une des valeurs suivantes :

  • 'none' ;
  • 'in' ;
  • 'out' ;
  • 'etched_in' - le défaut ;
  • 'etched_out'.

XIII-A. Exemple

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use strict ;
use Gtk2 '-init' ;
use constant TRUE => 1 ;
use constant FALSE => 0 ;

my $i ;
my $item ;

my $window =Gtk2::Window->new("toplevel") ;
$window->set_title("Les cadres") ;
$window->signal_connect("destroy" , sub { Gtk2->main_quit ; }) ;
$window->set_border_width(10) ;

# Crée un cadre
my $frame = Gtk2::Frame->new() ;
$window->add($frame) ;

# Déclare le label du cadre
$frame->set_label("Cadre") ;

# Aligne le label sur la droite du cadre
$frame->set_label_align(1.0 , 0.0) ;

##### Un menu à options pour changer le style de cadre
my $opt1 = Gtk2::OptionMenu->new() ;
my $menu1 = Gtk2::Menu->new() ;
my $hbox1 = Gtk2::HBox->new(FALSE,10) ;
$frame->add($hbox1) ;
my $label1 = Gtk2::Label->new ("Style") ;
$hbox1->pack_start($label1,FALSE,FALSE,0) ;
	# On automatise le création des options
	my @style=('none' ,'in' ,'out' ,'etched in' ,'etched out') ;
	foreach (@style) {
		$item = make_menu_item($_, \&change_style, $_) ;
		$menu1->append($item) ;
	}
# On place le tout dans le menu puis dans la boîte
$opt1->set_menu($menu1) ;
$hbox1->pack_start($opt1, FALSE, FALSE, 0) ;

# Déclare le style du cadre
$frame->set_shadow_type('etched out') ;

$window->show_all() ;

main Gtk2 ;

sub change_style {
	my ($item, $style) = @_ ;
	$frame->set_shadow_type($style) ;
}

sub make_menu_item {
	my ($name, $callback, $data) = @_ ;
	my $item ;
	$item = Gtk2::MenuItem->new($name) ;
	$item->signal_connect("activate" , $callback, $data) ;
	$item->show() ;
	return $item ;
}

XIV. Un champ de saisie texte

XIV-A. Les widgets « éditables »

Le widget éditable est la classe de base du widget entry dont l'objet est de gérer des chaînes de caractères courtes. Cette classe ne peut être instanciée (il n'y a pas de fonction new()) et fournit uniquement les fonctionnalités communes aux widgets textes. Tous les widgets qui éditent du texte vous permettent de sélectionner une région de texte avec :

 
Sélectionnez
$editable->select_region($start, $end);

Ou le texte sélectionné est composé des caractères de la position $start jusqu'à la position $end non inclus. Si $end est négatif, alors il compte à rebours à partir de la fin du texte. Du texte peut être inséré à une certaine position en utilisant :

 
Sélectionnez
$editable->insert_text($new_text, $position);

$new_text est la chaîne à insérer et $position la position où il faut placer le texte. Des caractères peuvent être effacés avec :

 
Sélectionnez
$editable->delete_text($start, $pos);

Comme pour select_region, les caractères à effacer sont compris entre $start et $end. Vous pouvez récupérer des caractères avec :

 
Sélectionnez
$editable->get_chars($start, $end);

Si vous avez sélectionné du texte, vous pouvez le couper et le placer dans le bloc note avec :

 
Sélectionnez
$editable->cut_clipboard();

Cela prend les caractères sélectionnés pour les placer dans le bloc note et les efface du widget éditable. Vous pouvez aussi copier le texte sélectionné dans le bloc note.

 
Sélectionnez
$editable->copy_clipboard();

Les caractères sont copiés dans le bloc note mais pas effacé du widget éditable. Le texte du bloc note peut être collé dans le widget éditable à l'endroit où se trouve le curseur :

 
Sélectionnez
$editable->paste_clipboard();

Pour effacer le texte sélectionné :

 
Sélectionnez
$editable->delete_selection();

La position du curseur peut être déclarée et retrouvée avec :

 
Sélectionnez
$editable->set_position($position);
$editable->get_position();

Les widgets éditables peuvent être en lecture seule ou bien éditable :

 
Sélectionnez
$editable->set_editable($is_editable);

$is_editable est une valeur vraie ou fausse qui détermine si le widget éditable peut être édité ou non par l'utilisateur.

Le widget éditable possède un grand nombre de signaux disponibles qui sont :

'changed' 'move-to-row'
'insert-text' 'move-to-column'
'delete-text' 'kill-char'
'activate' 'kill-word'
'set-editable' 'kill-line'
'move-cursor' 'cut-clipboard'
'move-word' 'copy-clipboard'
'move-page' 'paste-clipboard'

L'utilisation de ces signaux devrait être évidente. Si vous avez une question sur leur utilisation, consulter la documentation GTK. Le seul commentaire que je ferais est que vous pouvez émettre le signal 'changed' en appelant la fonction :

 
Sélectionnez
$editable->changed();

XIV-A-1. Le widget d'entrée

Le widget d'entrée permet de taper du texte dans une boîte d'une seule ligne. Le texte peut être placé avec des appels de fonctions qui déterminent si le nouveau texte doit remplacer, se placer devant ou après le contenu actuel du widget entrée. Pour créer un widget d'entrée :

 
Sélectionnez
$entry = Gtk::Entry->new();

Il existe plusieurs fonctions qui modifient le texte contenu dans le widget d'Entrée :

 
Sélectionnez
$entry->set_text($text);
$entry->append_text($text);
$entry->prepend_text($text);

La fonction set_text() déclare le contenu du widget Entrée remplaçant le contenu présent. Les fonctions append_text() et prepend_text() permettent de placer du texte après ou avant le contenu du widget.

Si vous utilisez une Entrée et que vous ne voulez pas que le texte entré soit visible, par exemple quand vous entrez un mot de passe, vous pouvez utiliser la fonction suivante qui prend comme argument une valeur vraie ou fausse :

 
Sélectionnez
$entry->set_visibility($visible);

Si nous voulons attraper le moment ou l'utilisateur a rentré du texte, nous pouvons connecter un signal 'activate' ou 'changed'. 'activate' se manifeste quand l'utilisateur appuie sur la touche « Entrée » à l'intérieur du widget. 'changed' se manifeste quand le texte change, pour chaque caractère entré ou effacé.

XIV-A-1-a. Exemple

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use strict ;
use Gtk2 '-init' ;

# Variables convenables pour vrai et faux
use constant TRUE => 1 ;
use constant FALSE => 0 ;

my $window = Gtk2::Window->new("toplevel") ;
$window->set_default_size(200 , 100) ;
$window->set_title("L'entrée texte") ;
$window->signal_connect("delete event" , sub { Gtk2->main_quit() ; }) ;

my $vbox = Gtk2::VBox->new(FALSE, 0) ;
$window->add($vbox) ;
$vbox->show() ;

my $entry = Gtk2::Entry->new() ;
$entry->signal_connect("activate" , \&enter_callback, $entry) ;
$entry->set_text("Salut ") ;
$entry->append_text("à tous !") ;
$entry->select_region(0 , length($entry->get_text())) ;
$vbox->pack_start($entry, TRUE, TRUE, 0) ;
$entry->show() ;

my $hbox = Gtk2::HBox->new(FALSE, 0) ;
$vbox->add($hbox) ;
$hbox->show() ;

my $check1 = Gtk2::CheckButton->new("Editable") ;
$hbox->pack_start($check1, TRUE, TRUE, 0) ;
$check1->signal_connect("toggled" , \&entry_toggle_editable, $entry) ;
$check1->set_active(TRUE) ;
$check1->show() ;

my $check2 = Gtk2::CheckButton->new("Visible") ;
$hbox->pack_start($check2, TRUE, TRUE, 0) ;
$check2->signal_connect("toggled" , \&entry_toggle_visibility, $entry) ;
$check2->set_active(TRUE) ;
$check2->show() ;

my $button = Gtk2::Button->new_from_stock("gtk-close") ;
$button->signal_connect("clicked" , sub { Gtk2->main_quit() ; }) ;
$vbox->pack_start($button, TRUE, TRUE, 0) ;
$button->can_default(TRUE) ;
$button->grab_default() ;
$button->show() ;

$window->show() ;

main Gtk2 ;

### Fonctions de rappels
sub enter_callback {
	my ($widget, $entry) = @_ ;
	my $entry_text = $entry->get_text() ;
	print("L'entrée texte contient : $entry_text\n") ;
}

sub entry_toggle_editable {
	my ($checkbutton, $entry) = @_ ;
	$entry->set_editable($checkbutton->get_active) ;

sub entry_toggle_visibility {
	my ($checkbutton, $entry) = @_ ;
	$entry->set_visibility($checkbutton->get_active) ;
}

XV. Les dialogues

Le widget dialogue est très simple et n'est en fait qu'une fenêtre avec quelques éléments préemballés pour vous. Sont inclus dans le widget une fenêtre, une vbox et une action_area.

Une widget dialogue crée une fenêtre et regroupe une vbox dans le haut qui contient un séparateur et dessous une HBox appelée action_area. Pour créer une boîte de dialogue :

 
Sélectionnez
$dialog = Gtk2::Dialog->new();
$dialog = Gtk2::Dialog->new($titre,$parent,$flags...);
$dialog = Gtk2::Dialog->new_with_buttons($titre,$parent,$flags...);

Les deux dernières formes sont équivalentes et présentent l'avantage de déclarer le titre, les propriétés de la fenêtre, de placer les boutons en une seule étape.

Pour avoir accès aux différents aires de la boîte de dialogue, on utilisera $dialog->vbox et $dialog->action_area.

 
Sélectionnez
$label = Gtk2::Label->new("Les boîtes de dialogue sont sympas !");
$dialog->vbox->pack_start($label, TRUE, TRUE, 0);
$label->show();

Si vous désirez une fenêtre modale (qui gèle l'application tant que l'utilisateur n'aura pas fermé la fenêtre de dialogue), il suffit d'utiliser la méthode set_modal dont la fenêtre de dialogue hérite de Gtk2::Window. Vous pouvez également la déclarer modale lors de l'appel de la méthode new, en utilisant le flag 'modal'.

Que vous ajoutiez des boutons avec la méthode new_with_buttons, add_buttons ou add_action_widget, cliquer sur le bouton émettra un signal « réponse » avec un identifiant que vous spécifierez. Gtk2-Perl n'a pas de comportement prédéfini en fonction de la réponse, c'est vous qui devez le définir. Pour des raisons de commodité, vous pouvez utiliser les identifiant définis dans Gtk2 : :ReponseType. Si une fenêtre de dialogue reçoit un événement delete-event, le signal « reponse » sera émis avec l'identifiant 'none' (sauf dans le cas de run détaillé ci-dessous). Pour afficher un simple message vous pouvez utilisez par exemple le code suivant, même si dans la réalité vous utiliserez plutôt Gtk2 : :MessageDialog :

 
Sélectionnez
sub simple_message {
	my $message = shift ;
	my $dialog = Gtk2::Dialog->new ('Message' , $fenetre_principale,
	  				'destroy-with-parent',
	 				'gtk-ok' => 'none');
	my $label = Gtk2::Label->new($message);
	$dialog->vbox->add($label);
	
	### Pour être sûr que la fenêtre de dialogue soit détruite après
	### le click de l'utilisateur
	$dialog->signal_connect (response => sub {$_[0]->destroy;});
	
	$dialog->show_all;
}

Les identifiants définis dans Gtk2 : :ReponseType sont :

'none' 'close'
'reject' 'yes'
'accept' 'no'
'delete-event' 'apply'
'ok' 'help'
'cancel'  

Si vous voulez bloquer le déroulement du programme, c'est-à-dire que vous désirez avoir la réponse de la fenêtre de dialogue avant de continuer à parcourir le code, vous devez appeler $dialog->run();. Cette fonction déclenche une boucle et en sort quand l'utilisateur clique sur un bouton. Elle renvoit de plus l'identifiant du bouton cliqué. Durant run, le comportement par défaut de delete-event est changé. Si la fenêtre de dialogue reçoit l'événement delete-event, celle-ci ne sera pas détruite comme d'habitude mais run retournera 'delete-event'.

run appelle pour vous $dialog->show(), mais ne vous dispense pas de montrer tous les enfants contenus dans la fenêtre. Pendant run la fenêtre sera modale. Et enfin, une fois que run a retourné une valeur, vous êtes responsable du sort de la fenêtre dialogue. Exemple d'utilisation dans le cas d'une demande de validation :

 
Sélectionnez
if ('accept' eq $dialog->run) {
	# faire quelque chose... }
else {
	# ne rien faire ...
}
	# destruction de la fenêtre de dialogue
	$dialog->destroy();
}

Si on veut ajouter à posteriori un ou plusieurs boutons à la fenêtre de dialogue :

 
Sélectionnez
$dialog->add_button($texte,$reponse_id);
$dialog->add_buttons($texte1=>$reponse_id1, $texte2=>$reponse_id2,...);

Chaque nouveau bouton est ajouté à la droite des autres. $response_id est bien entendu l'identifiant de la réponse. $texte peut être une chaîne de caractères arbitraires ou bien un élément stock.

Pour rendre un bouton sensible ou non :

 
Sélectionnez
$dialog->set_response_sensitive($reponse_id,$valeur_booléenne);

XV-A. Boîtes de messages

Les boîtes de messages sont des boîtes de dialogue prédéfinies. Elles permettent d'afficher un message à côté d'un icône. Pour créer une boîte de message :

 
Sélectionnez
my $dialog = Gtk2::MessageDialog->new ( $fenetre_parente,
					$flags,$type,$bouton,..
					$message);

Le principe de fonctionnement est exactement le même que pour les fenêtres de dialogues. L'intérêt est ici de vous faire économiser du code et d'assurer une certaine homogénéité pour les applications Gtk2-Perl. Les quatre types de fenêtres sont :

  • 'info' ;
  • 'question' ;
  • 'error' ;
  • 'warning'.

XV-A-1. Exemple

Un exemple pour illustrer des différents types de fenêtres de messages.

Image non disponible Image non disponible
Image non disponible Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use strict ;
use Gtk2 '-init' ;
use constant TRUE => 1 ;
use constant FALSE => 0 ;

my $window = new Gtk2::Window('toplevel') ;
$window->set_border_width(15) ;
$window->set_title('Salut les boutons !') ;
$window->signal_connect('delete event' , sub {Gtk2->main_quit() ;}) ;

my $box = Gtk2::HBox->new(FALSE,10) ;
$window->add($box) ;

### Liste des différents types de fenêtres de dialogue
my @liste =('info' ,'error' ,'warning' ,'question') ;
### On lance ensuite un constructeur de boutons
foreach (@liste) {
	creer_bouton($_) ;
}

# On montre la boîte
$box->show ;

# On montre la fenêtre
$window->show() ;

# On lance la boucle principale.
Gtk2->main ;

### La fonction de rappel qui est appelé quand on a cliqué sur un bouton.
sub rappel{
	my ($widget,$type)=@_ ;
	my $dialog = Gtk2::MessageDialog->new ($window,
	  [qw/modal destroy-with-parent/],
	  $type,
	  'ok' ,
	  "Une boîte de dialogue de type : $type ") ;
	
	# On ajoute un nouveau bouton à la boîte de dialogue. On associe à ce bouton
	# l'identifiant 'cancel'
	$dialog->add_button("Salut !" ,'cancel') ;
	
	# Si l'utilisateur clique sur le bouton "Salut", on lui répond poliment
	if ('cancel' eq $dialog->run) {
		my $dialog2 = Gtk2::Dialog->new ("Bonjour !" , $dialog,
		  [qw/modal destroy-with-parent/],
		  'gtk-ok' =>'ok') ;
		my $label = Gtk2::Label->new("Comment vas-tu ?") ;
		$dialog2->vbox->add($label) ;
		$label->show() ;
		
		$dialog2->run() ;
		# le code est bloqué  tant que l'utilisateur ne clique pas sur un bouton
		$dialog2->destroy ;
	}
	$dialog->destroy ;
}

### Cette fonction crée les différents boutons
sub creer_bouton {
	my $texte = shift ;
	my $button = Gtk2::Button->new($texte) ;
	$button->signal_connect('clicked' , \&rappel, $texte) ;
	$box->pack_start($button,TRUE,TRUE,5) ;
	$button->show ;
}

XV-B. Dialogue de sélection de fichiers

XV-B-1. Créer une boîte de dialogue de sélection de fichiers

Cette boîte de dialogue est un moyen simple et rapide pour que l'utilisateur choisisse un nom de fichier. Elle est complète avec les boutons Ok, Cancel et Help. C'est donc un moyen simple d'économiser du temps de programmation. Pour créer une nouvelle boîte de dialogue :

 
Sélectionnez
$file_dialog = Gtk2::FileSelection->new($title);

XV-B-2. Utiliser un nom de fichier

Pour déclarer le nom de fichier, par exemple pour accéder à un répertoire spécifique ou donner un nom de fichier par défaut, on utilise :

 
Sélectionnez
$file_dialog->set_filename($filename);

Pour prendre le texte que l'utilisateur a entré ou sur lequel il a cliqué, utilisez :

 
Sélectionnez
$file_dialog->get_filename();

Vous pouvez utiliser un filtre de fichier dans le répertoire courant à l'aide de :

 
Sélectionnez
$file_dialog->complete($pattern);

Si un fichier correspond, il apparaîtra dans l'entrée texte de la boîte de dialogue. Si un ensemble de fichiers correspond au filtre alors la liste des fichiers ne contiendra que ceux-là. Un exemple de filtre : *.txt ou gtk*.

XV-B-3. Opérations sur les fichiers

La boîte de dialogue de sélection de fichiers peut montrer des boutons d'opérations sur les fichiers :

 
Sélectionnez
$file_dialog->show_fileop_buttons();

Ou bien les cacher avec :

 
Sélectionnez
$file_dialog->hide_fileop_buttons();

XV-B-4. Les widgets de dialogue

Vous pouvez aussi accéder aux widgets contenus dans le widget de sélection de fichiers. Il s'agit de :

 
Sélectionnez
dir_list
file_list
selection_entry
selection_text
main_vbox
ok_button
cancel_button
help_button

Vous utiliserez certainement les boutons, Ok, Cancel et Help en signalant leurs utilisations.

XV-B-5. Exemple

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use strict ;
use Gtk2 '-init' ;
use constant TRUE => 1 ;
use constant FALSE => 0 ;

# Crée un nouveau widget de sélection de fichiers
my $file dialog = Gtk2::FileSelection->new("Sélection de fichier") ;
$file dialog->signal_connect("destroy" , sub { Gtk2->main_quit() ; }) ;

# Connecte le bouton ok à la fonction file_ok_sel
$file dialog->ok_button->signal_connect("clicked" ,
		\&file_ok_sel,
		$file dialog) ;

# Connecte le bouton cancel à la fonction qui détruit le widget
$file dialog->cancel_button->signal_connect("clicked" ,
		sub { Gtk2->main_quit() ; }) ;

# éclare une nom de fichier, comme quand on veut sauver un fichier
$file dialog->set_filename("penguin.png") ;
$file dialog->show() ;

main Gtk2 ;

# Récupère le nom du fichier et l'imprime sur la console
sub file_ok_sel {
	my ($widget, $file_selection) = @_ ;
	my $file = $file_selection->get_filename() ;
	print("$file\n") ;
}

XV-C. La sélection des couleurs

Le widget de sélection de couleurs est, ce n'est pas surprenant, un widget pour sélectionner les couleurs de manière interactive. Ce widget composé laisse l'utilisateur choisir une couleur en manipulant les triplés RGB (Rouge, Vert, Bleu) ou HVS (Teinte, Saturation, Valeur). On le fait soit en ajustant chacune des valeurs avec un curseur ou avec une entrée de texte soit en pointant sur la couleur désirée dans le cercle de teinte-saturation et dans la barre de valeur.

Le widget de sélection de couleurs n'émet qu'un seul signal color_changed qui est émis si la couleur courante du widget change ou quand l'utilisateur la change ou si elle est choisie explicitement à l'aide de la fonction selection_set_color().

Regardons ce que le widget de sélection de couleurs nous propose. Le widget existe sous deux moutures : Gtk2::ColorSelection et Gtk2::ColorSelectionDialog.

 
Sélectionnez
$color = Gtk2::ColorSelection->new();

Vous n'utiliserez probablement jamais ce constructeur directement. Il crée juste un widget de sélection de couleurs orphelin que vous aurez apparenté vous-même. Le widget de sélection de couleurs hérite du widget VBox.

 
Sélectionnez
$color = Gtk2::ColorSelectionDialog->new($titre);

C'est le constructeur le plus commun. Il crée une fenêtre de dialogue de sélection de couleur. Elle est composée d'un cadre contenant un widget de sélection de couleurs, un séparateur horizontal, une HBox avec trois boutons, Ok, Annuler et Aide. Vous pouvez atteindre ces boutons en accédant aux widgets ok_button, cancel_button et help_button dans la structure du widget de sélection de couleurs. (i.e., $colordialog->ok_button).

 
Sélectionnez
$colordialog->set_update_policy ($policy);

Cette fonction déclare la politique de mise à jour. La politique par défaut est 'continuous' qui signifie que la couleur courante est mise à jour continuellement quand l'utilisateur prend le curseur, clique sur la souris, se déplace dans le cercle de teinte-saturation ou la barre de valeur. Si vous rencontrez des problèmes de performance, n'hésitez pas à choisir 'discontinuous' ou 'delayed'.

 
Sélectionnez
$colordialog->set_has_opacity ($use_opacity);

Le widget de sélection de couleurs supporte le réglage de l'opacité d'une couleur (également connu comme courbe alpha). Ce n'est pas disponible par défaut. Appeler cette fonction avec une valeur de $use_opacity permet l'opacité.

De même, une valeur fausse rend l'opacité indisponible.

 
Sélectionnez
$colordialog->set_current_color ($color);

Déclarer explicitement la couleur courante en appelant cette fonction avec comme argument une référence vers un Gtk : :Gdk : :Color. Pour déclarer l'opacité (le canal alpha), vous devez utiliser :

 
Sélectionnez
$colordialog->set_current_alpha ($alpha);

$alpha devrait être une valeur comprise entre 0 (complètement transparent) et 65636 (complètement opaque).

 
Sélectionnez
$colordialog->get_current_color();
$colordialog->get_current_alpha();

Quand vous voulez vous renseigner sur la couleur actuelle et le canal alpha, typiquement quand vous avez reçu le signal color_changed, vous pouvez utiliser ces fonctions.

XV-C-1. Exemple

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use constant FALSE => 0 ;
use constant TRUE => 1 ;

use Gtk2 '-init' ;

my $window = undef ;
my $da ;
my $color ;

$color = Gtk2::Gdk::Color->new (0 , 65535 , 0) ;

$window = Gtk2::Window->new ;
$window->set_title ("Sélection de couleurs") ;

$window->signal_connect (destroy => sub { $window = undef }) ;

$window->set_border_width (8) ;

my $vbox = Gtk2::VBox->new (FALSE, 8) ;
$vbox->set_border_width (8) ;
$window->add ($vbox) ;

#
# Crée une zone de dessin
#

my $frame = Gtk2::Frame->new ;
$frame->set_shadow_type ('in') ;
$vbox->pack_start ($frame, TRUE, TRUE, 0) ;

$da = Gtk2::DrawingArea->new ;
# déclare une taille minimale
$da->set_size_request (200 , 200) ;
# déclare la couleur
$da->modify_bg ('normal' , $color) ;
$frame->add ($da) ;

my $alignment = Gtk2::Alignment->new (1.0 , 0.5 , 0.0 , 0.0) ;

my $button = Gtk2::Button->new_with_mnemonic (" Changer la couleur ci-dessus") ;
$alignment->add ($button) ;

$vbox->pack_start ($alignment, FALSE, FALSE, 0) ;

$button->signal_connect (clicked => \&change_color_callback) ;

$window->show_all ;

Gtk2->main ;

sub change_color_callback {
	my $button = shift ;

	my $dialog = Gtk2::ColorSelectionDialog->new ("Changement de couleur") ;

	$dialog->set_transient_for ($window) ;

	my $colorsel = $dialog->colorsel ;

	$colorsel->set_previous_color ($color) ;
	$colorsel->set_current_color ($color) ;
	$colorsel->set_has_palette (TRUE) ;

	my $response = $dialog->run ;

	if ($response eq 'ok') {
		$color = $colorsel->get_current_color ;
		$da->modify_bg ('normal' , $color) ;
	}
	$dialog->destroy ;
}

#Copyright (C) 2003 by the gtk2-Perl team (see the file AUTHORS for the full list)

XV-D. Dialogue de sélection de police

Il s'agit d'une boîte de dialogue qui permet à l'utilisateur de choisir une police et un style de police à l'aide du widget préconçu FontSelectionDialog. La fonction suivante crée un widget de sélection de police :

 
Sélectionnez
$font_dialog = Gtk2::FontSelectionDialog->new($titre);

Pour obtenir la police sélectionnée dans la boîte de dialogue :

 
Sélectionnez
$font = $font_dialog->get_font();

$font sera alors une objet Gtk2::Gdk::Font.

Pour obtenir le nom de la police courante :

 
Sélectionnez
$font_dialog->get_font_name();

Pour déclarer la police sélectionnée dans la boîte de dialogue :

 
Sélectionnez
font_dialog->set_font_name($font_name);

$font_name est un chaîne de caractères avec le nom de la police à charger. Cette fonction retourne une valeur vraie si la police a été trouvée ou fausse si ce n'est pas la cas. Vous pouvez changer le texte d'aperçu grâce à :

 
Sélectionnez
$font_dialog->set_preview_text($texte);

Ou bien le récupérer à l'aide de :

 
Sélectionnez
$font_dialog->get_preview_text($texte);

XV-D-1. Exemple

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use strict ;
use Gtk2 '-init' ;
use constant TRUE => 1 ;
use constant FALSE => 0 ;

# Crée un nouveau widget de sélection de fichiers
my $font_dialog = Gtk2::FontSelectionDialog->new("Sélection de police") ;
$font_dialog->signal_connect("destroy" , sub { Gtk2->main_quit() ; }) ;

# Connecte le bouton ok à la fonction font_ok_sel
$font_dialog->ok_button->signal_connect("clicked" ,
		\&font_ok_sel,
		$font_dialog) ;

# Connecte le bouton cancel à la fonction qui détruit le widget
$font_dialog->cancel_button->signal_connect("clicked" ,
		sub { Gtk2->main }) ;

$font_dialog->show() ;

main Gtk2 ;

# Récupère le nom de la police et l'imprime sur la console
sub font_ok_sel {
	my ($widget, $font_selection) = @_ ;
	my $font = $font_selection->get_font_name() ;
	print("$font\n") ;
}

XVI. Les boutons spin

Les boutons spins sont généralement utilisés pour permettre à l'utilisateur de sélectionner une valeur dans un intervalle de valeurs. Ils consistent en une boite d'entrée texte avec des boutons flèches attachés sur le côté. Cliquer sur l'un des boutons fait varier la valeur sur l'échelle des valeurs disponibles. La boîte entrée peut aussi être éditée directement pour entrer une valeur spécifique.

Le bouton spin permet à la valeur d'être entière ou avec un certain de décimales et d'être incrémentée/décrémentée selon des valeurs configurables. L'action de maintenir un bouton enfoncé peut, optionnellement, se traduire par une accélération de la variation de la valeur en fonction de la durée de la pression.

Le bouton spin utilise les ajustements pour prendre des informations à propos de l'échelle des valeurs que le bouton peut prendre. Rappelez-vous que le widget ajustement est créé par la fonction suivante et qui montre les informations qu'elle prend en compte :

 
Sélectionnez
$adj = Gtk2::Adjustment->new($value,
			     $lower,
			     $upper,
			     $step_increment,
			     $page_increment,
			     $page_size);

Les attributs de l'ajustement sont utilisés par les boutons spin sont :

  • $value : valeur initiale pour le bouton spin ;
  • $lower : valeur la plus basse ;
  • $upper : valeur la plus haute ;
  • $step_increment : valeur d'incrément/décrément en pressant le bouton 1 de la souris ;
  • $page_increment : valeur d'incrément/décrément en pressant le bouton 2 de la souris ;
  • $page_size : inutilisé.

En plus, le bouton trois de la souris peut être utilisé pour sauter directement à la valeur $upper ou $lower quand on l'utilise pour sélectionner l'un des boutons.

XVI-A. Créer un bouton spin

 
Sélectionnez
$spin = Gtk2::SpinButton->new($adjustment, $climb_rate, $digits);

L'argument $climb_rate prend une valeur entre 0.0 et 1.0 et indique la quantité d'accélération du bouton spin.

L'argument $digits spécifie le nombre de décimales que la valeur peut prendre.

XVI-B. Configuration

Un bouton spin peut être reconfiguré après sa création avec :

 
Sélectionnez
$spin->configure($adjustment, $climb_rate, $digits);

L'ajustement peut être réglé et retrouvé indépendamment un en utilisant les deux fonctions :

 
Sélectionnez
$spin->set_adjustment($adjustment);
$spin->get_adjustment();

Le nombre de décimales peut être changé par :

 
Sélectionnez
$spin->set_digits($digits);

XVI-C. Valeur

La valeur que le bouton spin est en train d'afficher peut-être changée par :

 
Sélectionnez
$spin->set_value($value);

La valeur du bouton spin peut être retrouvée sous la forme d'un nombre décimal ou d'un entier à l'aide de :

 
Sélectionnez
$spin->get_value();
$spin->get_value_as_int();

Si vous voulez changer la valeur du bouton spin :

 
Sélectionnez
$spin->spin($direction, $increment);

Le paramètre $direction peut prendre les valeurs :

  • 'forward' et 'backward' change la valeur du bouton selon la valeur spécifiée par $increment sauf si $increment vaut 0 auquel cas la valeur est changée à l'aide de la valeur $step_increment de l'ajustement ;
  • 'page_forward' et 'page_backward' change simplement la valeur du bouton spin selon $increment ;
  • 'home' déclare la valeur à la valeur basse de l'ajustement ;
  • 'end' déclare la valeur à la valeur haute de l'ajustement ;
  • 'user_defined' change la valeur selon une valeur spécifiée par l'utilisateur.

XVI-D. Comportement

Laissons maintenant les fonctions de réglage et de récupération pour nous intéresser aux fonctions qui agissent sur l'apparence et sur le comportement du bouton spin.

La première de ces fonctions est utilisée pour contraindre la boîte texte du bouton à ne contenir qu'une valeur numérique. Cela empêche l'utilisateur de taper autre chose qu'une valeur numérique.

 
Sélectionnez
$spin->set_numeric($numeric);

Vous pouvez indiquer si le bouton est compris entre la valeur la plus basse et la plus haute.

 
Sélectionnez
$spin->set_wrap($wrap);

Vous pouvez obliger le bouton à arrondir la valeur au plus proche step_increment qui est déclarée à l'intérieur de l'objet ajustement utilisé par le bouton spin :

 
Sélectionnez
$spin->set_snap_to_ticks($snap_to_ticks);

Le comportement du bouton peut être modifié par :

 
Sélectionnez
$spin->set_update_policy($policy);

Les valeurs possibles de $policy sont soit 'always' soit 'if_valid'. Ces valeurs affectent le comportement du bouton quand l'utilisateur écrit la valeur. Dans le cas de 'if_valid', la valeur du bouton spin ne change que si le texte d'entrée est une valeur numérique comprise entre les valeurs spécifiées par l'ajustement. Autrement le texte est effacé et remplacé par la valeur courante. Dans le cas de 'update_always', on ignore les erreurs en convertissant le texte en valeur numérique.

XVI-E. Exemple

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use Gtk2 '-init' ;
# Variables convenables pour vrai et faux
use constant TRUE => 1 ;
use constant FALSE => 0 ;

use strict ;

my $window = Gtk2::Window->new("toplevel") ;
$window->signal_connect("destroy" , sub { Gtk2->main_quit ; }) ;
$window->set_title("Les boutons Spin") ;

my $main_vbox = Gtk2::VBox->new(FALSE, 5) ;
$main_vbox->set_border_width(10) ;
$window->add($main_vbox) ;

# Un cadre dans lequel on place une boîte verticale
my $frame = Gtk2::Frame->new("Pas accéleré") ;
$main_vbox->pack_start($frame, TRUE, TRUE, 0) ;
my $vbox1 = new Gtk2::VBox(FALSE, 0) ;
$vbox1->set_border_width(5) ;
$frame->add($vbox1) ;

# Une boîte horizontale pour placer nos trois boutons spins
my $hbox1 = Gtk2::HBox->new(FALSE, 0) ;
$vbox1->pack_start($hbox1, TRUE, TRUE, 5) ;

# Création d'une boîte verticale dans lequel on place le label
# puis le bouton spin
my $vbox2 = Gtk2::VBox->new(FALSE, 0) ;
$hbox1->pack_start($vbox2, TRUE, TRUE, 5) ;
my $label1 = Gtk2::Label->new("Jour :") ;
$label1->set_alignment(0 , 0.5) ;
$vbox2->pack_start($label1, FALSE, TRUE, 0) ;
my $adj = Gtk2::Adjustment->new(1.0 , 1.0 , 31.0 , 1.0 , 5.0 , 0.0) ;
my $spinner1 = Gtk2::SpinButton->new($adj, 0 , 0) ;
$spinner1->set_wrap(TRUE) ;
$vbox2->pack_start($spinner1, FALSE, TRUE, 0) ;

# idem
my $vbox3 = Gtk2::VBox->new(FALSE, 0) ;
$hbox1->pack_start($vbox3, TRUE, TRUE, 5) ;
my $label2 = Gtk2::Label->new("Mois :") ;
$label2->set_alignment(0 , 0.5) ;
$vbox3->pack_start($label2, FALSE, TRUE, 0) ;
my $adj2 = Gtk2::Adjustment->new(1.0 , 1.0 , 12.0 , 1.0 , 5.0 , 0.0) ;
my $spinner2 = Gtk2::SpinButton->new($adj2, 0 , 0) ;
$spinner2->set_wrap(TRUE) ;
$vbox3->pack_start($spinner2, FALSE, TRUE, 0) ;

# idem
my $vbox4 = Gtk2::VBox->new(FALSE, 0) ;
$hbox1->pack_start($vbox4, TRUE, TRUE, 5) ;
my $label3 = Gtk2::Label->new("Année :") ;
$label3->set_alignment(0 , 0.5) ;
$vbox4->pack_start($label3, FALSE, TRUE, 0) ;
my $adj3 = Gtk2::Adjustment->new(1998.0 , 0.0 , 2100.0 , 1.0 , 100.0 , 0.0) ;
my $spinner3 = Gtk2::SpinButton->new($adj3, 0 , 0) ;
$spinner3->set_wrap(FALSE) ;
$vbox4->pack_start($spinner3, FALSE, TRUE, 0) ;

# Second cadre avec également une boite verticale
my $frame2 = Gtk2::Frame->new("Accéléré") ;
$main_vbox->pack_start($frame2, TRUE, TRUE, 0) ;
my $vbox5 = Gtk2::VBox->new(FALSE, 0) ;
$vbox5->set_border_width(5) ;
$frame2->add($vbox5) ;

# même technique que précédemment
my $hbox2 = Gtk2::HBox->new(FALSE, 0) ;
$vbox5->pack_start($hbox2, FALSE, TRUE, 5) ;

# idem
my $vbox6 = Gtk2::VBox->new(FALSE, 0) ;
$hbox2->pack_start($vbox6, TRUE, TRUE, 5) ;
my $label4 = Gtk2::Label->new("Valeur : ") ;
$label4->set_alignment(0 , 0.5) ;
$vbox6->pack_start($label4, FALSE, TRUE, 0) ;
my $adj4 = Gtk2::Adjustment->new(0.0 , -10000.0 , 10000.0 , 0.1 , 100.0 , 0.0) ;
my $spinner4 = Gtk2::SpinButton->new($adj4, 1.0 , 2) ;
$spinner4->set_wrap(TRUE) ;
$vbox6->pack_start($spinner4, FALSE, TRUE, 0) ;

# idem
my $vbox7 = Gtk2::VBox->new(FALSE, 0) ;
$hbox2->pack_start($vbox7, TRUE, TRUE, 5) ;
my $label5 = Gtk2::Label->new("Décimales :") ;
$label5->set_alignment(0 , 0.5) ;
$vbox7->pack_start($label5, FALSE, TRUE, 0) ;
my $adj5 = Gtk2::Adjustment->new(2 , 1 , 5 , 1 , 1 , 0) ;
my $spinner5 = Gtk2::SpinButton->new($adj5, 0.0 , 0) ;
$spinner5->set_wrap(TRUE) ;
$adj5->signal_connect("value changed" , \&change_digits, $spinner4) ;
$vbox7->pack_start($spinner5, FALSE, TRUE, 0) ;

# Les cases à cocher pour changer les propriétés du bouton $spinner4
my $hbox3 = Gtk2::HBox->new(FALSE, 0) ;
$vbox7->pack_start($hbox3, FALSE, TRUE, 5) ;
my $button1 = Gtk2::CheckButton->new("Arrondi à l'incrément près") ;
$button1->signal_connect("clicked" , \&toggle_snap, $spinner4) ;
$vbox5->pack_start($button1, TRUE, TRUE, 0) ;
$button1->set_active(TRUE) ;
# idem
my $button2 = Gtk2::CheckButton->new("Entrée uniquement numérique") ;
$button2->signal_connect("clicked" , \&toggle_numeric, $spinner4) ;
$vbox5->pack_start($button2, TRUE, TRUE, 0) ;
$button2->set_active(TRUE) ;

# Les boutons qui déclenchent la lecture du contenu du bouton $spinner4
my $hbox4 = Gtk2::HBox->new(FALSE, 0) ;
$vbox5->pack_start($hbox4, FALSE, TRUE, 5) ;

my $button3 = Gtk2::Button->new("Valeur entière") ;
$button3->signal_connect('clicked' ,\&get_value,1) ;
$hbox4->pack_start($button3, TRUE, TRUE, 5) ;

my $button4 = Gtk2::Button->new("Valeur décimale") ;
$button4->signal_connect("clicked" , \&get_value,2) ;
$hbox4->pack_start($button4, TRUE, TRUE, 5) ;

# Le label pour afficher la valeur retenue
my $val_label = Gtk2::Label->new("0") ;
$vbox5->pack_start($val_label, TRUE, TRUE, 0) ;

# Le bouton pour quitter
my $button5 = Gtk2::Button->new_from_stock('gtk-close') ;
$button5->signal_connect("clicked" , sub { Gtk2->main_quit ; }) ;
$main_vbox->pack_start($button5, TRUE, TRUE, 5) ;

$window->show_all() ;

Gtk2->main ;

### Routines
sub toggle_snap {
	my ($widget, $spin) = @_ ;
	$spin->set_snap_to_ticks($widget->get_active) ;
}

sub toggle_numeric {
	my ($widget, $spin) = @_ ;
	$spin->set_numeric($widget->get_active) ;
}

sub change_digits {
	my ($widget, $spin) = @_ ;
	$spin->set_digits($spinner5->get_value_as_int()) ;
}

sub get_value {
	my ($widget, $num) = @_ ;
	my $buf = "" ;
	if ($num == 1)
	{
		$buf = $spinner4->get_value_as_int() ;
	}
	else
	{
		$buf = $spinner4->get_value() ;
	}
	$val_label->set_text($buf) ;
}

XVII. Les notebooks

Le widget Notebook est une collection de « pages » qui se recouvrent, chaque page contient des informations différentes et seule l'une des pages est visible. Ce widget est devenu très commun dernièrement dans la programmation GUI, et c'est un bon moyen de montrer des blocks d'informations similaires tout en garantissant une séparation de leurs affichages.

La première fonction, bien évidemment, sert à créer un nouveau notebook :

 
Sélectionnez
$notebook = Gtk2::Notebook->new();

Détaillons les fonctions qui permettent de manipuler notre notebook fraîchement créé.

La première que nous rencontrerons sert à positionner les indicateurs de pages. Ces indicateurs de pages ou « tabs » c'est ainsi qu'ils sont référencés, peuvent être positionnés de quatre manières : en haut, en bas, à gauche ou à droite.

 
Sélectionnez
$notebook->set_tab_pos($position);

$position peut être :

  • 'left' - gauche ;
  • 'right' - droite ;
  • 'top' - en haut - le défaut ;
  • 'bottom' - en bas.

Ensuite, voyons comment ajouter des pages au notebook. Il existe trois moyens d'ajouter des pages à un Notebook.

Les deux premiers sont presque similaires :

 
Sélectionnez
$notebook->append_page($child, $tab_label);
$notebook->prepend_page($child, $tab_label);

Les fonctions ajoutent des pages les pages à la fin ou au début du notebook. $child est le widget qui est placé dans la page du notebook, et $tab_label est le label pour la page ajoutée. Le widget $child doit être créé séparément, et est typiquement un ensemble de déclarations d'options placés dans un autre widget conteneur, comme une table par exemple.

La dernière fonction pour ajouter des pages à un notebook contient toutes les propriétés des deux précédentes mais permet de spécifier à quelle position vous voulez placer la page.

 
Sélectionnez
$notebook->insert_page($child, $tab_label, $position);

Les paramètres sont les mêmes que pour append et prepend avec un paramètre supplémentaire, $position. Ce paramètre indique la position de la page à insérer sachant que la première page possède la position zéro.

Maintenant que nous savons comment ajouter des pages, voyons comment les enlever.

 
Sélectionnez
$notebook->remove_page($page_num);

Cette fonction enlève la page $page_num du notebook. Nous retrouvons la page courante à l'aide de :

 
Sélectionnez
$notebook->get_current_page();

Nous retrouvons le numéro de la page courante à l'aide de :

 
Sélectionnez
$notebook->page_num($child);

Cette fonction retourne -1 si $child n'est pas une page du $notebook.

Si vous voulez changer le numéro de page d'un enfant, vous pouvez utiliser :

 
Sélectionnez
$notebook->reorder_child($child, $position);

Les deux fonctions suivantes sont de simples appels qui permettent de bouger vers l'avant ou l'arrière les pages du notebook. Il suffit de fournir à chaque appel de fonction le widget notebook sur lequel on veut agir. Notez que, quand le notebook est sur la dernière page et que next_page() est appelé, le notebook se retrouvera sur la première page.

De même, quand on est sur la première page et que l'on appelle prev_page(), on se retrouve sur la dernière page.

 
Sélectionnez
$notebook->next_page();
$notebook->prev_page();

La fonction suivante déclare une page active. Si vous voulez que le notebook soit ouvert en page 5 par exemple, vous utiliserez cette fonction. Sans cette fonction, la page par défaut est la première.

 
Sélectionnez
$notebook->set_current_page($page_num);

Les deux fonctions suivantes ajoutent ou enlèvent respectivement les indicateurs des pages et les bords du notebook.

 
Sélectionnez
$notebook->set_show_tabs($show_tabs);
$notebook->set_show_border($show_border);

$show_tabs et $show_border sont des valeurs booléennes.

La fonction suivante est utile quand vous avez un grand nombre de pages, et que l'affichage des indicateurs de pages pose problème. Cela permet de faire défiler les tabs en utilisant deux boutons flèches.

 
Sélectionnez
$notebook->set_scrollable($scrollable);

La largeur des bords autour du bookmark peut être déclarée avec :

 
Sélectionnez
$notebook->set_tab_border($border_width);

Vous pouvez également décider si tous les indicateurs de pages sont de la même taille ou non :

 
Sélectionnez
$notebook->set_homogeneous_tabs($homogeneous);

$homogeneous est une valeur vraie ou fausse.

XVII-A. Exemple

Maintenant voyons un exemple. Il est inspiré du code de textgtk.c qui est fourni avec la distribution GTK. Ce petit programme crée une fenêtre avec un notebook et six boutons. Le notebook contient 11 pages, ajoutées selon les trois méthodes, au début, à la fin ou insérées. Les boutons vous permettent de décaler la position des indicateurs de pages, ajouter ou enlever les indicateurs ou les bords, enlever une page, changer les pages de deux manières (en avant, en arrière) et de sortir du programme.

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use strict ;
use Gtk2 '-init' ;
use constant TRUE => 1 ;
use constant FALSE => 0 ;

my $i ;
my $bufferf ;
my $bufferl ;
my $button ;
my $label ;
my $frame ;
my $window = Gtk2::Window->new("toplevel") ;
$window->signal_connect("delete event" , sub { Gtk2->main_quit() ; }) ;
$window->set_border_width(10) ;

my $table = Gtk2::Table->new(3 , 6 , FALSE) ;
$window->add($table) ;

# Crée un nouveau notebook, détermine la position des onglets
my $notebook = Gtk2::Notebook->new() ;
$notebook->set_tab_pos('top') ;
$table->attach_defaults($notebook, 0 , 6 , 0 , 1) ;
$notebook->show() ;

# Ajoutons quelques pages à la fin du notebook
for ($i = 0 ; $i < 5 ; $i++)
{
	$bufferf = "Append Frame " . ($i + 1) ;
	$bufferl = "Page " . ($i + 1) ;
	
	$frame = Gtk2::Frame->new($bufferf) ;
	$frame->set_border_width(10) ;
	$frame->set_size_request(100 , 75) ;
	$frame->show() ;

	$label = Gtk2::Label->new($bufferf) ;
	$frame->add($label) ;
	$label->show() ;

	$label = new Gtk2::Label($bufferl) ;
	$notebook->append_page($frame, $label) ;
}

# Maintenant ajoutons une page à une marque spécifique
my $checkbutton = Gtk2::CheckButton->new("Cliquez moi, s'il vous plaît !") ;
$checkbutton->set_size_request(100 , 75) ;
$checkbutton->show() ;

$label = Gtk2::Label->new("Page ajoutée") ;
$notebook->insert_page($checkbutton, $label, 2) ;

# Maintenant, enfin, ajoutons des pages au début du notebook
for ($i = 5 ; $i < 10 ; $i++)
{
	$bufferf = "Prepend Frame " . ($i + 1) ;
	$bufferl = "Page " . ($i + 1) ;
	
	$frame = Gtk2::Frame->new($bufferf) ;
	$frame->set_border_width(10) ;
	$frame->set_size_request(100 , 75) ;
	$frame->show() ;
	
	$label = Gtk2::Label->new($bufferf) ;
	$frame->add($label) ;
	$label->show() ;
	
	$label =Gtk2::Label->new($bufferl) ;
	$notebook->prepend_page($frame, $label) ;
}

# Indiquons à quel page ouvrir le notebook (page 4)
$notebook->set_current_page(3) ;

# Crée une brochette de boutons
$button = Gtk2::Button->new_from_stock('gtk-close') ;
$button->signal_connect("clicked" , sub { Gtk2->main_quit() ; }) ;
$table->attach_defaults($button, 0 , 1 , 1 , 2) ;
$button->show() ;

$button = Gtk2::Button->new("Page suivante") ;
$button->signal_connect("clicked" , sub { $notebook->next_page() ; }) ;
$table->attach_defaults($button, 1 , 2 , 1 , 2) ;
$button->show() ;

$button = Gtk2::Button->new("Page précédent") ;
$button->signal_connect("clicked" , sub { $notebook->prev_page() ; }) ;
$table->attach_defaults($button, 2 , 3 , 1 , 2) ;
$button->show() ;

$button = Gtk2::Button->new("Position des onglets") ;
$button->signal_connect("clicked" , \&rotate_book, $notebook) ;
$table->attach_defaults($button, 3 , 4 , 1 , 2) ;
$button->show() ;

$button = Gtk2::Button->new("Onglet ou pas") ;
$button->signal_connect("clicked" , \&tabsborder_book, $notebook) ;
$table->attach_defaults($button, 4 , 5 , 1 , 2) ;
$button->show() ;

$button = Gtk2::Button->new("Supprimer la page") ;
$button->signal_connect("clicked" , \&remove_book, $notebook) ;
$table->attach_defaults($button, 5 , 6 , 1 , 2) ;

$button->show() ;
$table->show() ;
$window->show() ;
Gtk2->main ;

### Routines
# Cette fonction décale la position des tabs
sub rotate_book {
	my ($button, $notebook) = @_ ;
	my %rotate = (top => 'right' ,
		      right => 'bottom' ,
		      bottom => 'left' ,
		      left => 'top') ;
	$notebook->set_tab_pos($rotate{ $notebook->get_tab_pos()}) ;
}

# Ajoute/Enlève les tabs des pages et les bords
sub tabsborder_book {
	my ($button, $notebook) = @_ ;
	my $tval = FALSE ;
	my $bval = FALSE ;
	if ($notebook->get_show_tabs == 0)
	{
		$tval = TRUE ;
	}
	if ($notebook->get_show_border == 0)
	{
		$bval = TRUE ;
	}
	$notebook->set_show_tabs($tval) ;
	$notebook->set_show_border($bval) ;
}

# Enlève une page du notebook
sub remove_book {
	my ($button, $notebook) = @_ ;
	my $page ;
	$page = $notebook->get_current_page() ;
	$notebook->remove_page($page) ;
	
	# On a besoin de rafraîchir le widget. Cela force
	# le widget à se redessiner
	$notebook->queue_draw() ;
}

XVIII. Les flèches

Le widget flèche dessine une pointe de flèche. On a le choix entre un certain nombre de directions possibles et une certain nombre de styles. Placée dans un bouton, une flèche peut être très utile. Comme le widget Label, elle n'émet aucun signal. Il n'y a que deux fonction pour manipuler les flèches.

 
Sélectionnez
$arrow = Gtk2::Arrow->new ($arrow_type , $shadow_type);
$arrow ->set ($arrow_type , $shadow_type);

La première crée une nouvelle flèche en précisant le type et l'apparence. La seconde permet à ces valeurs d'être modifiées rétrospectivement.

L'argument $arrow_type peut prendre l'une des valeurs suivantes :

  • 'up' ;
  • 'down' ;
  • 'left' ;
  • 'right'.

Ces valeurs indiquent évidemment la direction dans laquelle pointe la flèche.

L'argument $shadow_type peut prendre les valeurs :

  • 'in' ;
  • 'out' (valeur par défaut) ;
  • 'etched in' ;
  • 'etched out'.

XVIII-A. Exemple

Voici un bref exemple pour illustrer l'usage des flèches.

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w

use Gtk2 '-init' ;

# Variables convenables pour vrai et faux
use constant TRUE => 1 ;
use constant FALSE => 0 ;

my $window = Gtk2::Window->new("toplevel") ;
$window->set_title("Les flêches") ;
$window->signal_connect("destroy" , sub { Gtk2->main_quit ; }) ;
$window->set_border_width(10) ;

# Crée une boîte qui contiendra les boutons-flèches
my $box = Gtk2::HBox->new(FALSE, 0) ;
$box->set_border_width(2) ;
$window->add($box) ;

# Regroupe et montre tous les widgets
$box->show() ;

my $button1 = create_arrow_button('up' , 'in') ;
$box->pack_start($button1, FALSE, FALSE, 3) ;

my $button2 = create_arrow_button('down' , 'out') ;
$box->pack_start($button2, FALSE, FALSE, 3) ;

my $button3 = create_arrow_button('left' , 'in') ;
$box->pack_start($button3, FALSE, FALSE, 3) ;

my $button4 = create_arrow_button('right' , 'out') ;
$box->pack_start($button4, FALSE, FALSE, 3) ;

$window->show() ;

Gtk2->main ;

### Routines
# Crée un widget flèche avec les paramètres spécifiés et la
# place dans un bouton
sub create_arrow_button {
	my ($arrow_type, $shadow_type) = @_ ;
	my $button = Gtk2::Button->new() ;
	my $arrow = Gtk2::Arrow->new($arrow_type, $shadow_type) ;
	$button->add($arrow) ;
	$button->show() ;
	$arrow->show() ;
	return ($button) ;
}

XIX. Le calendrier

Le widget calendrier est un moyen efficace d'afficher et de retrouver des informations liées aux dates. C'est un widget très simple à créer et à utiliser.

Créer un widget calendrier est aussi simple que :

 
Sélectionnez
$calendar = Gtk2::Calendar->new();

Il arrive parfois que vous ayez beaucoup d'informations à changer à l'intérieur de ce widget. Les fonctions suivantes vous permettent de faire de multiples changements sans que l'utilisateur voit les multiples mises à jour à l'écran :

 
Sélectionnez
$calendar->freeze();
$calendar->thaw();

freeze « gèle » la mise à jour de l'affichage de sorte que les modifications de l'on peut faire n'apparaissent pas et thaw « dégèle » la mise à jour de l'affichage du widget ainsi les modifications que l'on a pu faire sont visibles.

Le widget calendrier possède quelques options qui vous permettent de changer le look du widget ainsi que la manière dont il opère :

 
Sélectionnez
$calendar->display_options($flags);

L'argument $flag peut être formé en combinant les cinq options suivantes :

  • 'show-heading' - cette option spécifie que le mois et l'année doivent être montrés quand on dessine le calendrier ;
  • 'show-day-names' - cette option spécifie que les trois premières lettres de chaque jour doivent être affichées (e.g. lun, mar,…) ;
  • 'no-month-change' - cette option stipule que l'utilisateur ne devrait pas et ne peut pas changer le mois affiché. Cela peut être bon si vous avez seulement besoin d'un mois particulier, par exemple quand vous affichez douze widgets calendrier pour chaque mois d'une année particulière ;
  • 'show_week_numbers' - cette option spécifie que le numéro de chaque semaine doit être affiché sous le côté gauche sur calendrier (e.g. 1 janvier = semaine 1, 31 décembre = semaine 52) ;
  • 'week_start_monday' - cette option stipule que le premier jour de la semaine est lundi à la place de dimanche qui est la valeur par défaut. Cela affecte uniquement l'ordre dans lequel sont affichés les jours de la gauche vers la droite.

Les fonctions suivantes sont utilisées pour déclarer la date courante affichée :

 
Sélectionnez
$calendar->selected_month ($mois , $annee) ;
$calendar->selected_day ($jour);

La valeur de retour de selected_month est une valeur booléenne qui indique si la sélection est réussie.

Avec selected_day, le nombre spécifié est sélectionné à l'intérieur du mois courant, si c'est possible. Une valeur $jour de 0 désélectionnera la sélection courante.

En plus d'avoir un jour sélectionné, n'importe quel nombre de jour dans le mois peuvent être « marqués ». Un jour marqué est surligné dans l'affichage du calendrier. Les fonctions suivantes sont fournies pour manipuler les jours marqués :

 
Sélectionnez
$calendar->mark_day ($jour) ;
$calendar->unmark_day ($jour);
$calendar->clear_marks();

Les jours marqués actuellement sont stockés dans un tableau. Ce tableau est composé de 31 éléments ainsi, si vous voulez savoir si un jour est marqué, vous devez accéder à l'élément correspondant du tableau (n'oubliez pas que les éléments d'un tableau sont numérotés de 0 à n-1). Par exemple :

 
Sélectionnez
if ($calendar->marked_date [$jour - 1])
{
print ("Le jour $jour est marqué.\n);
}

Notez que les marques sont persistantes à travers les changements de mois et d'années.

La dernière fonction concernant le calendrier est utilisée pour retrouver la date courante sélectionnée.

 
Sélectionnez
($annee , $mois , $jour) = $calendar->get_date();

Le widget calendrier peut générer un nombre de signaux indiquant les sélections de dates et les changements. Les noms des signaux sont très explicites :

  • 'month_changed' - mois changé ;
  • 'day_selected' - jour sélectionné ;
  • 'day_selected_double_click' - jour sélectionné par un double clic ;
  • 'prev_month' - mois précédent ;
  • 'next_month' - mois suivant ;
  • 'prev_year' - année précédente ;
  • 'next_year' - année suivante.

XIX-A. Exemple

Il ne nous reste plus qu'à mettre ensemble toutes ces fonctions. On obtient :

Image non disponible
 
Sélectionnez
# !/usr/bin/Perl -w
# Copyright (C) 1998 Cesar Miquel, Shawn T. Amundson, Mattias Gr¨onlund
# Copyright (C) 2000 Tony Gale
#
# Copyright (C) 2003 by the gtk2-Perl team (see the file AUTHORS for the full
# list)
#
# This library is free software ; you can redistribute it and/or modify it under
# the terms of the GNU Library General Public License as published by the Free
# Software Foundation ; either version 2.1 of the License, or (at your option)
# any later version.
#
# This library is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. ee the GNU Library General Public License for
# more details.
#
# You should have received a copy of the GNU Library General Public License
# along with this library ; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 2111-1307 SA.
#
# $Header : /cvsroot/gtk2-Perl/gtk2-Perl-xs/Gtk2/examples/calendar.pl,v 1.5 2003/09/22 00 :04 :23 rwmcfa1
# Exp
#
# this was originally gtk-2.2.1/examples/buttonbox/buttonbox.c
# ported to gtk2-Perl (and Perl-ized) by rm
use strict ;
use Gtk2 ;

use constant TRUE => 1 ;
use constant FALSE => 0 ;

use constant DEF PAD => 10 ;
use constant DEF PAD SMALL => 5 ;

use constant TM YEAR BASE => 1900 ;

sub calendar_select_font
{
	my $calendar = shift ;
	my $fsd = Gtk2::FontSelectionDialog->new('Sélection de police') ;
	$fsd->set_position('mouse') ;
	$fsd->ok_button->signal_connect('clicked' => sub {
		my $font_name = $fsd->get_font_name ;
		if ($font_name ne '')
		{
			$calendar->modify font(
				Gtk2::Pango : :FontDescription->from_string(
				$font_name)) ;
		}
		$fsd->destroy ;
	}) ;
	
	$fsd->cancel_button->signal_connect_swapped('clicked' => sub {
		$fsd->destroy ;
	}) ;
	
	$fsd->show ;
}

sub calendar_set_signal_strings
{
	my $sig_ref = shift ;
	my $new_sig = shift ;
	$sig_ref->{prev2}->set_text($sig_ref->{prev}->get_text) ;
	$sig_ref->{prev}->set_text($sig_ref->{curr}->get_text) ;
	$sig_ref->{curr}->set_text($new_sig) ;
}

sub create_calendar
{
	my $window ;
	my $vbox ;
	my $vbox2 ;
	my $vbox3 ;
	my $hbox ;
	my $hbbox ;
	my $calendar ;
	my @toggles ;
	my $button ;
	my $frame ;
	my $separator ;
	my $label ;
	my $bbox ;
	my $i ;
	
	my %signals = () ;
	
	$window = Gtk2::Window->new("toplevel") ;
	$window->set_title('Exemple de Calendrier') ;
	
	$window->set_border_width(5) ;
	$window->signal_connect('destroy' => sub {
		Gtk2->main_quit ;
	}) ;
	$window->set_resizable(FALSE) ;
	
	$vbox = Gtk2::VBox->new(FALSE, DEF PAD) ;
	$window->add($vbox) ;

	#
	# La partie haute de la fenêtre
	#

	$hbox = Gtk2::HBox->new(FALSE, DEF PAD) ;
	$vbox->pack_start($hbox, TRUE, TRUE, DEF PAD) ;
	
	$hbbox = Gtk2::HButtonBox->new ;
	$hbox->pack_start($hbbox, FALSE, FALSE, DEF PAD) ;
	$hbbox->set_layout('spread') ;
	$hbbox->set_spacing(5) ;

	# Calendar widget
	$frame = Gtk2::Frame->new('Calendrier') ;
	$hbbox->pack_start($frame, FALSE, TRUE, DEF PAD) ;
	$calendar = Gtk2::Calendar->new ;
	
	$calendar->mark_day(19) ;
	$frame->add($calendar) ;
	$calendar->display_options([]) ;
	
	$calendar->signal_connect('month changed' => sub {
			my ($year, $month, $day) = $calendar->get_date_;
			calendar_set_signal_strings($_[1 ], 'month changed : ' .
			sprintf("%02d/%d/%d" , $month+1 , $day, $year)) ;
		}, \%signals) ;
	$calendar->signal_connect('day selected' => sub {
			my ($year, $month, $day) = $calendar->get_date_;
			calendar_set_signal_strings($_[1 ], 'day selected : ' .
			sprintf("%02d/%d/%d" , $month+1 , $day, $year)) ;
		}, \%signals) ;
	$calendar->signal_connect('day selected double click' => sub {
			my ($year, $month, $day) = $calendar->get_date_;
			calendar_set_signal_strings($_[1 ],
			'day selected double click : ' .
			sprintf("%02d/%d/%d" , $month+1 , $day, $year)) ;
		}, \%signals) ;
	$calendar->signal_connect('prev month' => sub {
			my ($year, $month, $day) = $calendar->get_date_;
			calendar_set_signal_strings($_[1 ], 'prev