14.9. TreeView Drag and Drop14.9.1. Drag and Drop ReorderingReordering of the TreeView rows (and the
underlying tree model rows is enabled by using the
set_reorderable() method mentioned above. The
set_reorderable() method sets the "reorderable"
property to the specified value and enables or disables the internal drag
and drop of TreeView rows. When the "reorderable"
property is TRUE a user can drag
TreeView rows and drop them at a new location. This
action causes the underlying TreeModel rows to be
rearranged to match. Drag and drop reordering of rows only works with
unsorted stores. 14.9.2. External Drag and DropIf you want to control the drag and drop or deal
with drag and drop from external sources, you'll have to enable and control
the drag and drop using the following methods:
treeview.enable_model_drag_source(start_button_mask, targets, actions)
treeview.enable_model_drag_dest(targets, actions)
|
These methods enable using rows as a drag source and a drop site
respectively. start_button_mask is a modifier mask
(see the gtk.gtk
Constants reference in the PyGTK Reference
Manual) that specifies the buttons or keys that must be pressed to
start the drag operation. targets is a list of
3-tuples that describe the target information that can be given or
received. For a drag and drop to succeed at least one of the targets must
match in the drag source and drag destination (e.g. the "STRING"
target). Each target 3-tuple contains the target name, flags (a combination
of gtk.TARGET_SAME_APP and
gtk.TARGET_SAME_WIDGET or neither) and a unique int
identifier. actions describes what the result of the
operation should be: gtk.gdk.ACTION_DEFAULT, gtk.gdk.ACTION_COPY, | Copy the data. | gtk.gdk.ACTION_MOVE | Move the data, i.e. first copy it, then delete it from
the source using the DELETE target of the X selection
protocol. | gtk.gdk.ACTION_LINK | Add a link to the data. Note that this is only useful
if source and destination agree on what it means. | gtk.gdk.ACTION_PRIVATE | Special action which tells the source that the
destination will do something that the source doesn't understand. | gtk.gdk.ACTION_ASK | Ask the user what to do with the data. |
For example to set up a drag drop destination:
treeview.enable_model_drag_dest([('text/plain', 0, 0)],
gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
|
Then you'll have to handle the Widget
"drag-data-received" signal to receive that dropped data - perhaps replacing
the data in the row it was dropped on. The signature for the callback for
the "drag-data-received" signal is:
def callback(widget, drag_context, x, y, selection_data, info, timestamp)
|
where widget is the
TreeView, drag_context is a
DragContext containing the context of the selection,
x and y are the position where
the drop occurred, selection_data is the
SelectionData containing the data,
info is the ID integer of the type,
timestamp is the time when the drop occurred. The row
can be identified by calling the method:
drop_info = treeview.get_dest_row_at_pos(x, y)
|
where (x,
y) is the position passed to the callback
function and drop_info is a 2-tuple containing the
path of a row and a position constant indicating where the drop is with
respect to the row: gtk.TREE_VIEW_DROP_BEFORE,
gtk.TREE_VIEW_DROP_AFTER,
gtk.TREE_VIEW_DROP_INTO_OR_BEFORE or
gtk.TREE_VIEW_DROP_INTO_OR_AFTER. The callback function
could be something like:
treeview.enable_model_drag_dest([('text/plain', 0, 0)],
gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
treeview.connect("drag-data-received", drag_data_received_cb)
...
...
def drag_data_received_cb(treeview, context, x, y, selection, info, timestamp):
drop_info = treeview.get_dest_row_at_pos(x, y)
if drop_info:
model = treeview.get_model()
path, position = drop_info
data = selection.data
# do something with the data and the model
...
return
...
|
If a row is being used as a drag source it must handle the
Widget "drag-data-get" signal that populates a
selection with the data to be passed back to the drag drop destination with
a callback function with the signature:
def callback(widget, drag_context, selection_data, info, timestamp)
|
The parameters to callback are similar to
those of the "drag-data-received" callback function. Since the callback is
not passed a tree path or any easy way of retrieving information about the
row being dragged, we assume that the row being dragged is selected and the
selection mode is gtk.SELECTION_SINGLE or
gtk.SELECTION_BROWSE so we can retrieve the row by
getting the TreeSelection and retrieving the tree
model and TreeIter pointing at the row. For example,
text from a row could be passed in the drag drop by:
...
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", drag_data_get_cb)
...
...
def drag_data_get_cb(treeview, context, selection, info, timestamp):
treeselection = treeview.get_selection()
model, iter = treeselection.get_selected()
text = model.get_value(iter, 1)
selection.set('text/plain', 8, text)
return
...
|
The TreeView can be disabled as a drag
source and drop destination by using the methods:
treeview.unset_rows_drag_source()
treeview.unset_rows_drag_dest()
|
14.9.3. TreeView Drag and Drop ExampleA simple example program is needed to pull together the pieces
of code described above. This example (treeviewdnd.py) is a list that URLs
can be dragged from and dropped on. Also the URLs in the list can be
reordered by dragging and dropping within the
TreeView. A couple of buttons are provided to clear
the list and to clear a selected item.
1 #!/usr/bin/env python
2
3 # example treeviewdnd.py
4
5 import pygtk
6 pygtk.require('2.0')
7 import gtk
8
9 class TreeViewDnDExample:
10
11 TARGETS = [
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 # close the window and quit
18 def delete_event(self, widget, event, data=None):
19 gtk.main_quit()
20 return False
21
22 def clear_selected(self, button):
23 selection = self.treeview.get_selection()
24 model, iter = selection.get_selected()
25 if iter:
26 model.remove(iter)
27 return
28
29 def __init__(self):
30 # Create a new window
31 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
32
33 self.window.set_title("URL Cache")
34
35 self.window.set_size_request(200, 200)
36
37 self.window.connect("delete_event", self.delete_event)
38
39 self.scrolledwindow = gtk.ScrolledWindow()
40 self.vbox = gtk.VBox()
41 self.hbox = gtk.HButtonBox()
42 self.vbox.pack_start(self.scrolledwindow, True)
43 self.vbox.pack_start(self.hbox, False)
44 self.b0 = gtk.Button('Clear All')
45 self.b1 = gtk.Button('Clear Selected')
46 self.hbox.pack_start(self.b0)
47 self.hbox.pack_start(self.b1)
48
49 # create a liststore with one string column to use as the model
50 self.liststore = gtk.ListStore(str)
51
52 # create the TreeView using liststore
53 self.treeview = gtk.TreeView(self.liststore)
54
55 # create a CellRenderer to render the data
56 self.cell = gtk.CellRendererText()
57
58 # create the TreeViewColumns to display the data
59 self.tvcolumn = gtk.TreeViewColumn('URL', self.cell, text=0)
60
61 # add columns to treeview
62 self.treeview.append_column(self.tvcolumn)
63 self.b0.connect_object('clicked', gtk.ListStore.clear, self.liststore)
64 self.b1.connect('clicked', self.clear_selected)
65 # make treeview searchable
66 self.treeview.set_search_column(0)
67
68 # Allow sorting on the column
69 self.tvcolumn.set_sort_column_id(0)
70
71 # Allow enable drag and drop of rows including row move
72 self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
73 self.TARGETS,
74 gtk.gdk.ACTION_DEFAULT|
75 gtk.gdk.ACTION_MOVE)
76 self.treeview.enable_model_drag_dest(self.TARGETS,
77 gtk.gdk.ACTION_DEFAULT)
78
79 self.treeview.connect("drag_data_get", self.drag_data_get_data)
80 self.treeview.connect("drag_data_received",
81 self.drag_data_received_data)
82
83 self.scrolledwindow.add(self.treeview)
84 self.window.add(self.vbox)
85 self.window.show_all()
86
87 def drag_data_get_data(self, treeview, context, selection, target_id,
88 etime):
89 treeselection = treeview.get_selection()
90 model, iter = treeselection.get_selected()
91 data = model.get_value(iter, 0)
92 selection.set(selection.target, 8, data)
93
94 def drag_data_received_data(self, treeview, context, x, y, selection,
95 info, etime):
96 model = treeview.get_model()
97 data = selection.data
98 drop_info = treeview.get_dest_row_at_pos(x, y)
99 if drop_info:
100 path, position = drop_info
101 iter = model.get_iter(path)
102 if (position == gtk.TREE_VIEW_DROP_BEFORE
103 or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
104 model.insert_before(iter, [data])
105 else:
106 model.insert_after(iter, [data])
107 else:
108 model.append([data])
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 = TreeViewDnDExample()
118 main()
|
The result of running the example program treeviewdnd.py is illustrated in
Figure 14.8, “TreeView Drag and Drop Example”: The key to allowing both external drag and drop and internal row
reordering is the organization of the targets (the
TARGETS attribute - line 11). An application specific
target (MY_TREE_MODEL_ROW) is created and used to
indicate a drag and drop within the TreeView by
setting the gtk.TARGET_SAME_WIDGET flag. By setting this
as the first target the drag destination will attempt to match it first with
the drag source targets. Next the source drag actions must include
gtk.gdk.ACTION_MOVE and
gtk.gdk.ACTION_DEFAULT (see lines 72-75). When the
destination is receiving the data from the source, if the
DragContext action is
gtk.gdk.ACTION_MOVE the source is told to delete the data
(in this case the row) by calling the DragContext
method finish() (see lines 109-110). The
TreeView provides a number of internal functions that
we are leveraging to drag, drop and delete the data.
|