On Mon, 28 Jun 2004 12:03:57 -0400 Thomas Mills Hinkle
<[EMAIL PROTECTED]> wrote:
> Actually, if anyone can point me to an example that uses dragndrop to
> reorganize rows, I'd be appreciative. One reason I'm hesitant to work
> up some example code is that my current code is quite hackish -- I'd
> love to see an example of how to do this more elegantly!
Okay, I've gone ahead and put together some examples (based on the
tutorial's treeView dnd example) including both my up&down buttons and
drag&drop. The broken example uses the move_up and move_down calls and
is buggy on my current system, exhibiting the symptoms I described
earlier with the up&down buttons and segfaulting when I drag rows
internally.
The working example, which uses my own code to move the rows, works
smoothly on my system -- correctly allowing buttons and dnd to reorder
rows and handling dnd from other apps.
I still don't know the proper way to differentiate an internal drag from
an external one. A gander at my code will show the hack I've used -- I
define my own dnd type ('EXAMPLE_INTERNAL') and look for it when I
receive the drop signal. This clearly isn't the right solution (in
addition to its inelegance, it also will fail to do the right thing with
dnd between multiple instances of the example application). Can someone
tell me the right way to do this?
Thanks,
Tom
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class TreeViewExample:
# close the window and quit
def delete_event(self, widget, event, data=None):
gtk.main_quit()
return gtk.FALSE
def __init__(self):
# Create a new window
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_title("Treeview Example")
self.window.set_size_request(200, 200)
self.window.connect("delete_event", self.delete_event)
self.scrolledwindow = gtk.ScrolledWindow()
self.vbox = gtk.VBox()
self.hbox = gtk.HButtonBox()
self.vbox.pack_start(self.scrolledwindow, True)
self.vbox.pack_start(self.hbox, False)
self.b0 = gtk.Button(None,stock=gtk.STOCK_GO_UP)
self.b0.connect('clicked',self.upCB)
self.b1 = gtk.Button(None,stock=gtk.STOCK_GO_DOWN)
self.b1.connect('clicked',self.downCB)
self.hbox.pack_start(self.b0)
self.hbox.pack_start(self.b1)
# create a treestore with one string column to use as the model
self.treestore = gtk.TreeStore(str)
self.populate_treestore()
# create the TreeView using treestore
self.treeview = gtk.TreeView(self.treestore)
# set selection mode
self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
# create a CellRenderer to render the data
self.cell = gtk.CellRendererText()
# create the TreeViewColumns to display the data
self.tvcolumn = gtk.TreeViewColumn('URL', self.cell, text=0)
# add columns to treeview
self.treeview.append_column(self.tvcolumn)
# make treeview searchable
self.treeview.set_search_column(0)
# Enable drag and drop of rows
self.treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
[('text/plain', 0, 0),
('EXAMPLE_INTERNAL',0,0),
# EXAMPLE_INTERNAL is a hack to allow me to see
# when I'm the source of my own drag. This would
# break with multiple instances of example. What's
# the right way to do this?
('STRING',0,0)],
gtk.gdk.ACTION_COPY)
self.treeview.enable_model_drag_dest([('text/plain', 0, 0),
('EXAMPLE_INTERNAL',0,0),
('STRING',0,0)],
gtk.gdk.ACTION_COPY)
self.treeview.connect("drag_data_get", self.drag_data_get_data)
self.treeview.connect("drag_data_received",
self.drag_data_received_data)
self.scrolledwindow.add(self.treeview)
self.window.add(self.vbox)
self.window.show_all()
def populate_treestore (self):
for n in range(20):
iter=self.treestore.append(None)
self.treestore.set_value(iter,0,"Row %s"%n)
def upCB (self, *args):
ts, itera = self.treeview.get_selection().get_selected()
if itera:
path=ts.get_path(itera)
prev=path_next(path,-1)
prev_iter=ts.get_iter(prev)
move_iter(ts, itera,sibling=prev_iter,direction="before")
self.treeview.get_selection().select_path(prev)
else: print "No selection!"
def downCB (self, *args):
ts, itera = self.treeview.get_selection().get_selected()
if itera:
next = ts.iter_next(itera)
move_iter(ts, itera,sibling=next,direction="after")
next_path=ts.get_path(next)
self.treeview.get_selection().select_path(path_next(next_path,1))
else: print "No selection!"
def drag_data_get_data(self, treeview, context, selection, target, etime):
treeselection = treeview.get_selection()
model, iter = treeselection.get_selected()
data = model.get_value(iter, 0)
selection.set('text/plain', 8, data)
selection.set('EXAMPLE_INTERNAL', 0, data)
def drag_data_received_data(self, treeview, context, x, y, selection,
info, etime):
mod = treeview.get_model()
data = selection.data
drop_info = treeview.get_dest_row_at_pos(x, y)
if map(lambda x: "%s"%x, context.targets).__contains__('EXAMPLE_INTERNAL'):
mod,moving_iter=treeview.get_selection().get_selected()
else: moving_iter=False
if drop_info:
path, position = drop_info
iter = mod.get_iter(path)
if position == (gtk.TREE_VIEW_DROP_BEFORE
| gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
if moving_iter: move_iter(mod, moving_iter, iter, direction='before')
else: mod.insert_before(None, iter, [data])
treeview.get_selection().select_iter(iter)
# select our new insertion
else:
if moving_iter: move_iter(mod, moving_iter, iter, direction='after')
else: mod.insert_after(None, iter, [data])
# select our new insertion
treeview.get_selection().select_path(path_next(mod.get_path(iter)))
else:
if moving_iter: move_iter(mod, moving_iter, direction='after')
else: mod.append([data])
# select our new insertion
treeview.get_selection().select_iter(iter_last(mod))
def iter_last (mod):
"""surely there's a better way..."""
last = mod.get_iter_first()
flag = True
while flag:
try:
last = mod.get_iter_next(last)
except:
flag = False
return last
def path_next (path, inc=1):
"""Return the path NEXT rows after PATH. Next can be negative, in
which case we get previous paths."""
next=list(path[0:-1])
last=path[-1]
last += inc
if last < 0:
last=0
next.append(last)
next=tuple(next)
return next
def move_iter (mod, iter, sibling=None, parent=None, direction="before"):
"""move_iter will move iter relative to sibling or
parent. Direction (before or after) tells us whether to
insert_before or insert_after (with a sib) or to prepend or append
(with a parent)."""
if direction != "after":
direction = "before"
path = mod.get_path(iter)
if sibling:
dpath = mod.get_path(sibling)
elif parent:
dpath = mod.get_path(parent)
else:
dpath = ()
rowdata = get_rows(mod, iter)
def insert_new (parent):
"""A little subroutine to insert our row. We'll call this at the appropriate
time depending on the order of source and destination iters"""
if not parent:
parent=None
if len(dpath) > 1:
parent=mod.get_iter(dpath[0:-1])
if parent==sibling or not sibling:
"""If our parent is our destination iter or if we have
only a parent specified, we're moving inside, not before"""
if direction=="before":
mod.append(parent, rowdata)
else:
mod.prepend(parent, rowdata)
elif direction=="before":
mod.insert_before(parent,sibling,rowdata)
else:
mod.insert_after(parent,sibling,rowdata)
# if the source is before the destination, we add then remove. otherwise, we remove then add.
path_last = path_compare(path,dpath)
if path_last:
mod.remove(iter)
insert_new(parent)
elif path_last==0: debug("Moving into my self is nonsensical!",0)
else:
insert_new(parent)
mod.remove(iter)
def path_compare (p1, p2):
"""Return 1 if p1 > p2, 0 if p1 = p2 and -1 if p1 < p2
Greater than means comes after."""
flag = True
retval = None
n = 0
while flag:
if len(p1)>n and len(p2)>n:
if p1[n] > p2[n]:
retval=1
flag=False
elif p1[n] < p2[n]:
retval=-1
flag=False
else: flag=True
elif len(p1)<=n and len(p2)<=n:
## if we're both too short, we're done comparing and we're equal
retval=0
flag=False
else:
## otherwise one of these is greater (the longer path comes after/is greater than the shorter)
if len(p1) > len(p2):
retval=1
flag=False
else:
retval=-1
flag=False
n += 1
return retval
def get_rows (mod, iter):
"""Grab all values in iter and return as a list suitable for
feeding to 'row' argument of append/prepend/insert"""
n = 0
flag = True
row = []
while flag:
try:
row.append(mod.get_value(iter,n))
except:
flag=False
n += 1
return row
def main():
gtk.main()
if __name__ == "__main__":
treeviewex = TreeViewExample()
main()
#!/usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk
class TreeViewExample:
# close the window and quit
def delete_event(self, widget, event, data=None):
gtk.main_quit()
return gtk.FALSE
def __init__(self):
# Create a new window
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.set_title("Treeview Example")
self.window.set_size_request(200, 200)
self.window.connect("delete_event", self.delete_event)
self.scrolledwindow = gtk.ScrolledWindow()
self.vbox = gtk.VBox()
self.hbox = gtk.HButtonBox()
self.vbox.pack_start(self.scrolledwindow, True)
self.vbox.pack_start(self.hbox, False)
self.b0 = gtk.Button(None,stock=gtk.STOCK_GO_UP)
self.b0.connect('clicked',self.upCB)
self.b1 = gtk.Button(None,stock=gtk.STOCK_GO_DOWN)
self.b1.connect('clicked',self.downCB)
self.hbox.pack_start(self.b0)
self.hbox.pack_start(self.b1)
# create a treestore with one string column to use as the model
self.treestore = gtk.TreeStore(str)
self.populate_treestore()
# create the TreeView using treestore
self.treeview = gtk.TreeView(self.treestore)
# set selection mode
self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
# create a CellRenderer to render the data
self.cell = gtk.CellRendererText()
# create the TreeViewColumns to display the data
self.tvcolumn = gtk.TreeViewColumn('URL', self.cell, text=0)
# add columns to treeview
self.treeview.append_column(self.tvcolumn)
# make treeview searchable
self.treeview.set_search_column(0)
# Enable drag and drop of rows
self.treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
[('text/plain', 0, 0),
('EXAMPLE_INTERNAL',0,0),
# EXAMPLE_INTERNAL is a hack to allow me to see
# when I'm the source of my own drag. This would
# break with multiple instances of example. What's
# the right way to do this?
('STRING',0,0)],
gtk.gdk.ACTION_COPY)
self.treeview.enable_model_drag_dest([('text/plain', 0, 0),
('EXAMPLE_INTERNAL',0,0),
('STRING',0,0)],
gtk.gdk.ACTION_COPY)
self.treeview.connect("drag_data_get", self.drag_data_get_data)
self.treeview.connect("drag_data_received",
self.drag_data_received_data)
self.scrolledwindow.add(self.treeview)
self.window.add(self.vbox)
self.window.show_all()
def populate_treestore (self):
for n in range(20):
iter=self.treestore.append(None)
self.treestore.set_value(iter,0,"Row %s"%n)
def upCB (self, *args):
ts, itera = self.treeview.get_selection().get_selected()
if itera:
path=ts.get_path(itera)
prev=path_next(path,-1)
prev_iter=ts.get_iter(prev)
ts.move_before(itera,prev_iter)
self.treeview.get_selection().select_path(prev)
else: print "No selection!"
def downCB (self, *args):
ts, itera = self.treeview.get_selection().get_selected()
if itera:
next = ts.iter_next(itera)
ts.move_after(itera,next)
next_path=ts.get_path(next)
self.treeview.get_selection().select_path(path_next(next_path,1))
else: print "No selection!"
def drag_data_get_data(self, treeview, context, selection, target, etime):
treeselection = treeview.get_selection()
model, iter = treeselection.get_selected()
data = model.get_value(iter, 0)
selection.set('text/plain', 8, data)
selection.set('EXAMPLE_INTERNAL', 0, data)
def drag_data_received_data(self, treeview, context, x, y, selection,
info, etime):
mod = treeview.get_model()
data = selection.data
drop_info = treeview.get_dest_row_at_pos(x, y)
if map(lambda x: "%s"%x, context.targets).__contains__('EXAMPLE_INTERNAL'):
mod,moving_iter=treeview.get_selection().get_selected()
else: moving_iter=False
if drop_info:
path, position = drop_info
iter = mod.get_iter(path)
if position == (gtk.TREE_VIEW_DROP_BEFORE
| gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
if moving_iter: mod.move_before(moving_iter, iter)
else: mod.insert_before(None, iter, [data])
# select our new insertion
treeview.get_selection().select_iter(iter)
else:
if moving_iter: mod.move_after(moving_iter, iter)
else: mod.insert_after(None, iter, [data])
# select our new insertion
treeview.get_selection().select_path(path_next(mod.get_path(iter)))
else:
if moving_iter:
last=mod.iter_last(mod)
mod.move_after(moving_iter, last)
else:
mod.append([data])
# select our new insertion
treeview.get_selection().select_iter(iter_last(mod))
def iter_last (mod):
"""surely there's a better way..."""
last = mod.get_iter_first()
flag = True
while flag:
try:
last = mod.get_iter_next(last)
except:
flag = False
return last
def path_next (path, inc=1):
"""Return the path NEXT rows after PATH. Next can be negative, in
which case we get previous paths."""
next=list(path[0:-1])
last=path[-1]
last += inc
if last < 0:
last=0
next.append(last)
next=tuple(next)
return next
def main():
gtk.main()
if __name__ == "__main__":
treeviewex = TreeViewExample()
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/