From df73cc678ab19af60f644cda9cb391e8703b0471 Mon Sep 17 00:00:00 2001 Message-Id: From: Mark Wooding Date: Sun, 15 Nov 2009 14:14:39 +0000 Subject: [PATCH] Automatic scrolling when a drag+drop operation is near the top or bottom of a destination window. Organization: Straylight/Edgeware From: Richard Kettlewell --- disobedience/Makefile.am | 3 +- disobedience/autoscroll.c | 128 +++++++++++++++++++++++++++++++++++ disobedience/autoscroll.h | 38 +++++++++++ disobedience/queue-generic.c | 16 +++++ 4 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 disobedience/autoscroll.c create mode 100644 disobedience/autoscroll.h diff --git a/disobedience/Makefile.am b/disobedience/Makefile.am index 7935f3c..0171633 100644 --- a/disobedience/Makefile.am +++ b/disobedience/Makefile.am @@ -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 index 0000000..bfe71c1 --- /dev/null +++ b/disobedience/autoscroll.c @@ -0,0 +1,128 @@ +/* Derived from gtktreeview.c + * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford + * 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 + +#include +#include + +#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 index 0000000..0c0b28c --- /dev/null +++ b/disobedience/autoscroll.h @@ -0,0 +1,38 @@ +/* Derived from gtktreeview.c + * Copyright (C) 2000 Red Hat, Inc., Jonathan Blandford + * 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: +*/ diff --git a/disobedience/queue-generic.c b/disobedience/queue-generic.c index 7958aff..0347aa3 100644 --- a/disobedience/queue-generic.c +++ b/disobedience/queue-generic.c @@ -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, -- [mdw]