Les listes et arbres en GTK+

Jean-Robert SCHLOSSER

Ce cours aborde l'un des points les plus compliqués de la bibliothèque GTK+ : les GtkTreeView, qui permettent d'afficher des listes (GtkListStore) ou des arbres (GtkTreeStore). Compliqué, car très peut détaillé dans la documentation de référence et pourtant très riche. Ce tutoriel vous permettra de faire vos premiers pas ou d'y voir plus clair dans la multitude de fonctions proposées. ♪

- Titre : Les listes et arbres en GTK+

- Auteur : Jean-Robert SCHLOSSER

- Parution : 24 février 2004

- Mise à jour : 24 février 2004

- Licence : 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 ® 2004 Jean-Robert SCHLOSSER. 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.

Sommaire

- I. Présentation

- II. Principes de base

- II-A. Les concepts

- II-B. Le stockage des données

- II-C. La présentation en liste et en arbre

- II-D. La sélection

- III. La gestion des magasins en détail

- III-A. Les accès aux données (lignes/enregistrements)

- III-A-1. Les conversions

- III-A-2. Les traitements des itérateurs

- III-A-3. Les traitements des chemins

- III-A-4. Les traitements des références

- III-B. La création des lignes

- III-C. Le tri des lignes

- III-C-1. Les éléments de base pour les tris

- III-C-2. Le tri dans le magasin

- III-C-3. Le tri par les titres de colonne

- III-C-4. Le magasin trié intermédiaire

- III-C-4-a. La technique

- III-C-4-b. Remarque sur les itérateurs et les chemins

- III-C-5. Les signaux

- III-D. Les changements dans l'ordre des lignes

- III-E. Le parcours du magasin

- III-E-1. Parcours global avec la fonction de rappel

- III-E-2. En gérant la boucle d'itération

- IV. La gestion de l'affichage en détail

- IV-A. L'afficheur

- IV-A-1. Les liens avec le magasin

- IV-A-2. La configuration de l'afficheur

- IV-A-2-a. Masquer les titres de colonnes

- IV-A-2-b. Autoriser le clic sur les titres de colonne

- IV-A-2-c. Gestion des marques de suivi des lignes

- IV-A-2-d. Gestion des regroupements de lignes

- IV-A-3. La gestion des colonnes

- IV-A-4. La fonctionnalité de recherche interactive

- IV-A-5. L'ajout d'ascenseurs

- IV-B. La colonne

- IV-B-1. La gestion des afficheurs élémentaires

- IV-B-2. Visibilité et redimensionnement

- IV-B-2-a. Visibilité d'une colonne

- IV-B-2-b. Redimensionnement de la largeur d'une colonne

- IV-B-3. Autres attributs de la colonne

- IV-B-3-a. Gestion du titre de la colonne

- IV-C. La cellule

- IV-C-1. La gestion des attributs

- IV-C-1-a. La notion d'attribut

- IV-C-1-b. Les attributs variables

- IV-C-1-c. Les attributs constants

- IV-C-2. Les trois types d'afficheurs élémentaires

- IV-C-2-a. Afficheur de texte

- IV-C-2-b. Afficheur d'icône

- IV-C-2-c. Afficheur binaire

- IV-C-3. Fonction d'affichage spécifique

- IV-D. Édition « sur place »

- IV-D-1. Afficheur de texte

- IV-D-2. Afficheur d'icône

- IV-D-3. Afficheur binaire

- IV-E. Informations diverses

- IV-E-1. Double-clic sur une colonne

- IV-E-2. Gestion d'un menu contextuel

- V. La gestion des sélections en détail

- V-A. Récupérer les éléments sélectionnés

- V-A-1. Récupérer une sélection unique

- V-A-2. Récupérer la liste des éléments sélectionnés

- V-A-2-a. Fonction de récupération d'une sélection

- V-A-2-b. Transformation des chemins en références

- V-A-2-c. Parcours de la liste

- V-A-2-d. Libération de la mémoire

- V-A-3. Parcourir la liste des éléments sélectionnés

- V-B. Changement de la sélection

- V-B-1. Changer la sélection par programme

- V-B-2. Gérer le changement de sélection

- VI. Trucs et astuces divers

- VI-A. Remonter de la colonne d'un afficheur à la colonne d'un magasin

- VI-A-1. La méthode complète

- VI-A-2. Une astuce pour s'en sortir

- VI-A-2-a. Choisir entre colonne et afficheur élémentaire

- VI-A-2-b. Associer le numéro de colonne

- VII. Annexes

- VII-A. Définitions des constantes

- VII-A-1. Types de colonne

- VII-A-2. Modes de sélection

- VII-B. Attributs des afficheurs élémentaires

- VII-B-1. Afficheur de texte

- VII-B-2. Afficheur d'icône

- VII-B-3. Afficheur booléen

- VII-C. Documentation

- VII-C-1. API officielle

- VII-C-2. Autres tutoriels

- VII-D. Reste à faire

- VIII. Copyright

I - Présentation

GTK+ est une bibliothèque d'objets graphique qui a été conçue au départ pour faciliter le développement de GIMP (GNU Image Manipulation Program).

L'intérêt de ce type d'outil s'est vite manifesté et d'autres projets l'ont utilisé comme GNOME un environnement de travail pour Linux.

Depuis sa version 2.0, GTK+ recouvre un empilement de bibliothèques apportant des fonctions de plus en plus évoluées.

Glib / Gobject / Pango / ATK / GdkPixbuf / GDK / GTK

Comme beaucoup de bibliothèques graphiques, GTK+ définit des objets « widget » comme base de son architecture.

Parmi les changements importants apportés par la version 2.0, les techniques de présentation des listes, simples ou arborescentes, ont été profondément revues.

C'est la présentation de ces techniques et des « widgets » associés qui constitue l'objet de ce document.

Il s'inscrit naturellement dans un cycle d'apprentissage de l'utilisation de GTK ; il n'est donc pas nécessaire d'être un spécialiste pour profiter de cette présentation, mais la connaissance préalable des notions de base d'utilisation de la bibliothèque est indispensable.

Dans un premier temps, on traite ici d'un développement en langage C. Le document pourra ultérieurement être complété pour présenter les notions dans d'autres langages.

Cette présentation se veut résolument didactique et pédagogique. Elle est donc complète, mais pas exhaustive et elle n'est pas organisée comme un manuel de référence qu'elle ne saurait remplacer.

II - Principes de base

II-A - Les concepts

L'idée de base est de séparer l'aspect stockage des données de l'aspect présentation.

La partie stockage est prise en charge au travers de GtkTreeModel qui est l'objet générique qui se décline en GtkListStore pour les listes simples et GtkTreeStore pour les listes arborescentes. Il est possible, au prix d'un effort supplémentaire, de créer d'autres types de stockage(1).

La présentation quant à elle, se décline en GtkTreeView, GtkTreeViewColumn, GtkCellRenderer. Cela permet d'avoir une gestion fine de l'affichage souhaité.

Le but des « cell renderers » est de permettre d'avoir plusieurs façons d'afficher un même type de données. À titre d'exemple, regardons comment afficher une variable booléenne. Doit-on l'afficher comme un texte « Vrai » ou « Faux », « Oui » ou « Non », ou doit-on la rendre par une case à cocher ?

L'intérêt majeur d'une telle organisation et qu'elle permet d'avoir plusieurs vues sur un même ensemble de données.

Attention malgré tout, les deux parties ne sont pas indépendantes, et il convient de veiller à maintenir la cohérence.

Une notion complémentaire de « Sélection », à cheval entre les deux aspects principaux, vient compléter cet ensemble pour permettre la manipulation des données au travers de l'affichage.

II-B - Le stockage des données

Nous avons vu qu'il fallait choisir entre deux modèles (liste simple ou arborescente) pour le stockage des données (magasin).

Cependant la création reste très simple et homogène grâce aux fonctions :

GtkListStore* gtk_list_store_new(gint n_columns, GType *types...);
GtkTreeStore* gtk_list_store_new(gint n_columns, GType *types...);

Le premier paramètre donne le nombre de colonnes, tandis que les suivants donnent le type de chacune des colonnes (cf. Types de colonne).

Maintenant que nous avons créé le magasin, il faut le remplir avec les données. Pour cela, il nous faut un petit accessoire, un « itérateur » qui s'obtient grâce aux fonctions :

void gtk_list_store_append(GtkListStore *list_store, GtkTreeIter *iter);
void gtk_tree_store_append(GtkListStore *list_store, GtkTreeIter *iter, GtkTreeIter *iterparent);

Le premier paramètre donne bien évidemment le magasin que l'on remplit, tandis que le second donne l'emplacement à remplir.

Le troisième paramètre qui n'existe que dans le cas de l'arbre permet de gérer l'arborescence en indiquant l'itérateur du parent auquel est rattaché cet élément.

En fait, ces fonctions font plus que nous allouer un itérateur, elles créent une ligne vide et nous rendent un itérateur qui pointe vers la nouvelle ligne.

Et pour charger nos données, il suffit d'utiliser les fonctions :

void gtk_list_store_set(GtkListStore *list_store, GtkTreeIter *iter, ...);
void gtk_tree_store_set(GtkListStore *list_store, GtkTreeIter *iter, ...);

Le premier paramètre donne bien évidemment le magasin que l'on remplit, tandis que le second donne l'emplacement à remplir.

Les paramètres suivants marchent par couple : numéro de colonne et valeur de la colonne. Un dernier paramètre, à ne pas oublier, vaut toujours -1 et permet d'indiquer la fin de la liste des couples colonne/valeur.

Ces fonctions permettent de remplir un nombre quelconque de colonnes (champs) en une seule fois, il est donc possible de les appeler plusieurs fois de suite, ou de mettre toutes les valeurs en une seule fois.

Pour remplir la liste par le début (haut) plutôt que par la fin (bas), il faut utiliser les fonctions gtk_list_store_prepend() et gtk_tree_store_prepend() à la place de gtk_list_store_apppend() et gtk_tree_store_apppend().

Pour vider complètement un magasin de données, il faut utiliser les fonctions :

void gtk_list_store_clear(GtkListStore *list_store);
void gtk_tree_store_clear(GtkTreeStore *tree_store);

Tandis que pour supprimer une ligne dont on connaît l'itérateur (par exemple au travers d'une sélection, voir plus loin), il faut utiliser les fonctions :

gboolean gtk_list_store_remove(GtkListStore *list_store, GtkTreeIter *iter);
gboolean gtk_tree_store_remove(GtkTreeStore *tree_store, GtkTreeIter *iter);

Un exemple vaut mieux qu'un long discours :

/* Liste simple : Livres : Titre, Auteur, Lu (Oui/Non) */
GtkListStore* store;
GtkTreeIter iter

store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);

gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, 0, "Un livre", 1, "Matthieu",2 , true, -1);

gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, 0, "Autre livre", 1, "Pascal",2 , false, -1);

/* Arbre : Livres : Titre, Auteur, Lu (Oui/Non) */
GtkTreeStore* store;
GtkTreeIter iter1, iter2;

store = gtk_tree_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
gtk_tree_store_append(store, &iter1, NULL);
gtk_tree_store_set(store, &iter1, 0, "Un livre", 1, "Matthieu",2 , true, -1);

gtk_tree_store_append(store, &iter1, NULL);
gtk_tree_store_set(store, &iter1, 0, "Autre livre", 1, "Pascal",2 , false, -1);

gtk_tree_store_append(store, &iter2, &iter1);
gtk_tree_store_set(store, &iter2, 0, "Volume 1", 1, "Pascal",2 , true, -1);

gtk_tree_store_append(store, &iter2, &iter1);
gtk_tree_store_set(store, &iter2, 0, "Volume 2", 1, "Pascal",2 , false, -1);

II-C - La présentation en liste et en arbre

Il y a un seul afficheur pour les listes et les arbres ; le format s'adapte en fonction des données.

Il y a trois formateurs de cellule (« cell renderer ») qui font partie de GTK+ 2.0. Un format texte, un format image, et un format case à cocher. Il est relativement facile d'en écrire d'autres(2).

La première étape consiste à créer un afficheur et à l'associer au magasin de données :

GtkWidget* gtk_tree_view_new_with_model(GtkTreeModel *model);

Ensuite, il faut ajouter les colonnes à afficher :

- premièrement, il faut choisir le formateur de cellule approprié :

GtkCellRenderer *gtk_cell_renderer_text_new();
GtkCellRenderer *gtk_cell_renderer_pixbuf_new();
GtkCellRenderer *gtk_cell_renderer_toggle_new();

- ensuite, il faut créer la colonne avec ses attributs.

GtkTreeViewColumn* gtk_tree_view_column_new_with_attributes(const gchar *title, GtkCellRenderer *cell, ...);

Le premier paramètre est le titre de la colonne ; le second est le formateur de cellule choisi précédemment ; enfin on trouve une liste de couples attribut/valeur terminée par un NULL (la valeur est en fait le numéro de colonne du magasin qui contient la valeur).

Enfin, il faut associer la colonne à l'afficheur avec :

gint gtk_tree_view_append_column(GtkTreeView *tree_view, GtkTreeViewColumn *column);

Le premier paramètre est l'afficheur, le second la colonne, et la valeur de retour le nombre de colonnes de l'afficheur après exécution de la fonction.

Il ne reste plus maintenant qu'à insérer la vue dans la fenêtre, comme n'importe quel autre widget.

Un exemple vaut mieux qu'un long discours :

/* Affichage de la liste ou de l'arbre */
GtkWidget *tree;
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;

tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes("Auteur", renderer, "text", 0, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

/* Insertion du widget dans la fenêtre */
gtk_box_pack_start(GTK_BOX(pHBox), tree, FALSE, TRUE, 0);

Précisons aussi que l'afficheur de listes et d'arbres fait partie des 3 widgets qui peuvent facilement disposer d'ascenseurs. Il suffit de créer une fenêtre avec barres de défilement et d'insérer notre liste dedans. Pour créer la fenêtre avec barres de défilement, on utilise :

GtkWidget* gtk_scrolled_window_new(GtkAdjustment *hadjustment, GtkAdjustment *vadjustment);

On peut changer les propriétés d'affichage des ascenseurs avec :

void gtk_scrolled_window_set_policy(GtkScrolledWindow *scrolled_window, GtkPolicyType hscrollbar_policy, GtkPolicyType vscrollbar_policy);

Pour les deuxième (ascenseur horizontal) et troisième (ascenseur vertical) paramètres, on utilise les constantes GTK_POLICY_ALWAYS, GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC dont la signification est évidente.

Un exemple vaut mieux qu'un long discours :

/* Ajout dans la fenêtre avec des ascenseurs */
GtkWidget *tree, *pScrollbar;

pScrollbar = gtk_scrolled_window_new(NULL, NULL);

gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(pScrollbar), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_container_add(GTK_CONTAINER(pScrollbar), tree);
gtk_box_pack_start(GTK_BOX(pHBox), pScrollbar, FALSE, TRUE, 0);

II-D - La sélection

La sélection est à cheval entre l'affichage sur lequel elle est mise en évidence et les données sous-jacentes du magasin. Il s'agit donc d'un concept complémentaire, mis en œuvre à l'aide d'un widget spécifique.

La première étape consiste donc à récupérer l'objet sélection de l'afficheur grâce à la fonction :

GtkTreeSelection* gtk_tree_view_get_selection(GtkTreeView *tree_view);

Il est ensuite possible de fixer certains attributs de la sélection grâce à la fonction :

void gtk_tree_selection_set_mode(GtkTreeSelection *selection, GtkSelectionMode type);

qui permet de choisir le mode de sélection en utilisant les constantes : GTK_SELECTION_NONE, GTK_SELECTION_SINGLE, GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE. (cf. Modes de sélection.)

Maintenant, il peut être intéressant de récupérer la description des données sélectionnées. Dans le cas où on a choisi un mode de sélection simple, il est possible de récupérer le magasin et l'itérateur de la donnée par la fonction :

gboolean gtk_tree_selection_get_selected(GtkTreeSelection *selection, GtkTreeModel **model, GtkTreeIter *iter);

La fonction retourne une valeur vraie lorsqu'un élément est effectivement sélectionné.

Dans le cas où l'on utilise un mode de sélection multiple, c'est un peu plus compliqué ; les explications sont plus loin dans ce document.

Sous réserve de connaître l'itérateur, il est possible de sélectionner ou désélectionner une ligne grâce aux fonctions :

void gtk_tree_selection_select_iter(GtkTreeSelection *selection, GtkTreeIter *iter);
void gtk_tree_selection_unselect_iter(GtkTreeSelection *selection, GtkTreeIter *iter);

Enfin il est possible d'effacer toute sélection grâce à la fonction :

void gtk_tree_selection_unselect_all(GtkTreeSelection *selection);

Et si la sélection est multiple, de tout sélectionner par :

void gtk_tree_selection_select_all(GtkTreeSelection *selection);

Un exemple vaut mieux qu'un long discours :

/* Exemple de création de la sélection */
GtkTreeSelection *select;

select = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);

/* Exemple dans la fonction de rappel d'un bouton "Supprimer" */
GtkTreeIter iter;
GtkListStore *store;

if(gtk_tree_selection_get_selected (select, &store, &iter))
gtk_list_store_remove(store, &iter);

III - La gestion des magasins en détail

III-A - Les accès aux données (lignes/enregistrements)

Il existe plusieurs moyens de désigner l'emplacement des données dans le magasin. Ce sont :

- les itérateurs, qui constituent la méthode base ;

- les chemins (path), qui permettent une description textuelle de l'arborescence, donc particulièrement adaptés dans le cas des arbres, un chemin est conceptuellement la liste des numéros d'ordre à chaque niveau, sous forme de texte, c'est une chaîne de caractères constituée d'entiers positifs ou nuls séparés par des « : » ;

- les références, qui restent liées aux données même en cas de modification dans le magasin.

Il existe de nombreuses fonctions pour manipuler ces entités et les convertir entre elles. Sans être exhaustif, voici les principales.

III-A-1 - Les conversions

- Convertir un itérateur en chemin :

GtkTreePath* gtk_tree_model_get_path(GtkTreeModel *tree_model, GtkTreeIter *iter);

La mémoire allouée pour le chemin doit ensuite être libérée par :

void gtk_tree_path_free(GtkTreePath *path);

Et voici une autre fonction qui fait, presque, la même chose :

gchar *gtk_tree_model_get_string_from_iter(GtkTreeModel *tree_model, GtkTreeIter *iter);

- Convertir un chemin en référence :

GtkTreeRowReference* gtk_tree_row_reference_new(GtkTreeModel *model, GtkTreePath *path);

- Convertir une référence en chemin :

GtkTreePath* gtk_tree_row_reference_get_path(GtkTreeRowReference *reference);

renvoie NULL si la référence n'est pas valide.

- Convertir un chemin en itérateur :

gboolean gtk_tree_model_get_iter(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreePath *path);

et, en partant du texte du chemin, on utilise :

gboolean gtk_tree_model_get_iter_from_string(GtkTreeModel *tree_model, GtkTreeIter *iter, const gchar *path_string);

III-A-2 - Les traitements des itérateurs

- Connaître l'enregistrement suivant dans le niveau courant :

gboolean gtk_tree_model_iter_next(GtkTreeModel *tree_model, GtkTreeIter *iter);

Modifie l'itérateur courant pour passer au suivant, la fonction retourne FALSE s'il n'y a pas de suivant.

- Savoir si un enregistrement a des fils :

gboolean gtk_tree_model_iter_has_child(GtkTreeModel *tree_model, GtkTreeIter *iter);

- Savoir combien un enregistrement a de fils :

gint gtk_tree_model_iter_n_children(GtkTreeModel *tree_model, GtkTreeIter *iter);

- Accéder au premier fils d'un enregistrement :

gboolean gtk_tree_model_iter_children(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent);

- Accéder au nième fils d'un enregistrement :

gboolean gtk_tree_model_iter_nth_child(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent, gint n);

(le premier enregistrement porte le numéro 0)

- Accéder au père d'un enregistrement :

gboolean gtk_tree_model_iter_parent(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *child);

III-A-3 - Les traitements des chemins

- Créer un nouvel objet chemin :

GtkTreePath *gtk_tree_path_new(void);

- Convertir une chaîne de caractères (« 2:3:0:1 ») en chemin :

GtkTreePath *gtk_tree_path_new_from_string(const gchar *path);

- Créer un chemin à partir d'une liste d'entiers :

GtkTreePath *gtk_tree_path_new_from_indices(gint first_index, ..., -1);

- Convertir un chemin en chaîne de caractères :

gchar *gtk_tree_path_to_string(GtkTreePath *path);

- Accéder au chemin du premier enregistrement (« 0 ») :

GtkTreePath *gtk_tree_path_new_first(void);

- Ajouter un index (le numéro d'un fils) à un chemin :

void gtk_tree_path_append_index(GtkTreePath *path, gint index);

- Ajouter un index (numéro de parent de plus au niveau) au début d'un chemin :

void gtk_tree_path_prepend_index(GtkTreePath *path, gint index);

- Calculer le niveau d'imbrication d'un chemin :

gint gtk_tree_path_get_depth(GtkTreePath *path);

- Convertir un chemin en tableau d'entiers :

gint *gtk_tree_path_get_indices(GtkTreePath *path);

- Libérer la mémoire allouée pour un chemin :

void gtk_tree_path_free(GtkTreePath *path);

- Copier la valeur d'un chemin :

GtkTreePath *gtk_tree_path_copy(const GtkTreePath *path);

- Comparer deux chemins :

gint gtk_tree_path_compare(const GtkTreePath *a, const GtkTreePath *b);

renvoie 0 s'ils sont égaux, -1 si a est avant b, 1 si a est après b.

- Déplacer le chemin vers l'enregistrement suivant, au même niveau :

void gtk_tree_path_next(GtkTreePath *path);

- Déplacer le chemin vers l'enregistrement précédent, au même niveau :

gboolean gtk_tree_path_prev(GtkTreePath *path);

renvoie FALSE s’il n'y a plus d'enregistrement précédent à ce niveau.

- Faire pointer le chemin vers le père de l'enregistrement :

gboolean gtk_tree_path_up(GtkTreePath *path);

renvoie FALSE si l'enregistrement n'a pas de père.

- Faire pointer l'enregistrement vers le premier fils de l'enregistrement :

void gtk_tree_path_down(GtkTreePath *path);

- Vérifier la descendance entre deux chemins :

gboolean gtk_tree_path_is_ancestor(GtkTreePath *path, GtkTreePath *descendant);
gboolean gtk_tree_path_is_descendant(GtkTreePath *path, GtkTreePath *ancestor);

III-A-4 - Les traitements des références

- Vérifie si une référence est non NULL et valide (les données correspondantes peuvent avoir été supprimées) :

gboolean gtk_tree_row_reference_valid(GtkTreeRowReference *reference);

- Libère la mémoire allouée pour une référence :

void gtk_tree_row_reference_free(GtkTreeRowReference *reference);

- Copie la valeur d'une référence :

GtkTreeRowReference* gtk_tree_row_reference_copy(GtkTreeRowReference *reference);

III-B - La création des lignes

Il n'est pas obligatoire d'ajouter les nouvelles lignes à une extrémité de la liste, il est aussi possible d'ajouter une ligne juste avant, ou juste après, une liste connue par son itérateur. Pour cela, on dispose des fonctions :

void gtk_list_store_insert_before(GtkListStore *list_store, GtkTreeIter *iter, GtkTreeIter *sibling);
void gtk_list_store_insert_after(GtkListStore *list_store, GtkTreeIter *iter, GtkTreeIter *sibling);
void gtk_tree_store_insert_before(GtkListStore *list_store, GtkTreeIter *iter, GtkTreeIter *parent, GtkTreeIter *sibling);
void gtk_tree_store_insert_after(GtkListStore *list_store, GtkTreeIter *iter, GtkTreeIter *parent, GtkTreeIter *sibling);

Dans le cas des arbres la gestion est puissante, donc un peu plus complexe que dans les listes simples :

- si parent et sibling sont NULL, alors la nouvelle ligne est ajoutée au début ou la fin du niveau le plus haut ;

- si parent a une valeur, mais que sibling est NULL, alors, la nouvelle ligne est ajoutée au début ou à la fin de la liste des enfants de parent ;

- si sibling a une valeur, alors parent doit être NULL ou avoir être le parent de sibling, dans ce cas, la nouvelle ligne est ajoutée juste avant, ou juste après la ligne pointée par sibling.

III-C - Le tri des lignes

III-C-1 - Les éléments de base pour les tris

La base des méthodes pour obtenir une présentation triée des lignes est indépendante du type de modèle (liste simple ou arbre).

La première étape consiste à obtenir une copie « triable » du magasin par les instructions :

GtkTreeSortable *sortable;
sortable = GTK_TREE_SORTABLE(store);

ensuite, il faut écrire une fonction de comparaison dont le prototype est le suivant :

gint* GtkTreeIterCompareFunc(GtkTreeModel *model,

GtkTreeIter *a, GtkTreeIter *b, gpointer user_data);

- le premier paramètre donne bien sûr le modèle concerné, mais attention, il est de type GtkTreeModel donc il faudra généralement le transtyper en GtkListStore ou GtkTreeStore pour l'utiliser ;

- les deux paramètres suivants sont des itérateurs sur les lignes à comparer ;

- tandis que le dernier paramètre est un pointeur sur les données passées lors de l'attachement en tant que fonction de rappel (il est habituel de passer ici le numéro correspondant au type de tri) ;

- la valeur de retour doit être 0 si les deux lignes sont équivalentes, -1 si la ligne pointée par « a » est supérieure à la ligne pointée par « b », et 1 sinon.

Il est possible d'écrire une fonction qui fait un traitement différent en fonction du numéro de type de tri ; il est aussi possible d'écrire autant de fonctions que de types de tri.

Au départ, il était prévu de définir un type de tri par colonne, mais en fait, rien n'oblige à utiliser les valeurs de cette colonne comme critère pour réaliser les tests, et donc le « numéro de colonne » correspond désormais à un type de test ; une des conséquences de cela est que le numéro du critère de tri n'est pas limité au nombre de colonnes du magasin.

Il faut maintenant lier la fonction de tri au magasin en lui associant un numéro de critère (« column_id », voir l'explication ci-dessus). on utilise pour cela la fonction :

void gtk_tree_sortable_set_sort_func (GtkTreeSortable *sortable,
gint sort_column_id, GtkTreeIterCompareFunc sort_func,
gpointer user_data, GtkDestroyNotify destroy);

- Classiquement, le premier paramètre (sortable) est le magasin concerné, dans sa version « triable » ;

- le deuxième paramètre est le numéro (sort_column_id) du critère de tri ;

- le troisième paramètre (sort_func) est la fonction de comparaison définie précédemment ;

- le quatrième paramètre (user_data) est un pointeur vers les données à passer là la fonction de comparaison, on l'utilise souvent pour indiquer le critère de tri lorsqu'on ne développe qu'une seule fonction de comparaison ;

- le dernier paramètre (destroy) est une fonction de rappel qui est appelée en cas de destruction des données utilisateurs (user_data), la plupart du temps on passe la valeur NULL pour ce paramètre.

Il est possible de définir un tri par défaut pour le magasin en utilisant la fonction :

void gtk_tree_sortable_set_default_sort_func (GtkTreeSortable *sortable,
GtkTreeIterCompareFunc sort_func, gpointer user_data,
GtkDestroyNotify destroy);

- Le premier paramètre (sortable) est le magasin concerné, dans sa version « triable » ;

- le deuxième paramètre (sort_func) est la fonction de comparaison définie précédemment ;

- le troisième paramètre (user_data) est un pointeur vers les données à passer là la fonction de comparaison, on l'utilise souvent pour indiquer le critère de tri lorsqu'on ne développe qu'une seule fonction de comparaison ;

- le dernier paramètre (destroy) est une fonction de rappel qui est appelée en cas de destruction des données utilisateurs (user_data), la plupart du temps on passe la valeur NULL pour ce paramètre.

Si le troisième paramètre (user_data) a la valeur NULL, alors, par défaut le magasin n'est pas trié.

III-C-2 - Le tri dans le magasin

Il est possible de trier le magasin en définissant un critère de tri grâce à la fonction :

void gtk_tree_sortable_set_sort_column_id(GtkTreeSortable *sortable, gint sort_column_id, GtkSortType order);

Rappel : le critère de tri est défini sous le nom de « sort_column_id » dans le nom de la fonction aussi bien que dans le nom du paramètre en raison de la sémantique initiale de la fonctionnalité de tri.

Le dernier paramètre indique l'ordre de tri en utilisant les constantes : GTK_SORT_ASCENDING et GTK_SORT_DESCENDING.

Si la valeur du critère de tri est GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, alors, c'est le critère de tri par défaut qui est utilisé. Si la fonction de tri par défaut est NULL alors, le magasin redevient non trié ce qui permet d'utiliser d'autres formes de tri(3).

III-C-3 - Le tri par les titres de colonne

Une autre méthode de tri consiste à faire en sorte que le tri se déclenche en cliquant sur les titres de colonne. Cette méthode est simple à mettre en œuvre et naturelle pour l'utilisateur.

Il suffit d'associer un critère de tri à chacune des colonnes qui doivent permettre le tri lors de la création de l'afficheur. on utilise pour cela la fonction :

void gtk_tree_view_column_set_sort_column_id(GtkTreeViewColumn *tree_column, gint sort_column_id);

Le premier clic trie dans l'ordre ascendant, le second dans l'ordre descendant.

III-C-4 - Le magasin trié intermédiaire

Il se peut que vous ne souhaitiez pas trier directement les données dans le magasin ; par exemple parce que vous utilisez plusieurs afficheurs différents sur le même magasin.

III-C-4-a - La technique

Pour cela, nous allons utiliser un magasin intermédiaire, qui va gérer le tri. Ce magasin est créé par la fonction :

GtkTreeModel* gtk_tree_model_sort_new_with_model(GtkTreeModel *child_model);

Le paramètre child_model représente bien évidemment le magasin dans lequel se trouvent vos données. Comme ce magasin est de type GtkListStore ou GtkTreeStore, il faudra le transtyper grâce à la macro GTK_TREE_MODEL().

Ensuite, c'est sur ce magasin que vous appliquez vos fonctions de tri et que vous appliquez vos afficheurs.

Un exemple vaut mieux qu'un long discours :

/* Exemple de magasin intermédiaire pour le tri */
GtkListStore *store; /* Le magasin principal */
GtkTreeModel *sortstore; /* Le magasin intermédiaire */
GtkTreeSortable *sortable; /* Le magasin triable */

store = gtk_list_store_new(5, G_TYPE_STRING, /* [...] */ );

/* Remplissage du magasin avec les données */
/* Création du magasin intermédiaire */
sortstore = gtk_tree_model_sort_new_with_model(store);

/* Application des fonctions de tri */
gtk_tree_sortable_set_sort_func(sortable, 25, sort_iter_compare_func, NULL, NULL);

/* Éventuellement, appel de gtk_tree_sortable_set_sort_column_id() */
/* Création de l'afficheur */
GtkWidget *tree;
GtkCellRenderer *renderer;
GtkTreeViewColumn *column;

tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(sortstore));
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes("Auteur", renderer, "text", 0, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

/* Éventuellement appel de (gtk_tree_view_column_set_sort_column_id) */
gtk_tree_view_column_set_sort_column_id(column, 25);

III-C-4-b - Remarque sur les itérateurs et les chemins

Comme les données ne sont pas dans le même ordre dans le magasin principal et dans le magasin trié, les itérateurs ne correspondent pas. Il existe donc des fonctions de conversion pour passer d'un magasin à l'autre :

- pour calculer les valeurs dans le magasin principal, connaissant les valeurs dans le magasin trié :

GtkTreePath* gtk_tree_model_sort_convert_path_to_child_path(GtkTreeModelSort *tree_model_sort, GtkTreePath *sorted_path);
void gtk_tree_model_sort_convert_iter_to_child_iter(GtkTreeModelSort *tree_model_sort,GtkTreeIter *child_iter, GtkTreeIter *sorted_iter);

- pour calculer les valeurs dans le magasin trié, connaissant les valeurs dans le magasin principal :

GtkTreePath* gtk_tree_model_sort_convert_child_path_to_path(GtkTreeModelSort *tree_model_sort, GtkTreePath *child_path);
void gtk_tree_model_sort_convert_child_iter_to_iter(GtkTreeModelSort *tree_model_sort,GtkTreeIter *sort_iter, GtkTreeIter *child_iter);

Il peut aussi être intéressant de connaître le magasin principal lorsque l'on connaît le magasin trié. Pour cela il existe la fonction :

GtkTreeModel *gtk_tree_model_sort_get_model(GtkTreeModelSort *tree_model);

et donc, le paramètre, c'est le magasin trié, le résultat, c'est le magasin principal.

Et pour illustrer tout ça, l'exemple d'une fonction qui supprime la ligne sélectionnée :

/* Suppression de la ligne sélectionnée */
/* En début de programme on a créé une sélection unique */
GtkWidget *tree;
GtkTreeSelection *select;

select = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree));
gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);

/* Voici maintenant le contenu de la fonction de suppression */
GtkTreeIter sortiter, iter;
GtkTreeModelSort *sortstore;
GtkListStore *store;

if (gtk_tree_selection_get_selected(select, &sortstore, &sortiter)) {
store = GTK_LIST_STORE(gtk_tree_model_sort_get_model(sortstore));
gtk_tree_model_sort_convert_iter_to_child_iter(sortstore, &iter, &sortiter);
gtk_list_store_remove(store, &iter);
}

III-C-5 - Les signaux

Signal

Fonction de rappel

row-changed

void user_function(GtkTreeModel *treemodel,
GtkTreePath *arg1, GtkTreeIter *arg2, gpointer user_data);

row-deleted

void user_function(GtkTreeModel *treemodel,
GtkTreePath *arg1, gpointer user_data);

row-has-child-toggled

void user_function(GtkTreeModel *treemodel,
GtkTreePath *arg1, GtkTreeIter *arg2, gpointer user_data);

row-inserted

void user_function(GtkTreeModel *treemodel,
GtkTreePath *arg1, GtkTreeIter *arg2, gpointer user_data);

rows-reordered

void user_function(GtkTreeModel *treemodel,
GtkTreePath *arg1, GtkTreeIter *arg2, gpointer arg3,
gpointer user_data);

Paramètre

Description

treemodel

Pointeur vers le magasin qui a émis le signal

arg1

Chemin de la ligne concernée (attention dans le cas « row-deleted », ce chemin correspond au chemin de l'ancienne ligne, mais devient un chemin non valide ou d'une autre ligne)

arg2

non documenté dans la description de l'API de référence

arg3

non documenté dans la description de l'API de référence

user_data

Données passées à la connexion du signal

III-D - Les changements dans l'ordre des lignes

Ceci n'est possible que pour les magasins non triés. Dans le cas des arbres, les deux itérateurs doivent être du même niveau, ou position doit être NULL.

Nous disposons de deux fonctions pour les listes et deux pour les arbres :

void gtk_list_store_move_before(GtkListStore *store, GtkTreeIter *iter, GtkTreeIter *position);
void gtk_list_store_move_after(GtkListStore *store, GtkTreeIter *iter, GtkTreeIter *position);
void gtk_tree_store_move_before(GtkTreeStore *store, GtkTreeIter *iter, GtkTreeIter *position);
void gtk_tree_store_move_after(GtkTreeStore *store, GtkTreeIter *iter, GtkTreeIter *position);

Ces deux fonctions sont simples, mais elles sous-entendent que l'on dispose de l'itérateur de l'autre ligne. Si position est NULL, alors, la ligne est déplacée au début ou à la fin de la liste.

Il est aussi possible d'échanger la place de deux lignes, dans les cas d'un arbre, ces deux lignes doivent faire partie du même niveau, par la fonction :

void gtk_list_store_swap(GtkListStore *store, GtkTreeIter *a, GtkTreeIter *b);
void gtk_tree_store_swap(GtkTreeStore *store, GtkTreeIter *a, GtkTreeIter *b);

Enfin il est possible de réordonner les lignes grâce aux fonctions :

void gtk_list_store_reorder(GtkListStore *store, gint *new_order);
void gtk_tree_store_reorder(GtkTreeStore *tree_store, GtkTreeIter *parent, gint *new_order);

Ces fonctions permettent de réordonner le magasin, ou dans le cas des arbres une branche du magasin, selon un critère de tri. Ces fonctions ne sont applicables que si le magasin n'est pas trié (en utilisant gtk_tree_sortable_set_sort_column_id()). À la différence de la fonction de tri, l'ordre n'est pas maintenu en cas de changement dans le magasin (ajout de ligne, modification de valeur) ; il est nécessaire d'appeler la fonction reorder à chaque fois que l'on souhaite refaire le classement dans le magasin.

III-E - Le parcours du magasin

Il existe plusieurs méthodes pour se déplacer dans les données du magasin en fonction de ce que l'on veut faire. Le parcours de l'ensemble du magasin à l'aide d'une fonction de rappel est relativement simple (par exemple pour sauvegarder le contenu sur un fichier disque). Cependant la gestion de la fonction d'itération (passage à l'élément suivant) peut devenir relativement complexe dans le cas d'un arbre.

III-E-1 - Parcours global avec la fonction de rappel

Cette méthode fait partie de GtkTreeModel, elle est donc applicable indifféremment aux listes et aux arbres, mais elle nécessite un transtypage de votre magasin en magasin générique avec la macro GTK_TREE_MODEL().

Cette méthode se passe en deux temps :

- premièrement, il faut écrire la fonction à exécuter pour chaque enregistrement de données, comme elle va être utilisée en fonction de rappel, elle doit avoir un prototype imposé qui est le suivant :

gboolean user_func(GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data);

Cette fonction vous permet donc d'accéder aux données par un chemin ou un itérateur et disposez d'un accès aux données passées lors de la connexion de cette fonction.

Si la fonction renvoie la valeur TRUE, le parcours du magasin s'arrête.

Pour récupérer les données, la technique la plus simple est certainement l'utilisation de la fonction :

void gtk_tree_model_get(GtkTreeModel *model, GtkTreeIter *iter, ..., -1);

Les deux premiers paramètres sont évidemment ceux que vous avez reçus en paramètres d'entrée de votre fonction, tandis qu'en suite vous avez une liste, terminée par -1, de couples numéro de colonne/pointeur sur la valeur. Il s'agit de pointeurs sur la valeur puisque c'est la fonction qui va modifier ces valeurs.

- ensuite, il faut demander l'exécution de cette fonction sur toutes les données du magasin en appelant :

void gtk_tree_model_foreach(GtkTreeModel *model, GtkTreeModelForeachFunc func, gpointer user_data);

Rien de très original, mais comme un exemple(4) vaut mieux qu'un long discours :

/* Exemple de dump d'un arbre */
gboolean foreach_func(GtkTreeModel *model,
GtkTreePath *path, GtkTreeIter *iter, gpointer user_data)
{
gchar *name;
gdouble prix;
gint depth;

gtk_tree_model_get(model, iter, 0, &name, 2, &prix, -1);
depth = gtk_tree_store_iter_depth(GTK_TREE_STORE(model), iter);
if(depth)
g_fprintf(user_data, "+%s:%0.2f\n", name, prix);
else
g_fprintf(user_data, "%s\n", name);

g_free(name); /* Libérer la mémoire pour les chaînes de caractères */
return FALSE; /* Continuer à parcourir l'arbre */
}

gboolean xx_save_in_file(const gchar *filename)
{
FILE *fp;
/* Sauvegarder le magasin dans un fichier */
fp = fopen(filename,"w");
if(fp == NULL) return FALSE;

gtk_tree_model_foreach(GTK_TREE_MODEL(store), foreach_func, fp);
fclose(fp);
return TRUE;
}

III-E-2 - En gérant la boucle d'itération

Cette technique reste simple dans le cas d'une liste, c'est un peu plus compliqué pour un arbre (à cause de la gestion du niveau d'imbrication).

Bon, commençons par le début, donc récupérons l'itérateur du premier élément par la fonction :

gboolean gtk_tree_model_get_iter_first(GtkTreeModel *tree_model, GtkTreeIter *iter);

Ensuite, il faut récupérer un itérateur sur la donnée suivante :

gboolean gtk_tree_model_iter_next(GtkTreeModel *tree_model, GtkTreeIter *iter);

Dans le cas d'une liste, tout va bien ; on appelle cette fonction jusqu'à ce que la valeur de retour soit FALSE. Par contre, dans le cas d'un arbre, c'est plus compliqué, car cette fonction permet d'accéder à la donnée suivante de même niveau. Donc avant, de passer à la suite, il faut vérifier si la donnée n'a pas de fils par :

gboolean gtk_tree_model_iter_has_child(GtkTreeModel *tree_model, GtkTreeIter *iter);

Si c'est le cas, il faut récupérer le premier fils par :

gboolean gtk_tree_model_iter_children(GtkTreeModel *tree_model, GtkTreeIter *iter, GtkTreeIter *parent);

Il ne reste plus qu'à parcourir les fils comme vu plus haut, en n'oubliant pas de s'occuper de leurs enfants s'ils en ont.

Eh bien oui, un parcours d'arbre ça finit presque toujours par une fonction récursive. Alors, la voici :

/* Exemple de parcours d'arbre */
gboolean browse_tree(GtkTreeModel *model, GtkTreeIter *parent, gint level)
{
gboolean ret;
GtkTreeIter iter;

if(parent == NULL)
ret = gtk_tree_model_get_iter_first(model, &iter);
else
ret = gtk_tree_model_iter_children(model, &iter, parent);

/* Au cas où le (sous-)arbre à explorer serait vide */
if(!ret) return FALSE;

do {
gtk_tree_model_get(model, &iter, /* Données à récupérer, */ -1);
/** TRAITEMENT des données de l'enregistrement **/

if(gtk_tree_model_iter_has_child(model, &iter))
browse_tree(model, &iter, level+1);
} until (!gtk_tree_model_iter_next(model, &iter));
return TRUE;
}

/* Le premier appel se fait par : */
browse_tree(model, NULL, 0);

IV - La gestion de l'affichage en détail

Pour résumer, l'affichage est une table constituée de colonnes qui contiennent un titre puis des cellules permettant d'afficher les valeurs contenues dans un magasin.

Certaines caractéristiques sont applicables au niveau de l'afficheur, tandis que d'autres se rapportent à la colonne ou à la cellule.

IV-A - L'afficheur

L'afficheur lui-même n'apporte pas énormément de fonctionnalités. Il sert avant tout à définir le lien avec le magasin de données.

IV-A-1 - Les liens avec le magasin

Il est possible de créer un afficheur sans liaison avec un magasin par la fonction :

GtkWidget *gtk_tree_view_new(void);

On peut ensuite l'associer à un modèle avec la fonction :

void gtk_tree_view_set_model(GtkTreeView *tree_view, GtkTreeModel *model);

si l'afficheur était déjà lié à un magasin, ce lien est supprimé avant d'en créer un autre avec le magasin passé en paramètre.

Il peut aussi être intéressant de connaître le magasin qui contient les données affichées, pour cela, on utilise la fonction :

GtkTreeModel *gtk_tree_view_get_model(GtkTreeView *tree_view);

IV-A-2 - La configuration de l'afficheur

IV-A-2-a - Masquer les titres de colonnes

Pour choisir si les titres de colonnes doivent être affichés ou non (ils le sont par défaut), on utilise la fonction :

void gtk_tree_view_set_headers_visible(GtkTreeView *tree_view, gboolean headers_visible);

tandis qu'il est possible de connaître l'état courant par :

gboolean gtk_tree_view_get_headers_visible(GtkTreeView *tree_view);

IV-A-2-b - Autoriser le clic sur les titres de colonne

Il est possible de choisir si le clic sur le titre de colonne est géré ou non en utilisant la fonction :

void gtk_tree_view_set_headers_clickable(GtkTreeView *tree_view, gboolean setting);

L'utilisation d'une fonction de tri sur une colonne active automatiquement la gestion du clic sur les titres de colonne.

IV-A-2-c - Gestion des marques de suivi des lignes

Il est possible d'indiquer que l'on souhaite faciliter la lecture des lignes pour l'utilisateur (par un choix de couleur de fond alternée pour chaque ligne) par la fonction :

void gtk_tree_view_set_rules_hint (GtkTreeView *tree_view, gboolean setting);

et il est possible de connaître l'état courant par :

gboolean gtk_tree_view_get_rules_hint (GtkTreeView *tree_view);

La documentation officielle de l'API donne quelques précisions et conseils supplémentaires :

N'utilisez pas cette fonction uniquement parce que vous préférez l'apparence avec les marques ; ceci est une question de thème. Certains thèmes vont afficher les lignes avec des couleurs alternées même si les marques ne sont pas activées, et les utilisateurs qui préfèrent cette apparence peuvent choisir ces thèmes. Vous ne devriez utiliser cette fonction que comme une information sémantique pour le moteur de thème pour lui indiquer que votre afficheur rend utiles les marques de lignes pour une raison fonctionnelle (parce qu'il a un grand nombre de colonnes en général).

IV-A-2-d - Gestion des regroupements de lignes

Lorsque l'on utilise un afficheur d'arbre, il est possible de masque (collapse) ou afficher (expand) les enfants d'une ligne. Par défaut, seul le plus haut niveau est affiché. Plusieurs fonctions permettent de jouer avec ce fonctionnement :

- pour afficher tous les éléments :

void gtk_tree_view_expand_all(GtkTreeView *tree_view);

- pour masquer tous les éléments, sauf ceux de plus haut niveau :

void gtk_tree_view_collapse_all(GtkTreeView *tree_view);

- pour afficher les enfants directs d'une ligne (et tous ses parents si besoin) :

void gtk_tree_view_expand_to_path(GtkTreeView *tree_view, GtkTreePath *path);

- pour afficher la descendance d'une ligne :

gboolean gtk_tree_view_expand_row(GtkTreeView *tree_view, GtkTreePath *path, gboolean open_all);

si open_all est à TRUE, alors on affiche toute la descendance, sinon seuls les enfants directs sont affichés.

- pour masquer les enfants d'une ligne (s'il y en a) :

gboolean gtk_tree_view_collapse_row(GtkTreeView *tree_view, GtkTreePath *path);

- pour savoir si une ligne a ses enfants affichés ou masqués :

gboolean gtk_tree_view_row_expanded(GtkTreeView *tree_view, GtkTreePath *path);

- pour choisir la colonne qui affiche les icônes de regroupement :

void gtk_tree_view_set_expander_column(GtkTreeView *tree_view, GtkTreeViewColumn *column);

si la colonne est NULL alors l'indicateur de regroupement est dans la première colonne.

Enfin, pour appliquer une fonction sur toutes les lignes affichées :

void gtk_tree_view_map_expanded_rows(GtkTreeView *tree_view, GtkTreeViewMappingFunc func, gpointer data);

avec une fonction de rappel qui a le prototype suivant :

void func(GtkTreeView *tree_view, GtkTreePath *path, gpointer user_data);

IV-A-3 - La gestion des colonnes

Les fonctions qui permettent de gérer les colonnes, même si elles font partie de l'objet GtkTreeView, sont présentées plus loin avec la présentation détaillée des colonnes pour plus de clarté dans l'exposé.

IV-A-4 - La fonctionnalité de recherche interactive

Ceci ne semble pas fonctionner en GTK+ 2.2.4.

Le principe ressemble fort à la gestion des tris.

- D'abord, il faut écrire une fonction de comparaison qui a le prototype suivant :

gboolean equal_func(GtkTreeModel *model, gint column, const gchar *key, GtkTreeIter *iter, gpointer search_data);

- ensuite, il faut dire que l'on souhaite utiliser cette fonction pour la recherche interactive :

void gtk_tree_view_set_search_equal_func(GtkTreeView *tree_view,
GtkTreeViewSearchEqualFunc search_equal_func,
gpointer search_user_data, GtkDestroyNotify search_destroy);

- ensuite, il faut choisir la colonne du magasin où est appliquée la recherche :

void gtk_tree_view_set_search_column (GtkTreeView *tree_view, gint column);

ceci active en même temps la recherche.

Il est aussi possible d'activer ou désactiver la recherche par :

void gtk_tree_view_set_enable_search(GtkTreeView *tree_view, gboolean enable_search);

Enfin, il est possible de connaître le paramétrage courant de la recherche par les fonctions :

gboolean gtk_tree_view_get_enable_search (GtkTreeView *tree_view);
gint gtk_tree_view_get_search_column (GtkTreeView *tree_view);
GtkTreeViewSearchEqualFunc gtk_tree_view_get_search_equal_func(GtkTreeView *tree_view);

IV-A-5 - L'ajout d'ascenseurs

On voudra souvent accompagner l'afficheur de barres de défilement, horizontales et/ou verticales. L'objet GtkTreeView fait partie des quelques widgets (avec GtkTextView et GtkLayout) qui ont été conçus en prévoyant cet ajout ; il est donc très simple.

Il faut créer une fenêtre avec barres de défilement par la fonction :

GtkWidget* gtk_scrolled_window_new(GtkAdjustment *hadjustment, GtkAdjustment *vadjustment);

Les paramètres permettent de définir très précisément les caractéristiques des barres de défilement, comme le pas de celles-ci. En général, on laissera faire GTK+ en passant la valeur NULL.

Ensuite, il faut préciser la gestion des barres de défilement avec la fonction :

void gtk_scrolled_window_set_policy (GtkScrolledWindow *scrolled_window, GtkPolicyType hscroll_policy, GtkPolicyType vscroll_policy);

Pour les deux derniers paramètres, on utilise les constantes GTK_POLICY_ALWAYS, GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER.

Maintenant, il ne reste plus qu'à insérer l'afficheur dans cette fenêtre avec gtk_container_add().

Comme un exemple vaut mieux qu'un long discours :

/* Exemple d'afficheur d'arbre avec ascenseurs */
GtkTreeStore *store;
GtkWidget *tree, *pScrollbar;

tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
pScrollbar = gtk_scrolled_window_new(NULL, NULL);

gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(pScrollbar), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_container_add(GTK_CONTAINER(pScrollbar), tree);

IV-B - La colonne

Les colonnes constituent un élément intermédiaire dans la constitution de notre afficheur.

IV-B-1 - La gestion des afficheurs élémentaires

Nous avons vu plus haut qu'il est possible facilement de créer une colonne avec un afficheur élémentaire grâce à gtk_tree_view_column_new_with_attributes().

En fait, il est possible de décomposer toutes les opérations. GTK+ autorise même de mettre plusieurs afficheurs élémentaires dans une même colonne.

Pour mettre plusieurs afficheurs élémentaires dans une même cellule, le principe ressemble beaucoup à la Hbox ; on utilise les fonctions :

void gtk_tree_view_column_pack_start(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, gboolean expand);

et

void gtk_tree_view_column_pack_end(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, gboolean expand);

le paramètre expand à FALSE indique qu'aucun espace supplémentaire ne doit être utilisé par l'afficheur élémentaire ; cet espace est reparti entre les afficheurs pour lesquels il est à TRUE.

Il est possible de récupérer la liste des afficheurs contenus dans une colonne par la fonction :

Glist *gtk_tree_view_column_get_cell_renderers(GtkTreeViewColumn *tree_column);

Cette liste est doublement chaînée, elle n'est pas dans un ordre particulier. Après usage, il faut penser à libérer la mémoire avec g_list_free().

Il est possible de supprimer tous les afficheurs élémentaires contenus dans une colonne par la fonction :

void gtk_tree_view_column_clear(GtkTreeViewColumn *tree_column);

Cependant, dans ce cas, il faut ajouter les attributs(5) aux afficheurs élémentaires ensuite grâce aux fonctions :

void gtk_tree_view_column_set_attributes(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell_renderer, ..., NULL);

et

void gtk_tree_view_column_add_attribute(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell_renderer, const gchar *attribute, gint column);

ainsi que :

void gtk_tree_view_column_clear_attributes(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell_renderer);

La première fonction permet de fixer une liste de couples attribut/valeur pour l'afficheur élémentaire selon une syntaxe proche de celle de gtk_tree_view_column_new_with_attributes(). Attention, cette fonction efface toutes les affectations préexistantes.

La deuxième fonction affecte la valeur d'un attribut en complément des affectations existantes.

La dernière fonction permet bien évidemment d'effacer toutes les affectations existantes de valeur aux attributs.

En complément, il est possible de gérer un espacement entre les afficheurs élémentaires grâce aux fonctions :

void gtk_tree_view_column_set_spacing(GtkTreeViewColumn *tree_column, gint spacing);

et

gint gtk_tree_view_column_get_spacing(GtkTreeViewColumn *tree_column);

la valeur de l'espacement est mesurée en pixels.

IV-B-2 - Visibilité et redimensionnement

Parmi les paramètres de présentation que l'on peut gérer au niveau d'une colonne, on trouve la visibilité et le redimensionnement de la largeur.

IV-B-2-a - Visibilité d'une colonne

Pour afficher ou masquer une colonne, on utilise la fonction :

void gtk_tree_view_column_set_visible(GtkTreeViewColumn *tree_column, gboolean visible);

tandis que pour connaître l'état courant, on utilise :

gboolean gtk_tree_view_column_get_visible (GtkTreeViewColumn *tree_column);

IV-B-2-b - Redimensionnement de la largeur d'une colonne

Une colonne dispose d'une politique de gestion de sa largeur. Cette politique est définie au travers des constantes suivantes :

- GTK_TREE_VIEW_COLUMN_GROW_ONLY, dans ce cas, en cas de changement des valeurs du magasin, la largeur peut augmenter si besoin, mais elle ne rétrécit pas ;

- GTK_TREE_VIEW_COLUMN_AUTOSIZE, dans ce cas, la largeur s'adapte automatiquement à chaque changement dans le magasin ;

- GTK_TREE_VIEW_COLUMN_FIXED, dans ce cas la colonne a une largeur fixe, ce qui peut entraîner que certains affichages soient tronqués.

Pour gérer la politique de largeur d'une colonne, on utilise les fonctions :

void gtk_tree_view_column_set_sizing(GtkTreeViewColumn *tree_column, GtkTreeViewColumnSizing type);

et

GtkTreeViewColumnSizing gtk_tree_view_column_get_sizing(GtkTreeViewColumn *tree_column);

Pour gérer la largeur d'une colonne (en pixels), on utilise :

gint gtk_tree_view_column_get_width(GtkTreeViewColumn *tree_column);

cette fonction renvoie la largeur réelle de la colonne ;

gint gtk_tree_view_column_get_fixed_width(GtkTreeViewColumn *tree_column);

cette fonction renvoie la largeur imposée de la colonne (celle-ci peut être différente de la largeur réelle, voire plus loin les largeurs min et max) ;

void gtk_tree_view_column_set_fixed_width(GtkTreeViewColumn *tree_column, gint fixed_width);

cette fonction impose une largeur fixe à la colonne, la largeur réelle sera calculée en tenant compte des largeurs min et max, cette fonction n'a de sens que si la politique de largeur de la colonne est GTK_TREE_VIEW_COLUMN_FIXED ;

void gtk_tree_view_column_set_min_width(GtkTreeViewColumn *tree_column, gint min_width);

cette fonction impose une largeur minimum à la colonne ;

gint gtk_tree_view_column_get_min_width(GtkTreeViewColumn *tree_column);

cette fonction renvoie la largeur minimum de la colonne ;

void gtk_tree_view_column_set_max_width(GtkTreeViewColumn *tree_column, gint max_width);

cette fonction impose une largeur maximum à la colonne, une largeur de -1 signifie qu'il n'y a pas de maximum ;

gint gtk_tree_view_column_get_max_width(GtkTreeViewColumn *tree_column);

cette fonction renvoie la largeur maximum de la colonne ;

Dans le cas GTK_TREE_VIEW_COLUMN_FIXED, la largeur réelle est calculée en prenant le minimum si la largeur fixée est inférieure à ce minimum, le maximum si la largeur fixée est supérieure à ce maximum, et la largeur fixée sinon.

En plus de la gestion des largeurs précédente, une colonne a une gestion spécifique ; elle s'étend automatiquement pour couvrir toute la largeur allouée au GtkTreeView.

Par défaut, cette colonne est la dernière, mais il est possible de modifier ce comportement avec :

void gtk_tree_view_column_set_expand(GtkTreeViewColumn *treecolumn, gboolean expand);

L'espace disponible est alors réparti équitablement entre toutes les colonnes ayant cet attribut à TRUE.

On peut connaître l'état de l'attribut par :

gboolean gtk_tree_view_column_get_expand (GtkTreeViewColumn *tree_column);

Enfin, dernier point, il est possible de permettre à l'utilisateur de redimensionner lui-même la largeur de colonne en traînant le séparateur de droite de cette colonne ; pour cela, on utilise la fonction :

void gtk_tree_view_column_set_resizable(GtkTreeViewColumn *tree_column, gboolean resizable);

Si le dernier paramètre est à TRUE et que la politique de largeur est à GTK_TREE_VIEW_COLUMN_AUTOSIZE alors, elle passe automatiquement à GTK_TREE_VIEW_COLUMN_GROW_ONLY.

Et pour connaître la valeur de cette autorisation, il y a la fonction :

gboolean gtk_tree_view_column_get_resizable(GtkTreeViewColumn *tree_column);

IV-B-3 - Autres attributs de la colonne

En fait, il ne reste plus qu'une seule caractéristique : le titre.

IV-B-3-a - Gestion du titre de la colonne

D'abord, commençons par rappeler que l'affichage/masquage du titre doit être identique sur toutes les colonnes, il est donc du ressort de l'afficheur global (cf. Masquer les titres de colonnes).

Donc la seule chose que l'on puisse modifier, c'est le contenu du titre, ce que l'on fait avec :

void gtk_tree_view_column_set_title(GtkTreeViewColumn *tree_column, const gchar *title);

Et bien sûr, on peut en récupérer la valeur par :

G_CONST_RETURN gchar *gtk_tree_view_column_get_title(GtkTreeViewColumn *tree_column);

Mais il est aussi possible de remplacer le label du titre de colonne par un autre widget en utilisant la fonction :

void gtk_tree_view_column_set_widget(GtkTreeViewColumn *tree_column, GtkWidget *widget);

et de récupérer ce widget par :

GtkWidget* gtk_tree_view_column_get_widget(GtkTreeViewColumn *tree_column);

IV-C - La cellule

L'afficheur de cellule élémentaire (« Cell Renderer ») est un élément fondamental du système. C'est au travers de ses attributs que se fixe une part importante du fonctionnement de l'afficheur.

IV-C-1 - La gestion des attributs

IV-C-1-a - La notion d'attribut

Un afficheur élémentaire dispose d'un nombre important de paramètres lui indiquant ce qu'il doit afficher. Ce sont ses attributs.

L'essentiel de la gestion de l'affichage se fait en associant des valeurs aux attributs de l'afficheur élémentaire.

Fondamentalement, on associe un numéro de colonne (du magasin sous-jacent) à un attribut pour qu'à chaque ligne l'afficheur utilise la valeur correspondante dans le magasin. C'est ce que j'appelle les attributs variables

Il est aussi possible d'affecter une valeur fixe qui sera utilisée pour toute la colonne. C'est ce que j'appelle les attributs fixes.

IV-C-1-b - Les attributs variables

Pour un même afficheur élémentaire on peut associer plusieurs attributs, chacun à une colonne

différente du magasin. Comme en plus, il peut y avoir plusieurs afficheurs élémentaires dans une même colonne, on voit bien qu'il n'est pas possible de remonter à une colonne du magasin en partant d'une colonne de l'afficheur, ou même d'un afficheur élémentaire.

L'affectation des attributs variables se fait en utilisant les fonctions vues dans la description des colonnes (cf. La gestion des afficheurs élémentaires). Pour mémoire, les principales fonctions sont :

- gtk_tree_view_column_new_with_attributes()

- gtk_tree_view_column_set_attributes()

- gtk_tree_view_column_add_attribute()

- gtk_tree_view_column_clear_attributes()

IV-C-1-c - Les attributs constants

Il arrive aussi que l'on veuille définir certaines caractéristiques communes pour toute la colonne, par exemple la police d'affichage du texte.

Dans ce cas, plutôt que d'affecter une colonne du modèle à la valeur de l'attribut de l'afficheur élémentaire, on fixe directement la valeur de cet attribut par la fonction :

void g_object_set(gpointer object, ..., NULL);

Le premier paramètre est bien sûr l'afficheur élémentaire concerné, normalement transtypé par la macro G_OBJECT() ; ensuite on trouve une liste de couples nom d'attribut (format texte de type gchar *) / valeur de l'attribut (le type dépend de l'attribut), cette liste est terminée par un NULL.

Le résultat est imprévisible, ou tout au moins inconnu de l'auteur et non précisé dans la documentation officielle, lorsque l'on utilise le même attribut à la fois comme constant et variable.

IV-C-2 - Les trois types d'afficheurs élémentaires

GTK+ fournit trois afficheurs élémentaires(6) : un pour l'affichage de texte (même si c'est une valeur numérique ou booléenne), un pour l'affichage d'icônes, un pour l'affichage de case à cocher.

IV-C-2-a - Afficheur de texte

L'afficheur de texte permet d'afficher les valeurs des données du magasin sous forme textuelle en choisissant de nombreuses caractéristiques d'affichage.

On crée un afficheur élémentaire texte par la fonction :

GtkCellRenderer* gtk_cell_renderer_text_new (void);

Ensuite on fixe les caractéristiques de l'affichage au travers des attributs (cf. Les attributs variables et Les attributs constants).

L'attribut « text » permet de définir le lien entre le texte affiché et la donnée. Pour ne pas alourdir la lecture de ce document, les autres attributs sont présentés en annexe.

IV-C-2-b - Afficheur d'icône

L'afficheur d'icône permet d'afficher dans les cellules de petites images fournies sous forme de GdkPixBuf ou de StockItem.

On crée un afficheur d'icône par la fonction :

GtkCellRenderer* gtk_cell_renderer_pixbuf_new(void);

Ensuite on fixe les caractéristiques de l'affichage au travers des attributs (cf. Les attributs variables et Les attributs constants).

L'attribut « pixbuf » permet de définir avec l'image à afficher, tandis que « stock-id » définit l'icône lorsque l'on utilise des StockItem. Pour ne pas alourdir la lecture de ce document, les autres attributs sont présentés en annexe.

IV-C-2-c - Afficheur binaire

L'afficheur binaire permet d'afficher dans les cellules dune valeur booléenne sous forme de case à cocher ou de bouton radio.

On crée un afficheur binaire par la fonction :

GtkCellRenderer* gtk_cell_renderer_toggle_new(void);

Ensuite on fixe les caractéristiques de l'affichage au travers des attributs (cf. Les attributs variables et Les attributs constants).

L'attribut « active » permet de définir si la case doit être cochée ou non. Pour ne pas alourdir la lecture de ce document, les autres attributs sont présentés en annexe.

Il est possible de choisir si l'affichage doit se faire sous forme de bouton radio plutôt qu'une case à cocher en utilisant la fonction suivante plutôt qu'un attribut constant :

void gtk_cell_renderer_toggle_set_radio(GtkCellRendererToggle *toggle, gboolean radio);

Il reste à la charge du programme de s'assurer que dans le cas du bouton radio, une seule case est activée à la fois.

IV-C-3 - Fonction d'affichage spécifique

Parfois, on a envie d'avoir un formatage plus précis que celui qui est autorisé par les attributs de l'afficheur élémentaire. C'est par exemple le cas pour les valeurs flottantes lorsque l'on veut choisir le nombre de chiffres après la virgule.

GTK+ nous offre une méthode(7) pour préparer l'affichage avant le passage de l'afficheur élémentaire. Il suffit d'utiliser la fonction :

void gtk_tree_view_column_set_cell_data_func(GtkTreeViewColumn *tree_column,
GtkCellRenderer *renderer, GtkTreeCellDataFunc func,
gpointer func_data, GtkDestroyNotify destroy);

La fonction de rappel doit avoir le prototype suivant :

void user_func(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,

GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data);

Dans cette fonction de rappel, il devient possible de positionner les attributs de l'afficheur élémentaire avec plus de souplesse. Attention cependant, si un attribut doit être forcé dans certains cas et pas d'autres, il faut lui en supprimer la valeur quand c'est inutile.

Un bon exemple vaut mieux qu'un long discours :

/* Exemple data_cell_function */
void double_display_function(GtkTreeViewColumn *col,
GtkCellRenderer *renderer, GtkTreeModel *model,
GtkTreeIter *iter, gpointer user_data)
{
gdouble montant;
guint colnum = GPOINTER_TO_UINT(user_data);
gchar buf[20];

gtk_tree_model_get(model, iter, colnum, &montant, -1);
if(montant > 0.0) {
g_snprintf(buf, sizeof(buf), "%.2f", montant);
g_object_set(renderer, "text", buf, "visible", TRUE, NULL);
} else {
g_object_set(renderer, "visible", FALSE, NULL);
}
}

/* [...] */
renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes ("Montant",renderer,NULL);
gtk_tree_view_column_set_cell_data_func(column, renderer,
double_display_function, GUINT_TO_POINTER(4), NULL);

gtk_tree_view_append_column(GTK_TREE_VIEW(tree), column);

IV-D - Édition « sur place »

IV-D-1 - Afficheur de texte

Il est possible de modifier la valeur valeur d'une cellule texte en saisissant une nouvelle valeur dans la case.

Pour l'utilisateur, lorsque cette fonction est activée, il suffit de cliquer dans une case d'une ligne sélectionnée (par un clic précédent), de saisir la nouvelle valeur puis de valider par la touche « Entrée » ou en cliquant en dehors de la cellule en cours de saisie ; l'utilisateur peut renoncer à changer la valeur de la cellule en utilisant la touche « Escape(8) ».

Sur le plan de la programmation, il faut autoriser la saisie dans la cellule en donnant la valeur TRUE à l'attribut « editable » de l'afficheur élémentaire. Si toute la colonne doit être éditable, alors on utilise un attribut constant dont on fixe la valeur par :

g_object_set(renderer, "editable", TRUE, NULL);

(rien n'empêche de grouper cet attribut avec d'autres attributs constants.) mais il est aussi possible de lier cet attribut à une colonne dont les valeurs sont booléennes (cf. Les attributs variables).

Ensuite, il faut encore aller modifier le contenu du magasin lorsque l'utilisateur a saisi une nouvelle valeur dans la cellule. Pour cela, il faut récupérer le signal « edited » de l'afficheur élémentaire par la fonction g_signal_connect().

Pour ce signal, il faut définir une fonction de rappel dont le prototype est le suivant :

void user_function(GtkCellRendererText *renderer, gchar *path_string, gchar *new_text, gpointer user_data);

Le deuxième paramètre donne le chemin, sous forme de chaîne de caractères, de la ligne concernée par la saisie. Le troisième paramètre fournit le texte (en chaîne de caractères même pour une valeur numérique) qui a été saisi. Le dernier paramètre renvoie classiquement les données qui ont été fournies lors de l'attachement du signal.

Comme il est délicat de remonter au numéro de colonne du magasin en partant de l'afficheur élémentaire (cf. Remonter de la colonne d'un afficheur à la colonne d'un magasin), il est fortement recommandé de mettre ce numéro de colonne comme donnée utilisateur lors de l'attachement du signal (ou au moins parmi les données). L'autre solution consiste à attacher le numéro de colonne à l'afficheur comme décrit ici.

IV-D-2 - Afficheur d'icône

Aucune possibilité d'édition en ligne n'est prévue.

IV-D-3 - Afficheur binaire

Selon un principe analogue à l'afficheur texte, il faut rendre la cellule modifiable en positionnant à TRUE l'attribut « activatable ». Si toute la colonne doit être éditable, alors on utilise un attribut constant dont on fixe la valeur par :

g_object_set(renderer, "activatable", TRUE, NULL);

(rien n'empêche de grouper cet attribut avec d'autres attributs constants.) mais il est aussi possible de lier cet attribut à une colonne dont les valeurs sont booléennes (cf. Les attributs variables).

Ensuite, il faut encore aller modifier le contenu du magasin lorsque l'utilisateur a saisi une nouvelle valeur dans la cellule. Pour cela, il faut récupérer le signal « toggled » de l'afficheur élémentaire par la fonction g_signal_connect().

Pour ce signal, il faut définir une fonction de rappel dont le prototype est le suivant :

void user_function(GtkCellRendererToggle *renderer, gchar *path_string, gpointer user_data);

Le deuxième paramètre donne le chemin, sous forme de chaîne de caractères, de la ligne concernée par la saisie. Le dernier paramètre renvoie classiquement les données qui ont été fournies lors de l'attachement du signal.

Lorsque l'utilisateur clique sur la case, ni l'apparence de la case à cocher ni la valeur dans le magasin ne sont modifiées, pour que l'apparence change, il faut que le programme aille modifier la valeur dans le magasin (il faut d'abord lire l'ancienne valeur dans le magasin pour pouvoir forcer la valeur opposée).

IV-E - Informations diverses

IV-E-1 - Double-clic sur une colonne

Il est possible de récupérer facilement l'information du double clic dans une colonne en récupérant le signal « row-activated ».

Il faut connecter le signal au niveau de l'afficheur. La fonction de rappel doit avoir le prototype suivant :

void user_function(GtkTreeView *treeview, GtkTreePath *arg1, GtkTreeViewColumn *arg2, gpointer user_data);

En fait, lorsqu'on regarde les informations disponibles, on s'aperçoit que l'on dispose de la colonne, mais aussi de la ligne (chemin). Cependant, il ne faut pas en déduire qu'il est simple de remonter à la colonne correspondante du magasin (cf. Remonter de la colonne d'un afficheur à la colonne d'un magasin).

IV-E-2 - Gestion d'un menu contextuel

La gestion d'un menu contextuel se fait généralement par un clic sur le bouton droit de la souris.

Comme tous les widgets de GTK+, l'afficheur de listes et d'arbres gère ce clic, il suffit de connecter le signal « button-press-event ». Dans la fonction de traitement, il faut s'assurer que c'est bien le bouton droit de la souris qui a été utilisé.

Habituellement, le menu contextuel doit aussi pouvoir être accessible sur l'appui de la touche Shift-F10 sans utilisation de la souris. Pour cela, il faut connecter le signal « popup-menu » à l'afficheur.

La gestion d'un menu pop-up sort du cadre de ce document.

V - La gestion des sélections en détail

L'objet « selection » est systématiquement créé en même temps que chaque afficheur. En fait, cet objet n'existe que pour clarifier l'API. Il suffit de le récupérer avec la fonction gtk_tree_view_get_selection() (voir plus : La sélection).

V-A - Récupérer les éléments sélectionnés

V-A-1 - Récupérer une sélection unique

Dans le cas d'une sélection simple (GTK_SELECTION_SINGLE ou GTK_SELECTION_BROWSE), il est facile de récupérer un itérateur qui pointe sur la donnée sélectionnée.

Comme vu plus haut, il est possible de récupérer le magasin et l'itérateur de la donnée par la fonction :

gboolean gtk_tree_selection_get_selected(GtkTreeSelection *selection, GtkTreeModel **model, GtkTreeIter *iter);

La fonction retourne une valeur vraie lorsqu'un élément est effectivement sélectionné.

Quelques précisions complémentaires :

- Cette fonction ne doit pas être utilisée si le mode est GTK_SELECTION_MULTIPLE ;

- Si l'appel de la fonction a seulement pour objet de savoir s’il y a une ligne sélectionnée ou non, il est possible de mettre NULL pour le paramètre « iter ».

V-A-2 - Récupérer la liste des éléments sélectionnés

V-A-2-a - Fonction de récupération d'une sélection

Il est possible de récupérer la liste des sélections sous la forme d'une liste de chemins :

Glist *gtk_tree_selection_get_selected_rows(GtkTreeSelection *selection, GtkTreeModel **model);

Le dernier paramètre permet de récupérer la valeur du magasin associé ; on peut mettre NULL si on n'a pas besoin de cette valeur.

V-A-2-b - Transformation des chemins en références

La liste des éléments sélectionnés est fournie au travers de chemins. Mais ces chemins ne sont valables que tant que le contenu du magasin ne varie pas. Si dans le traitement que vous envisagez de faire vous souhaitez supprimer, ajouter ou déplacer des données, il vous faudra convertir ces chemins en références.

V-A-2-c - Parcours de la liste

La liste des éléments sélectionnés est au format d'une liste doublement chaînée de la glib. Cette bibliothèque met à notre disposition les outils dont nous avons besoin.

Pour appeler une fonction pour chaque élément de la liste :

void g_list_foreach(GList *list, GFunc func, gpointer user_data);

La fonction de traitement doit avoir le prototype suivant :

void func(gpointer data, gpointer user_data);

Le premier paramètre fournit l'élément sur lequel doit s'appliquer la fonction ; le deuxième paramètre fournit les données qui ont été passées lors de l'appel de g_list_foreach().

Et si l'on souhaite gérer soi-même le parcours de la liste, on peut utiliser les fonctions :

- pour connaître le nombre d'éléments sélectionnés :

gint gtk_tree_selection_count_selected_rows(GtkTreeSelection *selection);

- pour accéder aux données (le chemin) à partir d'un élément :

Glist *element; chemin = element->data;

- pour accéder au premier ou au dernier élément :

Glist *g_list_first(GList *list);

Glist *g_list_last(GList *list);

- pour accéder à l'élément suivant ou précédent :

Glist *g_list_next(GList *list);

Glist *g_list_previous(GList *list);

(en fait, il s'agit de macros, ce qui limite un peu leur utilisation, par exemple, il n'est pas possible de les utiliser directement comme fonction de rappel)

- pour accéder au nième élément :

Glist *g_list_nth(GList *list, guint n);

ou pour accéder directement aux données du nième élément (c'est-à-dire au chemin) :

gpointer g_list_nth_data(GList *list, guint n);

Il existe d'autres fonctions de manipulation de liste dans la glib, mais celles-ci devraient être suffisantes pour le cas présent.

V-A-2-d - Libération de la mémoire

Après avoir utilisé les informations fournies par gtk_tree_selection_get_selected_rows(), il faut libérer la mémoire correspondante (les chemins, et la liste elle-même) par :

g_list_foreach (list, gtk_tree_path_free, NULL);

g_list_free (list);

V-A-3 - Parcourir la liste des éléments sélectionnés

Il est aussi possible de parcourir directement (au niveau gtk) les éléments sélectionnés grâce à la fonction :

void gtk_tree_selection_selected_foreach(GtkTreeSelection *selection,

GtkTreeSelectionForeachFunc func, gpointer data);

la fonction de rappel doit avoir le prototype suivant :

gboolean func(GtkTreeModel *model,

GtkTreePath *path, GtkTreeIter *iter, gpointer data);

Le premier paramètre est le magasin, le deuxième le chemin, le troisième l'itérateur, et le dernier les données fournies lors de l'appel à gtk_tree_selection_selected_foreach().

Si la fonction renvoie la valeur TRUE, le parcours de la sélection s'arrête.

Cette fonction semble séduisante, mais elle souffre d'une limitation importante : il ne faut pas ajouter, supprimer ou déplacer les données dans le magasin au cours du traitement effectué.

V-B - Changement de la sélection

V-B-1 - Changer la sélection par programme

Si l'utilisateur peut gérer la sélection en utilisant le souris et/ou le clavier, il est aussi possible de la modifier par programme. Pour cela, GTK+ met à notre disposition les fonctions suivantes :

void gtk_tree_selection_select_iter(GtkTreeSelection *selection, GtkTreeIter *iter);

void gtk_tree_selection_unselect_iter(GtkTreeSelection *selection, GtkTreeIter *iter);

void gtk_tree_selection_select_path(GtkTreeSelection *selection, GtkTreePath *path);

void gtk_tree_selection_unselect_path(GtkTreeSelection *selection, GtkTreePath *path);

void gtk_tree_selection_select_all(GtkTreeSelection *selection);

void gtk_tree_selection_unselect_all(GtkTreeSelection *selection);

void gtk_tree_selection_select_range(GtkTreeSelection *selection,

GtkTreePath *start_path, GtkTreePath *end_path);

void gtk_tree_selection_unselect_range(GtkTreeSelection *selection,

GtkTreePath *start_path, GtkTreePath *end_path);

La majorité de ces fonctions ont bien évidemment un comportement qui dépend du mode de sélection en cours (GTK_SELECTION_SINGLE, GTK_SELECTION_BROWSE, GTK_SELECTION_MULTIPLE). Si le mode est SINGLE ou BROWSE, la fonction gtk_tree_selection_select_iter() désélectionnera la ligne précédemment sélectionnée avant d'en sélectionner une nouvelle ; tandis que gtk_tree_selection_select_all() n'aura pas d'effet.

V-B-2 - Gérer le changement de sélection

Il est possible d'être au courant que la sélection a changé en récupérant le signal « changed » avec une fonction de rappel dont le prototype est le suivant :

void user_function(GtkTreeSelection *treeselection, gpointer user_data);

Ce signal peut n'être émis qu'une seule fois lors de la sélection d'un groupe de lignes et il peut être émis alors qu'il n'y a pas de changement dans la sélection.

Mais on peut faire mieux, c'est-à-dire être prévenu chaque fois que l'utilisateur souhaite sélectionner ou désélectionner une ligne pour donner notre accord (par programme). pour cela, on utilise la fonction :

void gtk_tree_selection_set_select_function(GtkTreeSelection *selection,

GtkTreeSelectionFunc func, gpointer data, GtkDestroyNotify destroy);

Cette fonction utilise une fonction de rappel ayant le prototype :

gboolean user_func(GtkTreeSelection *selection, GtkTreeModel *model,

GtkTreePath *path, gboolean currently_selected, gpointer data);

Le paramètre path permet de connaître la ligne concernée par le changement de sélection, tandis que currently_selected donne l'état actuel de la sélection (avant l'action utilisateur).

Si la fonction retourne TRUE, alors le changement de sélection est effectué, sinon, il n'est pas pris en compte.

VI - Trucs et astuces divers

VI-A - Remonter de la colonne d'un afficheur à la colonne d'un magasin

VI-A-1 - La méthode complète

Lorsque l'on connaît une colonne de l'afficheur et que l'on veut remonter à la colonne correspondante du magasin, la technique n'est pas simple. En fait, c'est tout bonnement impossible sauf à faire certaines hypothèses, parce qu'une colonne d'afficheur peut être liée à plusieurs colonnes du magasin :

- parce la colonne d'affichage contient plusieurs afficheurs élémentaires ;

- parce que l'afficheur élémentaire a plusieurs attributs liés à des colonnes différentes du magasin.

Nous supposerons donc dans la suite qu'il n'y a qu'un seul afficheur dans la colonne concernée, et que nous recherchons la colonne du magasin associée à l'attribut principal de l'afficheur élémentaire (« text » pour un afficheur de texte, « pixbuf » pour un afficheur d'icônes et « active » pour un afficheur booléen.

Donc, procédons par étape :

- depuis la colonne de l'afficheur, remontons à l'afficheur élémentaire par :

Glist *gtk_tree_view_column_get_cell_renderers(GtkTreeViewColumn *tree_column);

- puis :

renderer = CellList->data;

- ensuite, il faudrait savoir à quel type d'afficheur élémentaire on a affaire, mais on va simplement rechercher les attributs qui nous intéressent :

… et là, on est coincé : pas moyen de remonter à la colonne, l'objet GtkTreeViewColumn ne fournit pas de fonction gtk_tree_view_column_get_attribute(), la fonction g_objetc_get() sur l'afficheur élémentaire ne nous rendrait elle qu'un attribut constant, ce qui n'est pas ce que l'on recherche.

VI-A-2 - Une astuce pour s'en sortir

Puisqu'il n'est pas possible de remonter de l'afficheur élémentaire à la colonne du magasin avec les fonctions du noyau de GTK, la solution consiste à ajouter cette information dans la colonne ou à l'afficheur élémentaire lors de la constitution de la vue.

Il s'agit d'une information théoriquement redondante il y a donc un risque d'incohérence et c'est au programmeur de garantir la validité de cette information.

VI-A-2-a - Choisir entre colonne et afficheur élémentaire

Associer l'information de colonne du magasin à l'objet colonne de l'afficheur, c'est un moyen de

simplifier au maximum lorsque l'on ne dispose, au départ, que de cet objet. Cependant, comme il peut y avoir plusieurs afficheurs élémentaires dans une même colonne, cela peut poser un problème.

Par contre certaines fonctions de rappel(9) fournissent un pointeur sur l'afficheur élémentaire, il est alors plus judicieux de stocker l'information avec l'afficheur élémentaire.

VI-A-2-b - Associer le numéro de colonne

Pour associer une information a un objet, on utilise la fonction :

void g_object_set_data(GObject *object, const gchar *key, gpointer data);

et pour retrouver la valeur, on utilise la fonction :

gpointer g_object_get_data(GObject *object, const gchar *key);

Ces deux fonctions peuvent bien entendu être utilisées aussi bien sur l'objet colonne de l'afficheur que sur l'afficheur élémentaire.

En pratique, cela donne les fragments de code suivants :

/* Exemple 1 de passage de la colonne afficheur au magasin */
GtkTreeView *view,;
GtkTreeViewColumn *column;
guint ncol;

column = gtk_tree_view_column_new();
g_object_set_data
(G_OBJECT(column), "columnnum", GUINT_TO_POINTER(COL_XXX));

/* [...] */
ncol = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(column), "columnnum");

/* Exemple 2 de passage de la colonne afficheur au magasin */
GtkTreeView *view,;
GtkTreeViewColumn *column;
GtkCellRenderer *renderer;
guint ncol;

renderer = gtk_cell_renderer_text_new();
g_object_set_data
(G_OBJECT(renderer), "columnnum", GUINT_TO_POINTER(COL_XXX));

column = gtk_tree_view_column_new_with_attributes
("Titre XXX", renderer, "text", COL_XXX, NULL);

/* [...] */
ncol = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(renderer), "columnnum");

VII - Annexes

VII-A - Définitions des constantes

VII-A-1 - Types de colonne

G_TYPE_NONE

G_TYPE_LONG

G_TYPE_DOUBLE

G_TYPE_INTERFACE

G_TYPE_ULONG

G_TYPE_STRING

G_TYPE_CHAR

G_TYPE_INT64

G_TYPE_POINTER

G_TYPE_UCHAR

G_TYPE_UINT64

G_TYPE_BOXED

G_TYPE_BOOLEAN

G_TYPE_ENUM

G_TYPE_PARAM

G_TYPE_INT

G_TYPE_FLAGS

G_TYPE_OBJECT

G_TYPE_UINT

G_TYPE_FLOAT

VII-A-2 - Modes de sélection

GTK_SELECTION_NONE

Pas de sélection possible

GTK_SELECTION_SINGLE

Sélection de 0 ou 1 élément à la fois

GTK_SELECTION_BROWSE

Sélection d'exactement 1 élément

GTK_SELECTION_MULTIPLE

Sélection de plusieurs éléments, contigus ou non, en utilisant les touches Shift et Ctrl

VII-B - Attributs des afficheurs élémentaires

VII-B-1 - Afficheur de texte

Attribut

Type

R/W

Description

« attributes »

PangoAttrList

R/W

A list of style attributes to apply to the text of the renderer.

« background »

gchararray

Background color as a string.

« background-gdk »

GdkColor

R/W

Background color as a GdkColor.

« background-set »

gboolean

R/W

Whether this tag affects the background color.

« editable »

gboolean

R/W

Whether the text can be modified by the user.

« editable-set »

gboolean

R/W

Whether this tag affects text editability.

« family »

gchararray

R/W

Name of the font family, e.g. Sans, Helvetica, Times, Monospace.

« family-set »

gboolean

R/W

Whether this tag affects the font family.

« font »

gchararray

R/W

Font description as a string.

« font-desc »

PangoFontDescription

R/W

Font description as a PangoFontDescription struct.

« foreground »

gchararray

W

Foreground color as a string.

« foreground-gdk »

GdkColor

R/W

Foreground color as a GdkColor.

« foreground-set »

gboolean

R/W

Whether this tag affects the foreground color.

« language »

gchararray

R/W

The language this text is in, as an ISO code. Pango can use this as a hint when rendering the text. If you don't understand this parameter, you probably don't need it.

« language-set »

gboolean

R/W

Whether this tag affects the language the text is rendered as.

« markup »

gchararray

W

Marked up text to render.

« rise »

gint

R/W

Offset of text above the baseline (below the baseline if rise is negative).

« rise-set »

gboolean

R/W

Whether this tag affects the rise.

« scale »

gdouble

R/W

Font scaling factor.

« scale-set »

gboolean

R/W

Whether this tag scales the font size by a factor.

« single-paragraph-mode »

gboolean

R/W

Whether or not to keep all text in a single paragraph.

« size »

gint

R/W

Font size.

« size-points »

gdouble

R/W

Font size in points.

« size-set »

gboolean

R/W

Whether this tag affects the font size.

« stretch »

PangoStretch

R/W

Font stretch.

« stretch-set »

gboolean

R/W

Whether this tag affects the font stretch.

« strikethrough »

gboolean

R/W

Whether to strike through the text.

« strikethrough-set »

gboolean

R/W

Whether this tag affects strikethrough.

« style »

PangoStyle

R/W

Font style.

« style-set »

gboolean

R/W

R/W Whether this tag affects the font style.

« text »

gchararray

R/W

Text to render.

« underline »

PangoUnderline

R/W

Style of underline for this text.

« underline-set »

gboolean

R/W

Whether this tag affects underlining.

« variant »

PangoVariant

R/W

Font variant.

« variant-set »

gboolean

R/W

Whether this tag affects the font variant.

« weight »

gint

R/W

Font weight.

« weight-set »

gboolean

R/W

Whether this tag affects the font weight.

VII-B-2 - Afficheur d'icône

Attribut

Type

R/W

Description

« pixbuf »

GdkPixbuf

R/W

The pixbuf to render.

« pixbuf-expander-closed »

GdkPixbuf

R/W

Pixbuf for closed expander.

« pixbuf-expander-open »

GdkPixbuf

R/W

Pixbuf for open expander.

« stock-detail »

gchararray

R/W

Render detail to pass to the theme engine.

« stock-id »

gchararray

R/W

The stock ID of the stock icon to render.

« stock-size »

GtkIconSize

R/W

The size of the rendered icon.

VII-B-3 - Afficheur booléen

Attribut

Type

R/W

Description

« activatable »

gboolean

R/W

The toggle button can be activated.

« active »

gboolean

R/W

The toggle state of the button.

« inconsistent »

gboolean

R/W

The inconsistent state of the button.

« radio »

gboolean

R/W

Draw the toggle button as a radio button.

VII-C - Documentation

VII-C-1 - API officielle

Glib https://developer.gnome.org/glib/

Gobject https://developer.gnome.org/gobject/stable/

Pango https://developer.gnome.org/pango/stable/

ATK https://developer.gnome.org/atk/stable/

GdkPixbuf https://developer.gnome.org/gdk-pixbuf/stable/

GDK https://developer.gnome.org/gdk3/stable/

GTK https://developer.gnome.org/gtk3/index.html

VII-C-2 - Autres tutoriels

par Tim-Philipp Müller http://scentric.net/tutorial en anglais

par gtk-fr.org http://www.gtk-fr.org/index.php?page=cours&id=23 en français

VII-D - Reste à faire

- Présenter le fonctionnement du Drag and drop

- Enrichir les Trucs et astuces

VIII - Copyright

Copyright © 2004 Jean-Robert SCHLOSSER

Vous avez la permission de copier et de distribuer ce document sans y apporter de modification.

Vous avez aussi la possibilité de modifier ce document à condition de garder intégralement cette mention de copyright ainsi que le nom de l'auteur et de fournir un accès direct au document original.

You have permission to copy and distribute this document without any modification You have also permission under the condition that you keep integrally this mention of copyright and the author's name, and give a direct access to the original document.

(1)Cet aspect ne sera pas abordé ici.

(2)Voir plus loin dans ce document.

(3)Cette dernière phrase est conforme au comportement décrit dans la documentation officielle, mais il semble que ça ne fonctionne pas en version 2.2.4 ; à revérifier en 2.4.

(4)Astuce : si vous avez dans le magasin des textes convertis en UTF8 pour avoir un affichage correct des accents, il faut penser à faire la conversion inverse avant d'écrire dans le fichier.

(5)Pour bien comprendre la gestion des attributs, on peut lire en parallèle « La gestion des attributs » un tout petit peu plus loin.

(6)La version 2.4 de GTK+ devrait apporter un modèle supplémentaire qui permettra de filtrer les données.

(7)Cette méthode peut s'avérer malgré tout un peu gourmande en ressources lorsqu'il y a un grand nombre d'enregistrements à afficher, et ainsi ralentir l'application.

(8)Touche « Echap » sur la plupart des claviers français.heur à la colonne d'

(9)La fonction de rappel du signal « éditable » d'un afficheur élémentaire par exemple.