chiark / gitweb /
Automatic scrolling when a drag+drop operation is near the top or
authorRichard Kettlewell <rjk@greenend.org.uk>
Sun, 15 Nov 2009 14:14:39 +0000 (14:14 +0000)
committerRichard Kettlewell <rjk@greenend.org.uk>
Sun, 15 Nov 2009 14:14:39 +0000 (14:14 +0000)
bottom of a destination window.

disobedience/Makefile.am
disobedience/autoscroll.c [new file with mode: 0644]
disobedience/autoscroll.h [new file with mode: 0644]
disobedience/queue-generic.c

index 7935f3c..0171633 100644 (file)
@@ -28,7 +28,8 @@ disobedience_SOURCES=disobedience.h disobedience.c client.c queue.c   \
        choose.c choose-menu.c choose-search.c popup.c misc.c           \
        control.c properties.c menu.c log.c progress.c login.c rtp.c    \
        help.c ../lib/memgc.c settings.c users.c lookup.c choose.h      \
-       popup.h playlists.c multidrag.c multidrag.h
+       popup.h playlists.c multidrag.c multidrag.h autoscroll.c
+       autoscroll.h
 disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT) \
        $(LIBASOUND) $(COREAUDIO) $(LIBDB) $(LIBICONV)
 disobedience_LDFLAGS=$(GTK_LIBS)
diff --git a/disobedience/autoscroll.c b/disobedience/autoscroll.c
new file mode 100644 (file)
index 0000000..bfe71c1
--- /dev/null
@@ -0,0 +1,128 @@
+/* Derived from gtktreeview.c
+ * Copyright (C) 2000  Red Hat, Inc.,  Jonathan Blandford <jrb@redhat.com>
+ * Portions copyright (C) 2009 Richard Kettlewell
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/** @file disobedience/autoscroll.c
+ * @brief Automatic scrolling of a GtkTreeView
+ *
+ * GTK+ doesn't expose the automatic scrolling support if you don't use its
+ * high-level treeview drag+drop features.
+ *
+ * Adapted from GTK+ upstream as of commit
+ * 7fda8e6378d90bc8cf670ffe9dea682911e5e241.
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <gtk/gtk.h>
+
+#include "autoscroll.h"
+
+/** @brief Object data key used to track the autoscroll timeout */
+#define AUTOSCROLL_KEY "autoscroll.greenend.org.uk"
+
+/** @brief Controls size of edge region that provokes scrolling
+ *
+ * Actually this is half the size of the scroll region.  In isolation this may
+ * seem bizarre, but GTK+ uses the value internally for other purposes.
+ */
+#define SCROLL_EDGE_SIZE 15
+
+/** @brief Called from time to time to check whether auto-scrolling is needed
+ * @param data The GtkTreeView
+ * @return TRUE, to keep on truckin'
+ */
+static gboolean autoscroll_timeout(gpointer data) {
+  GtkTreeView *tree_view = data;
+  GdkRectangle visible_rect;
+  gint wx, wy, tx, ty;
+  gint offset;
+  gfloat value;
+
+  /* First we must find the pointer Y position in tree coordinates.  GTK+
+   * natively knows what the bin window is and can get the pointer in bin
+   * coords and convert to tree coords.  But there is no published way for us
+   * to find the bin window, so we must start in widget coords. */
+  gtk_widget_get_pointer(GTK_WIDGET(tree_view), &wx, &wy);
+  //fprintf(stderr, "widget coords: %d, %d\n", wx, wy);
+  gtk_tree_view_convert_widget_to_tree_coords(tree_view, wx, wy, &tx, &ty);
+  //fprintf(stderr, "tree coords: %d, %d\n", tx, ty);
+
+  gtk_tree_view_get_visible_rect (tree_view, &visible_rect);
+
+  /* see if we are near the edge. */
+  offset = ty - (visible_rect.y + 2 * SCROLL_EDGE_SIZE);
+  if (offset > 0)
+    {
+      offset = ty - (visible_rect.y + visible_rect.height - 2 * SCROLL_EDGE_SIZE);
+      if (offset < 0)
+       return TRUE;
+    }
+
+  GtkAdjustment *vadjustment = gtk_tree_view_get_vadjustment(tree_view);
+
+  value = CLAMP (vadjustment->value + offset, 0.0,
+                vadjustment->upper - vadjustment->page_size);
+  gtk_adjustment_set_value (vadjustment, value);
+
+  return TRUE;
+}
+
+/** @brief Enable autoscrolling
+ * @param tree_view Tree view to enable autoscrolling
+ *
+ * It's harmless to call this if autoscrolling is already enabled.
+ *
+ * It's up to you to cancel the callback when no longer required (including
+ * object destruction).
+ */
+void autoscroll_add(GtkTreeView *tree_view) {
+  guint *scrolldata = g_object_get_data(G_OBJECT(tree_view), AUTOSCROLL_KEY);
+  if(!scrolldata) {
+    /* Set up the callback */
+    scrolldata = g_new(guint, 1);
+    *scrolldata = gdk_threads_add_timeout(150, autoscroll_timeout, tree_view);
+    g_object_set_data(G_OBJECT(tree_view), AUTOSCROLL_KEY, scrolldata);
+    //fprintf(stderr, "autoscroll enabled\n");
+  }
+}
+
+/** @brief Disable autoscrolling
+ * @param tree_view Tree view to enable autoscrolling
+ *
+ * It's harmless to call this if autoscrolling is not enabled.
+ */
+void autoscroll_remove(GtkTreeView *tree_view) {
+  guint *scrolldata = g_object_get_data(G_OBJECT(tree_view), AUTOSCROLL_KEY);
+  if(scrolldata) {
+    g_object_set_data(G_OBJECT(tree_view), AUTOSCROLL_KEY, NULL);
+    g_source_remove(*scrolldata);
+    g_free(scrolldata);
+    //fprintf(stderr, "autoscroll disabled\n");
+  }
+}
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
diff --git a/disobedience/autoscroll.h b/disobedience/autoscroll.h
new file mode 100644 (file)
index 0000000..0c0b28c
--- /dev/null
@@ -0,0 +1,38 @@
+/* Derived from gtktreeview.c
+ * Copyright (C) 2000  Red Hat, Inc.,  Jonathan Blandford <jrb@redhat.com>
+ * Portions copyright (C) 2009 Richard Kettlewell
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/** @file disobedience/autoscroll.h
+ * @brief Automatic scrolling of a GtkTreeView
+ */
+#ifndef AUTOSCROLL_H
+#define AUTOSCROLL_H
+
+void autoscroll_add(GtkTreeView *tree_view);
+void autoscroll_remove(GtkTreeView *tree_view);
+
+#endif /* AUTOSCROLL_H */
+
+/*
+Local Variables:
+c-basic-offset:2
+comment-column:40
+fill-column:79
+indent-tabs-mode:nil
+End:
+*/
index 7958aff..0347aa3 100644 (file)
@@ -41,6 +41,7 @@
 #include "popup.h"
 #include "queue-generic.h"
 #include "multidrag.h"
+#include "autoscroll.h"
 
 static const GtkTargetEntry queuelike_targets[] = {
   {
@@ -432,6 +433,8 @@ void ql_new_queue(struct queuelike *ql,
  * @param time_ Current time
  * @param user_data Pointer to queuelike
  * @return TRUE in a dropzone, otherwise FALSE
+ *
+ * This is the handler for the "drag-motion" signal.
  */
 static gboolean ql_drag_motion(GtkWidget *w,
                                GdkDragContext *dc,
@@ -500,6 +503,7 @@ static gboolean ql_drag_motion(GtkWidget *w,
    * As the code stands the drop works but the visual feedback is not quite
    * right.
    */
+  autoscroll_add(GTK_TREE_VIEW(w));
   return TRUE;                          /* We are (always) in a drop zone */
 }
 
@@ -508,6 +512,13 @@ static gboolean ql_drag_motion(GtkWidget *w,
  * @param dc Drag context
  * @param time_ Current time
  * @param user_data Pointer to queuelike
+ *
+ * This is the handler for the "drag-leave" signal.
+ *
+ * It turns out that we get a drag-leave event when the data is dropped, too
+ * (See _gtk_drag_dest_handle_event).  This seems logically consistent and is
+ * convenient too - for instance it's why autoscroll_remove() gets called at
+ * the end of a drag+drop sequence.
  */
 static void ql_drag_leave(GtkWidget *w,
                           GdkDragContext attribute((unused)) *dc,
@@ -516,6 +527,7 @@ static void ql_drag_leave(GtkWidget *w,
   //struct queuelike *const ql = user_data;
 
   gtk_tree_view_set_drag_dest_row(GTK_TREE_VIEW(w), NULL, 0);
+  autoscroll_remove(GTK_TREE_VIEW(w));
 }
 
 /** @brief Callback to add selected tracks to the selection data
@@ -549,6 +561,8 @@ static void ql_drag_data_get_collect(GtkTreeModel *model,
  * convenient.
  *
  * If there are no IDs for rows in this widget then the ID half is undefined.
+ *
+ * This is the handler for the "drag-data-get" signal.
  */
 static void ql_drag_data_get(GtkWidget attribute((unused)) *w,
                              GdkDragContext attribute((unused)) *dc,
@@ -583,6 +597,8 @@ static void ql_drag_data_get(GtkWidget attribute((unused)) *w,
  * @param info_ The target type that was chosen
  * @param time_ Time data received (for some reason not a @c time_t)
  * @param user_data The queuelike
+ *
+ * This is the handler for the "drag-data-received" signal.
  */
 static void ql_drag_data_received(GtkWidget attribute((unused)) *w,
                                   GdkDragContext attribute((unused)) *dc,