Chapter 10. Drag'n'Drop (DnD) **** needs revision ***

****** NEEDS REVISION

This section needs revision more than any other section. If you know anything about tree view drag'n'drop, you probably know more than the author of this text. Please give some feedback in that case.

If you want to dive into treeview drag'n'drop, you might want to check out Owen Taylor's mail on that topic. It might not be completely identical to what has actually been implemented, but it gives a great overview, and provides more information than the docs do.

In addition to the standard Gtk+ Drag and Drop mechanisms that work with any widget, there are special Drag and Drop mechanisms just for the tree view widget. You usually want to use the tree-view specific Drag-and-Drop framework.

10.1. Drag'n'Dropping Row-Unrelated Data to and from a Tree View from other Windows or Widgets

Drag'n'Dropping general information from or to a tree view widget works just like it works with any other widget and involves the standard Gtk+ Drag and Drop mechanisms. If you use this, you can receive drops to or initiate drags from anywhere in your tree view (including empty sections). This is not row- or column-specific and is most likely not want you want. Nevertheless, here is a small example of a tree view in which you can drag'n'drop URIs from other applications (browsers, for example), with the dropped URIs just being appended to the list (note that usually you would probably rather want to set up your whole window as a target then and not just the tree view widget):


#include <gtk/gtk.h>

enum
{
  COL_URI = 0,
  NUM_COLS
} ;


void
view_onDragDataReceived(GtkWidget *wgt, GdkDragContext *context, int x, int y,
                        GtkSelectionData *seldata, guint info, guint time,
                        gpointer userdata)
{
  GtkTreeModel *model;
  GtkTreeIter   iter;

  model = GTK_TREE_MODEL(userdata);

  gtk_list_store_append(GTK_LIST_STORE(model), &iter);

  gtk_list_store_set(GTK_LIST_STORE(model), &iter, COL_URI, (gchar*)seldata->data, -1);
}

static GtkWidget *
create_view_and_model (void)
{
  GtkTreeViewColumn   *col;
  GtkCellRenderer     *renderer;
  GtkListStore        *liststore;
  GtkWidget           *view;

  liststore = gtk_list_store_new(NUM_COLS, G_TYPE_STRING);

  view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(liststore));

  g_object_unref(liststore); /* destroy model with view */

  col = gtk_tree_view_column_new();
  renderer = gtk_cell_renderer_text_new();

  gtk_tree_view_column_set_title(col, "URI");
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);
  gtk_tree_view_column_pack_start(col, renderer, TRUE);
  gtk_tree_view_column_add_attribute(col, renderer, "text", COL_URI);

  gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(view)),
                              GTK_SELECTION_SINGLE);

  /* Make tree view a destination for Drag'n'Drop */
  if (1)
  {
    enum
    {
      TARGET_STRING,
      TARGET_URL
    };

    static GtkTargetEntry targetentries[] =
    {
      { "STRING",        0, TARGET_STRING },
      { "text/plain",    0, TARGET_STRING },
      { "text/uri-list", 0, TARGET_URL },
    };

    gtk_drag_dest_set(view, GTK_DEST_DEFAULT_ALL, targetentries, 3,
                      GDK_ACTION_COPY|GDK_ACTION_MOVE|GDK_ACTION_LINK);

    g_signal_connect(view, "drag_data_received",
                     G_CALLBACK(view_onDragDataReceived), liststore);
  }

  return view;
}

int
main (int argc, char **argv)
{
  GtkWidget *window, *vbox, *view, *label;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  g_signal_connect(window, "delete_event", gtk_main_quit, NULL); /* dirty */
  gtk_window_set_default_size(GTK_WINDOW(window), 400, 200);

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  label = gtk_label_new("\nDrag and drop links from your browser into the tree view.\n");
  gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);

  view = create_view_and_model();
  gtk_box_pack_start(GTK_BOX(vbox), view, TRUE, TRUE, 0);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

If you are receiving drops into a tree view, you can connect to the view's "drag-motion" signal to track the mouse pointer while it is in a drag and drop operation over the tree view. This is useful for example if you want to expand a collapsed node in a tree when the mouse hovers above the node for a certain amount of time during a drag'n'drop operation. Here is an example of how to achieve this:


/***************************************************************************
 *
 *   onDragMotion_expand_timeout
 *
 *   Timeout used to make sure that we expand rows only
 *    after hovering about them for a certain amount
 *    of time while doing Drag'n'Drop
 *
 ***************************************************************************/

gboolean
onDragMotion_expand_timeout (GtkTreePath **path)
{
        g_return_val_if_fail (  path != NULL, FALSE );
        g_return_val_if_fail ( *path != NULL, FALSE );

        gtk_tree_view_expand_row(GTK_TREE_VIEW(view), *path, FALSE);

        return FALSE; /* only call once */
}


/***************************************************************************
 *
 *   view_onDragMotion: we don't want to expand unexpanded nodes
 *                      immediately when the mouse pointer passes across
 *                      them during DnD. Instead, we only want to expand
 *                      the node if the pointer has been hovering above the
 *                      node for at least 1.5 seconds or so. To achieve this,
 *                      we use a timeout that is removed whenever the row
 *                      in focus changes.
 *
 ***************************************************************************/

static gboolean
view_onDragMotion (GtkWidget *widget, GdkDragContext *context, gint x,
                   gint y, guint time, gpointer data)
{
  static GtkTreePath  *lastpath;  /* NULL */
  GtkTreePath         *path = NULL;

  if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), x, y, &path, NULL, NULL, NULL))
  {
    if (!lastpath || ((lastpath) && gtk_tree_path_compare(lastpath, path) != 0))
    {
      (void) g_source_remove_by_user_data(&lastpath);

      if (!gtk_tree_view_row_expanded(GTK_TREE_VIEW(widget), path))
      {
        /* 1500 = 1.5 secs */
        g_timeout_add(1500, (GSourceFunc) onDragMotion_expand_timeout, &lastpath);
      }
    }
  }
  else
  {
    g_source_remove_by_user_data(&lastpath);
  }

  if (lastpath)
    gtk_tree_path_free(lastpath);

  lastpath = path;

  return TRUE;
}

Connect to the view's "drag-drop" signal to be called when the drop happens. You can translate the coordinates provided into a tree path with gtk_tree_view_get_path_at_pos.