Le but du widget Arbre est d'afficher des données hiérarchiquement organisées. Le widget Arbre lui-même est une conteneur vertical pour les widget de type TreeItem. Arbre n'est pas très différent d'une CList - les deux sont dérivés de Container, et les méthodes de Container fonctionnent aussi bien pour les widgets Arbre que pour les widgets CList. La différence est que les widgets Arbre peuvent être nichés dans d'autres widgets Arbre. Nous verrons brièvement comment faire cela.
Le widget Arbre possède sa propre fenêtre et possède par défaut un fond blanc comme les CList. Quoi qu'il en soit, Arbre ne dérive pas de CList donc nous ne pouvons utiliser l'un ou l'autre indifféremment.
Un arbre est créé selon la méthode habituelle :
$tree = new Gtk::Tree();
Comme les widgets CList, un widget Arbre continuera de grandir tant qu'on lui
ajoutera des éléments, ou quand les subtrees ( sous-arbres ) prendront de l'ampleur.
Pour cette raison, ils sont presque toujours placés dans une fenêtre défilable.
Vous voudrez certainement utiliser set_usize() pour la fenêtre défilable
afin de vous assurer qu'elle est assez grosse pour afficher les items de l'Arbre,
car la taille par défaut d'une fenêtre défilable est plutôt petite.
Maintenant que nous avez un arbre vous voudrez certainement y ajouter quelques éléments. La section sur les TreeItems donne des détails ``crus'' sur les TreeItem. Pour l'instant, il suffit d'en créer un :
$tree = new_with_label Gtk::TreeItem( $label );
Vous pouvez l'ajouter à l'arbre en utilisant une des fonctions suivantes :
$tree->append( $tree_item ); $tree->prepend( $tree_item );
Notez que vous devez ajouter les éléments un par un - Il n'y a pas l'équivalent des fonctions sur les items d'un Clist.
Un sous-arbre est créé comme un autre widget Arbre. Un sous-arbre est ajouté à un arbre sous un item arbre à l'aide de :
$tree_item->set_subtree( $subtree );
Vous n'avez pas besoin d'appeler la fonction show() pour un sous-arbre
avant ou après l'avoir ajouté à un TreeItem. Quoiqu'il en soit, vous devez ajouter
le TreeItem en question à un arbre parent avant d'appeler set_subtree().
C'est parce que, techniquement, le parent du sous-arbre n'est pas le TreeItem
qui le possède mais l'arbre qui possède le TreeItem.
Quand vous ajoutez un sous-arbre à un TreeItem, un signe plus ou moins apparaît à son côté sur lequel l'utilisateur peut cliquer pour le ``développer'' ou le ``réduire ``, ce qui signifie montrer ou cacher le sous-arbre. Les TreeItems sont par défaut réduits. Notez que quand vous réduisez un sous-arbre, n'importe quel item sélectionné dans son sous-arbre reste sélectionné ce que l'utilisateur n'attend pas spécialement.
Comme avec une CList, le type arbre possède un champs de sélection et il est possible de contrôler le comportement de l'arbre ( à peu près ) en déclarant le type de sélection avec :
$tree->set_selection_mode( $mode );
Le sens des mots associés aux différents modes de sélection sont décrits dans
la section sur les widgets Clist. Comme avec les widgets CList, les signaux
select_child, unselect_child ( pas vraiment - voir la section
sur les signaux ci-dessous )et selection_changed sont émis quand des
items de la liste sont sélectionnés ou désélectionnés. Toutefois, pour tirer
parti de ces signaux il faut savoir par quel widget arbre ils sont émis et où
trouver la liste des items sélectionnés.
C'est une source de confusion potentielle. Le meilleur moyen d'expliquer ceci
est de penser que tous les widgets Arbres sont créés égaux, et certains sont
plus égaux que d'autres. Tous les widgets Arbres possèdent leurs propres fenêtres
X, et peuvent par conséquent recevoir des évènements comme les clics de souris
( si leurs TreeItems ou leurs enfants peuvent les capter en premier ). Toutefois
pour faire que des sélections du type 'single' ou 'browse' se
comportent d'une manière saine, la liste des items sélectionnés est spécifique
au widget Arbre le plus élevé dans la hiérarchie, connu comme ``l'arbre racine''.
Ainsi, accéder à une champs de sélection directement dans un widget arbre
n'est pas une bonne idée à moins que vous ne sachiez s'il s'agit de l'arbre
racine.
Enfin, les signaux select_child ( et en théorie, unselect_child
) sont émis par tous les arbres mais le signal selection_changed est
uniquement émis par l'arbre racine. Par conséquent, si vous voulez manipuler
le signal select_child pour un arbre et tous ses sous-arbres, vous devrez
appeler signal_connect() pour tous les sous-arbres.
Le champs d'une arbre ( à partir de sa structure C ) ressemble à :
container children root_tree tree_owner selection level indent_value current_index selection_mode view_mode view_line
En plus, is_root_tree() retourne une valeur booléenne qui indique si
un arbre est un arbre racine dans une hiérarchie arbre alors que root_tree()
retourne l'arbre racine.
Plutôt que d'accéder directement au champs enfant d'un widget arbre, il est
probablement meilleur d'y accéder en utilisant la fonction children(),
héritée du widget Container.
Le champs tree_owner est défini uniquement dans les sous-arbres, où il
pointe vers le widget TreeItem qui possède l'arbre en question. Le champs level
indique à quel point est ``niché'' un arbre particulier ; les arbres racines
ont le niveau 0 et chaque niveau successif de sous-arbres a une niveau supérieur
à celui du niveau parent augmenté de 1. En fait, ce champs est déclaré un fois
que le widget Arbre est représenté ( tracé à l'écran ).
Le signal selection_changed sera émis quelque soit le champs selection
de l'arbre modifié. Cela arrive quand un enfant de l'arbre est sélectionné ou
désélectionné.
Le signal select_child est émis quand un enfant de l'arbre est sur le
point d'être sélectionné. Cela arrive sur les appels select_item(), select_child(),
sur tous les boutons pressés et les appels item_toggle() et toggle().
Cela peut parfois être déclenché en d'autres occasions, quand des enfants sont
ajoutés ou enlevés de l'arbre.
Le signal unselect_child est émis quand un enfant de l'arbre est sur
le point d'être désélectionné. Comme avec GTK 1.0.4, cela semble n'arriver que
lors des appels unselect_item() ou unselect_child() et peut être
en d'autres occasions, mais pas quand un bouton pressé désélectionne un enfant
( le signal select_child est émis à la place ), ni lors de l'émission
du signal toggle par gtk_item_toggle().
$tree->insert( $tree_item, $position );
Insère un item arbre dans un Arbre à la position spécifiée
$tree->remove_items( @items );
Enlève une liste d'éléments de l'Arbre. Notez qu'enlever un élément d'un arbre
le déréférence et ( habituellement ) le détruit lui et son sous-arbre, s'il
en a, et tous les sous-arbres dans ces sous-arbres. Si vous voulez enlever uniquement
un élément, vous pouvez utiliser remove().
$tree->clear_items( $start, $end );
Enlève les éléments compris entre la position $start et $end.
Le même avertissement ici à propos du déférencement qui s'applique car clear_items()
construit simplement la liste et la passe ensuite à remove_items().
$tree->select_item( $item );
Émet le signal select_item pour un enfant à la position $item,
le sélectionnant ainsi ( sauf si vous le désélectionnez avec un gestionnaire
de signal ).
$tree->unselect_item( $item );
Émet le signal unselect_item pour un enfant à la position $item,
le désélectionnant ainsi.
$tree->select_child( $tree_item );
Émet le signal select_item pour un enfant $tree_item, le sélectionnant
ainsi.
$tree->unselect_child( $tree_item );
Émet le signal unselect_item pour un enfant $tree_item, le désélectionnant
ainsi.
$tree->child_position( $child );
Retourne la position dans un un arbre de $child à moins que $child
ne soit pas dans l'arbre, dans ce cas retourne -1.
$tree->set_selection_mode( $mode );
Déclare le mode de sélection, qui peut être single ( le défaut ), browse,
multiple ou extended. Cela est seulement défini pour les arbres
racines, ce qui tombe sous le sens puisque les arbres racines ``possèdent''
la sélection. Faire cette déclaration sur un sous-arbre n'a aucun effet, la
valeur est simplement ignorée.
$tree->set_view_mode( $mode );
Déclare le mode ``view'' qui peut être soit line ( le défaut) soit item.
Le mode ``view'' se propage d'un arbre à ses sous-arbres quand ils sont représentés,
et ne concerne pas exclusivement un sous-arbre. ( le déclarer sur un sous-arbre
après qu'il ait été représenté pourrait avoir des effet quelque peu imprévisibles
).
Le terme mode ``view'' est plutôt ambigu - à la base, cela contrôle la manière
dont la coloration est représentée quand l'un des enfant de l'arbre est sélectionné.
Si c'est line, l'arbre entier est coloré alors que si c'est item,
seul le widget enfant ( habituellement le label ) est coloré.
$tree->set_view_lines( $show_lines );
Contrôle si les lignes qui relient les éléments de l'arbre sont dessinées. $show_lines
est une valeur vraie ou fausse.
$tree->root_tree();
Retourne l'arbre racine d'un objet arbre.
$tree->selection();
Retourne la liste de sélection d'un arbre racine d'un objet ``GtkTree''.
Le widget TreeItem, comme le CListItem, est dérivé de Item, qui à son tour est dérivé de Bin. Par conséquent, l'item lui-même est un conteneur générique contenant exactement un seul widget enfant qui peut être de n'importe quel type. Le widget TreeItem possède un nombre supplémentaire de champs mais le seul qui nous intéresse vraiment est le champs sous-arbre.
Les champs disponibles pour la structure TreeItem ressemblent à :
item subtree pixmaps_box plus_pix_widget minus_pix_widget pixmaps expanded
Le champs pixmaps_box est une Boîte d'évènements qui attrapent les clics
sur le signe plus/moins qui contrôle le développement ou la réduction. Le champs
pixmaps pointe vers une structure de données internes. Puisque vous pouvez
toujours obtenir le sous-arbre d'un TreeItem avec la fonction subtree(),
il est vivement conseillé de ne pas toucher à l'intérieur d'un TreeItem sauf
si vous savez exactement ce que vous faîtes.
Un TreeItem possède habituellement un label, donc la fonction new_with_label Gtk::TreeItem()
est fournie. Le même effet peut être obtenu en utilisant le code suivant :
$tree_item = new Gtk::TreeItem(); $label = new Gtk::Label( $text ); $label->set_alignment( 0.0, 0.5 ); $tree_item->add( $label ); $show( $label );
Comme personne ne nous oblige à ajouter un label à un TreeItem, nous pouvons également ajouter une HBox ou une flèche, ou même un Notebook ( bien que dans ce cas, votre application risque d'être plutôt impopulaire ).
Si vous enlevez tous les éléments d'un sous-arbre, celui-ci sera détruit et ne sera plus rattaché à aucun parent, à moins que vous ne le référenciez plus tôt, et le TreeItem auquel il appartient sera réduit. Donc, si vous voulez qu'il reste en place, faîtes comme ceci :
$tree->ref();
$owner = $tree->tree_owner;
$tree->remove( $item );
if ( $tree->parent )
{
$tree->unref();
}
else
{
$owner->expand();
$owner->set_subtree( $tree );
}
Enfin, le drag and drop fonctionne avec les TreeItems. Vous devez juste vous
assurer que le TreeItem dans lequel vous prenez un élément ou vous en placer
un, a non seulement été ajouté à l'arbre mais que chaque widget parent successif
possède lui-même un parent, tous jusqu'à une fenêtre toplevel ou dialogue, quand
vous utilisez dnd_drag_set() ou dnd_drop_set(). Autrement de drôles
de choses peuvent se produire.
TreeItem héritent des signaux select, unselect et toggle
des Items. En plus, il possède deux autres signaux qui lui sont propres :expand
et expand().
Le signal expand est émis quand le sous-arbre d'un arbre est sur le point
d'être développé, c'est-à-dire quand l'utilisateur clique sur le signe plus
placé à côté de l'élément, ou quand le programme appelle la fonction :expand().
Le signal collapse est émis quand le sous-arbre d'un arbre est sur le
point d'être réduit, c'est-à-dire quand l'utilisateur clique sur le signe moins
placé à côté de l'élément, ou quand le programme appelle la fonction :collapse().
$tree_item->remove_subtree();
Cela enlève tous les enfants de sous-arbres de tree_item ( donc les déréférence, les détruit, tous les sous-arbres des enfants et ainsi de suite...), puis le sous-arbre lui-même et cache le signe plus/moins.
C'est un exemple un peu plus compliqué que les autres mais je pense que le moment est approprié. Bien qu'il illustre les widgets arbres, cet exemple utilise quelque peu les widget CList. A partir de là, nous pourrions facilement ajouter un bouton ou un menu et ajouter des fonctionnalités comme effacer, copier ou bouger un fichier ce qui nous ferait un file manager ``light''.
#!/usr/bin/perl -wuse Gtk;
use strict;
init Gtk;
my $false = 0;
my $true = 1;
my $root_dir = "/";
my @titles;
my $window;
my $pane;
my $vbox;
my $tree_scrolled_win;
my $list_scrolled_win;
my $entry;
my $tree;
my $leaf;
my $subtree;
my $item;
my $list;
$window = new Gtk::Window( 'toplevel' );
$window->set_usize( 725, 500 );
$window->set_title( "File Viewer" );
$window->signal_connect( "delete_event", sub { Gtk->exit( 0 ); } );
$pane = new Gtk::HPaned();
$window->add( $pane );
$pane->set_handle_size( 10 );
$pane->set_gutter_size( 8 );
$pane->show();
# Crée une VBox pour l'entrée texte et la fenêtre défilable de l'arbre
$vbox = new Gtk::VBox( $false, 0 );
$pane->add1( $vbox );
$vbox->show();
# Crée l'entrée texte
$entry = new Gtk::Entry();
$vbox->pack_start( $entry, $false, $false, 4 );
$entry->signal_connect( 'activate', \&entry_activate );
$entry->show();
# Crée une fenêtre défilable pour l'arbre
$tree_scrolled_win = new Gtk::ScrolledWindow( undef, undef );
$tree_scrolled_win->set_usize( 150, 400 );
$vbox->pack_start( $tree_scrolled_win, $true, $true, 0 );
$tree_scrolled_win->set_policy( 'automatic', 'automatic' );
$tree_scrolled_win->show();
# Crée une fenêtre défilable pour la liste
$list_scrolled_win = new Gtk::ScrolledWindow( undef, undef );
$pane->add2( $list_scrolled_win );
$list_scrolled_win->set_policy( 'automatic', 'automatic' );
$list_scrolled_win->show();
# Crée l'arbre racine
$tree = new Gtk::Tree();
$tree_scrolled_win->add_with_viewport( $tree );
$tree->set_selection_mode( 'single' );
$tree->set_view_mode( 'item' );
$tree->show();
# Crée le widget tree item racine
$leaf = new_with_label Gtk::TreeItem( $root_dir );
$tree->append( $leaf );
$leaf->signal_connect( 'select', \&select_item, "/" );
$leaf->set_user_data( "/" );
$leaf->show();
# Crée le sous-arbre
if ( has_sub_trees( $root_dir ) ) { $subtree = new Gtk::Tree(); $leaf->set_subtree( $subtree ); $leaf->signal_connect( 'expand', \&expand_tree, $subtree ); $leaf->signal_connect( 'collapse', \&collapse_tree ); $leaf->expand(); }
# Crée une boîte pour la liste
@titles = qw( Filename Size Permissions Owner Group Time Date );
$list = new_with_titles Gtk::CList( @titles );
$list_scrolled_win->add_with_viewport( $list );
$list->set_column_width( 0, 100 );
$list->set_column_width( 1, 50 );
$list->set_column_width( 2, 80 );
$list->set_column_width( 3, 50 );
$list->set_column_width( 4, 50 );
$list->set_column_width( 5, 50 );
$list->set_column_width( 6, 100 );
$list->set_selection_mode( 'multiple' );
$list->set_shadow_type( 'none' );
$list->show();
$window->show();
main Gtk;
exit( 0 );
### Routines
# Rappel pour développer un arbre - trouve les sous répertoires et les
# ajoute à l'arbre
sub expand_tree { my ( $item, $subtree ) = @_; my $dir; my $dir_entry; my $path; my $item_new; my $new_subtree; $dir = $item->get_user_data(); chdir( $dir ); foreach $dir_entry ( <*> ) { if ( -d $dir_entry ) { $path = $dir . "/" . $dir_entry; $path =~ s|//|/|g; $item_new = new_with_label Gtk::TreeItem( $dir_entry ); $item_new->set_user_data( $path ); $item_new->signal_connect( 'select', \&select_item, $path ); $subtree->append( $item_new ); $item_new->show(); if ( has_sub_trees( $path ) ) { $new_subtree = new Gtk::Tree(); $item_new->set_subtree( $new_subtree ); $item_new->signal_connect( 'expand', \&expand_tree, $new_subtree ); $item_new->signal_connect( 'collapse', \&collapse_tree ); } } } chdir( ".." ); }
# Rappel pour réduire un arbre- enlève le sous-arbre
sub collapse_tree { my ( $item ) = @_; my $subtree = new Gtk::Tree(); $item->remove_subtree(); $item->set_subtree( $subtree ); $item->signal_connect( 'expand', \&expand_tree, $subtree ); }
# Teste si le répertoire possède des sous-répertoires
sub has_sub_trees { my ( $dir ) = @_; my $file; my $has_dirs = $false; foreach $file ( <$dir/*> ) { $has_dirs = $true if ( -d $file ); } return ( $has_dirs ); }
# Appelé si on clique sur un élément de l'arbre
sub select_item { my ( $widget, $path ) = @_; my $file; $entry->set_text( $path ); show_files( $path ); }
# Appelé si Entrée est pressée dans l'entrée texte
sub entry_activate { my ( $entry ) = @_; my $file; my $path = $entry->get_text(); if ( -d $path ) { show_files( $path ); } else { $entry->set_text( "/" ); } }
sub show_files { my ( $path ) = @_; my $file; $list->clear(); for $file ( <$path/*> ) { unless ( -d $file ) { my ( $mode, $uid, $gid, $size, $mtime) = ( stat( $file ) )[ 2, 4, 5, 7, 9 ]; my ( $mon, $day, $year, $hour, $min ) = ( localtime( $mtime ) )[ 4, 3, 5, 2, 1 ]; $min = "0" . $min if ( $min < 10 ); my $time = $hour . ":" . $min; my $month = ( "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" )[$mon]; my $date = $month . " " . $day . ", " . ( 1900 + $year ); my $user = getpwuid( $uid ); my $group = getgrgid( $gid ); $file =~ s|/.*/||g; $list->append( $file, $size, $mode, $user, $group, $time, $date ); } } }