2012-07-28 [ticho] 3.8.1cvs23
[claws.git] / src / gtk / gtkvscrollbutton.c
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
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
107         vscrollbutton_type = g_type_register_static (GTK_TYPE_VBOX, "GtkVScrollbutton", &vscrollbutton_info, (GTypeFlags)0);
108     }
109
110     return vscrollbutton_type;
111 }
112
113 static void gtk_vscrollbutton_class_init(GtkVScrollbuttonClass *class)
114 {
115 }
116
117 static GdkCursor *hand_cursor = NULL;
118
119 static gboolean vscroll_visi_notify(GtkWidget *widget,
120                                        GdkEventVisibility *event,
121                                        gpointer data)
122 {
123         gdk_window_set_cursor(gtk_widget_get_window(widget), hand_cursor);
124         return FALSE;
125 }
126
127 static gboolean vscroll_leave_notify(GtkWidget *widget,
128                                       GdkEventCrossing *event,
129                                        gpointer data)
130 {
131         gdk_window_set_cursor(gtk_widget_get_window(widget), NULL);
132         return FALSE;
133 }
134
135 static gboolean vscroll_enter_notify(GtkWidget *widget,
136                                       GdkEventCrossing *event,
137                                        gpointer data)
138 {
139         gdk_window_set_cursor(gtk_widget_get_window(widget), hand_cursor);
140         return FALSE;
141 }
142
143
144 static void gtk_vscrollbutton_init(GtkVScrollbutton *scrollbutton)
145 {
146     GtkWidget *arrow;
147
148     if (!hand_cursor)
149             hand_cursor = gdk_cursor_new(GDK_HAND2);
150
151     scrollbutton->upbutton = gtk_event_box_new();
152     scrollbutton->downbutton = gtk_event_box_new();
153     arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
154     gtk_widget_show(arrow);
155     gtk_container_add(GTK_CONTAINER(scrollbutton->upbutton), arrow);
156     gtk_widget_set_size_request(scrollbutton->upbutton, -1, 16);
157     arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
158     gtk_widget_show(arrow);
159     gtk_container_add(GTK_CONTAINER(scrollbutton->downbutton), arrow);
160     gtk_widget_set_size_request(scrollbutton->downbutton, -1, 16);
161     gtkut_widget_set_can_focus(scrollbutton->upbutton, FALSE);
162     gtkut_widget_set_can_focus(scrollbutton->downbutton, FALSE);
163     gtk_widget_show(scrollbutton->downbutton);
164     gtk_widget_show(scrollbutton->upbutton);
165
166     g_signal_connect(G_OBJECT(scrollbutton->upbutton), "motion-notify-event",
167                      G_CALLBACK(vscroll_visi_notify), NULL);
168     g_signal_connect(G_OBJECT(scrollbutton->upbutton), "leave-notify-event",
169                      G_CALLBACK(vscroll_leave_notify), NULL);
170     g_signal_connect(G_OBJECT(scrollbutton->upbutton), "enter-notify-event",
171                      G_CALLBACK(vscroll_enter_notify), NULL);
172     g_signal_connect(G_OBJECT(scrollbutton->downbutton), "motion-notify-event",
173                      G_CALLBACK(vscroll_visi_notify), NULL);
174     g_signal_connect(G_OBJECT(scrollbutton->downbutton), "leave-notify-event",
175                      G_CALLBACK(vscroll_leave_notify), NULL);
176     g_signal_connect(G_OBJECT(scrollbutton->downbutton), "enter-notify-event",
177                      G_CALLBACK(vscroll_enter_notify), NULL);
178
179     g_signal_connect(G_OBJECT(scrollbutton->upbutton),
180                        "button_press_event",
181                        G_CALLBACK(gtk_vscrollbutton_button_press),
182                        scrollbutton);
183     g_signal_connect(G_OBJECT(scrollbutton->downbutton),
184                        "button_press_event",
185                        G_CALLBACK(gtk_vscrollbutton_button_press),
186                        scrollbutton);
187     g_signal_connect(G_OBJECT(scrollbutton->upbutton),
188                      "button_release_event",
189                      G_CALLBACK
190                      (gtk_vscrollbutton_button_release), scrollbutton);
191     g_signal_connect(G_OBJECT(scrollbutton->downbutton),
192                      "button_release_event",
193                      G_CALLBACK
194                      (gtk_vscrollbutton_button_release), scrollbutton);
195     gtk_box_pack_start(GTK_BOX(&scrollbutton->vbox),
196                        scrollbutton->upbutton, TRUE, TRUE, 0);
197     gtk_box_pack_end(GTK_BOX(&scrollbutton->vbox),
198                      scrollbutton->downbutton, TRUE, TRUE, 0);
199     scrollbutton->timer = 0;
200 }
201
202 GtkWidget *gtk_vscrollbutton_new(GtkAdjustment *adjustment)
203 {
204     GtkWidget *vscrollbutton;
205     vscrollbutton = g_object_new (gtk_vscrollbutton_get_type(),
206                         NULL);
207     gtk_vscrollbutton_set_adjustment(GTK_VSCROLLBUTTON(vscrollbutton),
208                                      adjustment);
209     g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment),
210                        "value_changed",
211                        G_CALLBACK
212                        (gtk_vscrollbutton_set_sensitivity), vscrollbutton);
213     g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment),
214                        "changed",
215                        G_CALLBACK
216                        (gtk_vscrollbutton_set_sensitivity), vscrollbutton);
217     return vscrollbutton;
218 }
219
220
221 void gtk_vscrollbutton_set_adjustment(GtkVScrollbutton *scrollbutton,
222                                       GtkAdjustment *adjustment)
223 {
224     cm_return_if_fail(scrollbutton != NULL);
225     cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton));
226
227     if (!adjustment)
228             adjustment =
229             GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
230     else
231         cm_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
232
233     if (scrollbutton->adjustment != adjustment) {
234         if (scrollbutton->adjustment) {
235             g_signal_handlers_disconnect_matched(scrollbutton->adjustment,
236                                                  G_SIGNAL_MATCH_DATA,
237                                                  0, 0, NULL, NULL, 
238                                                  (gpointer) scrollbutton);
239             g_object_unref(G_OBJECT(scrollbutton->adjustment));
240         }
241
242         scrollbutton->adjustment = adjustment;
243         g_object_ref(G_OBJECT(adjustment));
244 #if GLIB_CHECK_VERSION(2,10,0)
245         g_object_ref_sink (G_OBJECT(adjustment));
246 #else
247         gtk_object_ref (G_OBJECT (adjustment));
248         gtk_object_sink (G_OBJECT (adjustment));
249 #endif
250     }
251 }
252
253 static gint gtk_vscrollbutton_button_press(GtkWidget *widget,
254                                            GdkEventButton *event,
255                                            GtkVScrollbutton *scrollbutton)
256 {
257     if (!gtk_widget_has_focus(widget))
258         gtk_widget_grab_focus(widget);
259
260     if (scrollbutton->button == 0) {
261         gtk_grab_add(widget);
262         scrollbutton->button = event->button;
263
264         if (widget == scrollbutton->downbutton)
265             scrollbutton->scroll_type = GTK_SCROLL_STEP_FORWARD;
266         else
267             scrollbutton->scroll_type = GTK_SCROLL_STEP_BACKWARD;
268         gtk_vscrollbutton_scroll(scrollbutton);
269         gtk_vscrollbutton_add_timer(scrollbutton);
270     }
271     return TRUE;
272 }
273
274
275 static gint gtk_vscrollbutton_button_release(GtkWidget *widget,
276                                              GdkEventButton *event,
277                                              GtkVScrollbutton *scrollbutton)
278 {
279     if (!gtk_widget_has_focus(widget))
280         gtk_widget_grab_focus(widget);
281
282     if (scrollbutton->button == event->button) {
283         gtk_grab_remove(widget);
284
285         scrollbutton->button = 0;
286         gtk_vscrollbutton_remove_timer(scrollbutton);
287         gtk_vscrollbutton_set_sensitivity(scrollbutton->adjustment, scrollbutton);
288     }
289     return TRUE;
290 }
291
292 gboolean gtk_vscrollbutton_scroll(GtkVScrollbutton *scrollbutton)
293 {
294     gfloat bound;
295     gfloat new_value;
296     gfloat page_size;
297     gfloat value;
298     gboolean return_val;
299
300     cm_return_val_if_fail(scrollbutton != NULL, FALSE);
301     cm_return_val_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton), FALSE);
302
303     new_value = value = gtk_adjustment_get_value(scrollbutton->adjustment);
304     return_val = TRUE;
305
306     switch (scrollbutton->scroll_type) {
307
308     case GTK_SCROLL_STEP_BACKWARD:
309         new_value = value - gtk_adjustment_get_step_increment(scrollbutton->adjustment);
310         bound = gtk_adjustment_get_lower(scrollbutton->adjustment);
311         if (new_value <= bound) {
312             new_value = bound;
313             return_val = FALSE;
314             scrollbutton->timer = 0;
315         }
316         break;
317
318     case GTK_SCROLL_STEP_FORWARD:
319         new_value = value + gtk_adjustment_get_step_increment(scrollbutton->adjustment);
320         bound = gtk_adjustment_get_upper(scrollbutton->adjustment);
321         page_size = gtk_adjustment_get_page_size(scrollbutton->adjustment);
322         if (new_value >= (bound - page_size)) {
323             new_value = bound - page_size;
324             return_val = FALSE;
325             scrollbutton->timer = 0;
326         }
327         break;
328     
329     default:
330         break;
331     
332     }
333
334         if (new_value != value) {
335         gtk_adjustment_set_value(scrollbutton->adjustment, new_value);
336         g_signal_emit_by_name(G_OBJECT
337                                 (scrollbutton->adjustment),
338                                 "value_changed");
339         gtk_widget_queue_resize(GTK_WIDGET(scrollbutton)); /* ensure resize */
340     }
341
342     return return_val;
343 }
344
345 static gboolean
346 gtk_vscrollbutton_timer_1st_time(GtkVScrollbutton *scrollbutton)
347 {
348     /*
349      * If the real timeout function succeeds and the timeout is still set,
350      * replace it with a quicker one so successive scrolling goes faster.
351      */
352     g_object_ref(G_OBJECT(scrollbutton));
353     if (scrollbutton->timer) {
354         /* We explicitely remove ourselves here in the paranoia
355          * that due to things happening above in the callback
356          * above, we might have been removed, and another added.
357          */
358         g_source_remove(scrollbutton->timer);
359         scrollbutton->timer = g_timeout_add(SCROLL_LATER_DELAY,
360                                             (GSourceFunc)
361                                             gtk_real_vscrollbutton_timer,
362                                             scrollbutton);
363     }
364     g_object_unref(G_OBJECT(scrollbutton));
365     return FALSE;               /* don't keep calling this function */
366 }
367
368
369 static void gtk_vscrollbutton_add_timer(GtkVScrollbutton *scrollbutton)
370 {
371     cm_return_if_fail(scrollbutton != NULL);
372     cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton));
373
374     if (!scrollbutton->timer) {
375         scrollbutton->need_timer = TRUE;
376         scrollbutton->timer = g_timeout_add(SCROLL_INITIAL_DELAY,
377                                             (GSourceFunc)
378                                             gtk_vscrollbutton_timer_1st_time,
379                                             scrollbutton);
380     }
381 }
382
383 static void gtk_vscrollbutton_remove_timer(GtkVScrollbutton *scrollbutton)
384 {
385     cm_return_if_fail(scrollbutton != NULL);
386     cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton));
387
388     if (scrollbutton->timer) {
389         g_source_remove(scrollbutton->timer);
390         scrollbutton->timer = 0;
391     }
392     scrollbutton->need_timer = FALSE;
393 }
394
395 static gboolean gtk_real_vscrollbutton_timer(GtkVScrollbutton *scrollbutton)
396 {
397     gboolean return_val;
398
399     GDK_THREADS_ENTER();
400
401     return_val = TRUE;
402     if (!scrollbutton->timer) {
403         return_val = FALSE;
404         if (scrollbutton->need_timer)
405             scrollbutton->timer =
406                 g_timeout_add(SCROLL_TIMER_LENGTH, 
407                               (GSourceFunc) gtk_real_vscrollbutton_timer,
408                               (gpointer) scrollbutton);
409         else {
410             GDK_THREADS_LEAVE();
411             return FALSE;
412         }
413         scrollbutton->need_timer = FALSE;
414     }
415     GDK_THREADS_LEAVE();
416     return_val = gtk_vscrollbutton_scroll(scrollbutton);
417     return return_val;
418 }
419
420 static void gtk_vscrollbutton_set_sensitivity   (GtkAdjustment    *adjustment,
421                                                  GtkVScrollbutton *scrollbutton)
422 {
423         gfloat value;
424         if (!gtk_widget_get_realized(GTK_WIDGET(scrollbutton))) return;
425         if (scrollbutton->button != 0) return; /* not while something is pressed */
426         
427         value = gtk_adjustment_get_value(adjustment);
428         gtk_widget_set_sensitive(scrollbutton->upbutton, 
429                                  (value > gtk_adjustment_get_lower(adjustment)));
430         gtk_widget_set_sensitive(scrollbutton->downbutton, 
431                                  (value < (gtk_adjustment_get_upper(adjustment) -
432                            gtk_adjustment_get_page_size(adjustment))));
433 }