XVII. Afficher l'arborescence du disque▲
XVII-A. Aperçu▲
XVII-B. Préparons le terrain▲
Nous allons avoir besoin d'ajouter un GtkTreeView à côté du GtkTextView. La première idée qui vient à l'esprit est de créer un GtkHBox dans la boîte principale. Mais pour varier les plaisirs, nous allons utiliser un nouveau type de conteneur : les GtkPaned, ils permettent de séparer une zone en deux parties séparées par une barre mobile.
Nous créons donc un GtkHPaned (la séparation se fait dans le sens horizontal, à l'opposé des GtkVPaned) :
GtkWidget *
p_hpaned =
NULL
;
/* ... */
p_hpaned =
gtk_hpaned_new (
);
gtk_box_pack_start (
GTK_BOX (
p_main_box), p_hpaned, TRUE, TRUE, 0
);
Et plutôt que d'afficher la GtkNotebook dans la boîte principale, nous l'affichons maintenant dans la seconde partie de notre nouveau conteneur :
gtk_paned_add2 (
GTK_PANED (
p_hpaned), p_notebook);
XVII-C. Création d'un GtkTreeView▲
Les GtkTreeView sont sûrement les widgets les plus difficiles à maîtriser. Ceci est dû au fait que la gestion du contenu et de l'affichage est séparée en deux widgets et qu'il est possible d'afficher une simple liste ou un arbre :
- GtkListStore et GtkTreeStore pour stocker respectivement, le contenu d'une liste et d'un arbre ;
- GtkTreeView pour afficher le contenu des Gtk*Store.
Nous avons donc le choix entre afficher le contenu du répertoire courant ou afficher toute l'arborescence du disque. Nous opterons pour la première solution, car lister tout le contenu du disque serait bien trop long (surtout de nos jours où un disque dur fait plusieurs dizaines de GO). Mais pour ne pas se limiter au répertoire où se trouve notre programme (ce qui serait gênant s'il se trouve dans /usr/bin), nous allons aussi lister les répertoires présents pour permettre à l'utilisateur de naviguer dans l'arborescence.
Pour résumer nos objectifs : nous voulons créer un GtkTreeView qui affichera un GtkListStore contenant le nom des fichiers et dossiers présents dans un répertoire donné. Lorsque l'utilisateur clique sur un nom représentant un fichier, on l'ouvre, s'il s'agit d'un dossier, on réactualise le GtkListStore avec le contenu de ce nouveau répertoire.
Y a plus qu'à…
XVII-C-1. Création du magasin▲
p_list_store =
gtk_list_store_new (
2
, GDK_TYPE_PIXBUF, G_TYPE_STRING);
docs.p_list_store =
p_list_store;
docs.dir_name =
g_strdup (
g_get_home_dir (
));
dir_list (
);
Qui a osé dire qu'il s'agissait de la partie la plus difficile ? Vous l'aurez compris, tout le code se trouve dans la fonction dir_list que nous verrons plus tard. Pour commencer, on construit notre GtkListStore en précisant le nombre de colonnes souhaité suivi de leurs types. Nous souhaitons deux colonnes, la première contiendra un GdkPixbuf (une image) pour différencier les dossiers des fichiers, et la seconde le nom du fichier.
Ensuite, nous sauvegardons notre GtkListStrore et le chemin du dossier à afficher (la fonction g_get_home_dir nous permet de connaître le dossier de l'utilisateur) dans notre structure globale, car nous en avons besoin dans plusieurs fonctions callback par la suite.
Maintenant passons au remplissage du magasin. Comme nous serons amenés à rafraîchir le contenu de ce dernier, on commence par le vider :
gtk_list_store_clear (
docs.p_list_store);
Pour lister le contenu du répertoire, nous allons utiliser la glib. Après avoir ouvert le répertoire, il nous suffit d'appeler la fonction g_dir_read_name qui à chaque appel nous renvoie le fichier (9) suivant puis NULL lorsque tout le répertoire a été parcouru :
void
dir_list (
void
)
{
GDir *
dir =
NULL
;
dir =
g_dir_open (
docs.dir_name, 0
, NULL
);
if
(
dir)
{
const
gchar *
read_name =
NULL
;
/* ... */
while
((
read_name =
g_dir_read_name (
dir)))
{
/* ... */
}
g_dir_close (
dir), dir =
NULL
;
}
}
Pour chaque fichier, il faut commencer par reconstruire son chemin complet :
gchar *
file_name =
NULL
;
file_name =
g_build_path (
G_DIR_SEPARATOR_S, docs.dir_name, read_name, NULL
);
La fonction g_build_path concatène l'ensemble de ses arguments, sauf le premier qui est le séparateur utilisé. Maintenant que nous avons notre nom complet, nous pouvons tester si notre fichier est un dossier ou pas :
GdkPixbuf *
p_file_image =
NULL
;
/* ... */
if
(
g_file_test (
file_name, G_FILE_TEST_IS_DIR))
{
p_file_image =
gdk_pixbuf_new_from_file (
"
dossier.png
"
, NULL
);
}
else
{
p_file_image =
gdk_pixbuf_new_from_file (
"
fichier.png
"
, NULL
);
}
La fonction permet de tester différentes propriétés relatives aux fichiers :
typedef
enum
{
G_FILE_TEST_IS_REGULAR =
1
<<
0
, /* TRUE si le fichier est un fichier standard (ni un lien symbolique ni un dossier) */
G_FILE_TEST_IS_SYMLINK =
1
<<
1
, /* TRUE si le fichier est un lien symbolique */
G_FILE_TEST_IS_DIR =
1
<<
2
, /* TRUE si le fichier est un dossier */
G_FILE_TEST_IS_EXECUTABLE =
1
<<
3
, /* TRUE si le fichier est un executable */
G_FILE_TEST_EXISTS =
1
<<
4
/* TRUE si le fichier existe */
}
GFileTest;
Donc selon le type de fichier, nous chargeons l'image appropriée dans un GdkPixbuf. Le second paramètre de la fonction gdk_pixbuf_new_from_file permet de récupérer plus d'information lorsqu'une erreur survient.
Pour finir, il nous reste plus qu'à ajouter une nouvelle colonne dans le GtkListStore. Ceci se réalise en deux étapes. La première consiste à ajouter une colonne :
GtkTreeIter iter;
/* ... */
gtk_list_store_append (
docs.p_list_store, &
iter);
Comme pour le GtkTextView, nous avons besoin d'un itérateur qui va nous permettre de remplir cette colonne à l'aide d'une seconde fonction :
gtk_list_store_set (
docs.p_list_store, &
iter, 0
, p_file_image, 1
, read_name, -
1
);
Le premier paramètre est bien sûr notre magasin, ensuite il s'agit de l'itérateur symbolisant la colonne nouvellement créée et enfin des couples numéro de colonne/donnée qui doivent correspondre au nombre et type précisé lors de la création du GkListStore.
En plus de cela, nous ajoutons aussi un dossier « .. », puisque la fonction g_dir_read_name ne le liste pas, pour permettre à l'utilisateur de remonter dans l'arborescence.
XVII-C-2. Affichage de l'arborescence▲
Voilà notre GtkListStore est prêt ! Maintenant il nous faut associer ce dernier au GtkTreeView. Ceci n'a besoin d'être fait qu'une seule fois lors de la création du widget :
p_tree_view =
gtk_tree_view_new_with_model (
GTK_TREE_MODEL (
p_list_store));
GtkTreeModel est une interface utilisée par GtkTreeView, la classe GtkListStore implémentant cette interface, il est possible de réaliser un transtypage.
Si l'histoire s'arrêtait là, créer un GtkTextView sera plutôt simple. Hélas nous devons créer les colonnes dans le GtkTextView et les faire correspondre à celles du magasin.
Le nombre d'éléments affichables par une cellule d'un GtkTreeView étant varié, il faut commencer par créer un GtkCellRenderer qui peut être de différents types :
- GtkCellRendererText : pour afficher du texte ;
- GtkCellRendererPixbuf : pour les images ;
- GtkCellRendererProgress : permet d'ajouter une barre de progression ;
- GtkCellRendererToggle : affiche une case à cocher.
Dans notre cas, nous avons besoin que des deux premiers. Voici le code pour la première colonne qui contiendra l'image :
GtkCellRenderer *
p_renderer =
NULL
;
/* ... */
p_renderer =
gtk_cell_renderer_pixbuf_new (
);
Une fois la cellule créée, il faut créer la colonne à proprement parler grâce à la fonction :
GtkTreeViewColumn*
gtk_tree_view_column_new_with_attributes
(
const
gchar *
title,
GtkCellRenderer *
cell,
...);
Après le titre de la colonne et le GtkCellRenderer, la fonction attend une chaîne de caractères qui diffère selon le type de GtkCellRenderer suivi du numéro de la colonne. Comme il est possible de passer plusieurs couples identifiant/numéro de colonne, nous terminons la liste par NULL.
Et pour terminer, on ajoute la colonne au GtkTextView :
gtk_tree_view_append_column (
GTK_TREE_VIEW (
p_tree_view), p_column);
La démarche est identique pour la seconde colonne :
p_renderer =
gtk_cell_renderer_text_new (
);
p_column =
gtk_tree_view_column_new_with_attributes (
NULL
, p_renderer, "
text
"
, 1
, NULL
);
gtk_tree_view_append_column (
GTK_TREE_VIEW (
p_tree_view), p_column);
Et comme nous n'avons pas besoin des en-têtes de colonnes, nous les cachons :
gtk_tree_view_set_headers_visible (
GTK_TREE_VIEW (
p_tree_view), FALSE);
Je suis passé rapidement sur cette partie, car la documentation officielle n'est pas très complète à ce sujet et le rôle des fonctions n'est pas très clair.
XVII-C-3. Sélectionner un fichier▲
Nous arrivons à afficher le contenu d'un dossier, il nous reste plus qu'à réagir à la sélection d'une colonne. Vous commencez à être habitué, il faut intercepter le signal row-activated du GtkTextView :
g_signal_connect (
G_OBJECT (
p_tree_view), "
row-activated
"
, G_CALLBACK (
cb_select), NULL
);
La fonction callback correspondante est différente de ce qu'on a l'habitude de voir :
void
cb_select (
GtkTreeView *
p_tree_view, GtkTreePath *
arg1, GtkTreeViewColumn *
arg2, gpointer user_data)
{
/* ... */
}
Ces arguments vont nous permettre de retrouver la cellule sélectionnée. La méthode est comparable à celle que l'on utilise pour les GtkTexView, on commence par récupérer notre magasin sous forme de GtkTreeModel comme nous pourrions le faire avec un GtkTextBuffer :
GtkTreeModel *
p_tree_model =
NULL
;
p_tree_model =
gtk_tree_view_get_model (
p_tree_view);
Ensuite à l'aide du GtkTreePath, qui est une représentation du chemin pour accéder à l'élément sélectionné, nous récupérons un GtkTreeIter :
GtkTreeIter iter;
/* ... */
gtk_tree_model_get_iter (
p_tree_model, &
iter, arg1);
Et pour finir, on récupère le contenu de la seconde colonne grâce au GtkTreeIter :
gchar *
str =
NULL
;
/* ... */
gtk_tree_model_get (
p_tree_model, &
iter, 1
, &
str, -
1
);
Nous pourrions récupérer plusieurs colonnes en même temps en ajoutant d'autres couples numéro de colonne/pointeur sur un type de variable compatible avec ce que nous avons stocké dans le GtkListStore.
Voilà c'est la fin de la partie floue (je m'en excuse, mais la documentation n'est pas très complète à se sujet, il faut donc faire des essais en tâtonnant jusqu'à se que cela fonctionne sans forcément en connaître les raisons :().
Maintenant que nous avons notre nom de fichier, il faut retrouver son chemin complet et tester s'il s'agit d'un dossier ou non. Ceci a déjà été vu lors de la création du GtkListStore :
gchar *
file_name =
NULL
;
/* ... */
file_name =
g_build_path (
G_DIR_SEPARATOR_S, docs.dir_name, str, NULL
);
g_free (
str), str =
NULL
;
if
(
g_file_test (
file_name, G_FILE_TEST_IS_DIR))
{
/* ... */
}
else
{
/* ... */
}
Commençons par le plus difficile : dans le cas où notre fichier est un dossier, il suffit de remplacer le nom du dossier à afficher et de rafraîchir le GtkListStore en faisant appel à la fonction dir_list :
g_free (
docs.dir_name), docs.dir_name =
NULL
;
docs.dir_name =
file_name;
dir_list (
);
Difficile de faire plus simple ! Pour ouvrir un fichier, il suffit de faire appel à la fonction open_file :
open_file (
file_name);