John Finlay <[EMAIL PROTECTED]> writes:

> Thomas Mills Hinkle wrote:
> 
> >On Mon, 28 Jun 2004 12:03:57 -0400 Thomas Mills Hinkle
> >
> >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.
> >
> Both programs work fine for me with PyGTK CVS and GTK+ 2.4.0.

John is very active working to improve gtk+, so his replies are
authoritative.  I have a few comments to add.

The first example didn't work quite right when I tried it.  It allows
trying to move the last row down off the end of the list and wraps
around to put that row at the top.  It needs to check the boundaries
in the up and down callbacks.  You can make this code easier to
understand by removing path_next(), move_iter() and get_rows() and
using something much simpler like this:

        def downCB (self, *args):
            selection = self.treeview.get_selection()
            model, selected = selection.get_selected()
            if selected is None:
                print 'No selection!'
                return
            path = model.get_path(selected)[0]
            next = path + 1
            if next >= len(model):
                print "Can't move down below the bottom entry"
                return
            swap_rows(model, path, next)
            selection.select_path(next)


    def swap_rows(model, path1, path2):
        ncolumns = model.get_n_columns()
        iter1 = model.get_iter(path1)
        iter2 = model.get_iter(path2)
        row1 = [model.get_value(iter1, n) for n in range(ncolumns)]
        row2 = [model.get_value(iter2, n) for n in range(ncolumns)]
        model[path1] = row2
        model[path2] = row1

> This is exactly the method (with a different target) that GTK+ uses to
> implement reorderable rows using set_reorderable() however you need to
> set the gtk.TARGET_SAME_WIDGET flag to limit the target to the same
> widget. Also you need to rearrange the source target order so that
> EXAMPLE_INTERNAL is matched first if available then text/plain and
> STRING:
> 
>         self.treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
>                                                [
>                                                 
> ('EXAMPLE_INTERNAL',gtk.TARGET_SAME_WIDGET,0),
>                                                 ('text/plain', 0, 1),
>                                                 ('STRING',0,2)
>                                                 ],
>                                                gtk.gdk.ACTION_COPY)
> 
> Likewise for the dest target order:
> 
>         self.treeview.enable_model_drag_dest([
>                                                 
> ('EXAMPLE_INTERNAL',gtk.TARGET_SAME_WIDGET,0),
>                                                 ('text/plain', 0, 1),
>                                                 ('STRING',0,2)],
>                                              gtk.gdk.ACTION_COPY)

As a matter of style and practicality I recommend factoring out the
repeated code:

    targets = [
        ('EXAMPLE_INTERNAL', gtk.TARGET_SAME_WIDGET, 0),
        ('text/plain', 0, 1),
        ('STRING', 0, 2),
        ]

    self.treeview.enable_model_drag_source(
        gtk.gdk.BUTTON1_MASK, targets, gtk.gdk.ACTION_COPY)

    self.treeview.enable_model_drag_dest(targets, gtk.gdk.ACTION_COPY)

> Then in drag_data_received_data()
> Use the selection.target to differentiate the drag sources:
> 
>     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 str(selection.target) == 'EXAMPLE_INTERNAL':
>             mod,moving_iter=treeview.get_selection().get_selected()
>         else: moving_iter=False
>      ....

This part code in the original example is really messy and should be
refactored.  A lot of code can be removed: iter_last(), path_next(),
move_iter() and path_compare() should all go.  The example doesn't do
this, but I think the drag should be ended by calling
drag_context.finish().  When we do this we can have gtk+ remove the
dropped row if needed.  This isn't exactly how I would write this
because this function is way too long, but it's close to the original
code:

    def drag_data_received_data(self, treeview, drag_context, x, y,
                                selection_data, info, eventtime):
        """Handler for 'drag-data-received' that moves dropped TreeView rows."""
        model = treeview.get_model()
        target_path, drop_position = treeview.get_dest_row_at_pos(x, y)
        target = model.get_iter(target_path)

        if str(selection_data.target) == 'EXAMPLE_INTERNAL':
            # Internal drag.  Get selected row, delete when done.
            source = treeview.get_selection().get_selected()[1]
            source_path = model.get_path(source)
            # Can't drop a row onto itself or its descendants.
            if (source_path == target_path
                or model.is_ancestor(source, target)):
                drag_context.finish(success=False, del_=False, time=eventtime)
                return
            source_row = model[source_path]
            delete = True
        else:
            # External drag.  Create a new model row and don't delete.
            source_row = [selection_data.data]
            delete = False

        if (drop_position == gtk.TREE_VIEW_DROP_BEFORE
            or drop_position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
            new = model.insert_before(None, sibling=target, row=source_row)
        else:
            new = model.insert_after(None, sibling=target, row=source_row)
        treeview.get_selection().select_iter(new)

        # Finish the drag and have gtk+ delete the drag source row
        # if it was an internal drag.
        drag_context.finish(success=True, del_=delete, time=eventtime)

(Although we are working with a treestore the example seems to treat
it as a list.  That's why the code above only inserts at the top level
by passing parent=None.)

> Also your drag_data_get_data() method should set the selection data like:
> 
>         selection.set(selection.target, 8, data)
> 
> This should allow reordering internal rows while still allowing
> external drag and drop which is what I'm guessing you want to do.

This suggestion works well.

Looking at the gtk+ docs, I thought the canonical way to do this when
all you care about is text target types would be

        selection.set_text(data)

This would also have the benefit of getting rid of the mysterious
constant 8.  (In the version of pygtk currently in Debian unstable
(2.2.0), SelectionData.set_text() requires two arguments, the string
and the length.  I see that John fixed this on back on May 13.)
Unfortunately when I try this it doesn't work.  The selected data is
always null.  Similarly, using SelectionData.get_text() on the
receiving side instead of SelectionData.data always gives me None.  Do
these functions work in pygtk or am I doing something wrong?
_______________________________________________
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