Chapitre 2
À 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.

minimum

# !/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. Le 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 :

Entre ces deux lignes, nous placerons tout ce qui compose notre application. Ici, nous avons juste créé puis affiché une fenêtre en écrivant :

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écoration 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.

Hello world à la Gtk2-Perl

helloworld

# !/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
# Close_Window définie plus loin.
$window->signal_connect( ’delete_event’ , \&Close_Window,’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’ , \&Destroy_Window ) ;

# 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 lebouton 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 Close_Window {
# 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 Destroy_Window {
Gtk2->main_quit ;
return FALSE ;
}

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 faîtes ? 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 terme 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.

Le 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().

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 click 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 :

$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 envoit 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 :

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

Une routine est habituellement définie par :

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.

Les évènements

En 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  
button_press_event  
button_release_event  
scroll_event  
motion_notify_event  
delete_event  
destroy_event  
expose_event  
key_press_event  
key_release_event  
enter_notify_event  
leave_notify_event  
configure_event  
focus_in_event  
focus_out_event  
map_event  
unmap_event  
property_notify_event  
selection_clear_event  
selection_request_event  
selection_notify_event  
proximity_in_event  
proximity_out_event  
visibility_notify_event  
client_event  
no_expose_event  
window_state_event  
selection_received  
selection_get  
drag_begin_event  
drag_end_event  
drag_data_delete  
drag_motion  
drag_drop  
drag_data_get  
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 :

my($widget,$evenement,$donnée)=@_;

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

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

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

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

Il y a également le champs button qui contient un nombre indiquant le bouton pressé ( typiquement 1, 2 ou 3 ) et le champs 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 ?

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.

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é.

Émettre un signal

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

$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'.

Supprimer des retours

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

$widget->signal_disconnect($id);

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

$widget->signal_handlers_destroy();

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

Désactiver temporairement des gestionnaires

Vous pouvez débrancher temporairement des gestionnaires avec :

$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);

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.

$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);

Un hello world un peu plus évolué

helloworld2

# !/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’ , \&Close_Window,”Puisqu’il faut partir...” ) ;
# Le signal ”destroy” sera émis parce que la fonction de rappel ”Close_Window”
# renvoie FALSE.
$window->signal_connect( ’destroy’ , \&Destroy_Window) ;

# 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 bôite
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 Close_Window {
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 Destroy_Window {
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.