1 /*
2 * fm-dnd-auto-scroll.c
3 *
4 * Copyright 2010 Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 /**
23 * SECTION:fm-dnd-auto-scroll
24 * @short_description: Autoscroll when dragged out of widget.
25 * @title: Drag and drop autoscrolling
26 *
27 * @include: libfm/fm-gtk.h
28 *
29 */
30 #include "fm-dnd-auto-scroll.h"
31 #include "gtk-compat.h"
32
33 #define SCROLL_EDGE_SIZE 15
34
35 typedef struct _FmDndAutoScroll FmDndAutoScroll;
36 struct _FmDndAutoScroll
37 {
38 GtkWidget* widget;
39 guint timeout;
40 GtkAdjustment* hadj;
41 GtkAdjustment* vadj;
42 };
43
44 static GQuark data_id = 0;
45
on_auto_scroll(gpointer user_data)46 static gboolean on_auto_scroll(gpointer user_data)
47 {
48 FmDndAutoScroll* as = (FmDndAutoScroll*)user_data;
49 int x, y;
50 GtkAdjustment* va = as->vadj;
51 GtkAdjustment* ha = as->hadj;
52 GtkWidget* widget = as->widget;
53 GtkAllocation allocation;
54
55 if(g_source_is_destroyed(g_main_current_source()))
56 return FALSE;
57
58 gdk_window_get_device_position (gtk_widget_get_window(widget),
59 gdk_device_manager_get_client_pointer(
60 gdk_display_get_device_manager(
61 gdk_window_get_display(
62 gtk_widget_get_window(widget)))),
63 &x, &y, NULL);
64 gtk_widget_get_allocation(widget, &allocation);
65
66 /*
67 HACK.
68 Sometimes we do not get drag-leave signal. (Why?)
69 This check prevents infinite scrolling.
70 */
71 if (y < 0 || y > allocation.height ||
72 x < 0 || x > allocation.width)
73 {
74 as->timeout = 0;
75 return FALSE;
76 }
77
78 if(va)
79 {
80 if(y < SCROLL_EDGE_SIZE) /* scroll up */
81 {
82 gdouble value = gtk_adjustment_get_value(va);
83 gdouble lower = gtk_adjustment_get_lower(va);
84 if(value > lower)
85 {
86 value -= gtk_adjustment_get_step_increment(va);
87 if(value < lower)
88 value = lower;
89 gtk_adjustment_set_value(va, value);
90 }
91 }
92 else if(y > (allocation.height - SCROLL_EDGE_SIZE))
93 {
94 gdouble value = gtk_adjustment_get_value(va);
95 gdouble upper = gtk_adjustment_get_upper(va) - gtk_adjustment_get_page_size(va);
96 /* scroll down */
97 if(value < upper)
98 {
99 value += gtk_adjustment_get_step_increment(va);
100 if(value > upper)
101 value = upper;
102 gtk_adjustment_set_value(va, value);
103 }
104 }
105 gtk_adjustment_value_changed(va);
106 }
107
108 if(ha)
109 {
110 if(x < SCROLL_EDGE_SIZE) /* scroll to left */
111 {
112 gdouble value = gtk_adjustment_get_value(ha);
113 gdouble lower = gtk_adjustment_get_lower(ha);
114 if(value > lower)
115 {
116 value -= gtk_adjustment_get_step_increment(ha);
117 if(value < lower)
118 value = lower;
119 gtk_adjustment_set_value(ha, value);
120 }
121 }
122 else if(x > (allocation.width - SCROLL_EDGE_SIZE))
123 {
124 gdouble value = gtk_adjustment_get_value(ha);
125 gdouble upper = gtk_adjustment_get_upper(ha) - gtk_adjustment_get_page_size(ha);
126 /* scroll to right */
127 if(value < upper)
128 {
129 value += gtk_adjustment_get_step_increment(ha);
130 if(value > upper)
131 value = upper;
132 gtk_adjustment_set_value(ha, value);
133 }
134 }
135 gtk_adjustment_value_changed(ha);
136 }
137 return TRUE;
138 }
139
on_drag_motion(GtkWidget * widget,GdkDragContext * drag_context,gint x,gint y,guint time,gpointer user_data)140 static gboolean on_drag_motion(GtkWidget *widget, GdkDragContext *drag_context,
141 gint x, gint y, guint time, gpointer user_data)
142 {
143 FmDndAutoScroll* as = (FmDndAutoScroll*)user_data;
144 /* FIXME: this is a dirty hack for GTK_TREE_MODEL_ROW. When dragging GTK_TREE_MODEL_ROW
145 * we cannot receive "drag-leave" message. So weied! Is it a gtk+ bug? */
146 GdkAtom target = gtk_drag_dest_find_target(widget, drag_context, NULL);
147 if(target == GDK_NONE)
148 return FALSE;
149 if(0 == as->timeout) /* install a scroll timeout if needed */
150 {
151 as->timeout = gdk_threads_add_timeout(150, on_auto_scroll, as);
152 }
153 return FALSE;
154 }
155
on_drag_leave(GtkWidget * widget,GdkDragContext * drag_context,guint time,gpointer user_data)156 static void on_drag_leave(GtkWidget *widget, GdkDragContext *drag_context,
157 guint time, gpointer user_data)
158 {
159 FmDndAutoScroll* as = (FmDndAutoScroll*)user_data;
160 if(as->timeout)
161 {
162 g_source_remove(as->timeout);
163 as->timeout = 0;
164 }
165 }
166
fm_dnd_auto_scroll_free(gpointer user_data)167 static void fm_dnd_auto_scroll_free(gpointer user_data)
168 {
169 FmDndAutoScroll* as = (FmDndAutoScroll*)user_data;
170 if(as->timeout)
171 g_source_remove(as->timeout);
172 if(as->hadj)
173 g_object_unref(as->hadj);
174 if(as->vadj)
175 g_object_unref(as->vadj);
176
177 g_signal_handlers_disconnect_by_func(as->widget, on_drag_motion, as);
178 g_signal_handlers_disconnect_by_func(as->widget, on_drag_leave, as);
179 g_slice_free(FmDndAutoScroll, as);
180 }
181
182 /**
183 * fm_dnd_set_dest_auto_scroll
184 * @drag_dest_widget: a drag destination widget
185 * @hadj: horizontal GtkAdjustment
186 * @vadj: vertical GtkAdjustment
187 *
188 * This function installs a "drag-motion" handler to the dest widget
189 * to support auto-scroll when the dragged item is near the margin
190 * of the destination widget. For example, when a user drags an item
191 * over the bottom of a GtkTreeView, the desired behavior should be
192 * to scroll up the content of the tree view and to expose the items
193 * below currently visible region. So the user can drop on them.
194 */
fm_dnd_set_dest_auto_scroll(GtkWidget * drag_dest_widget,GtkAdjustment * hadj,GtkAdjustment * vadj)195 void fm_dnd_set_dest_auto_scroll(GtkWidget* drag_dest_widget,
196 GtkAdjustment* hadj, GtkAdjustment* vadj)
197 {
198 FmDndAutoScroll* as;
199 if(G_UNLIKELY(data_id == 0))
200 data_id = g_quark_from_static_string("FmDndAutoScroll");
201
202 if(G_UNLIKELY(!hadj && !vadj))
203 {
204 g_object_set_qdata_full(G_OBJECT(drag_dest_widget), data_id, NULL, NULL);
205 return;
206 }
207
208 as = g_slice_new(FmDndAutoScroll);
209 as->widget = drag_dest_widget; /* no g_object_ref is needed here */
210 as->timeout = 0;
211 as->hadj = hadj ? GTK_ADJUSTMENT(g_object_ref(hadj)) : NULL;
212 as->vadj = vadj ? GTK_ADJUSTMENT(g_object_ref(vadj)) : NULL;
213
214 g_object_set_qdata_full(G_OBJECT(drag_dest_widget), data_id,
215 as, fm_dnd_auto_scroll_free);
216
217 g_signal_connect(drag_dest_widget, "drag-motion",
218 G_CALLBACK(on_drag_motion), as);
219 g_signal_connect(drag_dest_widget, "drag-leave",
220 G_CALLBACK(on_drag_leave), as);
221 }
222
223 /**
224 * fm_dnd_unset_dest_auto_scroll
225 * @drag_dest_widget: drag destination widget.
226 *
227 * Unsets what has been done by fm_dnd_set_dest_auto_scroll()
228 */
fm_dnd_unset_dest_auto_scroll(GtkWidget * drag_dest_widget)229 void fm_dnd_unset_dest_auto_scroll(GtkWidget* drag_dest_widget)
230 {
231 if(G_UNLIKELY(data_id == 0))
232 data_id = g_quark_from_static_string("FmDndAutoScroll");
233 g_object_set_qdata_full(G_OBJECT(drag_dest_widget), data_id, NULL, NULL);
234 }
235