Reclasser les lignes d'un TreeView
(et les lignes
sous-jacentes d'un tree model) 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.
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 :
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-a-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
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 :
def fonct_rappel(widget
,drag_context
,x
,y
,selection_data
,info
,timestamp
)
... où 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 :
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 :
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 quelquechose avec les donnees et le modele ... return ...
Si une ligne est utilisé 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 :
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 :
... 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 :
treeview.unset_rows_drag_source() treeview.unset_rows_drag_dest()
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é.
1 #!/usr/bin/env python 2 3 # exemple treeviewdnd.py 4 5 import pygtk 6 pygtk.require('2.0') 7 import gtk 8 9 class TreeViewDnDExemple: 10 11 CIBLES = [ 12 ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0), 13 ('text/plain', 0, 1), 14 ('TEXT', 0, 2), 15 ('STRING', 0, 3), 16 ] 17 # fermer la fenetre et quitter 18 def ferme_event(self, widget, event, donnees=None): 19 gtk.main_quit() 20 return False 21 22 def efface_selection(self, bouton): 23 selection = self.treeview.get_selection() 24 modele, iter = selection.get_selected() 25 if iter: 26 modele.remove(iter) 27 return 28 29 def __init__(self): 30 # Creer une nouvelle fenetre 31 self.fenetre = gtk.Window(gtk.WINDOW_TOPLEVEL) 32 33 self.fenetre.set_title("Cache URL") 34 35 self.fenetre.set_size_request(250, 200) 36 37 self.fenetre.connect("delete_event", self.ferme_event) 38 39 self.fen_deroule = gtk.ScrolledWindow() 40 self.vboite = gtk.VBox() 41 self.hboite = gtk.HButtonBox() 42 self.vboite.pack_start(self.fen_deroule, True) 43 self.vboite.pack_start(self.hboite, False) 44 self.b0 = gtk.Button('Effacer tout') 45 self.b1 = gtk.Button('Effacer selection') 46 self.hboite.pack_start(self.b0) 47 self.hboite.pack_start(self.b1) 48 49 # pour modele, creer une liste avec une colonne contenant une chaine 50 self.modeleliste = gtk.ListStore(str) 51 52 # creer la vue arborescente utilisant le modeleliste 53 self.treeview = gtk.TreeView(self.modeleliste) 54 55 # creer un CellRenderer pour preparer les donnees 56 self.cell = gtk.CellRendererText() 57 58 # creer unTreeViewColumn pour afficher les donnees 59 self.colonneTV = gtk.TreeViewColumn('URL', self.cell, text=0) 60 61 # ajouter la colonne au treeview 62 self.treeview.append_column(self.colonneTV) 63 self.b0.connect_object('clicked', gtk.ListStore.clear, self.modeleliste) 64 self.b1.connect('clicked', self.efface_selection) 65 # autoriser la recherche dans le treeview 66 self.treeview.set_search_column(0) 67 68 # autoriser le tri pour la colonne 69 self.colonneTV.set_sort_column_id(0) 70 71 # Autoriser le glisser-deposer y compris interne a la colonne 72 self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK, 73 self.CIBLES, 74 gtk.gdk.ACTION_DEFAULT| 75 gtk.gdk.ACTION_MOVE) 76 self.treeview.enable_model_drag_dest(self.CIBLES, 77 gtk.gdk.ACTION_DEFAULT) 78 79 self.treeview.connect("drag_data_get", self.donnees_du_glisser) 80 self.treeview.connect("drag_data_received", 81 self.donnees_du_deposer) 82 83 self.fen_deroule.add(self.treeview) 84 self.fenetre.add(self.vboite) 85 self.fenetre.show_all() 86 87 def donnees_du_glisser(self, treeview, context, selection, id_cible, 88 etime): 89 treeselection = treeview.get_selection() 90 modele, iter = treeselection.get_selected() 91 donnees = modele.get_value(iter, 0) 92 selection.set(selection.target, 8, donnees) 93 94 def donnees_du_deposer(self, treeview, context, x, y, selection, 95 info, etime): 96 modele = treeview.get_model() 97 donnees = selection.data 98 info_depot = treeview.get_dest_row_at_pos(x, y) 99 if info_depot: 100 chemin, position = info_depot 101 iter = modele.get_iter(chemin) 102 if (position == gtk.TREE_VIEW_DROP_BEFORE 103 or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE): 104 modele.insert_before(iter, [donnees]) 105 else: 106 modele.insert_after(iter, [donnees]) 107 else: 108 modele.append([donnees]) 109 if context.action == gtk.gdk.ACTION_MOVE: 110 context.finish(True, True, etime) 111 return 112 113 def main(): 114 gtk.main() 115 116 if __name__ == "__main__": 117 treeviewdndex = TreeViewDnDExemple() 118 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 :
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 corespondance 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.