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