Chapitre 8
Les widgets de réglage

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.

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 :

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

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 une 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).

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 :

get_adjustment->adjustment_name ;

ou adjustment_name est au choix :

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 :

$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, quelques soient ces changements de valeurs, vous créerez un rappel comme celui-ci :

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

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

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

$adjustment->signal_emit("changed");

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.

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

$range->set_update_policy($policy);

Obtenir et déclarer l’ajustement ”au vol” se fait par :

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

$adjustment->signal_emit ("changed");

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.

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

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.

range

# !/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 ) ;
}