Multiple item DnD for GtkTreeView



Hello,

Somebody just had to do it for 2.4. So I decided to sit down today and
implemented multi item DnD for GtkTreeView (it was about time).

Attached is a first patch, which is (of course), not quite ready yet. It
is, however, a working implementation, but has a few issues. I want to
discuss the new API first, when there is an agreement on the new API, I
can continue and fix up the issues.

The new API basically consists of:
 - 3 new methods in GtkDragSourceIface: multi_row_draggable,
multi_drag_data_get, multi_drag_data_delete.
 - a few new public functions:
     gtk_tree_drag_source_multi_row_draggable(),
     gtk_tree_drag_source_multi_drag_data_get(),
     gtk_tree_drag_source_multi_drag_data_delete(),
     gtk_tree_set_multi_row_drag_data(),
     gtk_tree_get_multi_row_drag_data(),
     gtk_tree_row_drag_data_is_multi().
   For the exact prototypes, please see the patch.

The patch contains an implementation of these functions, support for
multiple item DnD dragging in gtktreeview.c and an example
implementation of the new interface methods in GtkListStore.

Issues / steps to take after the API is approved:
1) I added a nasty hack to gtk_tree_view_button_press, so I could
test the new code. This breaks the behavior of the treeview and will of
course be fixed before I commit the DnD code. The offending lines are:

-      if (event->type == GDK_BUTTON_PRESS)
+      selection = gtk_tree_view_get_selection (tree_view);
+      if (event->type == GDK_BUTTON_PRESS
+          && !gtk_tree_selection_path_is_selected (selection, path))

The solution is to move a lot of stuff from gtk_tree_view_button_press
to gtk_tree_view_button_release. This is also needed in order to
implement drag/hover selection (which is needed for GtkComboBox). I will
write a patch for this as soon as the new DnD API is approved (:. Though
there probably are a lot of compatibility problems here ...

2) I assume the format of the things stored in selection data are part
of the API/ABI. I extended the format used in treednd to allow for lists
of paths, without breaking the single path case. So this change will not
affect older applications. So I assume this is a non-issue (:

3) The new functions/methods sometimes accept/return a list of paths and
sometimes a list of rowreferences. I have tried to pick the best option
for each function, to avoid unnecessary path -> ref/ref -> path
conversions. But maybe it is confusing?

4) Of course all models implementing GtkTreeDragSource will need to
implement the 3 new interface methods. I will do this for all models in
gtk+ after the API has been approved.


I would like people to look at the API and give
suggestions/opinions/etc. I would also like at least Jonathan and Owen
to look at the implementation and approve the patch, of course as time
permits.


Time for sleep; good night,



	-Kris
Index: gtktreednd.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktreednd.c,v
retrieving revision 1.8
diff -u -p -r1.8 gtktreednd.c
--- gtktreednd.c	9 Sep 2003 23:13:38 -0000	1.8
+++ gtktreednd.c	21 Dec 2003 02:46:51 -0000
@@ -155,6 +155,48 @@ gtk_tree_drag_source_drag_data_get    (G
   return (* iface->drag_data_get) (drag_source, path, selection_data);
 }
 
+
+gboolean
+gtk_tree_drag_source_multi_row_draggable (GtkTreeDragSource *drag_source,
+                                          GList             *path_list)
+{
+  GtkTreeDragSourceIface *iface = GTK_TREE_DRAG_SOURCE_GET_IFACE (drag_source);
+
+  g_return_val_if_fail (path_list != NULL, FALSE);
+
+  if (iface->multi_row_draggable)
+    return (* iface->multi_row_draggable) (drag_source, path_list);
+  else
+    return FALSE; /* safe bet */
+}
+
+gboolean
+gtk_tree_drag_source_multi_drag_data_get (GtkTreeDragSource *drag_source,
+                                          GList             *ref_list,
+                                          GtkSelectionData  *selection_data)
+{
+  GtkTreeDragSourceIface *iface = GTK_TREE_DRAG_SOURCE_GET_IFACE (drag_source);
+
+  g_return_val_if_fail (iface->multi_drag_data_get != NULL, FALSE);
+  g_return_val_if_fail (ref_list != NULL, FALSE);
+  g_return_val_if_fail (selection_data != NULL, FALSE);
+
+  return (* iface->multi_drag_data_get) (drag_source, ref_list, selection_data);
+}
+
+gboolean
+gtk_tree_drag_source_multi_drag_data_delete (GtkTreeDragSource *drag_source,
+                                             GList             *ref_list)
+{
+  GtkTreeDragSourceIface *iface = GTK_TREE_DRAG_SOURCE_GET_IFACE (drag_source);
+
+  g_return_val_if_fail (iface->multi_drag_data_delete != NULL, FALSE);
+  g_return_val_if_fail (ref_list != NULL, FALSE);
+
+  return (* iface->multi_drag_data_delete) (drag_source, ref_list);
+}
+
+
 /**
  * gtk_tree_drag_dest_drag_data_received:
  * @drag_dest: a #GtkTreeDragDest
@@ -217,6 +259,7 @@ typedef struct _TreeRowData TreeRowData;
 
 struct _TreeRowData
 {
+  guint multi_dnd_mode;
   GtkTreeModel *model;
   gchar path[4];
 };
@@ -246,24 +289,19 @@ gtk_tree_set_row_drag_data (GtkSelection
   g_return_val_if_fail (GTK_IS_TREE_MODEL (tree_model), FALSE);
   g_return_val_if_fail (path != NULL, FALSE);
 
-  if (selection_data->target != gdk_atom_intern ("GTK_TREE_MODEL_ROW", FALSE))
-    return FALSE;
-  
   path_str = gtk_tree_path_to_string (path);
-
   len = strlen (path_str);
 
-  /* the old allocate-end-of-struct-to-hold-string trick */
+  /* the old allocate-end-of-struct-to-hold-string-trick */
   struct_size = sizeof (TreeRowData) + len + 1 -
-    (sizeof (TreeRowData) - G_STRUCT_OFFSET (TreeRowData, path));
-
-  trd = g_malloc (struct_size); 
+                (sizeof (TreeRowData) - G_STRUCT_OFFSET (TreeRowData, path));
 
+  trd = g_malloc (struct_size);
   strcpy (trd->path, path_str);
-
   g_free (path_str);
-  
+
   trd->model = tree_model;
+  trd->multi_dnd_mode = FALSE;
   
   gtk_selection_data_set (selection_data,
                           gdk_atom_intern ("GTK_TREE_MODEL_ROW", FALSE),
@@ -318,6 +356,9 @@ gtk_tree_get_row_drag_data (GtkSelection
 
   trd = (void*) selection_data->data;
 
+  if (trd->multi_dnd_mode == TRUE)
+    return FALSE;
+
   if (tree_model)
     *tree_model = trd->model;
 
@@ -326,3 +367,123 @@ gtk_tree_get_row_drag_data (GtkSelection
   
   return TRUE;
 }
+
+gboolean
+gtk_tree_set_multi_row_drag_data (GtkSelectionData *selection_data,
+                                  GtkTreeModel     *tree_model,
+                                  GList            *ref_list)
+{
+  GList *list;
+  GList *strings = NULL;
+  gchar *pos;
+  gint strings_length = 0;
+  gint struct_size;
+  TreeRowData *trd;
+
+  g_return_val_if_fail (selection_data != NULL, FALSE);
+  g_return_val_if_fail (GTK_IS_TREE_MODEL (tree_model), FALSE);
+  g_return_val_if_fail (ref_list != NULL, FALSE);
+
+  trd = g_new0 (TreeRowData, 1);
+  trd->multi_dnd_mode = FALSE;
+  trd->model = tree_model;
+
+  for (list = ref_list; list; list = list->next)
+    {
+      GtkTreePath *path = gtk_tree_row_reference_get_path (list->data);
+      gchar *str = gtk_tree_path_to_string (path);
+
+      gtk_tree_path_free (path);
+
+      strings_length += strlen (str) + 1;
+      strings = g_list_append (strings, str);
+    }
+
+  /* old allocate-end-of-string-to-hold-string-list-trick */
+  struct_size = sizeof (TreeRowData) + strings_length + 1 -
+                (sizeof (TreeRowData) - G_STRUCT_OFFSET (TreeRowData, path));
+
+  trd = g_malloc (struct_size);
+  trd->model = tree_model;
+  trd->multi_dnd_mode = TRUE;
+
+  /* serialize our list of paths to a single string. paths are terminated
+   * with their own \0, at the end there is a second \0 to indicate EOL
+   */
+  for (list = strings, pos = trd->path; list; list = list->next)
+    {
+      strcpy (pos, list->data);
+      pos += strlen (list->data) + 1;
+
+      g_free (list->data);
+    }
+
+  *pos = '\0';
+  g_list_free (strings);
+
+  gtk_selection_data_set (selection_data,
+                          gdk_atom_intern ("GTK_TREE_MODEL_ROW", FALSE),
+                          8, /* bytes */
+                          (void *)trd,
+                          struct_size);
+
+  g_free (trd);
+
+  return TRUE;
+}
+
+gboolean
+gtk_tree_get_multi_row_drag_data (GtkSelectionData  *selection_data,
+                                  GtkTreeModel     **tree_model,
+                                  GList            **path_list)
+{
+  TreeRowData *trd;
+
+  g_return_val_if_fail (selection_data != NULL, FALSE);
+
+  if (tree_model)
+    *tree_model = NULL;
+  if (path_list)
+    *path_list = NULL;
+
+  if (selection_data->length < 0)
+    return FALSE;
+
+  trd = (void *)selection_data->data;
+
+  if (trd->multi_dnd_mode == FALSE)
+    return FALSE;
+
+  if (tree_model)
+    *tree_model = trd->model;
+
+  if (path_list)
+    {
+      gchar *pos = trd->path;
+
+      /* deserialize the list of paths */
+      while (*pos != '\0')
+        {
+          GtkTreePath *path = gtk_tree_path_new_from_string (pos);
+
+          *path_list = g_list_append (*path_list, gtk_tree_row_reference_new (trd->model, path));
+          gtk_tree_path_free (path);
+
+          pos += strlen (pos) + 1;
+        }
+    }
+
+  return TRUE;
+}
+
+gboolean
+gtk_tree_row_drag_data_is_multi (GtkSelectionData *selection_data)
+{
+  TreeRowData *trd;
+
+  g_return_val_if_fail (selection_data != NULL, FALSE);
+
+  trd = (void *) selection_data->data;
+
+  return trd->multi_dnd_mode;
+}
Index: gtktreednd.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktreednd.h,v
retrieving revision 1.5
diff -u -p -r1.5 gtktreednd.h
--- gtktreednd.h	9 Jan 2002 06:52:19 -0000	1.5
+++ gtktreednd.h	21 Dec 2003 02:46:51 -0000
@@ -48,8 +48,17 @@ struct _GtkTreeDragSourceIface
                                          GtkTreePath         *path,
                                          GtkSelectionData    *selection_data);
 
-  gboolean     (* drag_data_delete)     (GtkTreeDragSource *drag_source,
-                                         GtkTreePath       *path);
+  gboolean     (* drag_data_delete)     (GtkTreeDragSource   *drag_source,
+                                         GtkTreePath         *path);
+
+  /* equivalents for multiple items */
+  gboolean     (* multi_row_draggable)    (GtkTreeDragSource   *drag_source,
+                                           GList               *path_list);
+  gboolean     (* multi_drag_data_get)    (GtkTreeDragSource   *drag_source,
+                                           GList               *ref_list,
+                                           GtkSelectionData    *selection_data);
+  gboolean     (* multi_drag_data_delete) (GtkTreeDragSource   *drag_source,
+                                           GList               *ref_list);
 };
 
 GType           gtk_tree_drag_source_get_type   (void) G_GNUC_CONST;
@@ -69,6 +78,16 @@ gboolean gtk_tree_drag_source_drag_data_
                                                 GtkTreePath       *path,
                                                 GtkSelectionData  *selection_data);
 
+
+gboolean gtk_tree_drag_source_multi_row_draggable    (GtkTreeDragSource *drag_source,
+                                                      GList             *path_list);
+gboolean gtk_tree_drag_source_multi_drag_data_get    (GtkTreeDragSource *drag_source,
+                                                      GList             *ref_list,
+                                                      GtkSelectionData  *selection_data);
+gboolean gtk_tree_drag_source_multi_drag_data_delete (GtkTreeDragSource *drag_source,
+                                                      GList             *ref_list);
+
+
 #define GTK_TYPE_TREE_DRAG_DEST            (gtk_tree_drag_dest_get_type ())
 #define GTK_TREE_DRAG_DEST(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_TREE_DRAG_DEST, GtkTreeDragDest))
 #define GTK_IS_TREE_DRAG_DEST(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_TREE_DRAG_DEST))
@@ -118,6 +137,15 @@ gboolean gtk_tree_get_row_drag_data     
 						GtkTreeModel     **tree_model,
 						GtkTreePath      **path);
 
+gboolean gtk_tree_set_multi_row_drag_data      (GtkSelectionData  *selection_data,
+                                                GtkTreeModel      *tree_model,
+                                                GList             *ref_list);
+gboolean gtk_tree_get_multi_row_drag_data      (GtkSelectionData  *selection_data,
+                                                GtkTreeModel     **tree_model,
+                                                GList            **path_list);
+
+gboolean gtk_tree_row_drag_data_is_multi       (GtkSelectionData  *selection_data);
+
 
 #ifdef __cplusplus
 }
Index: gtktreeprivate.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktreeprivate.h,v
retrieving revision 1.60
diff -u -p -r1.60 gtktreeprivate.h
--- gtktreeprivate.h	18 Dec 2003 00:06:43 -0000	1.60
+++ gtktreeprivate.h	21 Dec 2003 02:46:51 -0000
@@ -198,6 +198,7 @@ struct _GtkTreeViewPrivate
 
   /* for DnD */
   guint empty_view_drop : 1;
+  guint multi_dnd_mode : 1;
 
   guint ctrl_pressed : 1;
   guint shift_pressed : 1;
Index: gtktreeview.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtktreeview.c,v
retrieving revision 1.352
diff -u -p -r1.352 gtktreeview.c
--- gtktreeview.c	20 Dec 2003 21:08:24 -0000	1.352
+++ gtktreeview.c	21 Dec 2003 02:46:53 -0000
@@ -38,6 +38,7 @@
 #include "gtkcontainer.h"
 #include "gtkentry.h"
 #include "gtktreemodelsort.h"
+#include "gtkstock.h"
 
 #define GTK_TREE_VIEW_SEARCH_DIALOG_KEY "gtk-tree-view-search-dialog"
 
@@ -2043,6 +2044,7 @@ gtk_tree_view_button_press (GtkWidget   
       gboolean row_double_click = FALSE;
       gboolean rtl;
       GtkWidget *grab_widget;
+      GtkTreeSelection *selection;
 
       /* are we in an arrow? */
       if (tree_view->priv->prelight_node &&
@@ -2196,7 +2198,9 @@ gtk_tree_view_button_press (GtkWidget   
 
       /* we only handle selection modifications on the first button press
        */
-      if (event->type == GDK_BUTTON_PRESS)
+      selection = gtk_tree_view_get_selection (tree_view);
+      if (event->type == GDK_BUTTON_PRESS
+          && !gtk_tree_selection_path_is_selected (selection, path))
         {
           if ((event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK)
             tree_view->priv->ctrl_pressed = TRUE;
@@ -4891,6 +4895,35 @@ set_source_row (GdkDragContext *context,
                           (GDestroyNotify) (source_row ? gtk_tree_row_reference_free : NULL));
 }
 
+static void
+ref_list_free (GList *list)
+{
+  g_list_foreach (list, (GFunc)gtk_tree_row_reference_free, NULL);
+  g_list_free (list);
+}
+
+static void
+set_source_rows (GdkDragContext *context,
+                 GtkTreeModel   *model,
+                 GList          *path_list)
+{
+  GList *refs = NULL;
+  GList *list;
+
+  if (!path_list)
+    {
+      g_object_set_data (G_OBJECT (context), "gtk-tree-view-source-rows",
+                         NULL);
+      return;
+    }
+
+  for (list = path_list; list; list = list->next)
+    refs = g_list_append (refs, gtk_tree_row_reference_new (model, list->data));
+
+  g_object_set_data_full (G_OBJECT (context), "gtk-tree-view-source-rows",
+                          refs, (GDestroyNotify) ref_list_free);
+}
+
 static GtkTreePath*
 get_source_row (GdkDragContext *context)
 {
@@ -4903,6 +4936,12 @@ get_source_row (GdkDragContext *context)
     return NULL;
 }
 
+static GList *
+get_source_rows (GdkDragContext *context)
+{
+  return g_object_get_data (G_OBJECT (context), "gtk-tree-view-source-rows");
+}
+
 typedef struct
 {
   GtkTreeRowReference *dest_row;
@@ -5119,6 +5158,7 @@ remove_scroll_timeout (GtkTreeView *tree
       tree_view->priv->scroll_timeout = 0;
     }
 }
+
 static gboolean
 check_model_dnd (GtkTreeModel *model,
                  GType         required_iface,
@@ -5246,9 +5286,7 @@ set_destination_row (GtkTreeView    *tre
 
   *target = gtk_drag_dest_find_target (widget, context, di->dest_target_list);
   if (*target == GDK_NONE)
-    {
-      return FALSE;
-    }
+    return FALSE;
 
   if (!gtk_tree_view_get_dest_row_at_pos (tree_view,
                                           x, y,
@@ -5396,13 +5434,14 @@ gtk_tree_view_maybe_begin_dragging_row (
   GdkDragContext *context;
   TreeViewDragInfo *di;
   GtkTreePath *path = NULL;
+  GList *path_list = NULL;
   gint button;
   gint cell_x, cell_y;
   GtkTreeModel *model;
   gboolean retval = FALSE;
 
+  /* if the user uses a custom DnD impl, we bail out */
   di = get_info (tree_view);
-
   if (di == NULL)
     goto out;
 
@@ -5417,7 +5456,7 @@ gtk_tree_view_maybe_begin_dragging_row (
 
   model = gtk_tree_view_get_model (tree_view);
 
-  if (model == NULL)
+  if (model == NULL || !GTK_IS_TREE_DRAG_SOURCE (model))
     goto out;
 
   button = tree_view->priv->pressed_button;
@@ -5434,9 +5473,24 @@ gtk_tree_view_maybe_begin_dragging_row (
   if (path == NULL)
     goto out;
 
-  if (!GTK_IS_TREE_DRAG_SOURCE (model) ||
-      !gtk_tree_drag_source_row_draggable (GTK_TREE_DRAG_SOURCE (model),
-					   path))
+  if (gtk_tree_selection_path_is_selected (tree_view->priv->selection, path))
+    {
+      /* the "dragged" path is in the selection; if there's more than
+       * one item selected we enable multi-DnD mode.
+       */
+      if (gtk_tree_selection_count_selected_rows (tree_view->priv->selection)
+          > 1)
+        {
+          path_list = gtk_tree_selection_get_selected_rows (tree_view->priv->selection, NULL);
+
+          if (!path_list || !gtk_tree_drag_source_multi_row_draggable (GTK_TREE_DRAG_SOURCE (model), path_list))
+            goto out;
+        }
+      else if (!gtk_tree_drag_source_row_draggable (GTK_TREE_DRAG_SOURCE (model), path))
+        goto out;
+    }
+  else if (!gtk_tree_drag_source_row_draggable (GTK_TREE_DRAG_SOURCE (model),
+                                                path))
     goto out;
 
   /* FIXME Check whether we're a start button, if not return FALSE and
@@ -5447,17 +5501,27 @@ gtk_tree_view_maybe_begin_dragging_row (
 
   retval = TRUE;
 
+  tree_view->priv->multi_dnd_mode = path_list ? TRUE : FALSE;
+
   context = gtk_drag_begin (GTK_WIDGET (tree_view),
                             di->source_target_list,
                             di->source_actions,
                             button,
                             (GdkEvent*)event);
 
-  set_source_row (context, model, path);
+  if (path_list)
+    set_source_rows (context, model, path_list);
+  else
+    set_source_row (context, model, path);
 
  out:
   if (path)
     gtk_tree_path_free (path);
+  if (path_list)
+    {
+      g_list_foreach (path_list, (GFunc)gtk_tree_path_free, NULL);
+      g_list_free (path_list);
+    }
 
   return retval;
 }
@@ -5488,18 +5552,32 @@ gtk_tree_view_drag_begin (GtkWidget     
 
   g_return_if_fail (path != NULL);
 
-  row_pix = gtk_tree_view_create_row_drag_icon (tree_view,
-                                                path);
+  if (tree_view->priv->multi_dnd_mode)
+   {
+     /* We are in multi DnD mode, and just show an icon instead of the
+      * actual row.
+      */
+     gtk_drag_set_icon_stock (context, GTK_STOCK_DND_MULTIPLE,
+                              tree_view->priv->press_start_x + 1, cell_y + 1);
+   }
+ else
+   {
+      /* single DnD */
+
+      row_pix = gtk_tree_view_create_row_drag_icon (tree_view,
+                                                    path);
 
-  gtk_drag_set_icon_pixmap (context,
-                            gdk_drawable_get_colormap (row_pix),
-                            row_pix,
-                            NULL,
-                            /* the + 1 is for the black border in the icon */
-                            tree_view->priv->press_start_x + 1,
-                            cell_y + 1);
+      gtk_drag_set_icon_pixmap (context,
+                                gdk_drawable_get_colormap (row_pix),
+                                row_pix,
+                                NULL,
+                                /* the + 1 is for the black border in the icon */
+                                tree_view->priv->press_start_x + 1,
+                                cell_y + 1);
+
+      g_object_unref (row_pix);
+   }
 
-  g_object_unref (row_pix);
   gtk_tree_path_free (path);
 }
 
@@ -5522,6 +5600,7 @@ gtk_tree_view_drag_data_get (GtkWidget  
   GtkTreeModel *model;
   TreeViewDragInfo *di;
   GtkTreePath *source_row;
+  GList *source_rows;
 
   tree_view = GTK_TREE_VIEW (widget);
 
@@ -5530,14 +5609,15 @@ gtk_tree_view_drag_data_get (GtkWidget  
   if (model == NULL)
     return;
 
+  /* if the user uses a custom DnD impl, we bail out */
   di = get_info (GTK_TREE_VIEW (widget));
-
   if (di == NULL)
     return;
 
   source_row = get_source_row (context);
+  source_rows = get_source_rows (context);
 
-  if (source_row == NULL)
+  if (source_row == NULL && source_rows == NULL)
     return;
 
   /* We can implement the GTK_TREE_MODEL_ROW target generically for
@@ -5545,22 +5625,28 @@ gtk_tree_view_drag_data_get (GtkWidget  
    * we also support.
    */
 
-  if (GTK_IS_TREE_DRAG_SOURCE (model) &&
-      gtk_tree_drag_source_drag_data_get (GTK_TREE_DRAG_SOURCE (model),
-                                          source_row,
-                                          selection_data))
-    goto done;
-
-  /* If drag_data_get does nothing, try providing row data. */
-  if (selection_data->target == gdk_atom_intern ("GTK_TREE_MODEL_ROW", FALSE))
+  if (GTK_IS_TREE_DRAG_SOURCE (model))
     {
-      gtk_tree_set_row_drag_data (selection_data,
-				  model,
-				  source_row);
+      gboolean got_data = FALSE;
+
+      if (source_row)
+        got_data = gtk_tree_drag_source_drag_data_get (GTK_TREE_DRAG_SOURCE (model), source_row, selection_data);
+      else if (source_rows)
+        got_data = gtk_tree_drag_source_multi_drag_data_get (GTK_TREE_DRAG_SOURCE (model), source_rows, selection_data);
+
+      if (got_data)
+        goto done;
     }
 
+  /* If drag_data_get does nothing, try providing row data. */
+  if (tree_view->priv->multi_dnd_mode)
+    gtk_tree_set_multi_row_drag_data (selection_data, model, source_rows);
+  else
+    gtk_tree_set_row_drag_data (selection_data, model, source_row);
+
  done:
   gtk_tree_path_free (source_row);
+  /* note that source_rows is not a copy of stored list! */
 }
 
 
@@ -5572,29 +5658,49 @@ gtk_tree_view_drag_data_delete (GtkWidge
   GtkTreeModel *model;
   GtkTreeView *tree_view;
   GtkTreePath *source_row;
+  GList *source_rows;
 
   tree_view = GTK_TREE_VIEW (widget);
   model = gtk_tree_view_get_model (tree_view);
 
-  if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_SOURCE, "drag_data_delete"))
-    return;
-
+  /* if the user uses a custom DnD impl, we bail out */
   di = get_info (tree_view);
-
   if (di == NULL)
     return;
 
   source_row = get_source_row (context);
+  source_rows = get_source_rows (context);
 
-  if (source_row == NULL)
+  if (source_row == NULL && source_rows == NULL)
     return;
 
-  gtk_tree_drag_source_drag_data_delete (GTK_TREE_DRAG_SOURCE (model),
-                                         source_row);
+  if (source_row)
+    {
+      if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_SOURCE,
+                            "drag_data_delete"))
+        {
+          gtk_tree_path_free (source_row);
+          return;
+        }
+
+      gtk_tree_drag_source_drag_data_delete (GTK_TREE_DRAG_SOURCE (model),
+                                             source_row);
 
-  gtk_tree_path_free (source_row);
+      /* free our copy of source_row, and free the data from the context */
+      gtk_tree_path_free (source_row);
+      set_source_row (context, NULL, NULL);
+    }
+  else if (source_rows)
+    {
+      if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_SOURCE,
+                            "multi_drag_data_delete"))
+        return;
+
+      gtk_tree_drag_source_multi_drag_data_delete (GTK_TREE_DRAG_SOURCE (model),
+                                                   source_rows);
 
-  set_source_row (context, NULL, NULL);
+      set_source_rows (context, NULL, NULL);
+    }
 }
 
 static void
@@ -5633,6 +5739,9 @@ gtk_tree_view_drag_motion (GtkWidget    
 
   tree_view = GTK_TREE_VIEW (widget);
 
+  /* set a "fake" destination row, if the user would now drop, we would
+   * drop here
+   */
   if (!set_destination_row (tree_view, context, x, y, &suggested_action, &target))
     return FALSE;
 
@@ -5707,8 +5816,8 @@ gtk_tree_view_drag_drop (GtkWidget      
   remove_scroll_timeout (GTK_TREE_VIEW (widget));
   remove_open_timeout (GTK_TREE_VIEW (widget));
 
+  /* if the user uses a custom DnD impl, we bail out */
   di = get_info (tree_view);
-
   if (di == NULL)
     return FALSE;
 
@@ -5774,8 +5883,8 @@ gtk_tree_view_drag_data_received (GtkWid
   if (!check_model_dnd (model, GTK_TYPE_TREE_DRAG_DEST, "drag_data_received"))
     return;
 
+  /* if the user uses a custom DnD impl, we bail out */
   di = get_info (tree_view);
-
   if (di == NULL)
     return;
 
Index: gtkliststore.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkliststore.c,v
retrieving revision 1.90
diff -u -p -r1.90 gtkliststore.c
--- gtkliststore.c	9 Sep 2003 23:13:39 -0000	1.90
+++ gtkliststore.c	21 Dec 2003 02:46:53 -0000
@@ -81,6 +81,15 @@ static gboolean gtk_list_store_drag_data
 static gboolean gtk_list_store_drag_data_get      (GtkTreeDragSource *drag_source,
                                                    GtkTreePath       *path,
                                                    GtkSelectionData  *selection_data);
+
+static gboolean gtk_list_store_multi_row_draggable    (GtkTreeDragSource *drag_source,
+                                                       GList             *path_list);
+static gboolean gtk_list_store_multi_drag_data_get    (GtkTreeDragSource *drag_source,
+                                                       GList             *ref_list,
+                                                       GtkSelectionData  *selection_data);
+static gboolean gtk_list_store_multi_drag_data_delete (GtkTreeDragSource *drag_source,
+                                                       GList             *ref_list);
+
 static gboolean gtk_list_store_drag_data_received (GtkTreeDragDest   *drag_dest,
                                                    GtkTreePath       *dest,
                                                    GtkSelectionData  *selection_data);
@@ -233,6 +242,10 @@ gtk_list_store_drag_source_init (GtkTree
   iface->row_draggable = real_gtk_list_store_row_draggable;
   iface->drag_data_delete = gtk_list_store_drag_data_delete;
   iface->drag_data_get = gtk_list_store_drag_data_get;
+
+  iface->multi_row_draggable = gtk_list_store_multi_row_draggable;
+  iface->multi_drag_data_delete = gtk_list_store_multi_drag_data_delete;
+  iface->multi_drag_data_get = gtk_list_store_multi_drag_data_get;
 }
 
 static void
@@ -1406,12 +1419,13 @@ gtk_list_store_iter_is_valid (GtkListSto
   return FALSE;
 }
 
-static gboolean real_gtk_list_store_row_draggable (GtkTreeDragSource *drag_source,
-                                                   GtkTreePath       *path)
+static gboolean
+real_gtk_list_store_row_draggable (GtkTreeDragSource *drag_source,
+                                   GtkTreePath       *path)
 {
   return TRUE;
 }
-  
+
 static gboolean
 gtk_list_store_drag_data_delete (GtkTreeDragSource *drag_source,
                                  GtkTreePath       *path)
@@ -1457,6 +1471,108 @@ gtk_list_store_drag_data_get (GtkTreeDra
 }
 
 static gboolean
+gtk_list_store_multi_row_draggable (GtkTreeDragSource *drag_source,
+                                    GList             *path_list)
+{
+  GList *list;
+  gint length;
+
+  g_return_val_if_fail (GTK_IS_LIST_STORE (drag_source), FALSE);
+
+  length = GTK_LIST_STORE (drag_source)->length;
+
+  /* check if all paths in the list exist */
+  for (list = path_list; list; list = list->next)
+    {
+      GtkTreePath *path = list->data;
+      gint *indices = gtk_tree_path_get_indices (path);
+
+      if (indices[0] < 0 || indices[0] >= length)
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+gtk_list_store_multi_drag_data_get (GtkTreeDragSource *drag_source,
+                                    GList             *ref_list,
+                                    GtkSelectionData  *selection_data)
+{
+  g_return_val_if_fail (GTK_IS_LIST_STORE (drag_source), FALSE);
+
+  if (gtk_tree_set_multi_row_drag_data (selection_data,
+                                        GTK_TREE_MODEL (drag_source),
+                                        ref_list))
+    return TRUE;
+  else
+    /* FIXME handle text targets at least. */;
+
+  return FALSE;
+}
+
+static gboolean
+gtk_list_store_multi_drag_data_delete (GtkTreeDragSource *drag_source,
+                                       GList             *ref_list)
+{
+  GList *list;
+
+  g_return_val_if_fail (GTK_IS_LIST_STORE (drag_source), FALSE);
+
+  for (list = ref_list; list; list = list->next)
+    {
+      GtkTreePath *path;
+      GtkTreeIter iter;
+
+      path = gtk_tree_row_reference_get_path (list->data);
+      if (gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path))
+        gtk_list_store_remove (GTK_LIST_STORE (drag_source), &iter);
+      /* else we can't just return here, really */
+
+      gtk_tree_path_free (path);
+    }
+
+  return TRUE;
+}
+
+static void
+copy_iter_data (GtkListStore *list_store,
+                GtkTreeIter  *src_iter,
+                GtkTreeIter  *dest_iter)
+{
+  GtkTreeDataList *dl = G_SLIST (src_iter->user_data)->data;
+  GtkTreeDataList *copy_head = NULL;
+  GtkTreeDataList *copy_prev = NULL;
+  GtkTreeDataList *copy_iter = NULL;
+  GtkTreePath *path;
+  gint col;
+
+  col = 0;
+  while (dl)
+    {
+      copy_iter = _gtk_tree_data_list_node_copy (dl, list_store->column_headers[col]);
+
+      if (copy_head == NULL)
+        copy_head = copy_iter;
+
+      if (copy_prev)
+        copy_prev->next = copy_iter;
+
+      copy_prev = copy_iter;
+
+      dl = dl->next;
+      ++col;
+    }
+
+  dest_iter->stamp = list_store->stamp;
+  G_SLIST (dest_iter->user_data)->data = copy_head;
+
+  path = gtk_list_store_get_path (GTK_TREE_MODEL (list_store), dest_iter);
+  gtk_tree_model_row_changed (GTK_TREE_MODEL (list_store), path, dest_iter);
+  gtk_tree_path_free (path);
+}
+
+static gboolean
 gtk_list_store_drag_data_received (GtkTreeDragDest   *drag_dest,
                                    GtkTreePath       *dest,
                                    GtkSelectionData  *selection_data)
@@ -1465,29 +1581,78 @@ gtk_list_store_drag_data_received (GtkTr
   GtkListStore *list_store;
   GtkTreeModel *src_model = NULL;
   GtkTreePath *src_path = NULL;
+  GtkTreePath *prev = NULL;
+  GList *src_path_list = NULL;
   gboolean retval = FALSE;
+  GtkTreeIter src_iter;
+  GtkTreeIter dest_iter;
 
   g_return_val_if_fail (GTK_IS_LIST_STORE (drag_dest), FALSE);
 
   tree_model = GTK_TREE_MODEL (drag_dest);
   list_store = GTK_LIST_STORE (drag_dest);
 
-  if (gtk_tree_get_row_drag_data (selection_data,
-				  &src_model,
-				  &src_path) &&
-      src_model == tree_model)
+  if (gtk_tree_row_drag_data_is_multi (selection_data))
     {
-      /* Copy the given row to a new position */
-      GtkTreeIter src_iter;
-      GtkTreeIter dest_iter;
-      GtkTreePath *prev;
-
-      if (!gtk_tree_model_get_iter (src_model,
-                                    &src_iter,
-                                    src_path))
+      if (gtk_tree_get_multi_row_drag_data (selection_data, &src_model,
+                                            &src_path_list)
+          && src_model == tree_model)
         {
-          goto out;
+          GList *list;
+
+          /* get path to insert _after_ (dest is path to insert _before_) */
+          prev = gtk_tree_path_copy (dest);
+
+          for (list = src_path_list; list; list = list->next)
+            {
+              GtkTreeRowReference *ref = list->data;
+              src_path = gtk_tree_row_reference_get_path (ref);
+
+              if (!gtk_tree_model_get_iter (src_model, &src_iter, src_path))
+                {
+                  gtk_tree_path_free (src_path);
+                  continue;
+                }
+
+              gtk_tree_path_free (src_path);
+              src_path = NULL;
+
+              if (!gtk_tree_path_prev (prev))
+                {
+                  /* dest was the first spot in the list; prepend */
+                  gtk_list_store_prepend (list_store, &dest_iter);
+                  copy_iter_data (list_store, &src_iter, &dest_iter);
+
+                  retval = TRUE;
+                }
+              else if (gtk_tree_model_get_iter (tree_model, &dest_iter, prev))
+                {
+                  GtkTreeIter tmp_iter = dest_iter;
+
+                  gtk_list_store_insert_after (list_store, &dest_iter,
+                                               &tmp_iter);
+                  copy_iter_data (list_store, &src_iter, &dest_iter);
+
+                  retval = TRUE;
+                }
+              else
+                continue;
+
+              /* new path to insert *after* */
+              gtk_tree_path_free (prev);
+              prev = gtk_list_store_get_path (tree_model, &dest_iter);
+              gtk_tree_path_next (prev);
+            }
+
+          gtk_tree_path_free (prev);
         }
+    }
+  else if (gtk_tree_get_row_drag_data (selection_data, &src_model, &src_path)
+           && src_model == tree_model)
+    {
+      /* Copy the given row to a new position */
+      if (!gtk_tree_model_get_iter (src_model, &src_iter, src_path))
+        goto out;
 
       /* Get the path to insert _after_ (dest is the path to insert _before_) */
       prev = gtk_tree_path_copy (dest);
@@ -1498,59 +1663,21 @@ gtk_list_store_drag_data_received (GtkTr
            * to prepend.
            */
           gtk_list_store_prepend (list_store, &dest_iter);
+          copy_iter_data (list_store, &src_iter, &dest_iter);
 
           retval = TRUE;
         }
-      else
+      else if (gtk_tree_model_get_iter (tree_model, &dest_iter, prev))
         {
-          if (gtk_tree_model_get_iter (tree_model, &dest_iter, prev))
-            {
-              GtkTreeIter tmp_iter = dest_iter;
+          GtkTreeIter tmp_iter = dest_iter;
 
-              gtk_list_store_insert_after (list_store, &dest_iter, &tmp_iter);
+          gtk_list_store_insert_after (list_store, &dest_iter, &tmp_iter);
+          copy_iter_data (list_store, &src_iter, &dest_iter);
 
-              retval = TRUE;
-            }
+          retval = TRUE;
         }
 
       gtk_tree_path_free (prev);
-
-      /* If we succeeded in creating dest_iter, copy data from src
-       */
-      if (retval)
-        {
-          GtkTreeDataList *dl = G_SLIST (src_iter.user_data)->data;
-          GtkTreeDataList *copy_head = NULL;
-          GtkTreeDataList *copy_prev = NULL;
-          GtkTreeDataList *copy_iter = NULL;
-	  GtkTreePath *path;
-          gint col;
-
-          col = 0;
-          while (dl)
-            {
-              copy_iter = _gtk_tree_data_list_node_copy (dl,
-                                                         list_store->column_headers[col]);
-
-              if (copy_head == NULL)
-                copy_head = copy_iter;
-
-              if (copy_prev)
-                copy_prev->next = copy_iter;
-
-              copy_prev = copy_iter;
-
-              dl = dl->next;
-              ++col;
-            }
-
-	  dest_iter.stamp = list_store->stamp;
-          G_SLIST (dest_iter.user_data)->data = copy_head;
-
-	  path = gtk_list_store_get_path (tree_model, &dest_iter);
-	  gtk_tree_model_row_changed (tree_model, path, &dest_iter);
-	  gtk_tree_path_free (path);
-	}
     }
   else
     {
@@ -1574,7 +1701,6 @@ gtk_list_store_row_drop_possible (GtkTre
 {
   gint *indices;
   GtkTreeModel *src_model = NULL;
-  GtkTreePath *src_path = NULL;
   gboolean retval = FALSE;
 
   g_return_val_if_fail (GTK_IS_LIST_STORE (drag_dest), FALSE);
@@ -1583,16 +1709,24 @@ gtk_list_store_row_drop_possible (GtkTre
   if (GTK_LIST_STORE_IS_SORTED (drag_dest))
     return FALSE;
 
-  if (!gtk_tree_get_row_drag_data (selection_data,
-				   &src_model,
-				   &src_path))
-    goto out;
+  if (gtk_tree_row_drag_data_is_multi (selection_data))
+    {
+      if (!gtk_tree_get_multi_row_drag_data (selection_data,
+                                             &src_model, NULL))
+        return retval;
+    }
+  else
+    {
+      if (!gtk_tree_get_row_drag_data (selection_data,
+                                       &src_model, NULL))
+        return retval;
+    }
 
   if (src_model != GTK_TREE_MODEL (drag_dest))
-    goto out;
+    return retval;
 
   if (gtk_tree_path_get_depth (dest_path) != 1)
-    goto out;
+    return retval;
 
   /* can drop before any existing node, or before one past any existing. */
 
@@ -1601,10 +1735,6 @@ gtk_list_store_row_drop_possible (GtkTre
   if (indices[0] <= GTK_LIST_STORE (drag_dest)->length)
     retval = TRUE;
 
- out:
-  if (src_path)
-    gtk_tree_path_free (src_path);
-  
   return retval;
 }
 


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