GTK+ par l'exemple


précédentsommairesuivant

XVII. Afficher l'arborescence du disque

XVII-A. Aperçu

Image non disponible
Cliquez pour agrandir

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

main.c
Sélectionnez
  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 containeur :

main.c
Sélectionnez
    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 due au fait que la gestion du contenu et de l'affichage est séparé 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'a...

XVII-C-1. Création du magasin

main.c
Sélectionnez
    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 :

callback.c
Sélectionnez
    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 :

callback.c
Sélectionnez
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 :

callback.c
Sélectionnez
      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 fichiers est un dossier ou pas :

callback.c
Sélectionnez
    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 :

 
Sélectionnez
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é 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 :

callback.c
Sélectionnez
    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 :

callback.c
Sélectionnez
      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 :

main.c
Sélectionnez
    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êterai 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 :

main.c
Sélectionnez
    GtkCellRenderer *p_renderer = NULL;
/* ... */
    p_renderer = gtk_cell_renderer_pixbuf_new ();

Une fois la cellule créée, il faut créer la colonne à proprement parlée grâce à la fonction :

 
Sélectionnez
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 :

main.c
Sélectionnez
    gtk_tree_view_append_column (GTK_TREE_VIEW (p_tree_view), p_column);

La démarche est identique pour la seconde colonne :

main.c
Sélectionnez
    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 :

main.c
Sélectionnez
    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 claire.

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 :

main.c
Sélectionnez
    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 :

 
Sélectionnez
void cb_select (GtkTreeView *p_tree_view, GtkTreePath *arg1, GtkTreeViewColumn *arg2, gpointer user_data)
{
  /* ... */
}

Ces arguments vont nous permettrent 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 :

 
Sélectionnez
  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 :

 
Sélectionnez
  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 :

 
Sélectionnez
  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'autre couple 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 :

 
Sélectionnez
  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 :

 
Sélectionnez
    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 :

 
Sélectionnez
    open_file (file_name);

XVII-D. Code source


précédentsommairesuivant
Ici fichier est à prendre au sens Unix du terme, c'est à dire que tout est fichier.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2006-2008 Nicolas Joseph. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.