#50070 - tree view DND API



I sat down last night and took a look at #50070 - which is to evaluate
the current GtkTreeView DND API. I was hoping that someone less
familiar with the DND internals  would have a chance to look at it,
but that hasn't happened, and we need to finish closing API issues
for 2.0.

At this point, the bug isn't really to fix the DND API, but rather
to figure out a combination of small tweeks and removing unfinished
API so that we'll have basic DND capabilities now, and extend 
them in the future.


Like the general GTK+ DND API, the problem with making a nice concise
API is that there are many possible levels of the simplicity / power
tradeoff.

 * Is DND of entire rows only or is DND to/from cells possible? From
   subportions of cells?

 * Does the treeview automatically provide drop feedback?

 * Does the tree-view automatically provide highlight indication?
   What about features like hover-to-open-rows and autoscrolling?

 * Does the tree view do anything on drop automatically? Allowing
   reordering of list stores / tree stores is a very common
   desire.


The concept of the API that Havoc had when writing it is basically

   +-------------------------------+
   | Automatic connection to model |
   +-------------------------------+
   +------------------------------+
   | Convenience TreeView DND API |
   +------------------------------+
   +--------------+
   | GTK+ DND API | + TreeView utility functions
   +--------------+

If you write to the lowest level, then everything is possible, but
nothing is done for you.  If you write to the middle layer, then all
the bookkeeping of rows is done for you, as well as things like
hover-to-open and auto-scroll, but you have to supply 

If you write to the top level, then you should be able to set up
row reordering with only a few lines of code, but

Each of the blocks in the above should be public API and should only
use public API in the lower blocks. Unfortunately, that's not the
case currently. The top two levels are currently dependent 
upon each other and in fact, the API for the middle level is
not really there.


I'll go over the current API; there are basically three pieces. 
A set of utility functions - conceptually these augment the GTK+ DND
API to form the bottom layer.

void       gtk_tree_view_set_drag_dest_row    (GtkTreeView              *tree_view,
					       GtkTreePath              *path,
					       GtkTreeViewDropPosition   pos);
void       gtk_tree_view_get_drag_dest_row    (GtkTreeView              *tree_view,
					       GtkTreePath             **path,
					       GtkTreeViewDropPosition  *pos);

set/get_drag_dest_row set and get the row that is highlighted for feedback; 


gboolean   gtk_tree_view_get_dest_row_at_pos  (GtkTreeView              *tree_view,
					       gint                      drag_x,
					       gint                      drag_y,
					       GtkTreePath             **path,
					       GtkTreeViewDropPosition  *pos);

Might be a bit better called pos_to_dest_row()...

GdkPixmap *gtk_tree_view_create_row_drag_icon (GtkTreeView              *tree_view,
					       GtkTreePath              *path);

This does creates the "row as the drag icon". It's neat, but unfortunately
also really big and clunky. If the app knows that one cell represents the
entire row - say "file name", it probably would be better to use just that cell
as the drag icon.

gboolean gtk_tree_set_row_drag_data            (GtkSelectionData  *selection_data,
						GtkTreeModel      *tree_model,
						GtkTreePath       *path);
gboolean gtk_tree_get_row_drag_data            (GtkSelectionData  *selection_data,
						GtkTreeModel     **tree_model,
						GtkTreePath      **path);

These functions store a model/path interface in a SelectionData object - this
is useful for doing local tree => tree drops.

Then, there are two interfaces that tree models can implement for doing
"automatic" drag and drop changing the model. These are part of the
top layer.

struct _GtkTreeDragSourceIface
{
  gboolean     (* drag_data_get)        (GtkTreeDragSource   *drag_source,
                                         GtkTreePath         *path,
                                         GtkSelectionData    *selection_data);

  gboolean     (* drag_data_delete)     (GtkTreeDragSource *drag_source,
                                         GtkTreePath       *path);
};

struct _GtkTreeDragDestIface
{
  gboolean     (* drag_data_received) (GtkTreeDragDest   *drag_dest,
                                       GtkTreePath       *dest,
                                       GtkSelectionData  *selection_data);

  gboolean     (* row_drop_possible)  (GtkTreeDragDest   *drag_dest,
                                       GtkTreeModel      *src_model,
                                       GtkTreePath       *src_path,
                                       GtkTreePath       *dest_path);
};

Finally, there are interfaces for turning on the automatic DND -

void gtk_tree_view_set_rows_drag_source   (GtkTreeView              *tree_view,
					   GdkModifierType           start_button_mask,
					   const GtkTargetEntry     *targets,
					   gint                      n_targets,
					   GdkDragAction             actions,
					   GtkTreeViewDraggableFunc  row_draggable_func,
					   gpointer                  user_data);
void gtk_tree_view_set_rows_drag_dest     (GtkTreeView              *tree_view,
					   const GtkTargetEntry     *targets,
					   gint                      n_targets,
					   GdkDragAction             actions,
					   GtkTreeViewDroppableFunc  location_droppable_func,
					   gpointer                  user_data);
void gtk_tree_view_unset_rows_drag_source (GtkTreeView              *tree_view);
void gtk_tree_view_unset_rows_drag_dest   (GtkTreeView              *tree_view);

Its not really clear if these functions are at the top layer or at the
middle layer -- they can only be used with the top layer functionality,
but the GtkTreeViewDraggableFunc/DroppableFunc may be intended to
be part of the middle layer.


Currently:

 * While you can list the targets for drag_dest, the only target that is
   actually useful is GTK_TREE_MODEL_ROW since there is only support
   for local DND.

 * The GtkTreeViewDraggableFunc and GtkTreeViewDroppableFunc functions
   are not actually hooked up at all.

So, what is possible currently? 

 * You can write models that support the GtkTreeDragSource and GtkTreeDragDest
   interfaces and let GTK+ do automatic in-process DND between them.

   If you go this route, you can allow DND _from_ a GtkListStore or GtkTreeStore
   but not _to_ a GtkListStore or GtkTreeStore since the only thing 
   drag_data_received function can do is insert the new row.

 * You can implement everything yourself, with some help from the first
   6 listed convenience function. If you go this route, you won't get 
   hover auto-open or auto-scroll without a _lot_ of work.

Suggestions for small changes:

 * The row_drop_possible method of GtkTreeDragDest should take a 
   a GtkSelectionData rather than a source-model / source-row,
   and the "best matching" target should be used rather than
   GTK_TREE_MODEL_ROW always. This would make it possible to
   use GtkTreeDragDest for things other than straight 
   model => model drags of rows, which is pretty much useless
   except for reordering.

   Problem here is that the selection data would be retrieved
   for every motion, which would kill performance. It should
   be possible to get the selection data only once for an
   enter/leave pair. Even this could be expensive if the data
   was a huge amount of text, but it should typically be OK.

 * We remove row_draggable_func, row_droppable_func from 
   gtk_tree_view_set_rows_drag_source since they are not hooked
   up currently. In order to not loose the capability to have
   header rows, etc, we can add:

    gboolean (* row_draggable) (GtkTreeDragSource   *drag_source,
                                GtkTreePath         *path);

   I'm actually not sure whether row_draggable_func/droppable_func
   were meant to augment the tree-model-interfaces or 
   substitute for them, but I don't think they really
   worked for either.

   I'd also like to rename set_rows_drag_source/dest to something like:

    void gtk_tree_view_enable_model_drag_source (...)
    void gtk_tree_view_enable_model_drag_dest   (...)

   To make it clear that they are at the top layer, not the middle layer.

 * We add:

    void gtk_tree_view_enable_reordering (GtkTreeView *tree_view);
  
   Or something of that nature since enable_model_drag_source/dest
   is not really simple.

This does leave a big chunk of "not there" capability - basically you
won't be able to do custom DND without writing a custom model or doing
everything from scratch. You can, however, derive from
GtkTreeStore/GtkListStore, and override the interface, which is
simple, thought it requires either understanding the type system or
copying a bunch of boilerplate without understanding it.

I think there is a need for a callback/signal based API where
GtkTreeView handles the row/column book-keeping, does feedback
and the app does the rest; this is a GTK+-2.2 thing.

Regards,
                                        Owen

Appendix: What should the middle layer look like.

 
Source side:

 For starting a drag, TreeView needs to know:

    a) Can I drag here
    b) If I drag here, what types and actions are possible
    c) If I drag here, what is the icon.

 This can be simplified by making b) uniform for the entire
 tree. Then this works out to a boolean predicate; c) can
 be done by connecting to ::drag_begin if you provide 
 a call like gtk_tree_get_drag_context_source_row().

 During the drag and drop, we have the operation:

    Get the data

 This could be done as a custom callback, or it could be
 just done with ::drag_data_get if you add get_drag_context_source_path().

 And after the drop, we have the operation:

    Delete the data

 This could be done as a custom callback, or it could be
 just done with ::drag_data_get if you add get_drag_context_source_path().


Target side:

 Here we have the operations:

  Drag motion - can I drop here, and with what action?

  Drag drop - Can I drop here - what data do you want?

  Drag data received - Here is the data
 
 One obvious thing to do here would be to mirror the GTK+
 DND API exactly, with:
  
  ::tree-drag-motion
  ::tree-drag-drop
  ::tree-drag-data-received
  ::tree-drag-data-leave
  
 But with 

    GtkTreePath              *path,
    GtkTreeViewDropPosition   pos

 Subsituting for x,y and also passed to drag-data-received. But
 then you end up with all the additional complexity of the
 three flags: GTK_DEST_DEFAULT_MOTION, GTK_DEST_DEFAULT_HIGHLIGHT,
 GTK_DEST_DEFAULT_DROP.

 An alternative would be to simplify saying that if you want
 full complexity you can always drop to the lowest level. A
 sketch of this would be two callbacks:

  - Here is the data, can you drop it here?
  - Here is the data, it was dropped.

 This is just a little more flexible than specifying all the
 flags in that you have the choice of accepting or not accepting
 the drag.


So, one possibility API would look a lot like our current one:

void gtk_tree_view_set_rows_drag_source   (GtkTreeView              *tree_view,
					   GdkModifierType           start_button_mask,
					   const GtkTargetEntry     *targets,
					   gint                      n_targets,
					   GdkDragAction             actions,
					   GtkTreeViewDraggableFunc  row_draggable_func,
					   gpointer                  user_data);
void gtk_tree_view_set_rows_drag_dest     (GtkTreeView              *tree_view,
					   const GtkTargetEntry     *targets,
					   gint                      n_targets,
					   GdkDragAction             actions,
					   GtkTreeViewDroppableFunc  location_droppable_func,
					   GtkTreeViewDroppedFunc    location_dropped_func,
					   gpointer                  user_data);
void gtk_tree_view_unset_rows_drag_source (GtkTreeView              *tree_view);
void gtk_tree_view_unset_rows_drag_dest   (GtkTreeView              *tree_view);
void gtk_tree_view_get_drag_source_path   (GtkTreeView              *tree_view,
                                           GdkDragContext           *drag_context,
                                           GtkTreePath             **path);

What this would exclude would be:

 - Different targets for different start rows.
 - cell-based instead of row based drag sources / targets. [ Passing in the 
   column is easy, but you'd also need a flag indicating whether the drop
   (though not the drag) was row or cell based, since the highlight would be
   different.)
 - Being able to get the data only on drop. (::drag-motion would always retrieve
   the data.)

Connecting to ::drag_data_get and calling gtk_tree_view_get_source_path()
could also be considered a bit cryptic.



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