IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Apprendre à utiliser le module Python PyGTK 2.0


précédentsommairesuivant

XIV. Chapitre 14. Le widget TreeView

Le widget TreeView affiche des listes et des arborescences sur plusieurs colonnes. Il remplace les anciens widgets List, CList, Tree and CTree par un ensemble beaucoup plus puissant et flexible d'objets utilisant le principe Modèle-Vue-Contrôleur (MVC). Il propose :

  • deux modèles prédéfinis : un pour les listes et un pour les arbres ;
  • l'actualisation automatique des différentes vues du même modèle lorsque celui-ci est modifié ;
  • l'affichage sélectif des données du modèle ;
  • l'utilisation des données du modèle pour personnaliser l'affichage du TreeView ligne par ligne ;
  • des objets prédéfinis de rendu des données pour l'affichage de texte, d'images ou de données booléennes ;
  • des modèles empilables afin de permettre des vues classées et filtrées des données du modèle inférieur ;
  • la possibilité de réordonner et de redimensionner les colonnes ;
  • la possibilité d'ordonner automatiquement les lignes du TreeView par un clic dans l'en-tête des colonnes ;
  • le support du glisser-déposer ;
  • le support de modèles personnels entièrement écrits en Python ;
  • le support de cell-renderers personnels entièrement écrits en Python.

La puissance de ce nouvel ensemble d'objets et d'interfaces a pour contrepartie une complexité indéniablement plus importante, pouvant même paraître insurmontable au début. Dans ce chapitre, les objets et les interfaces du TreeView seront présentés de manière à pouvoir en faire un usage courant. Nous vous laisserons le soin d'explorer par vous-même leurs aspects plus ésotériques.

Nous commencerons par un bref aperçu de ces objets et interfaces, avant de nous plonger dans l'interface TreeModel et dans les classes prédéfinies ListStore et TreeStore.

XIV-A. Présentation

Le widget TreeView est l'objet de l'interface utilisateur qui se charge d'afficher les données stockées dans un objet implémentant l'interface TreeModel. Deux classes TreeModel de base sont définies dans PyGTK 2.0 :

  • le TreeStore, qui permet un stockage hiérarchique des données, organisé en lignes d'arborescence comportant des données dans des colonnes. Chaque ligne d'arborescence peut avoir zéro ou plusieurs lignes filles, et toutes les lignes doivent avoir le même nombre de colonnes ;
  • le ListStore, qui permet un stockage tabulaire des données, organisé en lignes et en colonnes, de la même manière qu'un tableau dans une base de données relationnelle. Le ListStore est en réalité une version simplifiée du TreeStore dont les lignes n'ont pas de lignes filles. Il fut créé afin d'offrir une interface plus simple (et certainement plus efficace) de ce modèle commun de données.

Les deux autres TreeModel s'ajoutent par dessus (ou s'interposent entre) les modèles de base :

  • le TreeModelSort est un modèle dans lequel les données du TreeModel inférieur sont maintenues dans un ordre donné ;
  • le TreeModelFilter est un modèle contenant un sous-ensemble des données du modèle inférieur. Notez que ce modèle n'est disponible que dans PyGTK 2.4 et supérieurs.

Un TreeView affiche toutes les lignes d'un TreeModel, mais pas forcément toutes les colonnes. Ces dernières peuvent en outre ne pas être présentées dans le même ordre que dans le TreeModel.

Le TreeView utilise des TreeViewColumn pour organiser l'affichage des données des colonnes. Chaque TreeViewColumn affiche une colonne (avec ou sans en-tête) pouvant contenir les données de plusieurs colonnes de TreeModel. Dans chaque TreeViewColumn sont placés (comme dans des conteneurs HBox) des CellRenderer, qui prennent en charge l'affichage des données correspondantes en provenance d'un emplacement ligne-colonne dans un TreeModel. Trois classes CellRenderer sont prédéfinies :

  • le CellRendererPixbuf, qui prend en charge l'affichage d'une image pixbuf dans les cellules d'un TreeViewColumn ;
  • le CellRendererText, qui prend en charge l'affichage d'une chaine de texte dans les cellules d'un TreeViewColumn. Si les données des colonnes ne sont pas des chaines de texte, il effectue la conversion. Par exemple, pour afficher une colonne de modèle contenant une donnée à virgule flottante (float), le CellRendererText la convertira en chaine avant de l'afficher ;
  • Le CellRendererToggle affiche une valeur booléenne dans les cellules d'un TreeViewColumn sous la forme d'un bouton à bascule.

Un TreeViewColumn peut contenir plusieurs CellRenderer pour, par exemple, afficher de l'image et du texte dans une même colonne.

Enfin, les objets TreeIter, TreeRowReference et TreeSelection sont respectivement un pointeur temporaire vers une ligne d'un TreeModel, un pointeur permanent vers une ligne d'un TreeModel et un objet gérant les sélections dans un TreeView.

Pour afficher un TreeView, on effectuera les opérations suivantes (sans nécessairement respecter l'ordre proposé) :

  • création d'un objet TreeModel, généralement un ListStore ou un TreeStore, avec une ou plusieurs colonnes d'un type spécifique de données ;
  • placement éventuel d'une ou plusieurs lignes de données dans le TreeModel ;
  • création d'un widget TreeView et association de ce dernier avec le TreeModel ;
  • création d'un ou plusieurs TreeViewColumn et insertion d'un ou de ces derniers dans le TreeView. Chacun représentera une colonne affichée. ;
  • création, pour chaque TreeViewColumn, d'un ou plusieurs CellRenderer qui y sont ensuite placés ;
  • définition des attributs de chaque CellRenderer de manière à indiquer dans quelle colonne du TreeModel l'on doit récupérer les données de l'attribut. Par exemple, le texte à afficher. Cela permet au CellRenderer d'afficher différemment chaque colonne d'une ligne ;
  • insertion et affichage du TreeView dans une fenêtre (gtk.Window) ou une fenêtre à défilement (gtk.ScrolledWindow) ;
  • programmation des manipulations de données à effectuer en réponse aux actions de l'utilisateur. Le TreeView doit suivre automatiquement les modifications.

Le programme d'exemple treeviewbasique.py illustre la création et l'affichage d'un TreeView simple.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
#!/usr/bin/env python

# exemple treeviewbasique.py

import pygtk
pygtk.require('2.0')
import gtk

class ExempleTreeViewBasique:

    # fermeture de la fenêtre et sortie du programme
    def evnmt_delete(self, widget, evnmt, donnees=None):
        gtk.main_quit()
        return False

    def __init__(self):
        # Création d'une nouvelle fenêtre
        self.fenetre = gtk.Window(gtk.WINDOW_TOPLEVEL)

        self.fenetre.set_title("Exemple TreeView simple")

        self.fenetre.set_size_request(200, 200)

        self.fenetre.connect("delete_event", self.evnmt_delete)

        # Création d'un TreeStore avec une colonne de type chaine, pour servir de modèle
        self.treestore = gtk.TreeStore(str)

        # Ajoutons des données : 4 lignes ayant 3 lignes filles chacune
        for mere in range(4):
            m_iter = self.treestore.append(None, ['ligne mere %i' % mere])
            for fille in range(3):
                self.treestore.append(m_iter, ['ligne fille %i de la ligne %i'
                                              % (fille, mere)])

        # création du TreeView en utilisant notre TreeStore
        self.treeview = gtk.TreeView(self.treestore)

        # création du TreeViewColumn pour afficher les données
        self.tvcolumn = gtk.TreeViewColumn('Column 0')

        # on place tvcolumn dans notre TreeView
        self.treeview.append_column(self.tvcolumn)

        # création d'un CellRendererText pour afficher les données
        self.cell = gtk.CellRendererText()

        # on place cell dans le TreeViewColumn et on lui permet de s'étirer
        self.tvcolumn.pack_start(self.cell, True)

        # réglage de l'attribut "text" de cell sur la colonne 0 - récupère le
        # texte dans cette colonne du TreeStore
        self.tvcolumn.add_attribute(self.cell, 'text', 0)

        # on autorise la recherche
        self.treeview.set_search_column(0)

        # on autorise le classement de la colonne
        self.tvcolumn.set_sort_column_id(0)

        # on autorise le classement des lignes par glisser-déposer
        self.treeview.set_reorderable(True)

        self.fenetre.add(self.treeview)

        self.fenetre.show_all()

def main():
    gtk.main()

if __name__ == "__main__":
    exempletv = ExempleTreeViewBasique()
    main()

Dans de vrais programmes, les données seraient probablement fournies au TreeStore après l'affichage du TreeView, par une action de l'utilisateur. Nous étudierons en détail les interfaces du TreeView dans les prochaines sections. La Figure 14.1, « Programme d'exemple de TreeView simple » montre la fenêtre créée par le programme treeviewbasique.py après que quelques lignes ont été développées.

Image non disponible
Figure 14.1. Programme d'exemple de TreeView simple

Examinons à présent l'interface TreeModel et les modèles qui l'implémentent.

XIV-B. L'interface TreeModel et le stockage des données

XIV-B-1. Introduction

Toutes les sous-classes TreeModel implémentent l'interface TreeModel. Elle fournit des méthodes pour :

  • récupérer les caractéristiques du modèle, telles que le nombre de colonnes et le type de données dans une colonne ;
  • récupérer un TreeIter (une référence temporaire pointant sur une ligne du modèle) ;
  • récupérer des informations sur un nœud (ou une ligne) comme une liste de ses nœuds enfants, leur nombre, le contenu de ses colonnes ou un pointeur sur son nœud parent ;
  • obtenir des notifications de changement des données du TreeModel

XIV-B-2. Créer des objets TreeStore et ListStore

Les classes de base de stockage des données, le ListStore et le TreeStore, permettent de définir et de manipuler les lignes et les colonnes de données dans le modèle. Dans les constructeurs de ces deux objets, les colonnes devront être déclarées comme étant de l'un des types suivants :

  • un type Python, comme les types prédéfinis int, str, long, float et object ;
  • un type PyGTK, comme les Button, VBox, gdk.Rectangle, gdk.Pixbuf ;
  • un type GObject (les GTypes de GTK+), spécifié sous la forme soit d'une constante gobject.TYPE, soit d'une chaine de caractères. La majorité des GTypes sont mappés sur un type Python :

    • gobject.TYPE_CHAR ou 'gchar',
    • gobject.TYPE_UCHAR ou 'guchar',
    • gobject.TYPE_BOOLEAN ou 'gboolean',
    • gobject.TYPE_INT ou 'gint',
    • gobject.TYPE_UINT ou 'guint',
    • gobject.TYPE_LONG ou 'glong',
    • gobject.TYPE_ULONG ou 'gulong',
    • gobject.TYPE_INT64 ou 'gint64',
    • gobject.TYPE_UINT64 ou 'guint64',
    • gobject.TYPE_FLOAT ou 'gfloat',
    • gobject.TYPE_DOUBLE ou 'gdouble',
    • gobject.TYPE_STRING ou 'gchararray',
    • gobject.TYPE_OBJECT ou 'Gobject.

Par exemple, la création d'un ListStore ou d'un TreeStore dont les lignes contiendraient un gdk.Pixbuf, un entier, une chaine et un booléen, pourrait ressembler à ceci :

 
Sélectionnez
1.
2.
3.
  liststore = ListStore(gtk.gdk.Pixbuf, int, str, 'gboolean')

  treestore = TreeStore(gtk.gdk.Pixbuf, int, str, 'gboolean')

Une fois un ListStore ou un TreeStore créé et ses colonnes définies, ils ne peuvent plus être modifiés. Il est également important de savoir qu'il n'y a pas de relation prédéfinie entre les colonnes d'un TreeView et celles de son TreeModel. Ainsi, la cinquième colonne de données d'un TreeModel peut être affichée dans la première colonne d'un TreeView et dans la troisième d'un autre. On n'a donc pas à se soucier de l'affichage des données lorsque l'on crée un modèle.

Si ces deux modèles ne conviennent pas à votre application, il est possible de définir votre propre modèle personnalisé en Python, pourvu qu'il implémente l'interface TreeModel. Nous verrons ceci plus en détail dans la Section 14.11, « Le TreeModel générique »Le TreeModel générique.

XIV-B-3. Les références aux lignes du modèle

Avant de pouvoir manipuler des lignes de données dans un TreeStore ou un ListStore, il nous faut pouvoir désigner les lignes à traiter. PyGTK offre trois moyens de faire référence à des lignes de TreeModel : le chemin d'accès, le TreeIter et le TreeRowReference.

XIV-B-3-a. Le chemin d'accès

Le chemin d'accès consiste en un entier, une chaine ou un tuple représentant l'emplacement d'une ligne dans le modèle. Une valeur entière indique le numéro d'une ligne dans un modèle en partant de 0 (le premier niveau). Par exemple, un chemin d'accès valant 4 indiquerait la cinquième ligne du modèle. Par comparaison, la même ligne serait désignée par "4" sous forme de chaine et par (4) sous forme de tuple. Si cela suffit effectivement à désigner toutes les lignes d'un ListStore, il nous reste encore à désigner les lignes enfants pour un TreeStore. On n'utilisera pour ce faire que les représentations sous forme de chaine ou de tuple.

La profondeur de l'arborescence d'un TreeStore étant complètement arbitraire, la représentation sous forme de chaine indique le chemin d'accès en partant du premier niveau jusqu'à la ligne voulue, et en séparant chaque valeur par le caractère ":". La représentation par tuple suit le même principe, le tuple étant constitué d'une séquence d'entiers conduisant à la ligne voulue en partant du premier niveau. Par exemple, les chemins "0:2" (troisième ligne enfant de la première ligne) et "4:0:1" (deuxième ligne enfant du premier enfant de la cinquième ligne) sont des représentations valides sous la forme de chaines de caractères. Les équivalents en tuples de ces chemins d'accès sont respectivement (0, 2) et (4, 0, 1).

Le chemin d'accès représente la seule possibilité de mapper une ligne d'un TreeView sur une ligne d'un TreeModel, car leurs chemins d'accès sont identiques. Le chemin d'accès pose toutefois quelques problèmes :

  • il peut désigner une ligne qui n'existe pas dans le ListStore ou dans le TreeStore ;
  • il peut pointer vers une autre ligne de données après l'insertion ou la suppression d'une ligne dans le ListStore ou dans le TreeStore.

La représentation par tuple est utilisée par PyGTK lors du renvoi de chemins d'accès, mais les trois formes sont acceptées indifféremment en entrée. La représentation par tuple est conseillée dans un souci de cohérence.

On peut récupérer un chemin d'accès à partir d'un TreeIter à l'aide de la méthode get_path() :

 
Sélectionnez
  chemin = modele.get_path(iter)

iter est un TreeIter pointant sur une ligne de modele et chemin le chemin d'accès de la ligne sous la forme d'un tuple.

XIV-B-3-b. Les TreeIter

Un TreeIter est un objet offrant une référence temporaire à une ligne de ListStore ou de TreeStore. Si le contenu du modèle est modifié (généralement par l'insertion ou la suppression d'une ligne), le TreeIter peut devenir invalide. Un TreeModel supportant les TreeIter persistants devrait avoir le drapeau gtk.TREE_MODEL_ITERS_PERSIST. Une application peut vérifier la présence de ce drapeau avec la méthode get_flags().

Pour créer un TreeIter, on utilise une des méthodes du TreeModel qui sont applicables aux TreeStore comme aux ListStore :

 
Sélectionnez
  treeiter = modele.get_iter(path)

treeiter pointe sur la ligne indiquée par le chemin d'accès path. Si le chemin d'accès est invalide, l'exception ValueError est levée.

 
Sélectionnez
  treeiter = modele.get_iter_first()

treeiter est un TreeIter pointant sur la ligne située au chemin (0,). treeiter vaudra None si le modèle est vide.

 
Sélectionnez
  treeiter = modele.iter_next(iter)

treeiter est un TreeIter qui pointe sur la ligne suivante, sur le même niveau que le TreeIter indiqué par iter. treeiter vaudra None s'il n'y a pas de ligne suivante (et iter sera invalidé).

Les méthodes qui suivent ne servent à récupérer un TreeIter qu'à partir d'un TreeStore :

 
Sélectionnez
  treeiter = treestore.iter_children(parent)

treeiter est un TreeIter pointant sur le premier enfant de la ligne indiquée par le TreeIter parent.treeiter vaudra None s'il n'y pas de ligne enfant.

 
Sélectionnez
  treeiter = treestore.iter_nth_child(parent, n)

treeiter est un TreeIter pointant sur la ligne enfant d'index n de la ligne désignée par le TreeIter parent.parent peut valoir None dans le cas où l'on souhaite récupérer une ligne de premier niveau. treeiter vaudra None s'il n'y a pas de ligne enfant.

 
Sélectionnez
  treeiter = treestore.iter_parent(child)

treeiter est un TreeIter pointant sur la ligne parent de la ligne désignée par le TreeIter child.treeiter vaudra None s'il n'y a pas de ligne enfant.

La méthode get_path() permet de récupérer un chemin d'accès à partir d'un TreeIter :

 
Sélectionnez
  chemin = modele.get_path(iter)

iter est un Treeiter pointant sur une ligne de modele et chemin le chemin d'accès de la ligne sous la forme d'un tuple.

XIV-B-3-c. Les TreeRowReference

Un TreeRowReference est une référence persistante à une ligne de données dans un modèle. Alors que le chemin d'accès (c'est-à-dire la situation) de la ligne (c'est-à-dire son emplacement) est susceptible de changer avec l'ajout ou la suppression d'autres lignes, le TreeRowReference pointera toujours sur la même ligne de données.

Les TreeRowReference ne sont disponibles dans PyGTK qu'à partir de la version 2.4.

On crée un TreeRowReference grâce à son constructeur :

 
Sélectionnez
  treerowref = TreeRowReference(model, path)

model est le TreeModel contenant la ligne et path le chemin d'accès vers la ligne à laquelle faire référence. Si path n'est pas un chemin d'accès valide pour model, None est renvoyé.

XIV-B-4. Ajouter des lignes

XIV-B-4-a. Ajouter des lignes à un ListStore

Une fois que vous avez un ListStore, il vous faut lui ajouter des lignes de données. Plusieurs méthodes sont à votre disposition :

 
Sélectionnez
1.
2.
3.
4.
5.
  iter = append(row=None)
  iter = prepend(row=None)
  iter = insert(position, row=None)
  iter = insert_before(sibling, row=None)
  iter = insert_after(sibling, row=None)

Chacune de ces méthodes insère une ligne à un emplacement implicite ou spécifié du ListStore. Les méthodes append() et prepend() utilisent des emplacements implicites : respectivement après la dernière ligne et avant la première. La méthode insert() attend un entier (le paramètre position) qui spécifie l'emplacement où insérer la ligne. Les deux autres méthodes attendent un TreeIter (sibling) pointant sur une ligne du ListStore, afin d'insérer la ligne avant ou après.

Le paramètre row indique les données devant être insérées dans la ligne après sa création. Si row vaut None ou n'est pas spécifié, la ligne créée sera vide. Si row est spécifié, il doit être un tuple ou une liste contenant autant d'éléments que le nombre de colonnes dans le ListStore. Les éléments doivent également respecter le type de données de leurs colonnes respectives dans le ListStore.

Toutes ces méthodes renvoient un TreeIter pointant sur la ligne qui vient d'être insérée. La portion de code suivante illustre la création d'un ListStore et l'ajout de lignes de données :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
  ...
  liststore = gtk.ListStore(int, str, gtk.gdk.Color)
  liststore.append([0,'red',colormap.alloc_color('red')])
  liststore.append([1,'green',colormap.alloc_color('green')])
  iter = liststore.insert(1, (2,'blue',colormap.alloc_color('blue')) )
  iter = liststore.insert_after(iter, [3,'yellow',colormap.alloc_color('blue')])
  ...
XIV-B-4-b. Ajouter des lignes à un TreeStore

L'ajout de lignes à un TreeStore est identique à l'ajout de lignes dans un ListStore à ceci près qu'il faut également indiquer une ligne parent (avec un TreeIter) à laquelle ajouter la nouvelle ligne. Les méthodes pour les TreeStore sont :

 
Sélectionnez
1.
2.
3.
4.
5.
  iter = append(parent, row=None)
  iter = prepend(parent, row=None)
  iter = insert(parent, position, row=None)
  iter = insert_before(parent, sibling, row=None)
  iter = insert_after(parent, sibling, row=None)

Si parent vaut None, la ligne sera ajoutée au premier niveau.

Chacune de ces méthodes insère une ligne à un emplacement implicite ou spécifié du TreeStore. Les méthodes append() et prepend() utilisent des emplacements implicites : respectivement après la dernière ligne enfant et avant la première. La méthode insert() attend un entier (le paramètre position) qui spécifie l'emplacement où insérer la ligne enfant. Les deux autres méthodes attendent un TreeIter (sibling) pointant sur une ligne enfant du TreeStore, afin d'insérer la ligne avant ou après.

Le paramètre row indique les données devant être insérées dans la ligne après sa création. Si row vaut None ou n'est pas spécifié, la ligne créée sera vide. Si row est spécifié, il doit être un tuple ou une liste contenant autant d'éléments que le nombre de colonnes dans le TreeStore. Les éléments doivent également respecter le type de données de leurs colonnes respectives dans le TreeStore.

Toutes ces méthodes renvoient un TreeIter pointant sur la ligne qui vient d'être insérée. La portion de code suivante illustre la création d'un TreeStore et l'ajout de lignes de données :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
  ...
  pbdossier = gtk.gdk.pixbuf_from_file('dossier.xpm')
  pbfichier = gtk.gdk.pixbuf_from_file('fichier.xpm')
  treestore = gtk.TreeStore(int, str, gtk.gdk.Pixbuf)
  iter0 = treestore.append(None, [1,'(0,)',pbdossier] )
  treestore.insert(iter0, 0, [11,'(0,0)',pbfichier])
  treestore.append(iter0, [12,'(0,1)',pbfichier])
  iter1 = treestore.insert_after(None, iter0, [2,'(1,)',pbdossier])
  treestore.insert(iter1, 0, [22,'(1,1)',pbfichier])
  treestore.prepend(iter1, [21,'(1,0)',pbfichier])
  ...
XIV-B-4-c. Modèles de grande taille

Lorsqu'un ListStore ou un TreeStore contient déjà un grand nombre de lignes, en ajouter de nouvelles peut se révéler très lent. Pour pallier cet inconvénient, on pourra suivre les conseils suivants :

  • dans le cas de l'ajout d'un grand nombre de lignes, déconnecter le TreeModel de son TreeView (en utilisant la méthode set_model() avec son paramètre model à None). Ceci afin d'éviter que le TreeView n'actualise l'affichage à chaque nouvelle ligne ;
  • toujours dans le cas de l'ajout d'un grand nombre de lignes, désactiver le classement (en utilisant la méthode set_default_sort_func() avec son paramètre sort_func à None) ;
  • limiter le nombre de TreeRowReference utilisés, car ils actualisent leur chemin d'accès à chaque ajout/suppression ;
  • fixez la propriété "fixed-height-mode" du TreeView sur TRUE pour faire en sorte que toutes les lignes aient la même hauteur et ainsi éviter le calcul individuel de la hauteur pour chaque ligne (disponible seulement à partir de PyGTK 2.4).

XIV-B-5. Supprimer des lignes

XIV-B-5-a. Supprimer des lignes d'un ListStore

On peut supprimer une ligne de données d'un ListStore en faisant appel à la méthode remove() :

 
Sélectionnez
  treeiter = liststore.remove(iter)

iter est un TreeIter pointant sur la ligne à supprimer. Le TreeIter renvoyé (treeiter) pointera sur la ligne suivante ou sera invalide si iter pointait sur la dernière ligne.

La méthode clear() supprimer toutes les lignes du ListStore :

 
Sélectionnez
  liststore.clear()
XIV-B-5-b. Supprimer des lignes d'un TreeStore

Les méthodes servant à supprimer des lignes de données d'un TreeStore sont similaires à celles du ListStore :

 
Sélectionnez
  result = treestore.remove(iter)
  treestore.clear()

resultat vaudra TRUE si la ligne a été supprimée et iter pointera sur la ligne valide suivante. Dans le cas contraire, resultat vaudra FALSE et iter sera invalidé.

XIV-B-6. Manipuler les données des lignes

XIV-B-6-a. Définir et récupérer des valeurs

Les méthodes permettant d'accéder aux valeurs stockées dans un ListStore et dans un TreeStore ont le même format. Toutes les manipulations des données du modèle nécessitent un TreeIter pour indiquer la ligne sur laquelle on travaille. Une fois que l'on a un TreeIter, la méthode get_value() permet de récupérer les valeurs d'une colonne dans une rangée donnée :

 
Sélectionnez
  valeur = modele.get_value(iter, column)

iter est un TreeIter pointant sur une ligne, column le numéro d'une colonne de modele, et valeur la valeur stockée à l'emplacement ligne-colonne.

Pour récupérer les valeurs de plusieurs colonnes en un seul appel, on utilisera la méthode get() :

 
Sélectionnez
  valeurs = modele.get(iter, column, ...)

iter est un TreeIter pointant sur une ligne, column est le numéro d'une colonne de modele, ... représente d'éventuels numéros de colonnes supplémentaires et valeurs est un tuple contenant les valeurs récupérées. Par exemple, pour récupérer les valeurs des colonnes 0 et 2 :

 
Sélectionnez
  val0, val2 = modele.get(iter, 0, 2)

La méthode get() n'est disponible qu'à partir de PyGTK 2.4

Pour définir la valeur d'une colonne unique, on utilise la méthode set_value() :

 
Sélectionnez
  modele.set_value(iter, column, value)

iter (un TreeIter) et column (un entier) indiquent l'emplacement ligne-colonne dans modele et column est le numéro de la colonne où l'on veut stocker value. value doit être du même type de données que la colonne du modele.

Pour définir les valeurs de plusieurs colonnes d'une même ligne à la fois, on utilisera la méthode set() :

 
Sélectionnez
  modele.set(iter, ...)

iter désigne la ligne du modèle et ... est un ou plusieurs numéros de colonne - les paires de valeurs indiquant la colonne et la valeur à stocker. Par exemple, l'appel suivant :

 
Sélectionnez
  modele.set(iter, 0, 'Foo', 5, 'Bar', 1, 123)

stockera 'Foo' dans la première colonne, 'Bar' dans la sixième et 123 dans la deuxième, tout ceci à la ligne de modele indiquée par iter.

XIV-B-6-b. Réordonner les lignes d'un ListStore

Les lignes d'un ListStore peuvent être déplacées à l'aide des méthodes suivantes (disponibles à partir de PyGTK 2.2) :

 
Sélectionnez
1.
2.
3.
  liststore.swap(a, b)
  liststore.move_after(iter, position)
  liststore.move_before(iter, position)

swap() intervertit les positions des lignes désignées par les TreeIter a et b. move_after() et move_before() déplacent la ligne désignée par le TreeIter iter après ou avant la ligne désignée par le TreeIter position. Si position vaut None, move_after() placera la ligne au début du modèle et move_before() à la fin.

Pour réordonner complètement les lignes d'un ListStore, on fera appel à la méthode suivante :

 
Sélectionnez
  liststore.reorder(nouvel_ordre)

nouvel_ordre est une liste d'entiers représentant le nouvel ordre des lignes tel que :

 
Sélectionnez
  nouvel_ordre[nouvelleposition] = ancienneposition

Par exemple, si liststore contient quatre lignes :

 
Sélectionnez
1.
2.
3.
4.
  'un'
  'deux'
  'trois'
  'quatre'

l'appel suivant :

 
Sélectionnez
  liststore.reorder([2, 1, 3, 0])

produirait le nouvel ordre suivant :

 
Sélectionnez
1.
2.
3.
4.
  'trois'
  'deux'
  'quatre'
  'un'

Ces méthodes réordonnent uniquement les ListStore non ordonnés.

Pour réordonner les lignes avec PyGTK 2.0, il vous faudra les supprimer et les insérer avec les méthodes décrites aux Section 14.2.4, « Ajouter des lignes »Ajouter des lignes et Section 14.2.5, « Supprimer des lignes »Supprimer des lignes.

XIV-B-6-c. Réordonner les lignes d'un TreeStore

Les méthodes utilisées pour réordonner les lignes du TreeStore sont identiques à celles du ListStore à ceci près qu'elles n'affectent que les lignes enfants d'une ligne parent implicite - il n'est pas possible, par exemple, d'intervertir des lignes de parents différents :

 
Sélectionnez
1.
2.
3.
  treestore.swap(a, b)
  treestore.move_after(iter, position)
  treestore.move_before(iter, position)

swap() intervertit les positions des lignes enfants désignées par les TreeIter a et b. a et b doivent avoir la même ligne parent. move_after() et move_before() déplacent la ligne désignée par le TreeIter iter après ou avant la ligne désignée par le TreeIter position. iter et position doivent avoir la même ligne parent. Si position vaut None, move_after() placera la ligne au début du modèle et move_before() à la fin.

La méthode reorder() attend un paramètre supplémentaire qui désigne la ligne parent dont les enfants doivent être réordonnés :

 
Sélectionnez
  treestore.reorder(parent, new_order)

new_order est une liste d'entiers représentant le nouvel ordre des lignes enfants pour la ligne parent spécifiée par le TreeIter parent, telle que :

 
Sélectionnez
  new_order[nouvelleposition] = ancienneposition

Par exemple, si treestore contient quatre lignes :

 
Sélectionnez
1.
2.
3.
4.
5.
  'ligne parent'
      'un'
      'deux'
      'trois'
      'quatre'

l'appel suivant

 
Sélectionnez
  treestore.reorder(parent, [2, 1, 3, 0])

produit le nouvel ordre suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
  'ligne parent'
      'trois'
      'deux'
      'quatre'
      'un'

Ces méthodes réordonnent uniquement les TreeStore non ordonnés.

XIV-B-6-d. Manipuler plusieurs lignes

L'une des opérations les plus délicates avec les ListStore ou les TreeStore est la manipulation de plusieurs lignes, par exemple le déplacement de plusieurs lignes, d'une ligne parent à une autre, ou encore la suppression de certaines lignes en fonction de critères spéciaux. La difficulté provient de la nécessité d'utiliser un TreeIter qui peut devenir invalide du fait même de l'opération. Pour les ListStore et les TreeStore, les TreeIter sont persistants (afin de s'en assurer, la présence du drapeau gtk.TREE_MODEL_ITERS_PERSIST peut être vérifiée avec la méthode get_flags()). En revanche, les classes empilables TreeModelFilter et TreeModelSort ne disposent pas de TreeIter persistants.

Comment déplacer toutes les lignes enfants d'une ligne vers une autre si les TreeIter ne résistent pas à l'opération ? Il faut :

  • itérer sur les enfants de la ligne parent ;
  • récupérer les données de chaque ligne enfant ;
  • supprimer chaque ligne enfant ;
  • ajouter une nouvelle ligne, contenant les données de la ligne supprimée, à la liste de la nouvelle ligne parent.

On ne peut pas se fier à la méthode remove() pour renvoyer un TreeIter valide. On demandera donc simplement l'iter de la première ligne enfant, et ce jusqu'à recevoir None. Voici un exemple de fonction pouvant déplacer des lignes enfants :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
  def deplace_enfants(treestore, parent_source, parent_destination):
    nb_colonnes = treestore.get_n_columns()
    iter = treestore.iter_children(parent_source)
    while iter:
      valeurs = treestore.get(iter, *range(nb_colonnes))
      treestore.remove(iter)
      treestore.append(parent_destination, valeurs)
      iter = treestore.iter_children(parent_source)
    return

La fonction ci-dessus convient pour le simple cas où l'on souhaiterait déplacer tous les enfants d'une même ligne parent. Mais que faire si l'on veut supprimer toutes les lignes du TreeStore qui correspondent à un critère donné, comme la valeur de la première colonne par exemple ? Vous pensez peut-être à utiliser la méthode foreach() pour itérer sur toutes les lignes et supprimer celles qui correspondent :

 
Sélectionnez
  modele.foreach(fonction, donnees_utilisateur)

fonction est une fonction à invoquer pour chaque ligne du modèle et dont la signature est :

 
Sélectionnez
  def fonction(modele, chemin, iter, donnees_utilisateur):

modele est le TreeModel, chemin le chemin d'accès à une ligne de modele, iter un TreeIter pointant sur chemin et donnees_utilisateur les données transmises. Si fonction renvoie TRUE, la méthode foreach() cesse d'itérer et se termine.

Le problème de ce procédé est que modifier le contenu du modèle pendant que la méthode foreach() est en train d'itérer peut avoir des conséquences imprévisibles. Une meilleure stratégie serait d'utiliser la méthode foreach() pour créer et sauvegarder des TreeRowReferences pointant sur les lignes à supprimer, puis de supprimer celles-ci une fois la méthode foreach() terminée. Mais cela ne fonctionnerait pas avec PyGTK 2.0 et 2.2 dans lesquels les TreeRowReference n'existent pas.

Si l'on souhaite couvrir toutes les variantes de PyGTK, on peut utiliser la méthode foreach() pour rassembler les chemins d'accès des lignes à supprimer, puis les supprimer dans l'ordre inverse afin de préserver la validité des chemins d'accès. Par exemple, la portion de code suivante utilise cette stratégie :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
  ...
  # On vérifie que la valeur de la première colonne >= la valeur transmise
  # donnée est un tuple contenant la valeur de comparaison ainsi qu'une liste
  # pour sauvegarder les chemins d'accès
  def rappel_compar_valeur(modele, chemin, iter, donnees):
    if modele.get_value(iter, 0) >= donnees[0]:
      donnees[1].append(chemin)
    return False     # foreach continue d'itérer

  listechemins = []
  treestore.foreach(rappel_compar_valeur, (10, listechemins))

  # foreach fonctionne en profondeur d'abord (depth-first)
  listechemins.reverse()
  for chemin in listechemins:
    treestore.remove(treestore.get_iter(chemin))
  ...

Dans le cas où l'on voudrait rechercher la première ligne d'un TreeStore répondant à un critère donné, on pourrait effectuer soi-même l'itération :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
   treestore = TreeStore(str)
   ...
   def fonction_compar(modele, iter, donnees):
       colonne, critere = donnees # donnes est un tuple contenant un numéro de colonne et un critère
       valeur = modele.get_value(iter, colonne)
       return valeur == critere
   def recherche(modele, iter, fonction, donnees):
       while iter:
           if fonction(modele, iter, donnees):
               return iter
           resultat = recherche(modele, modele.iter_children(iter), fonction, donnees)
           if resultat: return resultat
           iter = modele.iter_next(iter)
       return None
   ...
   compar_iter = recherche(treestore, treestore.iter_children(None), 
                       fonction_compar, (0, 'bla'))

La fonction recherche() effectue une itération en profondeur sur la ligne (spécifiée par iter), sur ses pairs et sur leurs enfants à la recherche d'une ligne dont une colonne correspond à la chaine de caractères donnée. La recherche prend fin lorsqu'une ligne est trouvée.

XIV-B-7. Support du protocole Python

Les classes qui implémentent l'interface TreeModel (TreeStore, ListStore et, à partir de PyGTK 2.4 TreeModelSort et TreeModelFilter) supportent les protocoles Python de mappage et d'itération. Le protocole d'itération permet d'utiliser la fonction iter() de Python sur un Treemodel pour créer un itérateur qui servira à itérer sur ses lignes de premier niveau. Une possibilité plus utile est d'itérer en utilisant l'instruction for ou une création fonctionnelle de liste (list comprehension). Par exemple :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
  ...
  liststore = gtk.ListStore(str, str)
  ...
  # on ajoute des lignes au ListStore
  ...
  # une boucle for
  for ligne in liststore:
      # traitement individuel des lignes
  ...
  # création fonctionnelle de liste renvoyant une liste des valeurs de la première colonne
  valeurs = [ l[0] for l in liststore ]
  ...

L'utilisation de del pour supprimer une ligne du modèle et l'extraction d'un TreeModelRow PyGTK de ce dernier à partir d'une valeur-clé qui est un chemin d'accès ou un TreeIter, sont d'autres parties supportées des protocoles de mappage. Par exemple, les instructions suivantes renvoient toutes la première ligne d'un TreeModel et la dernière instruction supprime le premier enfant de la première ligne :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
  ligne = modele[0]
  ligne = modele['0']
  ligne = modele["0"]
  ligne = modele[(0,)]
  i = modele.get_iter(0)
  ligne = modele[i]
  del modele[(0,0)]

De plus, on peut fixer les valeurs d'une ligne existante comme ceci :

 
Sélectionnez
1.
2.
3.
4.
  ...
  liststore = gtk.ListStore(str, int, object)
  ...
  liststore[0] = ['Bouton', 23, gtk.Button('Etiquette')]

Les objets TreeModelRow de PyGTK supportent les protocoles de séquence et d'itération Python. On peut faire en sorte qu'un itérateur itère sur les valeurs des colonnes de la ligne, ou bien utiliser l'instruction for ou encore une création fonctionnelle de liste. Les TreeModelRow utilisent le numéro de colonne comme l'index à partir duquel extraire une valeur. Par exemple :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
  ...
  liststore = gtk.ListStore(str, int)
  liststore.append(['Chaine de caracteres', 514])
  ...
  ligne = liststore[0]
  valeur1 = ligne[1]
  valeur0 = liststore['0'][0]
  for valeur in ligne:
      print valeur
  val0, val1 = ligne
  ...

On utilise l'exemple de la section précédente pour itérer sur un TreeStore afin de localiser une ligne contenant une valeur particulière, le code devient :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
   treestore = TreeStore(str)
   ...
   def fonction_compar(ligne, donnees):
       colonne, critere = donnees # "donnees" est un tuple contenant un numéro de colonne et un critère
       return ligne[colonne] == critere
   ...
   def recherche(lignes, fonction, donnees):
       if not lignes: return None
       for ligne in lignes:
           if fonction(ligne, donnees):
               return ligne
           resultat = recherche(ligne.iterchildren(), fonction, donnees)
           if resultat: return resultat
       return None
   ...
   compar_ligne = recherche(treestore, fonction_compar, (0, 'bla'))

On peut également définir une valeur dans une colonne existante ainsi :

 
Sélectionnez
  treestore[(1,0,1)][1] = 'abc'

Les TreeModelRow supportent aussi l'instruction del et la conversion en listes ou tuples avec les fonctions list() et tuple() de Python. Comme illustré dans l'exemple suivant, le TreeModelRow dispose de la méthode iterchildren() qui renvoie un itérateur pour itérer sur les lignes enfants du TreeModelRow.

XIV-B-8. Les signaux du TreeModel

Votre application peut suivre les changements d'un TreeModel en se connectant aux signaux qu'il émet : "row-changed", "row-deleted", "row-inserted", "row-has-child-toggled" et "rows-reordered". Ces signaux sont utilisés par un TreeView pour suivre les changements dans son TreeModel.

Dans une application connectée à ces signaux, l'appel de certaines méthodes peut déclencher l'émission de plusieurs signaux. Par exemple, l'appel pour ajouter à une ligne sa première ligne enfant :

 
Sélectionnez
  treestore.append(parent, ['qwe', 'asd', 123])

déclenchera l'émission des signaux suivants :

  • "row-inserted" (ligne-insérée), du fait de l'insertion de la ligne (vide) ;
  • "row-has-child-toggled" (ligne-possède-enfant-modifié), puisque parent n'avait pas de ligne enfant auparavant ;
  • "row-changed" (ligne-modifiée), car la valeur de la première colonne est fixée à 'qwe' ;
  • "row-changed" (ligne-modifiée), car la valeur de la deuxième colonne est fixée à 'asd' ;
  • "row-changed" (ligne-modifiée), car la valeur de la troisième colonne est fixée à 123.

Notez qu'il n'est pas possible de récupérer l'ordre des lignes dans la fonction de rappel du signal "rows-reordered", car le nouvel ordre des lignes est transmis sous la forme d'un pointeur opaque vers un tableau d'entiers.

On trouvera plus d'information sur les signaux des TreeModel dans le PyGTK Reference Manual.

XIV-B-9. Ordonner les lignes d'un TreeModel

XIV-B-9-a. L'interface TreeSortable

Les objets ListStore et TreeStore implémentent l'interface TreeSortable qui fournit des méthodes pour contrôler le classement des lignes de TreeModel. L'élément clé de l'interface est l'identificateur de classement de colonne, une valeur entière arbitraire faisant référence à une fonction de comparaison de classement et à des données utilisateur associées. Un identificateur de classement de colonne doit être supérieur ou égal à zéro. On en crée un avec la méthode suivante :

 
Sélectionnez
  treesortable.set_sort_func(id_class_colonne, fonction_classement, donnees_utilisateur=None)

id_class_colonne est une valeur entière assignée par le programmeur, fonction_classement une fonction ou une méthode utilisée pour comparer des lignes et donnees_utilisateur des données contextuelles. fonction_classement a la signature suivante :

 
Sélectionnez
  def fonction_classement(modele, iter1, iter2, donnees)
  def methode_classement(self, modele, iter1, iter2, donnees)

modele est le TreeModel contenant les lignes sur lesquelles pointent les TreeIter iter1 et iter2, et où donnees est donnees_utilisateur. fonction_classement doit renvoyer -1 si la ligne iter1 précède la ligne iter2, 0 si les lignes sont égales, et 1 si la ligne iter2 précède la ligne iter1. La fonction de comparaison de classement devrait toujours considérer que l'ordre de classement est gtk.SORT_ASCENDING puisqu'il sera pris en compte par les implémentations TreeSortable.

La même fonction de comparaison de classement peut être utilisée pour plusieurs identificateurs de classement de colonne en variant les données utilisateur afin de fournir des informations contextuelles. Par exemple, les donnees_utilisateur spécifiées dans la méthode set_sort_func() pourraient être l'index de la colonne d'où extraire les données de classement.

Une fois qu'un identificateur de classement de colonne est créé, un modèle peut s'en servir pour le classement en appelant la méthode :

 
Sélectionnez
  treesortable.set_sort_column_id(sort_column_id, order)

order est l'ordre de classement ascendant gtk.SORT_ASCENDING ou descendant gtk.SORT_DESCENDING.

Un identificateur de classement de colonne valant -1 signifie que le modèle devrait utiliser la fonction de classement par défaut qui se définit avec la méthode suivante :

 
Sélectionnez
  treesortable.set_default_sort_func(fonction_classement, donnees_utilisateur=None)

On peut vérifier si un modèle a une fonction de classement par défaut avec la méthode :

 
Sélectionnez
  resultat = treesortable.has_default_sort_func()

qui renvoie TRUE si une fonction de classement par défaut a été définie.

Une fois qu'un identificateur de classement de colonne a été appliqué sur un TreeModel implémentant l'interface TreeSortable, il ne peut plus revenir à son état original non classé. On peut changer la fonction de classement ou utiliser une fonction de classement par défaut, mais on ne peut pas retirer au TreeModel la fonction de classement.

XIV-B-9-b. Classement dans les ListStore et les TreeStore

Lorsqu'un objet ListStore ou TreeStore est créé, il définit automatiquement des identificateurs de classement de colonne correspondant aux colonnes du modèle en utilisant le numéro de l'index de la colonne. Par exemple, un ListStore avec trois colonnes aurait trois identificateurs de classement de colonnes (0, 1, 2) créés automatiquement. Ces identificateurs de classement de colonne sont associés avec une fonction interne de comparaison de classement qui gère les types fondamentaux :

  • 'gboolean' ;
  • str ;
  • int ;
  • long ;
  • float.

Initialement, un ListStore ou un TreeStore sont définis avec un identificateur de classement de colonne de -2 qui indique qu'aucune fonction de classement n'est utilisée et que le modèle est non classé. Une fois défini un identificateur de classement de colonne sur un ListStore ou sur un TreeStore, il est impossible de le ramener à -2.

Si l'on souhaite maintenir les identificateurs de classement de colonne par défaut, on peut définir un identificateur de classement de colonne bien en dehors de l'intervalle du nombre de colonnes, comme 1000 ou plus. Puis l'on peut alterner entre la fonction de classement par défaut et les fonctions de classement de l'application selon les besoins.

XIV-C. Les TreeView

Le TreeView n'est rien de plus qu'un conteneur pour les objets TreeViewColumn et CellRenderer, les deux vrais responsables de l'affichage des données du modèle. Il fournit également une interface pour les lignes de données affichées et pour les caractéristiques contrôlant l'affichage des données.

XIV-C-1. Créer un TreeView

On crée un TreeView en faisant appel à son constructeur :

 
Sélectionnez
  treeview = gtk.TreeView(model=None)

model est un objet implémentant l'interface TreeModel (en général un ListStore ou un TreeStore). Si model vaut None ou n'est pas spécifié, le TreeView ne sera associé à aucun modèle.

XIV-C-2. Récupérer et spécifier le modèle du TreeView

Le modèle fournissant les données au TreeView peut être récupéré avec la méthode get_model() :

 
Sélectionnez
  modele = treeview.get_model()

Un TreeModel peut être associé simultanément avec plusieurs TreeView qui actualiseront automatiquement leur affichage lorsque les données du TreeModel seront modifiées. Si un TreeView affiche obligatoirement toutes les lignes de son modèle, il peut en revanche n'afficher que certaines des colonnes de ce dernier. Deux TreeView associés au même TreeModel peuvent donc donner deux affichages différents des mêmes données.

Il est également important de savoir qu'il n'y a pas de relation prédéfinie entre les colonnes d'un TreeView et celles de son TreeModel. Ainsi, la cinquième colonne de données d'un TreeModel peut être affichée dans la première colonne d'un TreeView et dans la troisième d'un autre.

Un TreeView peut changer de modèle grâce à la méthode set_model() :

 
Sélectionnez
  treeview.set_model(model=None)

model est un objet implémentant l'interface TreeModel (par exemple, un ListStore ou un TreeStore). Si model vaut None, le modèle courant est abandonné.

XIV-C-3. Les propriétés du TreeView

Le TreeView dispose de plusieurs propriétés ainsi que de méthodes pour traiter ces dernières :

"enable-search"

Lecture-écriture

Si elle vaut TRUE, l'utilisateur peut effectuer une recherche interactive dans les colonnes. TRUE par défaut.

"expander-column"

Lecture-écriture

La colonne où doit apparaitre la flèche de développement pour les lignes mères. Colonne 0 par défaut.

"fixed-height-mode"

Lecture-écriture

Si elle vaut TRUE, toutes les lignes seront de même hauteur (ce qui accélère l'affichage). Disponible à partir de GTK+ 2.4. FALSE par défaut.

"hadjustment"

Lecture-écriture

L'Adjustment horizontal pour le widget. Un nouveau est créé par défaut.

"headers-clickable"

Écriture

Si elle vaut TRUE, les en-têtes des colonnes répondront aux clics. FALSE par défaut.

"headers-visible"

Lecture-écriture

Si elle vaut TRUE, les boutons d'en-tête des colonnes seront visibles. TRUE par défaut.

"model"

Lecture-écriture

Le modèle du TreeView. None par défaut.

"reorderable"

Lecture-écriture

TRUE indique la possibilité de réordonner le TreeView. FALSE par défaut.

"rules-hint"

Lecture-écriture

TRUE ordonne au moteur de thèmes d'alterner les couleurs des lignes. FALSE par défaut.

"search-column"

Lecture-écriture

La colonne du modèle dans laquelle le code de recherche interactive doit effectuer la recherche. -1 par défaut.

"vadjustment"

Read-Write

L'Adjustment vertical pour le widget. Un nouveau est créé par défaut.

Les méthodes correspondantes sont les suivantes :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
  autorise_rechercher = treeview.get_enable_search()
  treeview.set_enable_search(enable_search)

  colonne = treeview.get_expander_column()
  treeview.set_expander_column(column)

  ajustement_horizontal = treeview.get_hadjustment()
  treeview.set_hadjustment(adjustment)

  treeview.set_headers_clickable(active)

  entetes_visibles = treeview.get_headers_visible()
  treeview.set_headers_visible(headers_visible)

  reordonner = treeview.get_reorderable()
  treeview.set_reorderable(reorderable)

  alterner_couleurs = treeview.get_rules_hint()
  treeview.set_rules_hint(setting)

  colonne = treeview.get_search_column()
  treeview.set_search_column(column)

  ajustement_vertical = treeview.get_vadjustment()
  treeview.set_vadjustment(adjustment)

Si la plupart se passent de description, signalons cependant que pour activer la propriété "enable-search" il est nécessaire d'indiquer un numéro de colonne valide à "search-column". Quand l'utilisateur pressera la combinaison de touches Control+f, une boite de dialogue de recherche apparaitra alors, dans laquelle il pourra saisir le texte à rechercher. La première ligne correspondante sera automatiquement sélectionnée pendant la saisie du texte.

La propriété "headers-clickable" ne fait en réalité qu'activer la propriété "clickable" des TreeViewColumn sous-jacents. Un TreeViewColumn ne pourra pas être réordonné si le modèle n'implémente pas l'interface TreeSortable et si la méthode de TreeViewColumn set_sort_column_id() n'a pas été appelée avec un numéro de colonne valide.

La propriété "reorderable" permet à l'utilisateur de réordonner le modèle du TreeView en glissant-déposant les lignes affichées du TreeView.

La propriété "rules-hint" ne devrait être activée que dans les cas où l'on aurait beaucoup de colonnes et où l'on penserait qu'alterner les couleurs pourrait aider l'utilisateur.

XIV-D. Les CellRenderer

XIV-D-1. Vue d'ensemble

Les TreeViewColumn et les CellRenderer travaillent ensemble pour afficher une colonne de données dans un TreeView. Le TreeViewColumn fournit un en-tête de colonne ainsi qu'un espace vertical que les CellRenderer utiliseront pour afficher une partie des données stockées dans le modèle du TreeView. Un CellRenderer prend en charge l'affichage des données de chaque ligne et colonne dans l'espace offert par le TreeViewColumn. Un TreeViewColumn peut contenir plusieurs CellRenderer pour afficher les lignes comme des HBox. On combine par exemple assez fréquemment un CellRendererPixbuf et un CellRendererText dans la même colonne.

La Figure 14.2, « TreeViewColumn avec CellRenderer » montre un exemple de disposition pour deux TreeViewColumn : l'un contenant deux CellRenderer et l'autre un seul.

Image non disponible
Figure 14.2. TreeViewColumn avec CellRenderer

L'application de chaque CellRenderer est indiquée avec une couleur d'arrière-plan : jaune pour le CellRendererPixbuf, cyan pour le CellRendererText et rose pour l'autre CellRendererText. Notez que le CellRendererPixbuf et le premier CellRendererText sont dans la même colonne, dont l'en-tête est "Pixbuf and Text". La couleur d'arrière-plan du CellRendererText qui affiche "Imprime un fichier" est la couleur par défaut afin de montrer la zone d'application sur une seule ligne.

La Figure 14.2, « TreeViewColumn avec CellRenderer » a été générée par le programme treeviewcolumn.py.

XIV-D-2. Les différents types de CellRenderer

Le type de CellRenderer dont on a besoin dépend du type d'affichage attendu pour les données du modèle ; PyGTK dispose de trois CellRenderer prédéfinis :

CellRendererPixbuf

affiche des images pixbuf créées par le programme ou disponibles dans le stock de PyGTK.

CellRendererText

affiche des chaines de caractères, ainsi que des nombres pouvant être convertis en chaines de caractères (incluant les entiers, les réels, les booléens).

CellRendererToggle

affiche une valeur booléenne sous la forme d'un bouton à bascule ou d'un bouton radio.

XIV-D-3. Les propriétés des CellRenderer

Les propriétés d'un CellRenderer déterminent la manière dont les données doivent être affichées :

"mode"

Lecture-écriture

La possibilité ou non d'éditer le CellRenderer. Peut être : gtk.CELL_RENDERER_MODE_INERT, gtk.CELL_RENDERER_MODE_ACTIVATABLE ou gtk.CELL_RENDERER_MODE_EDITABLE.

"visible"

Lecture-écriture

Si elle vaut TRUE, la cellule est affichée.

"xalign"

Lecture-écriture

La fraction (entre 0.0 et 1.0) d'espace vide devant se situer dans la partie gauche de la cellule.

"yalign"

Lecture-écriture

La fraction (entre 0.0 et 1.0) d'espace vide devant se situer dans la partie supérieure de la cellule.

"xpad"

Lecture-écriture

La quantité d'espacement à gauche et à droite de la cellule.

"ypad"

Read-Write

La quantité d'espacement au-dessus et au-dessous de la cellule.

"width"

Lecture-écriture

La largeur fixe de la cellule.

"height"

Lecture-écriture

La hauteur fixe de la cellule.

"is-expander"

Lecture-écriture

Si elle vaut TRUE, la ligne a des lignes filles.

"is-expanded"

Lecture-écriture

Si elle vaut TRUE, la ligne a des lignes filles et est développée afin de montrer celles-ci.

"cell-background"

Écriture

La couleur d'arrière-plan de la cellule sous la forme d'une chaine de caractères.

"cell-background-gdk"

Lecture-écriture

La couleur d'arrière-plan de la cellule sous la forme d'une gtk.gdk.Color.

"cell-background-set"

Lecture-écriture

Si elle vaut TRUE, la couleur d'arrière-plan de la cellule est définie par ce CellRenderer.

Les propriétés ci-dessus sont disponibles pour toutes les sous-classes de CellRenderer. Chaque type de CellRenderer dispose en plus de ses propres propriétés.

Le CellRendererPixbuf a les propriétés suivantes :

"pixbuf"

Lecture-écriture

La pixbuf à afficher - invalidée par "stock-id".

"pixbuf-expander-open"

Lecture-écriture

Pixbuf pour le symbole de développement ouvert.

"pixbuf-expander-closed"

Lecture-écriture

Pixbuf pour le symbole de développement fermé.

"stock-id"

Lecture-écriture

L'icône du stock de PyGTK à afficher.

"stock-size"

Lecture-écriture

La taille de l'icône à afficher.

"stock-detail"

Lecture-écriture

Détail d'affichage à transmettre au moteur de thèmes.

Le CellRendererText possède un grand nombre de propriétés donnant pour la plupart des indications stylistiques :

"text"

Lecture-écriture

Texte à afficher.

"markup"

Lecture-écriture

Texte balisé à afficher.

"attributes"

Lecture-écriture

Une liste d'attributs de style à appliquer au texte du CellRenderer.

"background"

Écriture

Couleur d'arrière-plan, sous la forme d'une chaine de caractères.

"foreground"

Écriture

Couleur d'avant-plan, sous la forme d'une chaine de caractères.

"background-gdk"

Lecture-écriture

Couleur d'arrière-plan, sous la forme d'une gtk.gdk.Color.

"foreground-gdk"

Lecture-écriture

Couleur d'avant-plan, sous la forme d'une gtk.gdk.Color.

"font"

Lecture-écriture

Description de police, sous la forme d'une chaine de caractères.

"font-desc"

Lecture-écriture

Description de police, sous la forme d'une pango.FontDescription.

"family"

Lecture-écriture

Nom de la famille de police (Sans, Helvetica, Times, Monospace, etc.).

"style"

Lecture-écriture

Style de police.

"variant"

Lecture-écriture

Variante de police.

"weight"

Lecture-écriture

Graisse de la police.

"stretch"

Lecture-écriture

Chasse de la police.

"size"

Lecture-écriture

Taille de la police.

"size-points"

Lecture-écriture

Taille de la police en points.

"scale"

Lecture-écriture

Facteur de taille de la police.

"editable"

Lecture-écriture

Si elle vaut TRUE, le texte peut être édité par l'utilisateur.

"strikethrough"

Lecture-écriture

Si elle vaut TRUE, le texte est barré.

"underline"

Lecture-écriture

Style de soulignement du texte.

"rise"

Lecture-écriture

Position du texte par rapport à la ligne de base (au-dessus si positif, au-dessous si négatif).

"language"

Lecture-écriture

La langue dans laquelle est rédigé le texte, sous la forme d'un code ISO. Peut servir à Pango lors de l'affichage du texte. Si vous ne comprenez pas ce paramètre, il est fort probable que vous n'en ayez pas besoin. GTK+ 2.4 et supérieur.

"single-paragraph-mode"

Lecture-écriture

Si elle vaut TRUE, tout le texte sera maintenu dans un seul paragraphe. GTK+ 2.4 et supérieur.

"background-set"

Lecture-écriture

Si elle vaut TRUE, applique la couleur d'arrière-plan.

"foreground-set"

Lecture-écriture

Si elle vaut TRUE, applique la couleur d'avant-plan.

"family-set"

Lecture-écriture

Si elle vaut TRUE, applique la famille de police.

"style-set"

Lecture-écriture

Si elle vaut TRUE, applique le style de police.

"variant-set"

Lecture-écriture

Si elle vaut TRUE, applique la variante de police.

"weight-set"

Lecture-écriture

Si elle vaut TRUE, applique la graisse de police.

"stretch-set"

Lecture-écriture

Si elle vaut TRUE, applique la chasse de police.

"size-set"

Lecture-écriture

Si elle vaut TRUE, applique la taille de police.

"scale-set"

Lecture-écriture

Si elle vaut TRUE, applique le facteur de taille de police.

"editable-set"

Lecture-écriture

Si elle vaut TRUE, applique la possibilité ou non d'éditer le texte.

"strikethrough-set"

Lecture-écriture

Si elle vaut TRUE, applique le trait barrant le texte.

"underline-set"

Lecture-écriture

Si elle vaut TRUE, applique le soulignement du texte.

"rise-set"

Lecture-écriture

Si elle vaut TRUE, applique le positionnement du texte par rapport à la ligne de base.

"language-set"

Lecture-écriture

Si elle vaut TRUE, applique la langue utilisée pour afficher le texte. GTK+ 2.4 et supérieur.

La quasi-totalité des propriétés du CellRendererText possède un équivalent booléen (avec le suffixe "-set") qui indique si la propriété doit être appliquée. Cela permet de définir une propriété globalement et d'activer ou non son application de manière sélective.

Le CellRendererToggle possède les propriétés suivantes :

"activatable"

Lecture-écriture

Si elle vaut TRUE, le bouton à bascule peut être activé.

"active"

Lecture-écriture

Si elle vaut TRUE, le bouton est actif.

"radio"

Lecture-écriture

Si elle vaut TRUE, le bouton à bascule sera dessiné comme un bouton radio.

"inconsistent"

Lecture-écriture

Si elle vaut TRUE, le bouton sera dans un état inconsistant. GTK+ 2.2 et supérieur.

Les propriétés peuvent être définies pour l'ensemble des lignes en utilisant la méthode gobject.set_property(), comme dans le programme treeviewcolumn.py, par exemple.

XIV-D-4. Les attributs du CellRenderer

Les attributs associent une colonne de TreeModel à une propriété de CellRenderer ; le CellRenderer définit la propriété à partir de la valeur de colonne de la ligne avant d'afficher la cellule. Cela permet de personnaliser l'affichage des cellules en utilisant les données du modèle. On peut ajouter un attribut à l'ensemble courant de la manière suivante :

 
Sélectionnez
  treeviewcolumn.add_attribute(cell_renderer, attribute, column)

où la propriété spécifiée par attribute est définie pour le cell_renderer à partir de la colonne column. Par exemple :

 
Sélectionnez
  treeviewcolumn.add_attribute(cell, "cell-background", 1)

fixe la couleur d'arrière-plan du CellRenderer à la couleur spécifiée par la chaine de caractères située dans la deuxième colonne du modèle.

Afin de supprimer tous les attributs et d'en fixer de nouveaux immédiatement, on utilisera :

 
Sélectionnez
  treeviewcolumn.set_attributes(cell_renderer, ...)

où les attributs de cell_renderer sont définis par des couples clé-valeur : propriété=colonne. Par exemple, pour un CellRendererText, la méthode :

 
Sélectionnez
  treeviewcolumn.set_attributes(cell, text=0, cell_background=1, xpad=3)

fixe, pour chaque ligne, le texte à partir de la première colonne, la couleur d'arrière-plan à partir de la deuxième et l'espacement à gauche et à droite de la cellule à partir de la quatrième. Le programme treeviewcolumn.py donne des exemples d'utilisation de ces méthodes.

Les attributs d'un CellRenderer peuvent être supprimés comme suit :

 
Sélectionnez
  treeviewcolumn.clear_attributes(cell_renderer)

XIV-D-5. Fonction d'affichage des données cellulaires

Si définir des attributs ne suffit pas, il est possible de définir une fonction à appeler à chaque ligne pour fixer les propriétés du CellRenderer :

 
Sélectionnez
  treeviewcolumn.set_cell_data_func(cell_renderer, fonction_rendu, donnees=None)

fonction_rendu est de la forme :

 
Sélectionnez
  def fonction_rendu(colonne, cell_renderer, tree_model, iter, donnees_utilisateur)

colonne est le TreeViewColumn contenant le cell_renderer, tree_model est le modèle et iter est un TreeIter pointant vers une ligne du tree_model. donnees_utilisateur contient la valeur des donnees transmises par la méthode set_cell_data_func().

Dans fonction_rendu on peut définir toutes les propriétés voulues du cell_renderer. La portion de code suivante définit la propriété "text" pour afficher les objets PyGTK sous la forme d'une chaine les identifiant.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
  ...
  def chaine_objet(treeviewcolumn, cellrenderer, modele, iter):
     objetpygtk = modele.get_value(iter, 0)
     cell.set_property('text', str(objetpygtk))
     return
  ...
  treestore = gtk.TreeStore(object)
  fenêtre = gtk.Window()
  treeview = gtk.TreeView(treestore)
  fenetre.add(treeview)
  cell = gtk.CellRendererText()
  tvcolumn = gtk.TreeViewColumn('Objet', cell)
  treeview.append_column(tvcolumn)
  iter = treestore.append(None, [fenêtre])
  iter = treestore.append(iter, [treeview])
  iter = treestore.append(iter, [tvcolumn])
  iter = treestore.append(iter, [cell])
  iter = treestore.append(None, [treestore])
  ...

La Figure 14.3, « Fonction d'affichage des données cellulaires » montre le résultat :

Image non disponible
Figure 14.3. Fonction d'affichage des données cellulaires

Cette fonction permet aussi de contrôler l'affichage des données numériques en format texte (par exemple, d'un nombre réel). Un CellRendererText affiche et convertit automatiquement un réel en chaine avec le format par défaut "%f".

Les fonctions d'affichage de données cellulaires permettent même de créer les données des colonnes à partir de données externes. Le programme listefichiers.py utilise un ListStore avec une seule colonne contenant une liste de fichiers. Le TreeView affiche des colonnes qui comprennent un pixbuf, le nom du fichier, sa taille, son mode et la date de dernière modification. Les données sont créées par la fonction suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
    def fichier_pixbuf(self, colonne, cell, modele, iter):
        nomfichier = os.path.join(self.nomrep, modele.get_value(iter, 0))
        statsfichier = os.stat(nomfichier)
        if stat.S_ISDIR(statsfichier.st_mode):
            pb = dossierpb
        else:
            pb = fichierpb
        cell.set_property('pixbuf', pb)
        return

    def nom_fichier(self, colonne, cell, modele, iter):
        cell.set_property('text', modele.get_value(iter, 0))
        return

    def taille_fichier(self, colonne, cell, modele, iter):
        nomfichier = os.path.join(self.nomrep, modele.get_value(iter, 0))
        statsfichier = os.stat(nomfichier)
        cell.set_property('text', statsfichier.st_size)
        return

    def mode_fichier(self, colonne, cell, modele, iter):
        nomfichier = os.path.join(self.nomrep, modele.get_value(iter, 0))
        statsfichier = os.stat(nomfichier)
        cell.set_property('text', oct(stat.S_IMODE(statsfichier.st_mode)))
        return


    def modif_fichier(self, colonne, cell, modele, iter):
        nomfichier = os.path.join(self.nomrep, modele.get_value(iter, 0))
        statsfichier = os.stat(nomfichier)
        cell.set_property('text', time.ctime(statsfichier.st_mtime))
        return

Ces fonctions d'affichage de données cellulaires récupèrent l'information sur chaque fichier par son nom, extraient les données nécessaires et fixent la propriété 'text' ou 'pixbuf' des cellules selon les données. La Figure 14.4, « Exemple de liste de fichiers utilisant les fonctions d'affichage de données cellulaires » illustre le résultat du programme.

Image non disponible
Figure 14.4. Exemple de liste de fichiers utilisant les fonctions d'affichage de données cellulaires

XIV-D-6. CellRendererText Markup

Un CellRendererText peut présenter un texte enrichi avec des polices et des styles différents plutôt qu'une simple chaine de caractères bruts grâce à l'utilisation du balisage Pango (en définissant la propriété "markup"). On trouvera davantage de renseignements sur le balisage Pango dans la section Pango Markup du PyGTK Reference Manual.

L'extrait de code ci-dessous montre l'utilisation de la propriété "markup" :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
  ...
  liststore = gtk.ListStore(str)
  cell = gtk.CellRendererText()
  tvcolumn = gtk.TreeViewColumn('Pango Markup', cell, markup=0)
  ...
  liststore.append(['<span foreground="blue"><b>Pango</b></span> markup peut'
' modifier\n<i>le style</i> <big>la taille</big>, <u>souligner,'
 <s>barrer</s></u>,\n'
'et <span font_family="URW Chancery L"><big>changer la police '
'ex : URW Chancery L</big></span>\n<span foreground="red">avant-plan'
' rouge et <span background="cyan">arrière-plan cyan</span></span>'])
  ...

et donne un résultat semblable à la Figure 14.5, « CellRendererText et balisage »:

Image non disponible
Figure 14.5. CellRendererText et balisage

Lorsque l'on crée un balisage Pango à la volée, il faut faire attention à remplacer les caractères spécifiques au langage balisé : "<", ">", "&". La fonction cgi.escape() de la bibliothèque Python peut réaliser ces conversions élémentaires.

XIV-D-7. Cellules de texte éditables

Les cellules CellRendererText peuvent devenir éditables pour permettre à un utilisateur de changer le contenu de la cellule sélectionnée en la cliquant ou en pressant une des touches Retour, Entrée, Espace ou Maj+Espace. On rend un CellRendererText éditable pour toutes les lignes en fixant sa propriété "editable" à TRUE de cette façon :

 
Sélectionnez
  cellrenderertext.set_property('editable', True)

Pour rendre éditable une seule cellule, il faut ajouter un attribut au TreeViewColumn et utiliser le CellRendererText ainsi :

 
Sélectionnez
  treeviewcolumn.add_attribute(cellrenderertext, "editable", 2)

qui fixe la propriété "editable" pour la valeur contenue dans la troisième colonne du treemodel.

Les modifications de la cellule terminées, l'application doit gérer le signal "edited" pour récupérer le nouveau texte et modifier la valeur du treemodel associé. Sinon, la cellule reprend sa valeur précédente. Le gestionnaire du signal "edited" a la signature suivante :

 
Sélectionnez
  def rappel_edited(celltext, chemin, nouveau_texte, donnees_utilisateur)

celltext est le CellRendererText, chemin est le chemin d'accès (une chaine) à la ligne contenant la cellule éditée, nouveau_texte est le texte modifié et donnees_utilisateur les données contextuelles. Le TreeModel étant indispensable pour définir le nouveau_texte dans le stock de données, il faudra peut-être transmettre le TreeModel comme donnees_utilisateur dans la méthode connect() :

 
Sélectionnez
  cellrenderertext.connect('edited', rappel_edited, modele)

S'il y a deux ou plusieurs cellules éditables dans une ligne, on peut passer le numéro de la colonne dans donnees_utilisateur, en même temps que le TreeModel :

 
Sélectionnez
  cellrenderertext.connect('edited', rappel_edited, (modele, num_col))

Ensuite, on peut définir le nouveau texte dans le gestionnaire "edited" comme le montre cet exemple avec un ListStore :

 
Sélectionnez
1.
2.
3.
4.
  def rappel_edited(celltext, chemin, nouveau_texte, donnees_utilisateur):
      liststore, colonne = donnees_utilisateur
      liststore[chemin][colonne] = nouveau_texte
      return

XIV-D-8. Cellules à bascule activables

Les boutons CellRendererToggle peuvent être rendus activables en fixant leur propriété "activatable" à TRUE. De la même manière que pour les cellules éditables CellRendererText, la propriété "activatable" peut être fixée pour l'ensemble des cellules CellRendererToggle en utilisant la méthode set_property() ou pour des cellules individuelles en ajoutant un attribut au TreeViewColumn contenant le CellRendererToggle.

 
Sélectionnez
1.
2.
3.
  cellrenderertoggle.set_property('activatable', True)

  treeviewcolumn.add_attribute(cellrenderertoggle, "activatable", 1)

La déclaration individuelle des boutons à bascule peut être obtenue à partir des valeurs d'une colonne du TreeModel en ajoutant un attribut, comme ceci :

 
Sélectionnez
  treeviewcolumn.add_attribute(cellrenderertoggle, "active", 2)

Une connexion au signal "toggled" est nécessaire si l'on veut savoir quand l'utilisateur a cliqué sur le bouton et pouvoir modifier la valeur dans le treemodel. Par exemple :

 
Sélectionnez
  cellrenderertoggle.connect("toggled", rappel_toggled, (modele, colonne))

La fonction de rappel aura la signature suivante :

 
Sélectionnez
  def rappel_toggled(cellrenderertoggle, chemin, donnees_utilisateur)

chemin est le chemin d'accès (une chaine), pointant sur la ligne contenant le bouton à bascule qui a été cliqué. On doit passer le TreeModel et éventuellement l'index de colonne dans le paramètre donnees_utilisateur pour fournir les éléments nécessaires à l'établissement des valeurs du treemodel. Par exemple, l'application peut modifier la valeur du treemodel de la façon suivante :

 
Sélectionnez
1.
2.
3.
4.
  def rappel_toggled(cellule, chemin, donnees_utilisateur):
      modele, colonne = donnees_utilisateur
      modele[chemin][colonne] = not modele[chemin][colonne]
      return

Si l'on veut afficher les boutons à bascule comme des boutons radio dont un seul serait sélectionné, l'application doit parcourir le treemodel pour désactiver le bouton radio actif et ensuite déclarer le bouton à activer. Par exemple, la fonction de rappel suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
  def rappel_toggled(cellule, chemin, donnees_utilisateur):
      modele, colonne = donnees_utilisateur
      for ligne in modele:
          ligne[colonne] = False
      modele[chemin][colonne] = True
      return

utilise la solution de facilité qui consiste à mettre toutes les valeurs du treemodel à FALSE avant de remettre à TRUE la valeur pour la ligne indiquée par le chemin.

XIV-D-9. Programme d'exemple avec cellules éditables et activables

Le programme cellrenderer.py illustre l'utilisation de CellRendererText éditables et de CellRendererToggle activables dans un TreeStore.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
#!/usr/bin/env python
# vim: ts=4:sw=4:tw=78:nowrap
""" Exemple avec Cellrenderer éditables et activables """
import pygtk
pygtk.require("2.0")
import gtk, gobject

taches =  {
    "Faire les courses": "Acheter une baguette",
    "Programmer": "Mettre a jour le programme",
    "Cuisiner": "Allumer le four",
    "Regarder la TV": "Enregistrer \"Urgences\""
    } 

class GUI_Controleur:
    """ La classe GUI est le contrôleur de l'application """
    def __init__(self):
        # Création de la fenêtre principale
        self.fenetre = gtk.Window(type=gtk.WINDOW_TOPLEVEL)
        self.fenetre.set_title("Exemple de CellRenderer")
        self.fenetre.connect("destroy", self.evnmt_destroy)
        # On récupère le modèle et on le relie à la vue
        self.modele = Ranger.recup_modele()
        self.vue = Afficher.cree_vue( self.modele )
        # Ajouter la vue à la fenêtre principale
        self.fenetre.add(self.vue)
        self.fenetre.show_all()
        return
    def evnmt_destroy(self, *kw):
        """ Fonction de rappel pour fermer l'application """
        gtk.main_quit()
        return
    def lance(self):
        """ La fonction est appelée pour lancer la boucle principale GTK """
        gtk.main()
        return  

class InfoModele:
    """ La classe du modèle contient l'information que nous voulons afficher """
    def __init__(self):
        """ Création et remplissage du gtk.TreeStore """
        self.tree_store = gtk.TreeStore( gobject.TYPE_STRING,
                                         gobject.TYPE_BOOLEAN )
        # on place les données utilisateur globales dans une liste
        # on crée une arborescence simple.
        for item in taches.keys():
            lignemere = self.tree_store.append( None, (item, None) )
            self.tree_store.append( lignemere, (taches[item],None) )
        return
    def recup_modele(self):
        """ Renvoie le modèle """
        if self.tree_store:
            return self.tree_store 
        else:
            return None

class AfficheModele:
    """ Affiche le modèle InfoModele dans un treeview """
    def cree_vue( self, modele ):
        """ Crée une vue pour le Tree Model """
        self.vue = gtk.TreeView( modele )
        # Crée le CellRendererText et permet aux cellules
        # d'être éditées.
        self.renderer = gtk.CellRendererText()
        self.renderer.set_property( 'editable', True )
        self.renderer.connect( 'edited', self.rappel_edited_col0, modele )

        # Crée le CellRendererToggle et permet sa 
        # modification (toggled) par l'utilisateur.
        self.renderer1 = gtk.CellRendererToggle()
        self.renderer1.set_property('activatable', True)
        self.renderer1.connect( 'toggled', self.rappel_toggled_col1, modele )

        # On connecte la colonne 0 de l'affichage à la colonne 0 du modèle
        # Le cellrenderer va alors afficher tout ce qui est en colonne 0 de
        # notre modèle .
        self.colonne0 = gtk.TreeViewColumn("Nom", self.renderer, text=0)

        # L'état actif des colonnes est relié à la seconde colonne
        # du modèle. Ainsi, quand le modèle indique True, le bouton
        # apparaitra comme actif.
        self.colonne1 = gtk.TreeViewColumn("Fait", self.renderer1 )
        self.colonne1.add_attribute( self.renderer1, "active", 1)
        self.vue.append_column( self.colonne0 )
        self.vue.append_column( self.colonne1 )
        return self.vue
    def rappel_edited_col0( self, cellrenderer, chemin, nouveau_texte, modele ):
        """
        Appelé quand un texte est modifie. Il inscrit le nouveau texte
        dans le modèle pour qu'il puisse être affiché correctement.
        """
        print "Change '%s' en '%s'" % (modele[chemin][0], nouveau_texte)
        modele[chemin][0] = nouveau_texte
        return
    def rappel_toggled_col1( self, cellrenderer, chemin, modele ):
        """
        Fixe l'état du bouton à bascule sur true ou false.
        """
        modele[chemin][1] = not modele[chemin][1]
        print "Valeur de '%s'  : %s" % (modele[chemin][0], modele[chemin][1],)
        return

if __name__ == '__main__':
    Ranger = InfoModele()    
    Afficher = AfficheModele()
    monGUI = GUI_Controleur()
    monGUI.lance()

Le programme propose des cellules éditables dans la première colonne et des cellules activables dans la deuxième. Les lignes 64-66 créent un CellRendererText éditable et connectent le signal "edited" à la fonction de rappel rappel_edited_col0() (lignes 87-94) qui modifie la valeur de colonne de la ligne adéquate dans le TreeStore. De même, les lignes 70-72 créent un CellRendererToggle activable et connectent le signal "toggled" à la fonction de rappel rappel_toggled_col0()() (lignes 95-101) qui modifie la valeur de la ligne adéquate. Lorsqu'une cellule éditable ou activable est modifiée, un message s'affiche, indiquant de quelle modification il s'agissait.

La Figure 14.6, « Cellules éditables et activables » montre le programme cellrenderer.py en action.

Image non disponible
Figure 14.6. Cellules éditables et activables

XIV-E. TreeViewColumn

XIV-E-1. Créer des TreeViewColumn

La fonction de création du TreeViewColumn est la suivante :

 
Sélectionnez
  treeviewcolumn = gtk.TreeViewColumn(titre=None, cell_renderer=None, ...)

titre est la chaine utilisée comme en-tête de colonne et cell_renderer est le premier CellRenderer placé dans la colonne. Les autres arguments pouvant être passés au constructeur sont des mot-clés (sous la forme attribute=colonne) qui donnent des attributs au cell_renderer. Par exemple :

 
Sélectionnez
  treeviewcolumn = gtk.TreeViewColumn('États', cellule, text=0, foreground=1)

crée un TreeViewColumn dont le CellRendererText récupère son texte dans la première colonne du TreeModel et la couleur du texte dans la seconde.

XIV-E-2. Gérer les CellRenderer

On peut ajouter un CellRenderer à un TreeViewColumn par l'une de ces méthodes :

 
Sélectionnez
  treeviewcolumn.pack_start(cell, expand)
  treeviewcolumn.pack_end(cell, expand)

Les méthodes pack_start() et pack_end() ajoutent une cellule respectivement au début ou à la fin du TreeViewColumn. Si le paramètre expand est réglé sur TRUE, la cellule s'étirera pour remplir tout l'espace donné par le TreeViewColumn.

La méthode get_cell_renderers() :

 
Sélectionnez
  liste_cell = treeviewcolumn.get_cell_renderers()

renvoie une liste de tous les CellRenderer de la colonne.

La méthode clear() enlève tous les attributs de CellRenderer du TreeViewColumn :

 
Sélectionnez
  treeviewcolumn.clear()

Il y a un grand nombre d'autres méthodes disponibles pour le TreeViewColumn - principalement pour fixer ou retrouver des propriétés. On trouvera davantage de renseignements sur les propriétés du TreeViewColumn dans le PyGTK Reference Manual. La possibilité de faire appel ou non à la fonction de tri intégré s'installe ainsi ;

 
Sélectionnez
  treeviewcolumn.set_sort_column_id(sort_column_id)

définit sort_column_id comme étant l'identifiant de classement de colonne du treemodel à utiliser lors du tri de l'affichage du TreeView. Cette méthode active aussi la propriété "clickable" de la colonne et permet ainsi à l'utilisateur de cliquer sur l'en-tête de colonne pour réaliser le classement. Quand l'utilisateur clique sur cet en-tête, l'identifiant de classement de colonne du TreeViewColumn est défini comme étant l'identifiant de classement de colonne du TreeModel et les lignes sont réordonnées selon la fonction associée de comparaison de classement. La fonction de classement automatique inverse également l'ordre de classement de la colonne et contrôle l'affichage de l'indicateur de classement. On trouvera plus de renseignements au sujet des identifiants de classement de colonne et des fonctions de comparaison de tri dans Section 14.2.9, « Ordonner les lignes d'un TreeModel »Ordonner les lignes d'un TreeModel. En règle générale, lorsqu'on utilise un ListStore ou un TreeStore, l'identifiant de classement de colonne par défaut (c'est-à-dire l'index de la colonne) de la colonne du TreeModel associée au TreeViewColumn est défini comme étant l'identifiant de classement de colonne du TreeViewColumn.

Si on se sert des en-têtes de colonne du TreeViewColumn pour le classement avec la méthode set_sort_column_id(), il n'est pas nécessaire d'utiliser la méthode de TreeSortable set_sort_column_id().

Il est possible de tracer les opérations de tri ou d'utiliser le clic sur l'en-tête à d'autres fins en le connectant au signal "clicked" de la colonne du TreeView. La fonction de rappel doit être de la forme :

 
Sélectionnez
  def callback(treeviewcolumn, donnees_utilisateur, ...)

XIV-F. Manipuler les TreeView

XIV-F-1. Gérer les colonnes

On peut récupérer un TreeViewColumn dans un TreeView soit isolément, soit comme une liste :

 
Sélectionnez
  treeviewcolumn = treeview.get_column(n)
  columnlist = treeview.get_columns()

où n est l'index (à partir de zéro) de la colonne à récupérer. Une colonne peut être effacée par la méthode :

 
Sélectionnez
  treeview.remove_column(column)

column est un TreeViewColumn du treeview.

Les lignes qui possèdent des lignes enfants sont affichées avec une flèche de développement (voir Figure 14.3, « Fonction d'affichage des données cellulaires ») sur laquelle l'utilisateur peut cliquer pour masquer ou afficher les lignes enfants. On peut choisir la colonne qui possède la flèche de développement par cette méthode :

 
Sélectionnez
  treeview.set_expander_column(column)

column est un TreeViewColumn du treeview. Cette méthode est utile lorsqu'on ne souhaite pas que la première colonne soit indentée. Par exemple, Figure 14.7, « Flèche de développement dans la seconde colonne » montre la flèche de développement dans la seconde colonne :

Image non disponible
Figure 14.7. Flèche de développement dans la seconde colonne

XIV-F-2. Afficher ou masquer les enfants d'une ligne

Toutes les lignes enfants d'un TreeView peuvent être affichées ou masquées grâce aux méthodes suivantes :

 
Sélectionnez
  treeview.expand_all()
  treeview.collapse_all()

Ces méthodes sont pratiques pour initialiser l'affichage du TreeView dans un état donné. Chaque ligne peut avoir ses lignes enfants affichées ou masquées de manière indépendante par :

 
Sélectionnez
  treeview.expand_row(path, open_all)
  treeview.collapse_row(path)

path est le chemin arborescent vers une ligne du treeview ; si open_all vaut TRUE, toute la descendance de la ligne est affichée, sinon, seuls ses enfants directs sont affichés.

On peut savoir si une ligne a ses enfants affichés par cette méthode :

 
Sélectionnez
  is_expanded = treeview.row_expanded(path)

XIV-G. Les signaux des TreeView

Les TreeView émettent un grand nombre de signaux qui permettent de repérer les modifications dans l'afficheur du modèle. Les signaux appartiennent généralement aux catégories suivantes :

  • afficher ou masquer les lignes : "row-collapsed", "row-expanded", "test-collapse-row", "test-expand-row" and "expand-collapse-cursor-row" ;
  • curseur : "cursor-changed", "expand-collapse-cursor-row", "move-cursor", "select-cursor-parent", "select-cursor-row" et "toggle-cursor-row" ;
  • sélection : "select-all", "select-cursor-parent", "select-cursor-row" et "unselect-all" ;
  • divers : "columns-changed", "row-activated", "set-scroll-adjustments" et "start-interactive-search".

Les signaux "test-collapse-row" et "test-expand-row" sont émis avant qu'une ligne masque ou affiche ses lignes enfants. La valeur de retour de la fonction de rappel peut annuler ou autoriser l'opération - TRUE pour l'autoriser, FALSE pour l'annuler.

 
Sélectionnez
  def callback(treeview, iter, chemin, donnees_utilisateur)

iter est un TreeIter, chemin est un chemin de l'arbre pointant sur la ligne et donnees_utilisateur représente les données indiquées dans la méthode connect().

Le signal "row-activated" est émis soit lors d'un double-clic sur une ligne, soit lorsqu'une ligne non modifiable est sélectionnée et une des touches suivantes enfoncée : Espace, Maj+Espace, Retour or Entrée

Les autres signaux sont émis après la modification du TreeView. Le curseur est la ligne entourée par une boite. La plupart du temps, déplacer le curseur déplace la sélection. Le curseur peut être déplacé de manière indépendante par un appui simultané sur les touches Ctrl+Flèche bas ou Ctrl+Flèche haut et diverses autres combinaisons de touches.

Pour plus d'information sur les signaux du TreeView, voir le PyGTK Reference Manual

XIV-H. Gestion des Treeselection

XIV-H-1. Obtenir un TreeSelection

Les TreeSelection sont des objets qui gèrent les sélections dans un TreeView. Quand on crée un TreeView, un TreeSelection est automatiquement créé en même temps. Le TreeSelection peut être obtenu à partir du TreeView par la méthode :

 
Sélectionnez
  treeselection = treeview.get_selection()

On peut retrouver le TreeView associé au TreeSelection par la méthode :

 
Sélectionnez
  treeview = treeselection.get_treeview()

XIV-H-2. Modes du TreeSelection

Le TreeSelection dispose des modes de sélection suivants :

gtk.SELECTION_NONE

Aucune sélection n'est permise.

gtk.SELECTION_SINGLE

Permet une sélection unique par un clic.

gtk.SELECTION_BROWSE

Permet une sélection unique par un survol du pointeur.

gtk.SELECTION_MULTIPLE

Plusieurs items peuvent être sélectionnés ensemble.

On peut récupérer le mode de sélection en cours par la méthode :

 
Sélectionnez
  mode = treeselection.get_mode()

Ce mode peut être fixé en utilisant :

 
Sélectionnez
  treeselection.set_mode(mode)

mode est l'un des modes de sélection ci-dessus.

XIV-H-3. Retrouver la sélection

La méthode à utiliser pour retrouver la sélection est fonction du mode de sélection en cours. Si le mode de sélection est gtk.SELECTION_SINGLE ou gtk.SELECTION_BROWSE, il faut utiliser la méthode suivante :

 
Sélectionnez
  (modele, iter) = treeselection.get_selected()

qui renvoie un 2-tuple contenant modele, le TreeModel utilisé par le TreeView associé au treeselection et iter, un TreeIter pointant sur la ligne sélectionnée. S'il n'y a pas de ligne sélectionnée, alors iter vaut None. Si le mode de sélection est gtk.SELECTION_MULTIPLE, une exception TypeError est déclenchée.

Pour un TreeView utilisant le mode de sélection gtk.SELECTION_MULTIPLE, il faut utiliser la méthode :

 
Sélectionnez
  (modele, listechemins) = treeselection.get_selected_rows()

qui renvoie un 2-tuple contenant le modèle et une liste des chemins des lignes sélectionnées dans l'arborescence. Cette méthode n'est pas disponible en PyGTK 2.0 ; pour retrouver cette liste, il faut passer par une fonction intermédiaire en utilisant :

 
Sélectionnez
  treeselection.selected_foreach(fonct, donnees=None)

fonct est une fonction appelée pour chaque ligne sélectionnée avec les données donnees. La fonction a la signature suivante :

 
Sélectionnez
  def fonct(modele, chemin, iter, donnees)

modele est le TreeModel, chemin est le chemin de la ligne sélectionnée dans l'arborescence et iter est un TreeIter pointant sur la ligne sélectionnée.

Cette méthode peut être utilisée pour simuler la méthode get_selected_row() de la manière suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
  ...
  def rappel_chacune(modele, chemin, iter, listechemins) :
      liste.append(chemin)
  ...
  def ma_recup_selection(treeselection) :
      listechemins = []
      treeselection.selected_foreach(rappel_chacune, listechemins)
      modele = choix.get_treeview().get_model()
      return(modele, listechemins)
  ...

La méthode selected_foreach() ne peut servir à modifier le treemodel ou la sélection, mais peut permettre de modifier les données des lignes.

XIV-H-4. Utiliser une fonction TreeSelection

Si on souhaite avoir un contrôle total sur la sélection de ligne, on peut définir une fonction à appeler avant qu'une ligne soit sélectionnée ou déselectionnée grâce à la méthode :

 
Sélectionnez
  treeselection.set_select_function(fonct, donnees)

fonct est une fonction de rappel et donnees sont les données utilisateur qui sont transmises à fonct quand celle-ci est appelée. La méthode fonct a pour signature :

 
Sélectionnez
  def fonct(selection, modele, chemin, est_choisi, donnees_utilisateur)

selection est le TreeSelection, modele est le TreeModel utilisé avec le TreeView associé à la selection, chemin est le chemin dans l'arbre de la ligne sélectionnée, est_choisi vaut TRUE si la ligne est actuellement sélectionnée et donnees_utilisateur est données. fonct renvoie TRUE si l'état de sélection de la ligne peut être modifié.

Établir une fonction de sélection est utile si :

  • on souhaite contrôler la sélection ou désélection d'une ligne en fonction d'une information complémentaire du contexte. On doit indiquer d'une façon ou d'une autre que le changement de sélection ne peut être réalisé et pourquoi. Par exemple, on peut différencier visuellement la ligne ou ouvrir un MessageDialog contextuel ;
  • on doit maintenir soi-même la liste des lignes sélectionnées ou désélectionnées quoique cela puisse aussi être réalisé, mais de manière plus laborieuse, en se connectant au signal "changed" ;
  • on veut faire un traitement complémentaire avant qu'une ligne soit sélectionnée ou non. Changer par exemple l'apparence de la ligne ou modifier les données de cette ligne.

XIV-H-5. Sélectionner et désélectionner les lignes

Il est possible de modifier la sélection par programme en employant les méthodes suivantes :

 
Sélectionnez
1.
2.
3.
4.
5.
  treeselection.select_path(path)
  treeselection.unselect_path(path)

  treeselection.select_iter(iter)
  treeselection.unselect_iter(iter)

Ces méthodes sélectionnent ou désélectionnent une ligne unique indiquée, soit par le path, un chemin de l'arbre, soit par iter, un TreeIter pointant sur la ligne. Les méthodes qui suivent sélectionnent ou désélectionnent plusieurs lignes à la fois :

 
Sélectionnez
1.
2.
3.
4.
5.
  treeselection.select_all()
  treeselection.unselect_all()

  treeselection.select_range(start_path, end_path)
  treeselection.unselect_range(start_path, end_path)

Les méthodes select_all() et select_range() nécessitent que le mode de sélection soit gtk.SELECTION_MULTIPLE. Les méthodes unselect_all() et unselect_range() fonctionnent quel que soit le mode de sélection. À noter que la méthode unselect_all() n'est pas disponible en PyGTK 2.0.

On peut vérifier si une ligne est sélectionnée en utilisant l'une de ces méthodes :

 
Sélectionnez
  resultat = treeselection.path_is_selected(chemin)
  resultat = treeselection.iter_is_selected(iter)

qui renvoie TRUE si la ligne indiquée par chemin ou iter est actuellement sélectionnée. On peut connaître le nombre de lignes sélectionnées avec la méthode :

 
Sélectionnez
  compte = treeselection.count_selected_rows()

Cette méthode n'est pas disponible en PyGTK 2.0 ; il faut donc la simuler en utilisant la méthode selected_foreach() semblable à l'émulation de la méthode get_selected_rows() dans Section 21.2, « Retrieving the Selection »Récupérer la sélection (pas encore traduit). Par exemple :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
  ...
  def rappel_chacune(modele, chemin, iter, compteur) :
      compteur[0] += 1
  ...
  def mon_compte_lignes_choisies(treeselection) :
      compteur = [0]
      treeselection.selected_foreach(rappel_chacune, compteur)
      return compteur[0]
  ...

XIV-I. Glisser-déposer dans un TreeView

XIV-I-1. Reclasser avec un glisser-déposer

Reclasser les lignes d'un TreeView (et les lignes sous-jacentes d'un treemodel) s'effectue par la méthode set_reorderable() mentionnée précédemment. La méthode set_reorderable() fixe la propriété "reorderable" à la valeur indiquée et autorise ou interdit le glisser- déposer interne aux lignes du TreeView. Lorsque la propriété "reorderable" vaut TRUE, l'utilisateur peut faire glisser des lignes et les relâcher à un nouvel endroit. Cette action provoque le reclassement en parallèle des lignes sous-jacentes du TreeModel. Reclasser avec un glisser-déposer n'est possible qu'avec des treeview non triés.

XIV-I-2. Glisser-déposer externe

Si l'on souhaite contrôler ou gérer un glisser-déposer depuis une source extérieure, il faut permettre et contrôler le glisser-déposer avec les méthodes suivantes :

 
Sélectionnez
  treeview.enable_model_drag_source(start_button_mask, targets, actions)
  treeview.enable_model_drag_dest(targets, actions)

Ces méthodes permettent d'utiliser les lignes respectivement comme source du glisser et cible du déposer. Le paramètre start_button_mask est un masque modificateur (voir la référence à gtk.gtk Constants dans le PyGTK Reference Manual qui indique les boutons ou les touches qui doivent être utilisés pour débuter le glisser. Le paramètre targets est une liste de 3-tuples qui définit l'information de cible qui peut être envoyée ou reçue. Pour qu'un glisser-déposer réussisse, au moins une des cibles de la source doit correspondre à une des cibles de la destination (par exemple, la cible "STRING"). Chaque tuple de cible contient le type de la cible, des indicateurs (une combinaison de gtk.TARGET_SAME_APP et gtk.TARGET_SAME_WIDGET ou aucune) et un identifiant unique entier. Le paramètre actions définit le résultat attendu de l'opération :

gtk.gdk.ACTION_DEFAULT, gtk.gdk.ACTION_COPY

Copie les données.

gtk.gdk.ACTION_MOVE

Déplace les données, c.-à-d. réalise une copie puis efface les données de la source en utilisant le DELETE cible du protocole de sélection de X.

gtk.gdk.ACTION_LINK

Crée un lien vers les données. Ceci n'est utile que si source et cible concordent sur ce qu'il signifie.

gtk.gdk.ACTION_PRIVATE

Action spécifique qui informe la source que la destination fera quelque chose que la source ne comprend pas.

gtk.gdk.ACTION_ASK

Demande à l'utilisateur que faire avec les données.

Par exemple, pour créer la destination d'un glisser-déposer

 
Sélectionnez
  treeview.enable_model_drag_dest([('text/plain', 0, 0)],
                  gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)

Ensuite, il faut gérer le signal "drag-data-received" du Widget pour accueillir ces données déposées - peut-être remplacer les données de la ligne où on les dépose. La fonction de rappel du signal "drag-data-received" a pour signature :

 
Sélectionnez
  def fonct_rappel(widget, drag_context, x, y, selection_data, info, timestamp)

widget est le TreeView, drag_context est un DragContext contenant le contexte de la sélection, x et y représente la position où le "déposer" est fait, selection_data est le SelectionData contenant les données, info est l'identifiant entier du type, timestamp est le moment où le "déposer" est réalisé. La ligne peut être récupérée par cette méthode :

 
Sélectionnez
  info_depot = treeview.get_dest_row_at_pos(x, y)

où (x, y) représentent la position transmise à la fonction de rappel, info_depot est un 2-tuple contenant le chemin d'une ligne et une constante indiquant la position du "déposer" par rapport à cette ligne : gtk.TREE_VIEW_DROP_BEFORE, gtk.TREE_VIEW_DROP_AFTER, gtk.TREE_VIEW_DROP_INTO_OR_BEFORE ou gtk.TREE_VIEW_DROP_INTO_OR_AFTER (avant, après, dedans ou avant, dedans ou après). La fonction de rappel ressemble à ceci :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
  treeview.enable_model_drag_dest([('text/plain', 0, 0)],
                  gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
  treeview.connect("drag-data-received", rappel_donnees_du__deposer)
  ...
  ...
  def rappel_donnees_du__deposer(treeview, contexte, x, y, selection, info, dateur):
      depot_info = treeview.get_dest_row_at_pos(x, y)
      if depot_info:
          modele = treeview.get_model()
          chemin, position = depot_info
          donnees = selection.data
          # faire quelque chose avec les données et le modèle
          ...
      return
  ...

Si une ligne est utilisée comme source du glisser, elle doit gérer le signal "drag-data-get" du Widget, lequel remplit une sélection avec les données devant être transmises à la destination du glisser-déposer par une fonction de rappel ayant comme signature :

 
Sélectionnez
  def fonct_rappel(widget, drag_context, selection_data, info, timestamp)

Les paramètres de cette fonction de rappel sont semblables à ceux de la fonction de rappel "drag-data-received". Comme la fonction de rappel ne transmet pas le chemin de l'arborescence ou tout autre moyen simple de récupérer l'information sur la ligne qui est glissée, nous présumons que la ligne glissée est celle qui est sélectionnée et que le mode de sélection est gtk.SELECTION_SINGLE ou gtk.SELECTION_BROWSE. De cette façon, il est possible de récupérer la ligne en obtenant le TreeSelection et de retrouver le modele et le TreeIter pointant sur cette ligne. Par exemple, un texte dans une ligne peut être transmis ainsi dans le glisser-déposer :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
  ...
  treestore = gtk.TreeStore(str, str)
  ...
  treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
                  [('text/plain', 0, 0)],
                  gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
  treeview.connect("drag-data-get", rappel_donnees_du_glisser)
  ...
  ...
  def rappel_donnees_du_glisser(treeview, contexte, selection, info, dateur):
      treeselection = treeview.get_selection()
      modele, iter = treeselection.get_selected()
      texte = modele.get_value(iter, 1)
      selection.set('text/plain', 8, texte)
      return
  ...

On peut désactiver l'utilisation du TreeView comme source ou destination du glisser-déposer par la méthode :

 
Sélectionnez
  treeview.unset_rows_drag_source()
  treeview.unset_rows_drag_dest()

XIV-I-3. Exemple de glisser-déposer dans un TreeView

Un exemple simple est nécessaire pour assembler les extraits de code présentés ci-dessus. Cet exemple (treeviewdnd.py) consiste en une liste dans laquelle des URL peuvent être glissées et déposées. Les URL peuvent aussi être reclassées en effectuant un glisser-déposer à l'intérieur du TreeView. Deux boutons permettent de vider la liste ou de supprimer un item sélectionné.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
#!/usr/bin/env python

# exemple treeviewdnd.py

import pygtk
pygtk.require('2.0')
import gtk

class TreeViewDnDExemple:

    CIBLES = [
        ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
        ('text/plain', 0, 1),
        ('TEXT', 0, 2),
        ('STRING', 0, 3),
        ]
    # fermer la fenêtre et quitter
    def ferme_event(self, widget, event, donnees=None):
        gtk.main_quit()
        return False

    def efface_selection(self, bouton):
        selection = self.treeview.get_selection()
        modele, iter = selection.get_selected()
        if iter:
            modele.remove(iter)
        return

    def __init__(self):
        # Créer une nouvelle fenêtre
        self.fenetre = gtk.Window(gtk.WINDOW_TOPLEVEL)

        self.fenetre.set_title("Cache URL")

        self.fenetre.set_size_request(250, 200)

        self.fenetre.connect("delete_event", self.ferme_event)

        self.fen_deroule = gtk.ScrolledWindow()
        self.vboite = gtk.VBox()
        self.hboite = gtk.HButtonBox()
        self.vboite.pack_start(self.fen_deroule, True)
        self.vboite.pack_start(self.hboite, False)
        self.b0 = gtk.Button('Effacer tout')
        self.b1 = gtk.Button('Effacer selection')
        self.hboite.pack_start(self.b0)
        self.hboite.pack_start(self.b1)

        # pour modèle, créer une liste avec une colonne contenant une chaine
        self.modeleliste = gtk.ListStore(str)

        # créer la vue arborescente utilisant le modeleliste
        self.treeview = gtk.TreeView(self.modeleliste)

        # créer un CellRenderer pour préparer les données
        self.cell = gtk.CellRendererText()

        # créer unTreeViewColumn pour afficher les données
        self.colonneTV = gtk.TreeViewColumn('URL', self.cell, text=0)

        # ajouter la colonne au treeview
        self.treeview.append_column(self.colonneTV)
        self.b0.connect_object('clicked', gtk.ListStore.clear, self.modeleliste)
        self.b1.connect('clicked', self.efface_selection)
        # autoriser la recherche dans le treeview
        self.treeview.set_search_column(0)

        # autoriser le tri pour la colonne
        self.colonneTV.set_sort_column_id(0)

        # Autoriser le glisser-deposer y compris interne à la colonne
        self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
                                                self.CIBLES,
                                                gtk.gdk.ACTION_DEFAULT|
                                                gtk.gdk.ACTION_MOVE)
        self.treeview.enable_model_drag_dest(self.CIBLES,
                                             gtk.gdk.ACTION_DEFAULT)

        self.treeview.connect("drag_data_get", self.donnees_du_glisser)
        self.treeview.connect("drag_data_received",
                              self.donnees_du_deposer)

        self.fen_deroule.add(self.treeview)
        self.fenetre.add(self.vboite)
        self.fenetre.show_all()

    def donnees_du_glisser(self, treeview, context, selection, id_cible,
                           etime):
        treeselection = treeview.get_selection()
        modele, iter = treeselection.get_selected()
        donnees = modele.get_value(iter, 0)
        selection.set(selection.target, 8, donnees)

    def donnees_du_deposer(self, treeview, context, x, y, selection,
                                info, etime):
        modele = treeview.get_model()
        donnees = selection.data
        info_depot = treeview.get_dest_row_at_pos(x, y)
        if info_depot:
            chemin, position = info_depot
            iter = modele.get_iter(chemin)
            if (position == gtk.TREE_VIEW_DROP_BEFORE
                or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
                modele.insert_before(iter, [donnees])
            else:
                modele.insert_after(iter, [donnees])
        else:
            modele.append([donnees])
        if context.action == gtk.gdk.ACTION_MOVE:
            context.finish(True, True, etime)
        return

def main():
    gtk.main()

if __name__ == "__main__":
    treeviewdndex = TreeViewDnDExemple()
    main()

Voici une copie d'écran Figure 14.8, « Exemple de glisser-déposer dans un TreeView » du programme exemple treeviewdnd.py en cours d'exécution :

Image non disponible
Figure 14.8. Exemple de glisser-déposer dans un TreeView

L'essentiel pour permettre à la fois un glisser-déposer externe et un reclassement interne des lignes est l'organisation des cibles (l'attribut TARGETS - ligne 11). Une cible spécifique à l'application (MY_TREE_MODEL_ROW) est créée et utilisée pour signifier un glisser-déposer interne au treeview en fixant l'indicateur à gtk.TARGET_SAME_WIDGET. En indiquant celle-ci comme première cible, la destination tentera d'abord cette correspondance avec les cibles sources. Ensuite, les actions de glisser de la source doivent comprendre gtk.gdk.ACTION_MOVE et gtk.gdk.ACTION_DEFAULT (voir lignes 72-75). Quand la destination reçoit les données en provenance de la source, si l'action du DragContext est gtk.gdk.ACTION_MOVE, la source est informée qu'elle doit détruire les données (la ligne dans cet exemple) en appelant la méthode finish() du DragContext (lignes 109-110). Le TreeView fournit un certain nombre de fonctions internes que nous complétons pour glisser, déposer et effacer les données.

XIV-J. TreeModelSort et TreeModelFilter

Les TreeModelSort et TreeModelFilter sont des modèles d'arbre qui s'intercalent entre le TreeModel de base (un TreeStore ou un ListStore) et le TreeView pour fournir un modèle modifié en conservant la structure d'origine du modèle de base. Ces modèles ,qui peuvent être interposés, implémentent les interfaces TreeModel et TreeSortable, mais ne fournissent aucune méthode pour insérer ou retirer des lignes dans le modèle, il faut insérer ou retirer ces lignes dans le magasin de données sous-jacent. Le TreeModelSort fournit un modèle dans lequel les lignes sont toujours classées, le TreeModelFilter fournit un modèle contenant un sous-ensemble des lignes du modèle de base.

Ces modèles peuvent être reliés sur une longueur arbitraire, si désiré. Par exemple, un TreeModelFilter peut avoir un enfant TreeModelSort qui peut avoir un enfant TreeModelFilter et ainsi de suite. Tant qu'il existe un TreeStore ou un ListStore comme point de départ de la chaine, cela devrait fonctionner. Sous PyGTK :2.0 et 2.2 les objets TreeModelSort et TreeModelFilter ne fonctionnent pas avec le protocole de mappage (mise en correspondance) de Python.

XIV-J-1. Le TreeModelSort

Le TreeModelSort maintient un modèle trié du modèle enfant indiqué dans son constructeur. L'usage principal du TreeModelSort est de fournir, pour un modèle, des vues multiples qui peuvent être classées diversement. Lorsque l'on a plusieurs vues d'un même modèle, toute opération de tri se répercute dans toutes les vues. Utiliser le TreeModelSort permet de laisser le modèle de départ dans son état originel pendant que les modèles de tri absorbent toutes les opérations de tri. Pour créer un TreeModelSort, il faut utiliser le constructeur :

 
Sélectionnez
  treemodelsort = gtk.TreeModelSort(child_model)

child_model est un TreeModel. La plupart des méthodes du TreeModelSort portent sur la conversion des chemins de l'arborescence et des TreeIter du modèle enfant vers le modèle trié, et réciproquement :

 
Sélectionnez
  sorted_path = treemodelsort.convert_child_path_to_path(child_path)
  child_path = treemodelsort.convert_path_to_child_path(sorted_path)

Ces méthodes de conversion de chemin renvoient None si le chemin donné ne peut être converti en chemin dans le modèle trié ou dans le modèle enfant. Les méthodes de conversion du TreeIter sont :

 
Sélectionnez
  sorted_iter = treemodelsort.convert_child_iter_to_iter(sorted_iter, child_iter)
 child_iter = treemodelsort.convert_iter_to_child_iter(child_iter, sorted_iter)

Les méthodes de conversion du TreeIter dupliquent l'argument converti (la valeur de retour comme le premier argument) pour une préserver une compatibilité antérieure. Il faut donner la valeur None au premier argument et n'utiliser que la valeur de retour. Par exemple :

 
Sélectionnez
  sorted_iter = treemodelsort.convert_child_iter_to_iter(None, child_iter)
  child_iter = treemodelsort.convert_iter_to_child_iter(None, sorted_iter)

Comme les méthodes de conversion du chemin, ces méthodes renvoient None si le TreeIter indiqué ne peut être converti.

On peut retrouver le TreeModel enfant grâce à la méthode get_model().

treemodelsort.py est un exemple simple utilisant les objets du TreeModelSort. Les résultats obtenus avec six lignes sont illustrés par Figure 14.9, « Exemple de TreeModelSort ».

Image non disponible
Figure 14.9. Exemple de TreeModelSort

Chacune des colonnes d'une fenêtre peut être réordonnée avec un clic sur son titre indépendamment des autres fenêtres. Lorsque le bouton "Add a Row" est pressé, une nouvelle ligne est ajoutée dans le ListStore de base et cette nouvelle ligne est affichée dans chaque fenêtre comme étant la ligne sélectionnée.

XIV-J-2. Le TreeModelFilter

Le TreeModelFilter est disponible avec PyGTK 2.4 et supérieur.

Un objet TreeModelFilter fournit plusieurs façons de modifier la vue du TreeModel de base, y compris :

  • afficher un sous-ensemble de lignes dans le modèle fils basé soit sur la valeur booléenne d'une "colonne visible", soit sur la valeur booléenne de retour d'une "fonction visible" ayant comme arguments un modèle fils, un TreeIter pointant sur une ligne du modèle fils et des données utilisateurs. Dans les deux cas, si la valeur booléenne vaut TRUE, la ligne est affichée sinon elle est cachée ;
  • utiliser une racine virtuelle qui fournit une vue d'un sous-arbre des enfants d'une ligne dans un modèle fils. Ceci n'est réalisable que si les données sont dans un TreeStore ;
  • combiner les colonnes et données d'un modèle relativement aux données du modèle fils. Par exemple, on peut afficher une colonne dans laquelle les données sont calculées à partir de données dans plusieurs colonnes du modèle fils.

Un objet TreeModelFilter est créé par la méthode TreeModel :

 
Sélectionnez
  treemodelfilter = treemodel.filter_new(root=None)

root est un chemin dans l'arbre du treemodel précisant une racine virtuelle du modèle ou None si la racine du treemodel doit être utilisée.

Indiquer une "racine virtuelle" quand on crée le TreeModelFilter permet de limiter la vue aux enfants de cette ligne "racine" dans la hiérarchie du modèle fils. Ceci, bien sûr, n'est utile que si le modèle fils est basé sur un TreeStore. Par exemple, on peut vouloir fournir une vue de la liste des pièces qui composent un lecteur de CDROM, distincte de la liste complète des pièces de l'ordinateur.

Les modes de visibilité sont mutuellement exclusifs et ne peuvent être fixés qu'une seule fois. Une fois la visibilité de la colonne ou de la fonction établie, on ne peut plus la modifier et l'autre mode ne peut plus être utilisé. Le mode de visibilité le plus simple extrait une valeur booléenne d'une colonne du modèle fils pour déterminer si la ligne doit être affichée. La visibilité colonne est fixée par :

 
Sélectionnez
  treemodelfilter.set_visible_column(column)

column est le numéro de la colonne dans le TreeModel fils, de laquelle extraire les valeurs booléennes. Par exemple, le code suivant utilise les valeurs de la troisième colonne pour fixer la visibilité des lignes :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
  ...
  treestore = gtk.TreeStore(str, str, "gboolean")
  ...
  modelfilter = treestore.filter_new()
  modelfilter.set_visible_column(2)
  ...

Ainsi, toutes les lignes du treestore qui possèdent la valeur TRUE dans la troisième colonne seront affichées.

Si on veut utiliser des critères de visibilité plus complexes, une fonction visibilité devrait fournir des capacités suffisantes :

 
Sélectionnez
  treemodelfilter.set_visible_func(func, data=None)

func est la fonction appelée pour chaque ligne du modèle fils afin de décider si elle doit être affichée et data représente les données utilisateur transmises à la fonction func. La fonction func renvoie TRUE si la ligne doit être affichée. Cette fonction a pour signature :

 
Sélectionnez
  def func(modele, iter, donnees_utilisateur)

modele est le TreeModel fils, iter est un TreeIter pointant sur une ligne du modele et donnees_utilisateur sont les data passées dans la fonction.

Si on modifie le critère de visibilité, il faut utiliser :

 
Sélectionnez
  treemodelfilter.refilter()

pour imposer un nouveau filtrage des lignes du modèle fils.

Par exemple, l'extrait de code ci-dessous illustre un TreeModelFilter qui affiche des lignes selon une comparaison entre la valeur de la troisième colonne et le contenu des données utilisateurs :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
  ...
  def match_type(modele, iter, donnees_utilisateur):
      valeur = model.get_value(iter, 2)
      return valeur in donnees_utilisateur
  ...
  show_vals = ['OPEN', 'NEW', 'RESO']
  liststore = gtk.ListStore(str, str, str)
  ...
  modelfilter = liststore.filter_new()
  modelfilter.set_visible_func(match_type, show_vals)
  ...

Le programme treemodelfilter.py illustre l'utilisation de la méthode set_visible_func(). Figure 14.10, « Exemple de visibilité d'un TreeModelFilter » montre le résultat obtenu.

Image non disponible
Figure 14.10. Exemple de visibilité d'un TreeModelFilter

En agissant sur les boutons du bas, on modifie le contenu du TreeView pour afficher seulement les lignes qui correspondent à l'étiquette du bouton.

Une fonction modify permet un autre niveau de contrôle sur l'affichage du TreeView sur la manière dont on peut combiner une ou plusieurs (ou toutes) colonnes présentées par le TreeModelFilter. Il faut utiliser un modèle fils de base, un TreeStore ou un ListStore pour déterminer le nombre de lignes et la hiérarchie, mais les colonnes peuvent être tout ce qui est indiqué dans la méthode :

 
Sélectionnez
  treemodelfilter.set_modify_func(types, func, data=None)

types est une suite (liste ou tuple) précisant le type des colonnes présentes, func est une fonction appelée pour renvoyer la valeur pour une rangée et une colonne et data est un argument passé à la fonction func. La fonction func a pour signature :

 
Sélectionnez
  def func(modele, iter, colonne, donnees_utilisateur)

modele est le TreeModelFilter, iter le TreeIter qui pointe sur une ligne du modèle, colonne est le numéro de la colonne pour laquelle une valeur est nécessaire et donnees_utilisateur est le paramètre data. La fonction func doit renvoyer une valeur correspondant au type de colonne.

Une fonction de modification est utile quand on souhaite fournir une colonne de données qui doivent être générées à partir de données de colonnes du modèle fils. Par exemple, on dispose d'une colonne contenant des dates de naissance et on veut fournir une colonne affichant les âges ; une fonction de modification peut générer l'information sur l'âge à partir de la date de naissance et de la date actuelle. Un autre exemple serait de choisir l'image à afficher selon l'analyse des données (un nom de fichier) dans une colonne. Cette action peut aussi être réalisée par la méthode set_cell_data_func() du TreeViewColumn.

Généralement, avec la fonction modify, il faut convertir le TreeIter du TreeModelFilter en un TreeIter dans le modèle fils par :

 
Sélectionnez
  child_iter = treemodelfilter.convert_iter_to_child_iter(filter_iter)

Bien sûr, il faut aussi retrouver le modèle fils par :

 
Sélectionnez
  child_model = treemodelfilter.get_model()

Ceci donne accès à la ligne du modèle fils et à son contenu pour fournir la valeur de la ligne et colonne indiquées du TreeModelFilter. Il existe aussi une méthode pour convertir les chemins du modèle filtre de et vers les chemins de l'arborescence fille.

 
Sélectionnez
1.
2.
3.
4.
  filter_iter = treemodelfilter.convert_child_iter_to_iter(child_iter)

  child_path = treemodelfilter.convert_path_to_child_path(filter_path)
  filter_path = treemodelfilter.convert_child_path_to_path(child_path)

Bien sûr, il est possible de combiner les modes de visibilité et la fonction modify pour, à la fois, filtrer les lignes et produire les colonnes. Pour obtenir encore plus de contrôle sur la vue, il faudrait utiliser un TreeModel personnalisé.

XIV-K. Le TreeModel générique

Si le TreeModel standard n'est pas assez puissant pour les besoins de l'application, il est possible d'utiliser le GenericTreeModel pour construire son propre TreeModel personnalisé en Python. Créer un GenericTreeModel peut être utile en cas de problème de performance avec les objets TreeStore et ListStore standards, ou si on veut une interface directe avec une source de données externe (une base de données ou un système de fichiers) pour éviter une duplication des données de et vers le TreeStore ou le ListStore.

XIV-K-1. Aperçu du GenericTreeModel

Avec un GenericTreeModel, on construit et gère son modèle de données et on fournit un accès externe à travers l'interface du TreeModel standard en définissant un ensemble de méthodes de classe. PyGTK implémente l'interface du TreeModel et prend en charge les méthodes du TreeModel appelées pour fournir le modèle de données réel.

Les détails de l'implémentation de votre modèle devraient rester complètement cachés aux applications externes. Ce qui signifie que la manière dont votre modèle identifie, range et retrouve les données n'est pas connue de l'application. En général, la seule information qui est sauvegardée en dehors du GenericTreeModel sont les références de ligne qui sont enveloppées par les TreeIter externes. Ces références ne sont pas visibles par l'application.

Voici un examen détaillé de l'interface GenericTreeModel qu'il faut fournir.

XIV-K-2. Interface du GenericTreeModel

L'interface du GenericTreeModel comprend les méthodes suivantes qui doivent être implémentées dans le modèle personnalisé :

 
Sélectionnez
def on_get_flags(self)
def on_get_n_columns(self)
def on_get_column_type(self, index)
def on_get_iter(self, path)
def on_get_path(self, rowref)
def on_get_value(self, rowref, column)
def on_iter_next(self, rowref)
def on_iter_children(self, parent)
def on_iter_has_child(self, rowref)
def on_iter_n_children(self, rowref)
def on_iter_nth_child(self, parent, n)
def on_iter_parent(self, child)

Il faut remarquer que ces méthodes supportent entièrement l'interface du TreeModel, y compris :

 
Sélectionnez
def get_flags()
def get_n_columns()
def get_column_type(index)
def get_iter(path)
def get_iter_from_string(path_string)
def get_string_from_iter(iter)
def get_iter_root()
def get_iter_first()
def get_path(iter)
def get_value(iter, column)
def iter_next(iter)
def iter_children(parent)
def iter_has_child(iter)
def iter_n_children(iter)
def iter_nth_child(parent, n)
def iter_parent(child)
def get(iter, column, ...)
def foreach(func, user_data)

Pour illustrer l'utilisation du GenericTreeModel j'ai modifié le programme listefichiers.py et montré comment les méthodes d'interface sont réalisées. Le programme listefichiers-gtm.py affiche les fichiers d'un répertoire avec une icône indiquant si le fichier est ou non un répertoire, le nom du fichier, sa taille, son mode et sa date de dernière modification.

La méthode on_get_flags() doit retourner une valeur qui est une combinaison de  :

gtk.TREE_MODEL_ITERS_PERSIST

Les TreeIter perdurent quels que soient les signaux émis par l'arbre.

gtk.TREE_MODEL_LIST_ONLY

Le modèle est uniquement une liste et n'a jamais d'enfant.

Si un modèle possède des références de lignes valides malgré les changements de lignes (réordonnancement, ajout, suppression), on utilise gtk.TREE_MODEL_ITERS_PERSIST. De la même façon, si un modèle est seulement une liste, on utilise gtk.TREE_MODEL_LIST_ONLY. Autrement, on renvoie 0 si le modèle ne possède pas de références de lignes persistantes et est une arborescence. Dans l'exemple, le modèle est une liste avec des TreeIter persistants.

 
Sélectionnez
    def on_get_flags(self):
        return gtk.TREE_MODEL_LIST_ONLY|gtk.TREE_MODEL_ITERS_PERSIST

La méthode on_get_n_columns() doit retourner le nombre de colonnes que le modèle exporte vers l'application. L'exemple garde une liste de types de colonnes, ainsi on peut renvoyer la longueur de la liste :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
class Fichmodeleliste(gtk.GenericTreeModel):
    ...
    column_types = (gtk.gdk.Pixbuf, str, long, str, str)
    ...
    def on_get_n_columns(self):
        return len(self.types_colonnes)

La méthode on_get_column_type() doit renvoyer le type de la colonne pour l'index indiqué. Cette méthode est généralement appelée par un TreeView quand le modèle est établi. On peut soit créer une liste ou un tuple contenant l'information de type de colonne, soit le créer à la volée. Dans l'exemple :

 
Sélectionnez
    def on_get_column_type(self, n):
        return self.types_colonnes[n]

L'interface GenericTreeModel convertit le type Python en un GType, donc le code suivant :

 
Sélectionnez
  flm = Fichmodeleliste()
  print flm.on_get_column_type(1), flm.get_column_type(1)

imprimerait :

 
Sélectionnez
<type 'str'> <GType gchararray (64)>

Les méthodes suivantes utilisent les références de ligne qui sont conservées comme données privées dans un TreeIter. L'application ne peut lire la référence de ligne dans un TreeIter, donc on peut utiliser n'importe quel item unique voulu comme référence de ligne. Par exemple, dans un modèle comportant des lignes comme des tuples, il est possible d'utiliser l'index de tuple comme la référence de ligne. Un autre exemple serait d'utiliser un nom de fichier comme référence de ligne dans un modèle représentant des fichiers dans un répertoire. Dans ces deux cas, le référence de ligne n'est pas modifiée par les changements du modèle, aussi les TreeIter peuvent être déclarés persistants. L'interface de l'application PyGTK GenericTreeModel extraira vos références de ligne à partir des TreeIter et enveloppera vos références de ligne dans les TreeIter selon les besoins.

Dans les méthodes suivantes, refligne se réfère à une référence de ligne interne.

La méthode on_get_iter() devrait renvoyer un refligne pour le chemin de l'arborescence indiqué par le chemin. Le chemin de l'arborescence doit toujours être représenté par un tuple. L'exemple utilise le nom de fichier comme refligne. Les noms de fichier sont conservés dans une liste dans le modèle, ainsi on prend le premier index du chemin comme index du nom de fichier :

 
Sélectionnez
    def on_get_iter(self, chemin):
        return self.fichiers[chemin[0]]

Il faut être cohérent dans l'utilisation de la référence de ligne puisqu'on récupérera une référence de ligne dans les appels des méthodes GenericTreeModel qui prennent les arguments TreeIter : on_get_path(), on_get_value(), on_iter_next(), on_iter_children(), on_iter_has_child(), on_iter_n_children(), on_iter_nth_child() et on_iter_parent().

La méthode on_get_path() devrait retourner un chemin de l'arborescence donnant un refligne. Par exemple, poursuivant l'exemple précédent où le nom de fichier est utilisé comme refligne, on pourrait définir la méthode on_get_path() comme suit :

 
Sélectionnez
    def on_get_chemin(self, refligne):
        return self.fichiers.index(refligne)

Cette méthode trouve l'index de la liste contenant le nom de fichier dans refligne. Cet exemple montre clairement qu'un choix judicieux de référence de ligne rend l'exécution plus efficiente. Par exemple, on pourrait utiliser un dictionnaire Python pour relier refligne à un chemin.

La méthode on_get_value() devrait renvoyer les données rangées à la ligne et colonne indiquées par refligne et colonne. Dans l'exemple :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
    def on_get_value(self, refligne, colonne):
        fname = os.path.join(self.nomrep, refligne)
        try:
            statutfich = os.stat(fname)
        except OSError:
            return None
        mode = statutfich.st_mode
        if colonne is 0:
            if stat.S_ISDIR(mode):
                return dossierpb
            else:
                return fichierpb
        elif colonne is 1:
            return refligne
        elif colonne is 2:
            return statutfich.st_size
        elif colonne is 3:
            return oct(stat.S_IMODE(mode))
        return time.ctime(statutfich.st_mtime)

on doit extraire l'information de fichier associée et renvoyer la valeur appropriée selon la colonne indiquée.

La méthode on_iter_next() devrait retourner une référence de ligne, à la ligne (de même niveau), après la ligne indiquée par refligne. Dans l'exemple :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
    def on_iter_next(self, refligne):
        try:
            i = self.fichiers.index(refligne)+1
            return self.fichiers[i]
        except IndexError:
            return None

L'index du nom de fichier de refligne est établi et le nom de fichier suivant est renvoyé ou s'il n'existe pas, None est renvoyé.

La méthode on_iter_children() devrait retourner une référence de ligne vers la première ligne enfant de la ligne indiquée par refligne. Si refligne vaut None, une référence à la première ligne du niveau supérieur est retournée. S'il n'existe pas de ligne enfant, None est retourné. Dans l'exemple :

 
Sélectionnez
1.
2.
3.
4.
    def on_iter_children(self, refligne):
        if refligne:
            return None
        return self.fichiers[0]

Puisque le modèle est une liste, seul le niveau supérieur (refligne=None) peut posséder des lignes enfant. None est retourné si refligne contient un nom de fichier.

La méthode on_iter_has_child() doit renvoyer TRUE si la ligne indiquée par refligne possède des lignes enfant, FALSE sinon. Dans l'exemple, on renvoie FALSE puisqu’aucune ligne ne peut posséder d'enfant :

 
Sélectionnez
    def on_iter_has_child(self, refligne):
        return False

La méthode on_iter_n_children() renvoie le nombre de lignes enfant que possède la ligne indiquée par refligne. Si refligne vaut None, on renvoie le nombre de lignes du niveau supérieur. Dans l'exemple, on renvoie 0 si refligne ne vaut pas None :

 
Sélectionnez
1.
2.
3.
4.
    def on_iter_n_children(self, refligne):
        if refligne:
            return 0
        return len(self.fichiers)

La méthode on_iter_nth_child() renvoie une référence de ligne à la énième ligne enfant de la ligne indiquée par parent. Si parent vaut None, une référence à la énième ligne du niveau supérieur est retournée. Dans l'exemple, on retourne une référence à la énième ligne du niveau supérieur si parent vaut None, None sinon :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
    def on_iter_nth_child(self, refligne, n):
        if refligne:
            return None
        try:
            return self.fichiers[n]
        except IndexError:
            return None

La méthode on_iter_parent() renvoie une référence de ligne à la ligne parent de la ligne indiquée par refligne. Si refligne pointe sur une ligne du niveau supérieur, on renvoie None. Dans l'exemple, None est toujours renvoyé en supposant que refligne doit pointer sur une ligne du niveau supérieur :

 
Sélectionnez
    def on_iter_parent(child):
        return None

Ces exemples sont assemblés dans le programme listefichiers-gtm.py. La Figure 14.11, « Exemple de TreeModel générique » montre le résultat.

Image non disponible
Figure 14.11. Exemple de TreeModel générique

XIV-K-3. Ajouter et supprimer des lignes

Le programme listefichiers-gtm.py calcule la liste des noms de fichier lorsqu'il crée une instance de FileListModel. Si l'on souhaite vérifier régulièrement la création de nouveaux fichiers et ajouter ou retirer des fichiers du modèle, on peut soit créer un nouveau FileListModel pour le même répertoire, soit ajouter une méthode pour ajouter ou retirer des lignes dans le modèle. Selon le type de modèle créé, il peut être nécessaire d'ajouter une méthode semblable à celles des modèles de TreeStore et de ListStore :

  • insert()
  • insert_before()
  • insert_after()
  • prepend()
  • append()
  • remove()
  • clear()

Il n'est, bien sûr, pas nécessaire d'utiliser toutes ou même une seule de ces méthodes. On peut créer ses propres méthodes plus adaptées à son modèle.

En utilisant le programme exemple précédent pour montrer l'ajout de méthodes pour supprimer ou ajouter des fichiers, voici comment on implémente ces méthodes :

 
Sélectionnez
def remove(iter)
def add(filename)

La méthode remove() retire le fichier indiqué par le paramètre iter. Outre retirer la ligne du modèle, la méthode efface aussi le fichier du répertoire. Évidemment, si l'utilisateur n'a pas les droits de suppression du ficher, la ligne n'est pas supprimée non plus. Par exemple :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
    def remove(self, iter):
        path = self.get_path(iter)
        pathname = self.get_pathname(path)
        try:
            if os.path.exists(pathname):
                os.remove(pathname)
            del self.files[path[0]]
            self.row_deleted(path)
        except OSError:
            pass
        return

La méthode transmet un TreeIter qu'il faut transformer en un chemin à utiliser pour récupérer le chemin du fichier par la méthode get_pathname(). Il est possible que le fichier ait déjà été supprimé, il faut donc tester son existence avant de le supprimer. Si une exception OSError intervient pendant la suppression du fichier, c'est probablement parce que le fichier est dans un répertoire où l'utilisateur n'a pas suffisamment de droits. Finalement, le fichier est supprimé et le signal "row-deleted" est émis par la méthode rows_deleted(). Le signal "file-deleted" indique aux TreeView utilisant le modèle que celui-ci a changé, ainsi ils peuvent mettre à jour leur état interne et afficher le modèle modifié.

La méthode add() impose de créer un fichier dans le répertoire courant avec le nom donné. Si le fichier est créé, son nom est ajouté à la liste des fichiers du modèle. Par exemple :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
    def add(self, filename):
        pathname = os.path.join(self.dirname, filename)
        if os.path.exists(pathname):
            return
        try:
            fd = file(pathname, 'w')
            fd.close()
            self.dir_ctime = os.stat(self.dirname).st_ctime
            files = self.files[1:] + [filename]
            files.sort()
            self.files = ['. '] + files
            path = (self.files.index(filename),)
            iter = self.get_iter(path)
            self.row_inserted(path, iter)
        except OSError:
            pass
        return

Cet exemple simple s'assure que le fichier n'existe pas déjà, puis tente d'ouvrir le fichier en écriture. Si cela réussit, le fichier est refermé et son nom inséré dans la liste de fichiers. Le chemin et le TreeIter de la ligne du fichier ajouté sont récupérés pour être utilisés dans la méthode row_inserted() qui émet le signal "row-inserted". Ce signal "row-inserted" sert à indiquer aux TreeView utilisant le modèle qu'ils doivent mettre à jour leur état et rafraîchir leur affichage.

Les autres méthodes mentionnées précédemment (par exemple, append et prepend) n'ont pas de signification pour cet exemple puisque le modèle a sa liste de fichiers triée.

D'autres méthodes peuvent être utiles dans un TreeModel découlant du GenericTreeModel :

  • set_value()
  • reorder()
  • swap()
  • move_after()
  • move_before()

L'implémentation de ces méthodes est similaire à celle des méthodes précédentes. Il faut synchroniser le modèle et l'état externe, et ensuite indiquer aux TreeView que le modèle a changé. Les méthodes suivantes sont utilisées pour indiquer aux TreeView les changements du modèle en envoyant le signal approprié :

 
Sélectionnez
def row_changed(path, iter)
def row_inserted(path, iter)
def row_has_child_toggled(path, iter)
def row_deleted(path)
def rows_reordered(path, iter, new_order)

XIV-K-4. Gestion de la mémoire

L'un des problèmes avec le GenericTreeModel est que le TreeIter contient une référence à un objet Python venant du modèle personnalisé. Puisque le TreeIter peut être créé et initialisé dans un module en C et être présent dans la pile, il n'est pas possible de connaître le moment où le TreeIter est détruit et la référence à l'objet Python n'est plus d'utilité. Donc l'objet Python référencé dans un TreeIter voit par défaut son compteur incrémenté, mais il n'est pas décrémenté lorsque le TreeIter est détruit. Ceci garantit que l'objet Python ne peut être détruit quand il est utilisé par un TreeIter et produire éventuellement une erreur de segmentation. Malheureusement les comptes de référence supplémentaires font que, au mieux, l'objet Python aura un compte de référence excessif et, au pire, il ne sera jamais libéré même lorsqu’il n'est pas utilisé. Le dernier cas cause des fuites de mémoire et le premier, des fuites de références.

Pour parer à la situation où le TreeModel personnalisé maintient une référence à l'objet Python jusqu'à ce qu'il ne soit plus disponible (le TreeIter est invalide, car le modèle a changé) et il n'y a pas besoin de relâcher les références, le GenericTreeModel possède une propriété "leak-references". Par défaut, "leak-references" vaut TRUE pour indiquer que le GenericTreeModel relâchera les références. Si "leak-references" vaut FALSE, le compteur de références de l'objet Python ne sera pas incrémenté quand il sera référencé dans un TreeIter. Ce qui signifie que le TreeModel personnalisé doit conserver une référence à tous les objets Python utilisés dans un TreeIter jusqu'à la destruction du modèle. Malheureusement, même ceci ne protège pas d'un mauvais code qui tente d'utiliser un TreeIter sauvegardé dans un GenericTreeModel différent. Pour se protéger contre ce cas de figure, l'application doit conserver une référence à tous les objets Python référencés dans un TreeIter pour toutes les instances du GenericTreeModel. Naturellement, ceci a le même effet qu'une fuite de références.

Avec PyGTK 2.4 et ultérieurs, les méthodes invalidate_iters() et iter_is_valid() sont disponibles comme aide à la gestion des TreeIter et des références des objets Python :

 
Sélectionnez
1.
2.
3.
  generictreemodel.invalidate_iters()

  result = generictreemodel.iter_is_valid(iter)

Ceci est particulièrement utile lorsque la propriété "leak-references" vaut FALSE. Les modèles d'arbre dérivés du GenericTreeModel sont protégés des problèmes de TreeIter périmés, car la validité des iters est automatiquement vérifiée avec le modèle arbre.

Si un modèle d'arbre personnalisé ne gère pas les iters persistants (par exemple, gtk.TREE_MODEL_ITERS_PERSIST n'est pas établi dans le retour de la méthode TreeModel.get_flags(), il est possible d'appeler la méthode invalidate_iters() pour annuler tous les TreeIter en cours quand le modèle est modifié (après insertion d'une nouvelle ligne, par exemple). Le modèle d'arbre peut aussi utiliser n'importe quel objet Python qui a été référencé par un TreeIter après l'appel à la méthode invalidate_iters().

Les applications peuvent utiliser la méthode iter_is_valid() pour déterminer si un TreeIter est encore valide pour le modèle d'arbre personnalisé.

XIV-K-5. Autres interfaces

Les modèles ListStore et TreeStore comprennent outre l'interface TreeModel, les interfaces TreeSortable, TreeDragSource et TreeDragDest. Le GenericTreeModel ne comprend que l'interface TreeModel. Je pense que c'est à cause de la référence directe au modèle dans le langage C par les modèles TreeView, TreeModelSort et TreeModelFilter. Créer et utiliser un TreeIter exige un code collant au C pour l'interface avec le modèle d'arbre personnalisé Python qui contient les données. Ce code collant est fourni par le GenericTreeModel et il semble qu'il n'y a pas d'alternative purement Python de réaliser ceci, car le TreeView et les autres modèles appellent les fonctions du GtkTreeModel en C en passant leur référence au modèle d'arbre personnalisé.

L'interface TreeSortable nécessite aussi un code collant au C pour agir sur le mécanisme de tri par défaut du TreeViewColumn ainsi qu'il est expliqué dans la Section 14.2.9, « Ordonner les lignes d'un TreeModel »Ordonner les lignes d'un TreeModel. Cependant un modèle personnalisé doit réaliser ses propres tris et une application doit gérer l'utilisation des critères de tri en prenant en compte les clics sur les en-têtes des TreeViewColumn et en appelant les méthodes de tri du modèle d'arbre personnalisé. Le modèle effectue la mise à jour du TreeView en émettant le signal "rows-reordered" grâce à la méthode rows_reordered() du TreeModel. Ainsi le GenericTreeModel ne nécessite probablement pas d'implémenter l'interface TreeSortable.

Pareillement, le GenericTreeModel n'a pas besoin d' implémenter les interfaces TreeDragSource et TreeDragDest puisque le modèle d'arbre personnalisé peut effectuer ses propres interfaces de glisser-déposer et l'application peut gérer les signaux TreeView appropriés et faire appel aux méthodes du modèle d'arbre personnalisé tant que nécessaire.

XIV-K-6. Utilisation du GenericTreeModel

Je crois que le GenericTreeModel ne devrait être utilisé qu'en dernier ressort. Les objets standards du TreeView comprennent des mécanismes puissants qui devraient être suffisants pour la plupart des applications. Sans doute, il existe des applications qui peuvent avoir besoin du GenericTreeModel,mais il faudrait d'abord essayer d'utiliser ce qui suit :

Cell Data Functions

Comme il est montré dans la Section 14.4.5, « Fonction d'affichage des données cellulaires »Fonction d'affichage des données cellulaires, les fonctions de données des cellules peuvent être utilisées pour modifier et même produire les données pour un affichage d'une colonne du TreeView. On peut créer autant d'affichages de colonnes avec des données générées que l'on souhaite. Cela donne beaucoup de contrôle de la présentation de données d'une source de données sous-jacente.

TreeModelFilter

En PyGTK 2.4, le TreeModelFilter, comme indiqué dans la Section 14.10.2, « Le TreeModelFilter »Le TreeModelFilter, permet un fort contrôle sur l'affichage des colonnes et lignes d'un TreeModel enfant, y compris afficher seulement les lignes filles d'une ligne. Les colonnes de données peuvent être produites d'une façon semblable à l'utilisation des fonctions de données en cellule ; ici le modèle est un TreeModel avec un nombre et un type de colonnes indiqués alors qu'une fonction de données de cellule laisse les colonnes modèles inchangées et modifie juste l'affichage dans un TreeView.

Si un GenericTreeModel doit être utilisé, il faut veiller à :

  • l'interface complète du TreeModel doit être créée et être en mesure de fonctionner comme indiqué. I y a des finesses qui peuvent induire des erreurs. Par opposition, le TreeModel standard a été complètement testé ;
  • gérer les références des objets Python utilisés par un TreeIter peut se révéler difficile, particulièrement pour des programmes longs avec beaucoup d'affichages variés ;
  • il faut créer une interface pour ajouter, supprimer ou modifier le contenu des lignes. Le lien d'un TreeIter aux objets de Python et aux rangées modèles dans cette interface n'est pas très élégant ;
  • le développement d'interfaces de glisser-déposer et de tri demande un effort important, l'application doit très probablement participer à ce que ces interfaces soient entièrement fonctionnelles.

XIV-L. The Generic CellRenderer


précédentsommairesuivant

Copyright © 2005 John Finlay. Aucune reproduction, même partielle, ne peut être faite de ce site ni 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.