GtkTreeView drag-and-drop API



Hi,

I have read through Kris's patch for multi-row DnD in GtkTreeView, and
I have a bunch of comments.  For reference, the patch is here:

http://mail.gnome.org/archives/gtk-devel-list/2003-December/msg00160.html

I'm CC-ing desktop-devel-list because I'm sure people there will have
things to say about DnD in GtkTreeView as well.

The general requirements for DnD in trees are as follows:

 1. The API should be able to do everything that the normal DnD API for
    widgets can do, but it should make things easier for trees --- for
    example, operating with GtkTreePaths rather than plain (x, y)
    coordinates.

 2. Right now the tree drag interfaces only have methods, not signals.
    This makes it hard to provide custom DnD functionality from
    applications, especially if one uses GtkListStore or GtkTreeStore
    --- one has to derive a new class from them, and re-install the
    drag source/dest interfaces.  With signals this would be much
    easier.

 3. Right now, the data models have to carry the drag source/dest
    interfaces with them, but yet one must call
    gtk_tree_view_enable_model_drag_source() and
    gtk_tree_view_enable_model_drag_dest() on the *view*, not the
    model.  These functions take an array of GtkTargetEntry, which
    means that the view-side caller needs to know about which data
    types the model can import/export.  I think this separation is
    wrong.

 4. If we have methods/signals to make the normal GtkWidget drag
    signals easier for tree views, they should have access to the
    GdkDragContext.  This is especially necessary to notify about
    legal drag actions from the destination side, e.g. situations
    where you do not pass GTK_DEST_DEFAULT_MOTION and you call
    gdk_drag_status() by hand --- you can move/copy/link a dragged
    file that is hovering over a directory, but you can only "copy" if
    it is being dragged into an executable.

Source-side API:

 5. The available data types and drag actions may change depending on the
    selection.  However, the simple GtkTreeDragSource::row_draggable()
    method does not let one specify these values from within --- or is
    one supposed to call gtk_tree_view_enable_model_drag_source() with
    each selection change in order to reset the target list?

    I think it would be better to remove/deprecate the
    gtk_tree_view_enable_model_drag_source() function, and add the
    following:

	- A function:	

	  void gtk_tree_view_enable_drag_source (GtkTreeView *tree_view);

	- A signal in GtkTreeViewClass:

	  gboolean maybe_begin_drag (GtkTreeView *tree_view,
				     gint         button,
				     GdkEvent    *event);

       This would get emitted somewhere in GtkTreeView::motion_event
       after crossing the drag threshold, but only if
       gtk_tree_view_enable_drag_source() has been called beforehand.
       The application could then see if it makes sense to start a
       drag, e.g. by looking at the selection.  If starting a drag
       makes sense, it should set up whatever state is necessary, and
       call a function like

	 GdkDragContext *gtk_tree_view_drag_begin (GtkTreeView   *tree_view,
						   GtkTargetList *targets,
						   GdkDragAction  actions,
						   gint           button,
						   GdkEvent      *event);

       The "button" and "event" parameters come from the
       ::maybe_begin_drag() signal, of course.  This function is
       analogous to gtk_drag_begin(), and the difference is that it
       would create a reasonable drag pixmap cursor by default.  The
       tree view, of course, would set up whatever drag state it needs
       and call gtk_drag_begin() on its own.

 6. Why do we need GtkTreeDragSource::drag_data_get() and
    ::multi_drag_data_get()?  The normal GtkWidget::drag_data_get()
    should be enough; moreover, it does expose the GdkDragContext.
    The "path" and "ref_list" arguments, respectively, are anologous
    to looking at the tree selection by hand from within the signal
    handler.

 7. Similar to (6), do we need GtkTreeDragSource::drag_data_delete()
    and ::multi_drag_data_delete()?  The normal
    GtkWidget::drag_data_delete() should be enough; again, one can
    look at the selection to see which rows need to be considered.

Destination-side API:

 8. Similar to (5), I am not sure that gtk_tree_view_enable_model_drag_dest() 
    is the best way to do things --- why is it tied to the model?
    Analogous to (5), perhaps we should just rename it to

      void gtk_tree_view_enable_drag_dest (GtkTreeView          *tree_view,
					   const GtkTargetEntry *targets,
					   gint                  n_targets,
					   GdkDragAction         actions);

 9. The existing GtkTreeDragDest::row_drop_possible() is not
    sufficient, as it has the following limitations:

      - It does not expose the GdkDragContext, nor the timestamp, so
        we cannot use ::row_drop_possible as an equivalent to the
        GtkWidget::drag_motion signal and call gdk_drag_status() on
        our own.

      - It does not tell us whether the user wants to drop on a row,
        or before/after it; that is, it does not make use of
        GtkTreeViewDropPosition.  Since it does not expose the actual
        drop coordinates, either, we cannot figure this out by hand
        with gtk_tree_view_get_dest_row_at_pos().

    The usage case I'm envisioning is as follows.  Say you have a list
    of text strings, displayed in a tree view.  You want to be able to
    do the following things:

      - Reorder the strings within the list.

      - Drag strings from the list to the outside world, exporting
        them as UTF8_STRING.

      - Drag in strings from the outside.  You can either drop a string
        between rows to create a new entry, or drop it in an existing
        row to change its contents.

    To do this, your handler would look like this:

	static GtkTreeViewDropPosition
	my_row_drop_possible_handler (GtkTreeView             *tree_view,
				      GdkDragContext          *context,
				      GtkTreePath             *path,
				      GtkTreeViewDropPosition  position,
				      guint                    time_)
	{
	  GdkDragAction action;
	  gboolean valid;

	  valid = FALSE;

	  if (has_target (context, INTERNAL_REORDER) &&
	      (context->actions & GDK_ACTION_MOVE))
	    {
	      valid = TRUE;
	      action = GDK_ACTION_MOVE;

	      /* Fix up tentative position */
	      if (position == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
		position = GTK_TREE_VIEW_DROP_BEFORE;
	      else if (position == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
		position = GTK_TREE_VIEW_DROP_AFTER;
	    }
	  else if (has_target (context, UTF8_STRING) && 
		   (context->actions & GDK_ACTION_COPY))
	    {
	      valid = TRUE;
	      action = GDK_ACTION_COPY;

	      /* No need to fix up the position, as it *may* go into
	       * an existing row rather than between rows.
	       */
	    }

	  if (valid)
	    {
	      gdk_drag_status (context, action, time_);
	      return position;
	    }
	  else
	    return GTK_TREE_VIEW_DROP_POSITION_INVALID;
        }

    Note the addition of GTK_TREE_VIEW_DROP_POSITION_INVALID.

10. Do we need a special way to indicate that the user wants to drop
    on the blank area of a tree view, or is this handled with
    GTK_TREE_VIEW_DROP_AFTER plus the path of the last row in the
    tree?  What happens when the tree is empty?

11. In addition to the GdkDragContext and the timestamp, the
    ::row_drop_possible() handler may need to know the exact column,
    cell renderer and position over which the cursor is hovering.  A
    pixbuf cell may want to change the pixbuf to an "armed" version
    when you hover on it.  I'm not sure if this is necessary.

12. We may need an equivalent of the GtkWidget::drag_leave() signal
    for rows if we add (11); that would be when you reset the "armed"
    icon to the normal one.

13. Same as before, the GtkTreeDragDest::drag_data_received() needs to
    be able to see the GdkDragContext to determine the proper action
    (which one among move/copy/link/ask in a file manager, for
    instance).

14. I have never had the need to implement my own ::drag_drop()
    handler in normal widgets, so I don't know if we need a similar
    signal in the DnD interfaces for trees.

In summary:

- It would be better to have signals, rather than methods only, for
  the DnD interfaces.  This would make them easy for casual use.

- All the tree-specific drag signals need to have access to the
  GdkDragContext, plus any extra tree-specific information such as the
  GtkTreeViewDropPosition.

- We need to be able to fix up positions --- a suggested "drop before
  or inside" can turn into "drop inside".  We also need to be able to
  say "this is an invalid position, can't drop here"; see (9) and the
  comment at the end.

- I think it's wrong to tie the drag interfaces to the model.  This
  forces the programmer to know which drag types it supports when
  calling gtk_tree_view_drag_model_enable_*().

I hope these comments are useful in designing a better API :)

  Federico





[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]