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