Multiple-track drag+drop queue rearrangement.
[disorder] / disobedience / multidrag.c
diff --git a/disobedience/multidrag.c b/disobedience/multidrag.c
new file mode 100644 (file)
index 0000000..95a133a
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * This file is part of DisOrder
+ * Copyright (C) 2009 Richard Kettlewell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+/** @file disobedience/multidrag.c
+ * @brief Drag multiple rows of a GtkTreeView
+ */
+#include "disobedience.h"
+
+static gboolean multidrag_selection_block(GtkTreeSelection attribute((unused)) *selection,
+                                         GtkTreeModel attribute((unused)) *model,
+                                         GtkTreePath attribute((unused)) *path,
+                                         gboolean attribute((unused)) path_currently_selected,
+                                         gpointer data) {
+  return *(gboolean *)data;
+}
+
+static void block_selection(GtkWidget *w, gboolean block,
+                           int x, int y) {
+  static gboolean which[] = { FALSE, TRUE };
+  GtkTreeSelection *s = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
+  gtk_tree_selection_set_select_function(s,
+                                        multidrag_selection_block,
+                                        &which[!!block],
+                                        NULL);
+  // Remember the pointer location
+  int *where = g_object_get_data(G_OBJECT(w), "multidrag-where");
+  if(!where) {
+    where = g_malloc(2 * sizeof (int));
+    g_object_set_data(G_OBJECT(w), "multidrag-where", where);
+  }
+  where[0] = x;
+  where[1] = y;
+}
+
+static gboolean multidrag_button_press_event(GtkWidget *w,
+                                            GdkEventButton *event,
+                                            gpointer attribute((unused)) user_data) {
+  /* By default we assume that anything this button press does should
+   * act as normal */
+  block_selection(w, TRUE, -1, -1);
+  /* We are only interested in left-button behavior */
+  if(event->button != 1)
+    return FALSE;
+  /* We are only interested in unmodified clicks (not SHIFT etc) */
+  if(event->state & GDK_MODIFIER_MASK)
+    return FALSE;
+  /* We are only interested if a well-defined path is clicked */
+  GtkTreePath *path;
+  if(!gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(w),
+                                   event->x, event->y,
+                                   &path,
+                                   NULL,
+                                   NULL, NULL))
+    return FALSE;
+  //gtk_widget_grab_focus(w);    // TODO why??
+  /* We are only interested if a selected row is clicked */
+  GtkTreeSelection *s = gtk_tree_view_get_selection(GTK_TREE_VIEW(w));
+  if(!gtk_tree_selection_path_is_selected(s, path))
+    return FALSE;
+  /* We block subsequent selection changes and remember where the
+   * click was */
+  block_selection(w, FALSE, event->x, event->y);
+  return FALSE;                        /* propagate */
+}
+
+static gboolean multidrag_button_release_event(GtkWidget *w,
+                                              GdkEventButton *event,
+                                              gpointer attribute((unused)) user_data) {
+  int *where = g_object_get_data(G_OBJECT(w), "multidrag-where");
+
+  if(where && where[0] != -1) {
+    // Remember where the down-click was
+    const int x = where[0], y = where[1];
+    // Re-allow selections
+    block_selection(w, TRUE, -1, -1);
+    if(x == event->x && y == event->y) {
+      // If the up-click is at the same location as the down-click,
+      // it's not a drag.
+      GtkTreePath *path;
+      GtkTreeViewColumn *col;
+      if(gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(w),
+                                      event->x, event->y,
+                                      &path,
+                                      &col,
+                                      NULL, NULL)) {
+       gtk_tree_view_set_cursor(GTK_TREE_VIEW(w), path, col, FALSE);
+      }
+    }
+  }
+  return FALSE;                        /* propagate */
+}
+
+void make_treeview_multidrag(GtkWidget *w) {
+  g_signal_connect(w, "button-press-event",
+                  G_CALLBACK(multidrag_button_press_event), NULL);
+  g_signal_connect(w, "button-release-event",
+                  G_CALLBACK(multidrag_button_release_event), NULL);
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/