Walter Anger <[EMAIL PROTECTED]> writes:
> I couldn't find any example of a gtk.TreeView fully reorderable per
> drag and drop, neither in python nor in C.
> gtk.TreeView.set_reorderable() has many drawbacks, most annoyingly
> those that one cannot "parent" an iter. but, as dnd in a
> gtk.TreeView is a most demanded feature, I fiddled around some
> weeks. It turned out not an easy task for a beginner in pygtk,
> python und programming as well.
>
> well, the attachment is a working example. please send comments of
> all kinds - suggestions on enhancement, error handling, style, etc.
I think your example is splendid, thanks for sharing it with the list.
Here are a few thoughts that I hope you find helpful.
Pygtk already includes the method TreeModel.is_ancestor() that does
the same thing as checkSanity().
The pygtk bindings make the TreeStore and ListStore models more
convenient to use than the standard gtk+ C binding. Pygtk allows you
to add a row directly with the TreeModel.append() and
TreeModel.insert() methods instead of requiring a separate call to
TreeModel.set_value(). See the pygtk FAQ topic 13.2 for more
information.
For example, you can save one line by filling your TreeStore like this:
for item in data:
model.append(parent=None, row=item)
I have used keyword arguments only to make the parameters clear.
The pygtk binding helps the iterCopy() method even more.
def iterCopy(self, treeview, model, iter_to_copy, target_iter, pos):
source_row = model[iter_to_copy]
if (pos == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE
or pos == gtk.TREE_VIEW_DROP_INTO_OR_AFTER):
new_iter = model.append(parent=target_iter, row=source_row)
elif pos == gtk.TREE_VIEW_DROP_BEFORE:
new_iter = model.insert_before(
parent=None, sibling=target_iter, row=source_row)
elif pos == gtk.TREE_VIEW_DROP_AFTER:
new_iter = model.insert_after(
parent=None, sibling=target_iter, row=source_row)
for i in range(model.iter_n_children(iter_to_copy)):
next_iter_to_copy = model.iter_nth_child(iter_to_copy, i)
self.iterCopy(treeview, model, next_iter_to_copy,
new_iter, gtk.TREE_VIEW_DROP_INTO_OR_BEFORE)
Another benefit is that setting the model rows this way doesn't
require that we know how many columns are in the model. The method
can be used for treemodels with any number of rows. (Also, the
treeview parameter isn't used so you could eliminate it.)
(That's a nice use of recursion to copy the children. If you're new
to programming, congratulations -- you have a talent for it.)
The program needs [("Example", 0, 0)] twice, so it's probably best to
name it by using a variable. GTK+ calls this an array of
gtkTargetEntry, so we can make the program more self documenting like
this:
target_entries = [("example", gtk.TARGET_SAME_WIDGET, 0)]
treeview.enable_model_drag_source(
gtk.gdk.BUTTON1_MASK, target_entries, gtk.gdk.ACTION_MOVE)
treeview.enable_model_drag_dest(target_entries, gtk.gdk.ACTION_MOVE)
I changed ACTION_COPY to ACTION_MOVE since I think that better
reflects the intent, although it doesn't change the behavior. Also, I
use a gtkTargetFlag that specifies same widget drops only, since we
don't handle drops from outside the treeview or from other
applications.
Here's an alternate way to add the TreeViewColumns to the TreeView.
It isn't better than your code, but it shows a little shortcut that
you can use sometimes. The TreeView.insert_column_with_attributes()
method is very handy, but its name is too long. This uses a Python
idiom to make the code more concise:
renderer = gtk.CellRendererText()
tv_insert = treeview.insert_column_with_attributes
tv_insert(-1, "Integer", renderer, text=0)
tv_insert(-1, "String", renderer, text=1)
I have attached code that I modified a bit from your example to
correctly set the expanded/collapsed state of copied rows. It also
includes some comments and some completely gratuitous variable and
function name changes. Thanks again for your interesting example.
#!/usr/bin/env python
try:
import pygtk; pygtk.require("2.0")
except:
pass
import gtk
def treeview_expand_to_path(treeview, path):
"""Expand row at path, expanding any ancestors as needed.
This function is provided by gtk+ >=2.2, but it is not yet wrapped
by pygtk 2.0.0."""
for i in range(len(path)):
treeview.expand_row(path[:i+1], open_all=False)
def treeview_copy_row(treeview, model, source, target, drop_position):
"""Copy tree model rows from treeiter source into, before or after treeiter target.
All children of the source row are also copied and the
expanded/collapsed status of each row is maintained."""
source_row = model[source]
if drop_position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE:
new = model.prepend(parent=target, row=source_row)
elif drop_position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER:
new = model.append(parent=target, row=source_row)
elif drop_position == gtk.TREE_VIEW_DROP_BEFORE:
new = model.insert_before(
parent=None, sibling=target, row=source_row)
elif drop_position == gtk.TREE_VIEW_DROP_AFTER:
new = model.insert_after(
parent=None, sibling=target, row=source_row)
# Copy any children of the source row.
for n in range(model.iter_n_children(source)):
child = model.iter_nth_child(source, n)
treeview_copy_row(treeview, model, child, new,
gtk.TREE_VIEW_DROP_INTO_OR_BEFORE)
# If the source row is expanded, expand the newly copied row
# also. We must add at least one child before we can expand,
# so we expand here after the children have been copied.
source_is_expanded = treeview.row_expanded(model.get_path(source))
if source_is_expanded:
treeview_expand_to_path(treeview, model.get_path(new))
def treeview_on_drag_data_received(treeview, drag_context, x, y,
selection_data, info, eventtime):
"""Handler for 'drag-data-received' that moves dropped TreeView rows. """
target_path, drop_position = treeview.get_dest_row_at_pos(x, y)
model, source = treeview.get_selection().get_selected()
target = model.get_iter(target_path)
if not model.is_ancestor(source, target):
treeview_copy_row(treeview, model, source, target, drop_position)
# If the drop has created a new child (drop into), expand
# the parent so the user can see the newly copied row.
if (drop_position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE
or drop_position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER):
treeview.expand_row(target_path, open_all=False)
# Finish the drag and have gtk+ delete the drag source rows.
drag_context.finish(success=True, del_=True, time=eventtime)
else:
drag_context.finish(success=False, del_=False, time=eventtime)
def treeview_setup_dnd(treeview):
"""Setup a treeview to move rows using drag and drop. """
target_entries = [('example', gtk.TARGET_SAME_WIDGET, 0)]
treeview.enable_model_drag_source(
gtk.gdk.BUTTON1_MASK, target_entries, gtk.gdk.ACTION_MOVE)
treeview.enable_model_drag_dest(target_entries, gtk.gdk.ACTION_MOVE)
treeview.connect('drag-data-received', treeview_on_drag_data_received)
if __name__ == '__main__':
window = gtk.Window()
window.connect("delete_event", gtk.mainquit)
window.set_default_size(250, 350)
scrolledwin = gtk.ScrolledWindow()
scrolledwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
window.add(scrolledwin)
data = [[0, "zero"], [1, "one"], [2, "two"], [3, "three"],
[4, "four"], [5, "five"], [6, "six"], [7, "seven"],]
model = gtk.TreeStore(int, str)
for item in data:
model.append(parent=None, row=item)
treeview = gtk.TreeView(model)
scrolledwin.add(treeview)
cell = gtk.CellRendererText()
tv_insert = treeview.insert_column_with_attributes
tv_insert(-1, 'Integer', cell, text=0)
tv_insert(-1, 'String', cell, text=1)
treeview_setup_dnd(treeview)
window.show_all()
gtk.main()
_______________________________________________
pygtk mailing list [EMAIL PROTECTED]
http://www.daa.com.au/mailman/listinfo/pygtk
Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/