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/

Reply via email to