Object +-- Data +-- Adjustment
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 fait aux widget range. 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'Adjustment est de stocker les paramètres de configuration et les valeurs des widgets ``échelles'' 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.
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 :
new Gtk::Adjustment($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 ``paning''. 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.
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 le
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 leurs 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 ).
Maintenant vous pensez probablement, puisque les widgets texte et viewport insistent sur le fait du déclarer tout sauf la valeur de leur réglage alors que les barres de défilement ne toucheront qu'à cette valeur, que si vous partagez un réglage entre une barre de défilement et un widget texte, la manipulation de la barre de défilement ajustera automatiquement le widget texte ! Et bien oui ! Juste avec :
# Créer son propre réglage$text = new Gtk::Text('''','''');
# utilise le réglage tout fraîchement créé pour la barre de défilement
$vscrollbar = new Gtk::VScrollbar($text->vadj);
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 Gtk-Perl, vous utiliserez :
get_adjustment->adjustment_name ;
ou adjustment_name est au choix :
valuelower
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->emit_by_name(``changed'');
La catégorie des widgets Range inclut la barre de défilement ``ubiquitous'' et le widget moins commun échelle (scale). 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.
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 le 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 le 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 '' );
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.
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.
#!/usr/bin/perl -wuse Gtk;
use strict;
init Gtk;
my $false = 0;
my $true = 1;
my $hscale;
my $vscale;
my $window;
my $box1;
my $box2;
my $box3;
my $button;
my $scrollbar;
my $separator;
my $opt;
my $menu;
my $item;
my $label;
my $scale;
my $adj1;
my $adj2;
# Création standard de la fenêtre
$window = new Gtk::Window( "toplevel" );
$window->set_title( "range controls" );
$window->signal_connect( "destroy", sub { Gtk->exit( 0 ); } );
$box1 = new Gtk::VBox( $false, 0 );
$window->add( $box1 );
$box1->show();
$box2 = new Gtk::HBox( $true, 10 );
$box2->border_width( 10 );
$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();
# 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).
$adj1 = new Gtk::Adjustment( 0.0, 0.0, 101.0, 0.1, 1.0, 1.0 );
$vscale = new Gtk::VScale( $adj1 );
scale_set_default_values( $vscale );
$box2->pack_start( $vscale, $true, $true, 0 );
$vscale->show();
$box3 = new Gtk::VBox( $false, 10 );
$box2->pack_start( $box3, $true, $true, 0 );
$box3->show();
# Réutilise le même adjustement
$hscale = new Gtk::HScale( $adj1 );
$hscale->set_usize( 200, 30 );
scale_set_default_values( $hscale );
$box3->pack_start( $hscale, $true, $true, 0 );
$hscale->show();
# Réutilise encore le même adjustement
$scrollbar = new Gtk::HScrollbar( $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();
$box2 = new Gtk::HBox( $false, 10 );
$box2->border_width( 10 );
$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();
# Une case à cocher pour contrôler si la valeur est affichée ou non
$button = new Gtk::CheckButton( "Display value on scale widgets" );
$button->set_active( $true );
$button->signal_connect( "toggled", \&cb_draw_value );
$box2->pack_start( $button, $true, $true, 0 );
$button->show();
$box2 = new Gtk::HBox( $false, 10 );
$box2->border_width( 10 );
# Une option du menu pour changer la position de la valeur.
$label = new Gtk::Label( "Scale Value Position:" );
$box2->pack_start( $label, $false, $false, 0 );
$label->show();
$opt = new Gtk::OptionMenu();
$menu = new Gtk::Menu();
$item = make_menu_item( "Top", \&cb_pos_menu_select, 'top' );
$menu->append( $item );
$item = make_menu_item( "Bottom", \&cb_pos_menu_select, 'bottom' );
$menu->append( $item );
$item = make_menu_item( "Left", \&cb_pos_menu_select, 'left' );
$menu->append( $item );
$item = make_menu_item( "Right", \&cb_pos_menu_select, 'right' );
$menu->append( $item );
$opt->set_menu( $menu );
$box2->pack_start( $opt, $true, $true, 0 );
$opt->show();
$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();
$box2 = new Gtk::HBox( $false, 10 );
$box2->border_width( 10 );
# Une autre option du menu, cette fois ci pour changer la politique
# de mise à jour des widgets échelles
$label = new Gtk::Label( "Scale Update Policy:" );
$box2->pack_start( $label, $false, $false, 0 );
$label->show();
$opt = new Gtk::OptionMenu();
$menu = new Gtk::Menu();
$item = make_menu_item ("Continuous", \&cb_update_menu_select, 'continuous' );
$menu->append( $item );
$item = make_menu_item ("Discontinuous", \&cb_update_menu_select, 'discontinuous' );
$menu->append( $item );
$item = make_menu_item ("Delayed", \&cb_update_menu_select, 'delayed' );
$menu->append( $item );
$opt->set_menu( $menu );
$box2->pack_start( $opt, $true, $true, 0 );
$opt->show();
$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();
$box2 = new Gtk::HBox( $false, 10 );
$box2->border_width( 10 );
# Un widget HScale pour ajuster le nombre de digits
# sur les exemple d'échelle.
$label = new Gtk::Label( "Scale Digits:" );
$box2->pack_start( $label, $false, $false, 0 );
$label->show();
$adj2 = new Gtk::Adjustment( 1.0, 0.0, 5.0, 1.0, 1.0, 0.0 );
$adj2->signal_connect( "value_changed", \&cb_digits_scale );
$scale = new Gtk::HScale( $adj2 );
$scale->set_digits( 0 );
$box2->pack_start( $scale, $true, $true, 0 );
$scale->show();
$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();
$box2 = new Gtk::HBox( $false, 10 );
$box2->border_width( 10 );
# Et un dernier widget HScale pour ajuster la taille de la page de la scrollbar
$label = new Gtk::Label( "Scrollbar Page Size:" );
$box2->pack_start( $label, $false, $false, 0 );
$label->show();
$adj2 = new Gtk::Adjustment( 1.0, 1.0, 101.0, 1.0, 1.0, 0.0 );
$adj2->signal_connect( "value_changed", \&cb_page_size, $adj1 );
$scale = new Gtk::HScale( $adj2 );
$scale->set_digits( 0 );
$box2->pack_start( $scale, $true, $true, 0 );
$scale->show();
$box1->pack_start( $box2, $true, $true, 0 );
$box2->show();
$separator = new Gtk::HSeparator();
$box1->pack_start( $separator, $false, $true, 0 );
$separator->show();
$box2 = new Gtk::VBox( $false, 10 );
$box2->border_width( 10 );
$box1->pack_start( $box2, $false, $true, 0 );
$box2->show();
$button = new Gtk::Button( "Quit" );
$button->signal_connect( "clicked", sub { Gtk->exit( 0 ); } );
$box2->pack_start( $button, $true, $true, 0 );
$button->can_default( $true );
$button->grab_default();
$button->show();
$window->show();
main Gtk;
exit( 0 );
### Subroutines
sub cb_pos_menu_select { my ( $item, $pos ) = @_; # Déclare le valeur de la position pour chaque widget scale $hscale->set_value_pos( $pos ); $vscale->set_value_pos( $pos ); }
sub cb_update_menu_select { my ( $item, $policy ) = @_; # Déclare la politique de mise à jour pour chaque widget scale $hscale->set_update_policy( $policy ); $vscale->set_update_policy( $policy ); }
sub cb_digits_scale { my ( $adj ) = @_; # Déclare le nombre de décimale pour adj->value $hscale->set_digits( $adj->value ); $vscale->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 par 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_by_name( "changed" ); }
sub cb_draw_value { my ( $button ) = @_; # Branche la valeur de l'affichage, débranche les widgets échelle # selon l'état de la cases à cocher $hscale->set_draw_value( $button->active ); $vscale->set_draw_value( $button->active ); }
# Fonctions pratiques
sub make_menu_item { my ( $name, $callback, $data ) = @_; my $item; $item = new Gtk::MenuItem( $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 ); }
Vous remarquerez que le programme n'appelle pas signal_connect() pour
le 'delete_event', mais seulement pour le signal 'destroy'. Cela
accomplira tout de même la fonction désirée car un 'delete_event' sans
gestionnaire se transformera en signal 'destroy' passé à la fenêtre.