2005-07-06 [colin] 1.9.12cvs24
[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 2 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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Modified by the GTK+ Team and others 1997-1999.  See the AUTHORS
22  * file for a list of people on the GTK+ Team.  See the ChangeLog
23  * files for a list of changes.  These files are distributed with
24  * GTK+ at ftp://ftp.gtk.org/pub/gtk/. 
25  */
26
27  /*
28   * Simple composite widget to provide vertical scrolling, based
29   * on GtkRange widget code.
30   * Modified by the Sylpheed Team and others 2003
31   */
32
33 #ifdef HAVE_CONFIG_H
34 #  include "config.h"
35 #endif
36
37 #include <gtk/gtksignal.h>
38 #include <gtk/gtkbutton.h>
39 #include <gtk/gtkmisc.h>
40 #include <gtk/gtk.h>
41 #include <gtk/gtkbox.h>
42 #include <gtk/gtkmain.h>
43
44 #include "gtkvscrollbutton.h"
45
46 #define SCROLL_TIMER_LENGTH  20
47 #define SCROLL_INITIAL_DELAY 100        /* must hold button this long before ... */
48 #define SCROLL_LATER_DELAY   20 /* ... it starts repeating at this rate  */
49 #define SCROLL_DELAY_LENGTH  300
50
51
52 enum {
53     ARG_0,
54     ARG_ADJUSTMENT
55 };
56
57 static void gtk_vscrollbutton_class_init(GtkVScrollbuttonClass * klass);
58 static void gtk_vscrollbutton_init(GtkVScrollbutton * vscrollbutton);
59
60 GtkType gtk_vscrollbutton_get_type              (void);
61 GtkWidget *gtk_vscrollbutton_new                (GtkAdjustment    *adjustment);
62
63 static gint gtk_vscrollbutton_button_release    (GtkWidget        *widget,
64                                                  GdkEventButton   *event,
65                                                  GtkVScrollbutton *scrollbutton);
66
67 static void gtk_vscrollbutton_set_adjustment    (GtkVScrollbutton *scrollbutton,
68                                                  GtkAdjustment    *adjustment);
69
70 static gint gtk_vscrollbutton_button_press      (GtkWidget        *widget,
71                                                  GdkEventButton   *event,
72                                                  GtkVScrollbutton *scrollbutton);
73
74 static gint gtk_vscrollbutton_button_release    (GtkWidget        *widget,
75                                                  GdkEventButton   *event,
76                                                  GtkVScrollbutton *scrollbutton);
77
78 static gint gtk_vscrollbutton_scroll            (GtkVScrollbutton *scrollbutton);
79
80 static gboolean gtk_vscrollbutton_timer_1st_time(GtkVScrollbutton *scrollbutton);
81
82 static void gtk_vscrollbutton_add_timer         (GtkVScrollbutton *scrollbutton);
83
84 static void gtk_vscrollbutton_remove_timer      (GtkVScrollbutton *scrollbutton);
85
86 static gint gtk_real_vscrollbutton_timer        (GtkVScrollbutton *scrollbutton);
87
88 static void gtk_vscrollbutton_set_sensitivity   (GtkAdjustment    *adjustment,
89                                                  GtkVScrollbutton *scrollbutton);
90
91 GtkType gtk_vscrollbutton_get_type(void)
92 {
93     static GtkType vscrollbutton_type = 0;
94
95     if (!vscrollbutton_type) {
96         static const GtkTypeInfo vscrollbutton_info = {
97             "GtkVScrollbutton",
98             sizeof(GtkVScrollbutton),
99             sizeof(GtkVScrollbuttonClass),
100             (GtkClassInitFunc) gtk_vscrollbutton_class_init,
101             (GtkObjectInitFunc) gtk_vscrollbutton_init,
102             /* reserved_1 */ NULL,
103             /* reserved_2 */ NULL,
104             (GtkClassInitFunc) NULL,
105         };
106
107         vscrollbutton_type =
108             gtk_type_unique(GTK_TYPE_VBOX, &vscrollbutton_info);
109     }
110
111     return vscrollbutton_type;
112 }
113
114 static void gtk_vscrollbutton_class_init(GtkVScrollbuttonClass *class)
115 {
116 }
117
118 static void gtk_vscrollbutton_init(GtkVScrollbutton *scrollbutton)
119 {
120     GtkWidget *arrow;
121     scrollbutton->upbutton = gtk_button_new();
122     scrollbutton->downbutton = gtk_button_new();
123     arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
124     gtk_widget_show(arrow);
125     gtk_container_add(GTK_CONTAINER(scrollbutton->upbutton), arrow);
126     gtk_widget_set_size_request(scrollbutton->upbutton, -1, 16);
127     arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
128     gtk_widget_show(arrow);
129     gtk_container_add(GTK_CONTAINER(scrollbutton->downbutton), arrow);
130     gtk_widget_set_size_request(scrollbutton->downbutton, -1, 16);
131     GTK_WIDGET_UNSET_FLAGS(scrollbutton->upbutton, GTK_CAN_FOCUS);
132     GTK_WIDGET_UNSET_FLAGS(scrollbutton->downbutton, GTK_CAN_FOCUS);
133     gtk_widget_show(scrollbutton->downbutton);
134     gtk_widget_show(scrollbutton->upbutton);
135
136     g_signal_connect(G_OBJECT(scrollbutton->upbutton),
137                        "button_press_event",
138                        G_CALLBACK(gtk_vscrollbutton_button_press),
139                        scrollbutton);
140     g_signal_connect(G_OBJECT(scrollbutton->downbutton),
141                        "button_press_event",
142                        G_CALLBACK(gtk_vscrollbutton_button_press),
143                        scrollbutton);
144     g_signal_connect(G_OBJECT(scrollbutton->upbutton),
145                      "button_release_event",
146                      G_CALLBACK
147                      (gtk_vscrollbutton_button_release), scrollbutton);
148     g_signal_connect(G_OBJECT(scrollbutton->downbutton),
149                      "button_release_event",
150                      G_CALLBACK
151                      (gtk_vscrollbutton_button_release), scrollbutton);
152     gtk_box_pack_start(GTK_BOX(&scrollbutton->vbox),
153                        scrollbutton->upbutton, TRUE, TRUE, 0);
154     gtk_box_pack_end(GTK_BOX(&scrollbutton->vbox),
155                      scrollbutton->downbutton, TRUE, TRUE, 0);
156     scrollbutton->timer = 0;
157 }
158
159 GtkWidget *gtk_vscrollbutton_new(GtkAdjustment *adjustment)
160 {
161     GtkWidget *vscrollbutton;
162     vscrollbutton = GTK_WIDGET(gtk_type_new(gtk_vscrollbutton_get_type()));
163     gtk_vscrollbutton_set_adjustment(GTK_VSCROLLBUTTON(vscrollbutton),
164                                      adjustment);
165     g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment),
166                        "value_changed",
167                        G_CALLBACK
168                        (gtk_vscrollbutton_set_sensitivity), vscrollbutton);
169     g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment),
170                        "changed",
171                        G_CALLBACK
172                        (gtk_vscrollbutton_set_sensitivity), vscrollbutton);
173     return vscrollbutton;
174 }
175
176
177 void gtk_vscrollbutton_set_adjustment(GtkVScrollbutton *scrollbutton,
178                                       GtkAdjustment *adjustment)
179 {
180     g_return_if_fail(scrollbutton != NULL);
181     g_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton));
182
183     if (!adjustment)
184             adjustment =
185             GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
186     else
187         g_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
188
189     if (scrollbutton->adjustment != adjustment) {
190         if (scrollbutton->adjustment) {
191             g_signal_handlers_disconnect_matched(scrollbutton->adjustment,
192                                                  G_SIGNAL_MATCH_DATA,
193                                                  0, 0, NULL, NULL, 
194                                                  (gpointer) scrollbutton);
195             g_object_unref(G_OBJECT(scrollbutton->adjustment));
196         }
197
198         scrollbutton->adjustment = adjustment;
199         g_object_ref(G_OBJECT(adjustment));
200         gtk_object_sink(GTK_OBJECT(adjustment));
201     }
202 }
203
204 static gint gtk_vscrollbutton_button_press(GtkWidget *widget,
205                                            GdkEventButton *event,
206                                            GtkVScrollbutton *scrollbutton)
207 {
208     if (!GTK_WIDGET_HAS_FOCUS(widget))
209         gtk_widget_grab_focus(widget);
210
211     if (scrollbutton->button == 0) {
212         gtk_grab_add(widget);
213         scrollbutton->button = event->button;
214
215         if (widget == scrollbutton->downbutton)
216             scrollbutton->scroll_type = GTK_SCROLL_STEP_FORWARD;
217         else
218             scrollbutton->scroll_type = GTK_SCROLL_STEP_BACKWARD;
219         gtk_vscrollbutton_scroll(scrollbutton);
220         gtk_vscrollbutton_add_timer(scrollbutton);
221     }
222     return TRUE;
223 }
224
225
226 static gint gtk_vscrollbutton_button_release(GtkWidget *widget,
227                                              GdkEventButton *event,
228                                              GtkVScrollbutton *scrollbutton)
229 {
230     if (!GTK_WIDGET_HAS_FOCUS(widget))
231         gtk_widget_grab_focus(widget);
232
233     if (scrollbutton->button == event->button) {
234         gtk_grab_remove(widget);
235
236         scrollbutton->button = 0;
237         gtk_vscrollbutton_remove_timer(scrollbutton);
238         gtk_vscrollbutton_set_sensitivity(scrollbutton->adjustment, scrollbutton);
239     }
240     return TRUE;
241 }
242
243 static gint gtk_vscrollbutton_scroll(GtkVScrollbutton *scrollbutton)
244 {
245     gfloat new_value;
246     gint return_val;
247
248     g_return_val_if_fail(scrollbutton != NULL, FALSE);
249     g_return_val_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton), FALSE);
250
251     new_value = scrollbutton->adjustment->value;
252     return_val = TRUE;
253
254     switch (scrollbutton->scroll_type) {
255
256     case GTK_SCROLL_STEP_BACKWARD:
257         new_value -= scrollbutton->adjustment->step_increment;
258         if (new_value <= scrollbutton->adjustment->lower) {
259             new_value = scrollbutton->adjustment->lower;
260             return_val = FALSE;
261             scrollbutton->timer = 0;
262         }
263         break;
264
265     case GTK_SCROLL_STEP_FORWARD:
266         new_value += scrollbutton->adjustment->step_increment;
267         if (new_value >=
268             (scrollbutton->adjustment->upper -
269              scrollbutton->adjustment->page_size)) {
270             new_value =
271                 scrollbutton->adjustment->upper -
272                 scrollbutton->adjustment->page_size;
273             return_val = FALSE;
274             scrollbutton->timer = 0;
275         }
276         break;
277     
278     default:
279         break;
280     
281     }
282
283     if (new_value != scrollbutton->adjustment->value) {
284         scrollbutton->adjustment->value = new_value;
285         g_signal_emit_by_name(G_OBJECT
286                                 (scrollbutton->adjustment),
287                                 "value_changed");
288         gtk_widget_queue_resize(GTK_WIDGET(scrollbutton)); /* ensure resize */
289     }
290
291     return return_val;
292 }
293
294 static gboolean
295 gtk_vscrollbutton_timer_1st_time(GtkVScrollbutton *scrollbutton)
296 {
297     /*
298      * If the real timeout function succeeds and the timeout is still set,
299      * replace it with a quicker one so successive scrolling goes faster.
300      */
301     g_object_ref(G_OBJECT(scrollbutton));
302     if (scrollbutton->timer) {
303         /* We explicitely remove ourselves here in the paranoia
304          * that due to things happening above in the callback
305          * above, we might have been removed, and another added.
306          */
307         g_source_remove(scrollbutton->timer);
308         scrollbutton->timer = gtk_timeout_add(SCROLL_LATER_DELAY,
309                                               (GtkFunction)
310                                               gtk_real_vscrollbutton_timer,
311                                               scrollbutton);
312     }
313     g_object_unref(G_OBJECT(scrollbutton));
314     return FALSE;               /* don't keep calling this function */
315 }
316
317
318 static void gtk_vscrollbutton_add_timer(GtkVScrollbutton *scrollbutton)
319 {
320     g_return_if_fail(scrollbutton != NULL);
321     g_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton));
322
323     if (!scrollbutton->timer) {
324         scrollbutton->need_timer = TRUE;
325         scrollbutton->timer = gtk_timeout_add(SCROLL_INITIAL_DELAY,
326                                               (GtkFunction)
327                                               gtk_vscrollbutton_timer_1st_time,
328                                               scrollbutton);
329     }
330 }
331
332 static void gtk_vscrollbutton_remove_timer(GtkVScrollbutton *scrollbutton)
333 {
334     g_return_if_fail(scrollbutton != NULL);
335     g_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton));
336
337     if (scrollbutton->timer) {
338         gtk_timeout_remove(scrollbutton->timer);
339         scrollbutton->timer = 0;
340     }
341     scrollbutton->need_timer = FALSE;
342 }
343
344 static gint gtk_real_vscrollbutton_timer(GtkVScrollbutton *scrollbutton)
345 {
346     gint return_val;
347
348     GDK_THREADS_ENTER();
349
350     return_val = TRUE;
351     if (!scrollbutton->timer) {
352         return_val = FALSE;
353         if (scrollbutton->need_timer)
354             scrollbutton->timer =
355                 gtk_timeout_add(SCROLL_TIMER_LENGTH, 
356                                 (GtkFunction) gtk_real_vscrollbutton_timer,
357                                 (gpointer) scrollbutton);
358         else {
359             GDK_THREADS_LEAVE();
360             return FALSE;
361         }
362         scrollbutton->need_timer = FALSE;
363     }
364     GDK_THREADS_LEAVE();
365     return_val = gtk_vscrollbutton_scroll(scrollbutton);
366     return return_val;
367 }
368
369 static void gtk_vscrollbutton_set_sensitivity   (GtkAdjustment    *adjustment,
370                                                  GtkVScrollbutton *scrollbutton)
371 {
372         if (!GTK_WIDGET_REALIZED(GTK_WIDGET(scrollbutton))) return;
373         if (scrollbutton->button != 0) return; /* not while something is pressed */
374         
375         gtk_widget_set_sensitive(scrollbutton->upbutton, 
376                                  (adjustment->value > adjustment->lower));
377         gtk_widget_set_sensitive(scrollbutton->downbutton, 
378                                  (adjustment->value <  (adjustment->upper - adjustment->page_size)));
379 }