1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 3 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 /*
19  * Modified by the GTK+ Team and others 1997-1999.  See the AUTHORS
20  * file for a list of people on the GTK+ Team.  See the ChangeLog
21  * files for a list of changes.  These files are distributed with
22  * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
23  */
24 
25  /*
26   * Simple composite widget to provide vertical scrolling, based
27   * on GtkRange widget code.
28   * Modified by the Sylpheed Team and others 2003
29   */
30 
31 #ifdef HAVE_CONFIG_H
32 #  include "config.h"
33 #include "claws-features.h"
34 #endif
35 
36 #include <glib.h>
37 #include <gtk/gtk.h>
38 #include "utils.h"
39 #include "gtkvscrollbutton.h"
40 #include "gtkutils.h"
41 
42 #define SCROLL_TIMER_LENGTH  20
43 #define SCROLL_INITIAL_DELAY 100	/* must hold button this long before ... */
44 #define SCROLL_LATER_DELAY   20	/* ... it starts repeating at this rate  */
45 #define SCROLL_DELAY_LENGTH  300
46 
47 
48 enum {
49     ARG_0,
50     ARG_ADJUSTMENT
51 };
52 
53 static void gtk_vscrollbutton_class_init(GtkVScrollbuttonClass * klass);
54 static void gtk_vscrollbutton_init(GtkVScrollbutton * vscrollbutton);
55 
56 GType gtk_vscrollbutton_get_type		(void);
57 GtkWidget *gtk_vscrollbutton_new		(GtkAdjustment 	  *adjustment);
58 
59 static gint gtk_vscrollbutton_button_release	(GtkWidget 	  *widget,
60 					     	 GdkEventButton   *event,
61 					     	 GtkVScrollbutton *scrollbutton);
62 
63 static void gtk_vscrollbutton_set_adjustment	(GtkVScrollbutton *scrollbutton,
64 					     	 GtkAdjustment 	  *adjustment);
65 
66 static gint gtk_vscrollbutton_button_press	(GtkWidget 	  *widget,
67 					   	 GdkEventButton   *event,
68 					   	 GtkVScrollbutton *scrollbutton);
69 
70 static gint gtk_vscrollbutton_button_release	(GtkWidget 	  *widget,
71 					     	 GdkEventButton   *event,
72 					     	 GtkVScrollbutton *scrollbutton);
73 
74 gint gtk_vscrollbutton_scroll		(GtkVScrollbutton *scrollbutton);
75 
76 static gboolean gtk_vscrollbutton_timer_1st_time(GtkVScrollbutton *scrollbutton);
77 
78 static void gtk_vscrollbutton_add_timer		(GtkVScrollbutton *scrollbutton);
79 
80 static void gtk_vscrollbutton_remove_timer	(GtkVScrollbutton *scrollbutton);
81 
82 static gboolean gtk_real_vscrollbutton_timer	(GtkVScrollbutton *scrollbutton);
83 
84 static void gtk_vscrollbutton_set_sensitivity   (GtkAdjustment    *adjustment,
85 						 GtkVScrollbutton *scrollbutton);
86 
gtk_vscrollbutton_get_type(void)87 GType gtk_vscrollbutton_get_type(void)
88 {
89     static GType vscrollbutton_type = 0;
90 
91     if (!vscrollbutton_type) {
92 	static const GTypeInfo vscrollbutton_info = {
93 			sizeof (GtkVScrollbuttonClass),
94 
95 			(GBaseInitFunc) NULL,
96 			(GBaseFinalizeFunc) NULL,
97 
98 			(GClassInitFunc) gtk_vscrollbutton_class_init,
99 			(GClassFinalizeFunc) NULL,
100 			NULL,	/* class_data */
101 
102 			sizeof (GtkVScrollbutton),
103 			0,	/* n_preallocs */
104 			(GInstanceInitFunc) gtk_vscrollbutton_init,
105 
106 			(const GTypeValueTable *) NULL	/* value table */
107 	};
108 
109 	vscrollbutton_type = g_type_register_static (GTK_TYPE_VBOX, "GtkVScrollbutton", &vscrollbutton_info, (GTypeFlags)0);
110     }
111 
112     return vscrollbutton_type;
113 }
114 
gtk_vscrollbutton_class_init(GtkVScrollbuttonClass * class)115 static void gtk_vscrollbutton_class_init(GtkVScrollbuttonClass *class)
116 {
117 }
118 
119 static GdkCursor *hand_cursor = NULL;
120 
vscroll_visi_notify(GtkWidget * widget,GdkEventVisibility * event,gpointer data)121 static gboolean vscroll_visi_notify(GtkWidget *widget,
122 				       GdkEventVisibility *event,
123 				       gpointer data)
124 {
125 	gdk_window_set_cursor(gtk_widget_get_window(widget), hand_cursor);
126 	return FALSE;
127 }
128 
vscroll_leave_notify(GtkWidget * widget,GdkEventCrossing * event,gpointer data)129 static gboolean vscroll_leave_notify(GtkWidget *widget,
130 				      GdkEventCrossing *event,
131 				       gpointer data)
132 {
133 	gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
134 	return FALSE;
135 }
136 
vscroll_enter_notify(GtkWidget * widget,GdkEventCrossing * event,gpointer data)137 static gboolean vscroll_enter_notify(GtkWidget *widget,
138 				      GdkEventCrossing *event,
139 				       gpointer data)
140 {
141 	gdk_window_set_cursor(gtk_widget_get_window(widget), hand_cursor);
142 	return FALSE;
143 }
144 
145 
gtk_vscrollbutton_init(GtkVScrollbutton * scrollbutton)146 static void gtk_vscrollbutton_init(GtkVScrollbutton *scrollbutton)
147 {
148     GtkWidget *arrow;
149 
150     if (!hand_cursor)
151 	    hand_cursor = gdk_cursor_new(GDK_HAND2);
152 
153     scrollbutton->upbutton = gtk_event_box_new();
154     scrollbutton->downbutton = gtk_event_box_new();
155     arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
156     gtk_widget_show(arrow);
157     gtk_container_add(GTK_CONTAINER(scrollbutton->upbutton), arrow);
158     gtk_widget_set_size_request(scrollbutton->upbutton, -1, 16);
159     arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
160     gtk_widget_show(arrow);
161     gtk_container_add(GTK_CONTAINER(scrollbutton->downbutton), arrow);
162     gtk_widget_set_size_request(scrollbutton->downbutton, -1, 16);
163     gtk_widget_set_can_focus(scrollbutton->upbutton, FALSE);
164     gtk_widget_set_can_focus(scrollbutton->downbutton, FALSE);
165     gtk_widget_show(scrollbutton->downbutton);
166     gtk_widget_show(scrollbutton->upbutton);
167 
168     g_signal_connect(G_OBJECT(scrollbutton->upbutton), "motion-notify-event",
169 		     G_CALLBACK(vscroll_visi_notify), NULL);
170     g_signal_connect(G_OBJECT(scrollbutton->upbutton), "leave-notify-event",
171 		     G_CALLBACK(vscroll_leave_notify), NULL);
172     g_signal_connect(G_OBJECT(scrollbutton->upbutton), "enter-notify-event",
173 		     G_CALLBACK(vscroll_enter_notify), NULL);
174     g_signal_connect(G_OBJECT(scrollbutton->downbutton), "motion-notify-event",
175 		     G_CALLBACK(vscroll_visi_notify), NULL);
176     g_signal_connect(G_OBJECT(scrollbutton->downbutton), "leave-notify-event",
177 		     G_CALLBACK(vscroll_leave_notify), NULL);
178     g_signal_connect(G_OBJECT(scrollbutton->downbutton), "enter-notify-event",
179 		     G_CALLBACK(vscroll_enter_notify), NULL);
180 
181     g_signal_connect(G_OBJECT(scrollbutton->upbutton),
182 		       "button_press_event",
183 		       G_CALLBACK(gtk_vscrollbutton_button_press),
184 		       scrollbutton);
185     g_signal_connect(G_OBJECT(scrollbutton->downbutton),
186 		       "button_press_event",
187 		       G_CALLBACK(gtk_vscrollbutton_button_press),
188 		       scrollbutton);
189     g_signal_connect(G_OBJECT(scrollbutton->upbutton),
190 		     "button_release_event",
191 		     G_CALLBACK
192 		     (gtk_vscrollbutton_button_release), scrollbutton);
193     g_signal_connect(G_OBJECT(scrollbutton->downbutton),
194 		     "button_release_event",
195 		     G_CALLBACK
196 		     (gtk_vscrollbutton_button_release), scrollbutton);
197     gtk_box_pack_start(GTK_BOX(&scrollbutton->vbox),
198 		       scrollbutton->upbutton, TRUE, TRUE, 0);
199     gtk_box_pack_end(GTK_BOX(&scrollbutton->vbox),
200 		     scrollbutton->downbutton, TRUE, TRUE, 0);
201     scrollbutton->timer = 0;
202 }
203 
gtk_vscrollbutton_new(GtkAdjustment * adjustment)204 GtkWidget *gtk_vscrollbutton_new(GtkAdjustment *adjustment)
205 {
206     GtkWidget *vscrollbutton;
207     vscrollbutton = g_object_new (gtk_vscrollbutton_get_type(),
208 			NULL);
209     gtk_vscrollbutton_set_adjustment(GTK_VSCROLLBUTTON(vscrollbutton),
210 				     adjustment);
211     g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment),
212 		       "value_changed",
213 		       G_CALLBACK
214 		       (gtk_vscrollbutton_set_sensitivity), vscrollbutton);
215     g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment),
216 		       "changed",
217 		       G_CALLBACK
218 		       (gtk_vscrollbutton_set_sensitivity), vscrollbutton);
219     return vscrollbutton;
220 }
221 
222 
gtk_vscrollbutton_set_adjustment(GtkVScrollbutton * scrollbutton,GtkAdjustment * adjustment)223 void gtk_vscrollbutton_set_adjustment(GtkVScrollbutton *scrollbutton,
224 				      GtkAdjustment *adjustment)
225 {
226     cm_return_if_fail(scrollbutton != NULL);
227     cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton));
228 
229     if (!adjustment)
230 	    adjustment =
231 	    GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
232     else
233 	cm_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
234 
235     if (scrollbutton->adjustment != adjustment) {
236 	if (scrollbutton->adjustment) {
237 	    g_signal_handlers_disconnect_matched(scrollbutton->adjustment,
238 	    					 G_SIGNAL_MATCH_DATA,
239 	    					 0, 0, NULL, NULL,
240 						 (gpointer) scrollbutton);
241 	    g_object_unref(G_OBJECT(scrollbutton->adjustment));
242 	}
243 
244 	scrollbutton->adjustment = adjustment;
245 	g_object_ref(G_OBJECT(adjustment));
246 	g_object_ref_sink (G_OBJECT(adjustment));
247     }
248 }
249 
gtk_vscrollbutton_button_press(GtkWidget * widget,GdkEventButton * event,GtkVScrollbutton * scrollbutton)250 static gint gtk_vscrollbutton_button_press(GtkWidget *widget,
251 					   GdkEventButton *event,
252 					   GtkVScrollbutton *scrollbutton)
253 {
254     if (!gtk_widget_has_focus(widget))
255 	gtk_widget_grab_focus(widget);
256 
257     if (scrollbutton->button == 0) {
258 	gtk_grab_add(widget);
259 	scrollbutton->button = event->button;
260 
261 	if (widget == scrollbutton->downbutton)
262 	    scrollbutton->scroll_type = GTK_SCROLL_STEP_FORWARD;
263 	else
264 	    scrollbutton->scroll_type = GTK_SCROLL_STEP_BACKWARD;
265 	gtk_vscrollbutton_scroll(scrollbutton);
266 	gtk_vscrollbutton_add_timer(scrollbutton);
267     }
268     return TRUE;
269 }
270 
271 
gtk_vscrollbutton_button_release(GtkWidget * widget,GdkEventButton * event,GtkVScrollbutton * scrollbutton)272 static gint gtk_vscrollbutton_button_release(GtkWidget *widget,
273 					     GdkEventButton *event,
274 					     GtkVScrollbutton *scrollbutton)
275 {
276     if (!gtk_widget_has_focus(widget))
277 	gtk_widget_grab_focus(widget);
278 
279     if (scrollbutton->button == event->button) {
280 	gtk_grab_remove(widget);
281 
282 	scrollbutton->button = 0;
283 	gtk_vscrollbutton_remove_timer(scrollbutton);
284 	gtk_vscrollbutton_set_sensitivity(scrollbutton->adjustment, scrollbutton);
285     }
286     return TRUE;
287 }
288 
gtk_vscrollbutton_scroll(GtkVScrollbutton * scrollbutton)289 gboolean gtk_vscrollbutton_scroll(GtkVScrollbutton *scrollbutton)
290 {
291     gfloat bound;
292     gfloat new_value;
293     gfloat page_size;
294     gfloat value;
295     gboolean return_val;
296 
297     cm_return_val_if_fail(scrollbutton != NULL, FALSE);
298     cm_return_val_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton), FALSE);
299 
300     new_value = value = gtk_adjustment_get_value(scrollbutton->adjustment);
301     return_val = TRUE;
302 
303     switch (scrollbutton->scroll_type) {
304 
305     case GTK_SCROLL_STEP_BACKWARD:
306 	new_value = value - gtk_adjustment_get_step_increment(scrollbutton->adjustment);
307 	bound = gtk_adjustment_get_lower(scrollbutton->adjustment);
308 	if (new_value <= bound) {
309 	    new_value = bound;
310 	    return_val = FALSE;
311 	    scrollbutton->timer = 0;
312 	}
313 	break;
314 
315     case GTK_SCROLL_STEP_FORWARD:
316 	new_value = value + gtk_adjustment_get_step_increment(scrollbutton->adjustment);
317 	bound = gtk_adjustment_get_upper(scrollbutton->adjustment);
318 	page_size = gtk_adjustment_get_page_size(scrollbutton->adjustment);
319 	if (new_value >= (bound - page_size)) {
320 	    new_value = bound - page_size;
321 	    return_val = FALSE;
322 	    scrollbutton->timer = 0;
323 	}
324 	break;
325 
326     default:
327 	break;
328 
329     }
330 
331 	if (new_value != value) {
332 	gtk_adjustment_set_value(scrollbutton->adjustment, new_value);
333 	g_signal_emit_by_name(G_OBJECT
334 				(scrollbutton->adjustment),
335 				"value_changed");
336 	gtk_widget_queue_resize(GTK_WIDGET(scrollbutton)); /* ensure resize */
337     }
338 
339     return return_val;
340 }
341 
342 static gboolean
gtk_vscrollbutton_timer_1st_time(GtkVScrollbutton * scrollbutton)343 gtk_vscrollbutton_timer_1st_time(GtkVScrollbutton *scrollbutton)
344 {
345     /*
346      * If the real timeout function succeeds and the timeout is still set,
347      * replace it with a quicker one so successive scrolling goes faster.
348      */
349     g_object_ref(G_OBJECT(scrollbutton));
350     if (scrollbutton->timer) {
351 	/* We explicitely remove ourselves here in the paranoia
352 	 * that due to things happening above in the callback
353 	 * above, we might have been removed, and another added.
354 	 */
355 	g_source_remove(scrollbutton->timer);
356 	scrollbutton->timer = g_timeout_add(SCROLL_LATER_DELAY,
357 					    (GSourceFunc)
358 					    gtk_real_vscrollbutton_timer,
359 					    scrollbutton);
360     }
361     g_object_unref(G_OBJECT(scrollbutton));
362     return FALSE;		/* don't keep calling this function */
363 }
364 
365 
gtk_vscrollbutton_add_timer(GtkVScrollbutton * scrollbutton)366 static void gtk_vscrollbutton_add_timer(GtkVScrollbutton *scrollbutton)
367 {
368     cm_return_if_fail(scrollbutton != NULL);
369     cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton));
370 
371     if (!scrollbutton->timer) {
372 	scrollbutton->need_timer = TRUE;
373 	scrollbutton->timer = g_timeout_add(SCROLL_INITIAL_DELAY,
374 					    (GSourceFunc)
375 					    gtk_vscrollbutton_timer_1st_time,
376 					    scrollbutton);
377     }
378 }
379 
gtk_vscrollbutton_remove_timer(GtkVScrollbutton * scrollbutton)380 static void gtk_vscrollbutton_remove_timer(GtkVScrollbutton *scrollbutton)
381 {
382     cm_return_if_fail(scrollbutton != NULL);
383     cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton));
384 
385     if (scrollbutton->timer) {
386 	g_source_remove(scrollbutton->timer);
387 	scrollbutton->timer = 0;
388     }
389     scrollbutton->need_timer = FALSE;
390 }
391 
gtk_real_vscrollbutton_timer(GtkVScrollbutton * scrollbutton)392 static gboolean gtk_real_vscrollbutton_timer(GtkVScrollbutton *scrollbutton)
393 {
394     gboolean return_val;
395 
396     GDK_THREADS_ENTER();
397 
398     return_val = TRUE;
399     if (!scrollbutton->timer) {
400 	return_val = FALSE;
401 	if (scrollbutton->need_timer)
402 	    scrollbutton->timer =
403 		g_timeout_add(SCROLL_TIMER_LENGTH,
404 			      (GSourceFunc) gtk_real_vscrollbutton_timer,
405 			      (gpointer) scrollbutton);
406 	else {
407 	    GDK_THREADS_LEAVE();
408 	    return FALSE;
409 	}
410 	scrollbutton->need_timer = FALSE;
411     }
412     GDK_THREADS_LEAVE();
413     return_val = gtk_vscrollbutton_scroll(scrollbutton);
414     return return_val;
415 }
416 
gtk_vscrollbutton_set_sensitivity(GtkAdjustment * adjustment,GtkVScrollbutton * scrollbutton)417 static void gtk_vscrollbutton_set_sensitivity   (GtkAdjustment    *adjustment,
418 						 GtkVScrollbutton *scrollbutton)
419 {
420 	gfloat value;
421 	if (!gtk_widget_get_realized(GTK_WIDGET(scrollbutton))) return;
422 	if (scrollbutton->button != 0) return; /* not while something is pressed */
423 
424 	value = gtk_adjustment_get_value(adjustment);
425 	gtk_widget_set_sensitive(scrollbutton->upbutton,
426 				 (value > gtk_adjustment_get_lower(adjustment)));
427 	gtk_widget_set_sensitive(scrollbutton->downbutton,
428 				 (value < (gtk_adjustment_get_upper(adjustment) -
429                            gtk_adjustment_get_page_size(adjustment))));
430 }
431