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