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