d519a972efbcc84adbf537743afdf518b2297e16
[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
40 #define SCROLL_TIMER_LENGTH  20
41 #define SCROLL_INITIAL_DELAY 100        /* must hold button this long before ... */
42 #define SCROLL_LATER_DELAY   20 /* ... it starts repeating at this rate  */
43 #define SCROLL_DELAY_LENGTH  300
44
45
46 enum {
47     ARG_0,
48     ARG_ADJUSTMENT
49 };
50
51 static void gtk_vscrollbutton_class_init(GtkVScrollbuttonClass * klass);
52 static void gtk_vscrollbutton_init(GtkVScrollbutton * vscrollbutton);
53
54 GType gtk_vscrollbutton_get_type                (void);
55 GtkWidget *gtk_vscrollbutton_new                (GtkAdjustment    *adjustment);
56
57 static gint gtk_vscrollbutton_button_release    (GtkWidget        *widget,
58                                                  GdkEventButton   *event,
59                                                  GtkVScrollbutton *scrollbutton);
60
61 static void gtk_vscrollbutton_set_adjustment    (GtkVScrollbutton *scrollbutton,
62                                                  GtkAdjustment    *adjustment);
63
64 static gint gtk_vscrollbutton_button_press      (GtkWidget        *widget,
65                                                  GdkEventButton   *event,
66                                                  GtkVScrollbutton *scrollbutton);
67
68 static gint gtk_vscrollbutton_button_release    (GtkWidget        *widget,
69                                                  GdkEventButton   *event,
70                                                  GtkVScrollbutton *scrollbutton);
71
72 gint gtk_vscrollbutton_scroll           (GtkVScrollbutton *scrollbutton);
73
74 static gboolean gtk_vscrollbutton_timer_1st_time(GtkVScrollbutton *scrollbutton);
75
76 static void gtk_vscrollbutton_add_timer         (GtkVScrollbutton *scrollbutton);
77
78 static void gtk_vscrollbutton_remove_timer      (GtkVScrollbutton *scrollbutton);
79
80 static gint gtk_real_vscrollbutton_timer        (GtkVScrollbutton *scrollbutton);
81
82 static void gtk_vscrollbutton_set_sensitivity   (GtkAdjustment    *adjustment,
83                                                  GtkVScrollbutton *scrollbutton);
84
85 GType gtk_vscrollbutton_get_type(void)
86 {
87     static GType vscrollbutton_type = 0;
88
89     if (!vscrollbutton_type) {
90         static const GTypeInfo vscrollbutton_info = {
91                         sizeof (GtkVScrollbuttonClass),
92
93                         (GBaseInitFunc) NULL,
94                         (GBaseFinalizeFunc) NULL,
95
96                         (GClassInitFunc) gtk_vscrollbutton_class_init,
97                         (GClassFinalizeFunc) NULL,
98                         NULL,   /* class_data */
99
100                         sizeof (GtkVScrollbutton),
101                         0,      /* n_preallocs */
102                         (GInstanceInitFunc) gtk_vscrollbutton_init,
103         };
104
105         vscrollbutton_type = g_type_register_static (GTK_TYPE_VBOX, "GtkVScrollbutton", &vscrollbutton_info, (GTypeFlags)0);
106     }
107
108     return vscrollbutton_type;
109 }
110
111 static void gtk_vscrollbutton_class_init(GtkVScrollbuttonClass *class)
112 {
113 }
114
115 static GdkCursor *hand_cursor = NULL;
116
117 static gboolean vscroll_visi_notify(GtkWidget *widget,
118                                        GdkEventVisibility *event,
119                                        gpointer data)
120 {
121         gdk_window_set_cursor(widget->window, hand_cursor);
122         return FALSE;
123 }
124
125 static gboolean vscroll_leave_notify(GtkWidget *widget,
126                                       GdkEventCrossing *event,
127                                        gpointer data)
128 {
129         gdk_window_set_cursor(widget->window, NULL);
130         return FALSE;
131 }
132
133 static gboolean vscroll_enter_notify(GtkWidget *widget,
134                                       GdkEventCrossing *event,
135                                        gpointer data)
136 {
137         gdk_window_set_cursor(widget->window, hand_cursor);
138         return FALSE;
139 }
140
141
142 static void gtk_vscrollbutton_init(GtkVScrollbutton *scrollbutton)
143 {
144     GtkWidget *arrow;
145
146     if (!hand_cursor)
147             hand_cursor = gdk_cursor_new(GDK_HAND2);
148
149     scrollbutton->upbutton = gtk_event_box_new();
150     scrollbutton->downbutton = gtk_event_box_new();
151     arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
152     gtk_widget_show(arrow);
153     gtk_container_add(GTK_CONTAINER(scrollbutton->upbutton), arrow);
154     gtk_widget_set_size_request(scrollbutton->upbutton, -1, 16);
155     arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
156     gtk_widget_show(arrow);
157     gtk_container_add(GTK_CONTAINER(scrollbutton->downbutton), arrow);
158     gtk_widget_set_size_request(scrollbutton->downbutton, -1, 16);
159     GTK_WIDGET_UNSET_FLAGS(scrollbutton->upbutton, GTK_CAN_FOCUS);
160     GTK_WIDGET_UNSET_FLAGS(scrollbutton->downbutton, GTK_CAN_FOCUS);
161     gtk_widget_show(scrollbutton->downbutton);
162     gtk_widget_show(scrollbutton->upbutton);
163
164     g_signal_connect(G_OBJECT(scrollbutton->upbutton), "visibility-notify-event",
165                      G_CALLBACK(vscroll_visi_notify), NULL);
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), "visibility-notify-event",
173                      G_CALLBACK(vscroll_visi_notify), NULL);
174     g_signal_connect(G_OBJECT(scrollbutton->downbutton), "motion-notify-event",
175                      G_CALLBACK(vscroll_visi_notify), NULL);
176     g_signal_connect(G_OBJECT(scrollbutton->downbutton), "leave-notify-event",
177                      G_CALLBACK(vscroll_leave_notify), NULL);
178     g_signal_connect(G_OBJECT(scrollbutton->downbutton), "enter-notify-event",
179                      G_CALLBACK(vscroll_enter_notify), NULL);
180
181     g_signal_connect(G_OBJECT(scrollbutton->upbutton),
182                        "button_press_event",
183                        G_CALLBACK(gtk_vscrollbutton_button_press),
184                        scrollbutton);
185     g_signal_connect(G_OBJECT(scrollbutton->downbutton),
186                        "button_press_event",
187                        G_CALLBACK(gtk_vscrollbutton_button_press),
188                        scrollbutton);
189     g_signal_connect(G_OBJECT(scrollbutton->upbutton),
190                      "button_release_event",
191                      G_CALLBACK
192                      (gtk_vscrollbutton_button_release), scrollbutton);
193     g_signal_connect(G_OBJECT(scrollbutton->downbutton),
194                      "button_release_event",
195                      G_CALLBACK
196                      (gtk_vscrollbutton_button_release), scrollbutton);
197     gtk_box_pack_start(GTK_BOX(&scrollbutton->vbox),
198                        scrollbutton->upbutton, TRUE, TRUE, 0);
199     gtk_box_pack_end(GTK_BOX(&scrollbutton->vbox),
200                      scrollbutton->downbutton, TRUE, TRUE, 0);
201     scrollbutton->timer = 0;
202 }
203
204 GtkWidget *gtk_vscrollbutton_new(GtkAdjustment *adjustment)
205 {
206     GtkWidget *vscrollbutton;
207     vscrollbutton = g_object_new (gtk_vscrollbutton_get_type(),
208                         NULL);
209     gtk_vscrollbutton_set_adjustment(GTK_VSCROLLBUTTON(vscrollbutton),
210                                      adjustment);
211     g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment),
212                        "value_changed",
213                        G_CALLBACK
214                        (gtk_vscrollbutton_set_sensitivity), vscrollbutton);
215     g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment),
216                        "changed",
217                        G_CALLBACK
218                        (gtk_vscrollbutton_set_sensitivity), vscrollbutton);
219     return vscrollbutton;
220 }
221
222
223 void gtk_vscrollbutton_set_adjustment(GtkVScrollbutton *scrollbutton,
224                                       GtkAdjustment *adjustment)
225 {
226     cm_return_if_fail(scrollbutton != NULL);
227     cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton));
228
229     if (!adjustment)
230             adjustment =
231             GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0));
232     else
233         cm_return_if_fail(GTK_IS_ADJUSTMENT(adjustment));
234
235     if (scrollbutton->adjustment != adjustment) {
236         if (scrollbutton->adjustment) {
237             g_signal_handlers_disconnect_matched(scrollbutton->adjustment,
238                                                  G_SIGNAL_MATCH_DATA,
239                                                  0, 0, NULL, NULL, 
240                                                  (gpointer) scrollbutton);
241             g_object_unref(G_OBJECT(scrollbutton->adjustment));
242         }
243
244         scrollbutton->adjustment = adjustment;
245         g_object_ref(G_OBJECT(adjustment));
246 #if GLIB_CHECK_VERSION(2,10,0)
247         g_object_ref_sink (G_OBJECT(adjustment));
248 #else
249         gtk_object_ref (G_OBJECT (adjustment));
250         gtk_object_sink (G_OBJECT (adjustment));
251 #endif
252     }
253 }
254
255 static gint gtk_vscrollbutton_button_press(GtkWidget *widget,
256                                            GdkEventButton *event,
257                                            GtkVScrollbutton *scrollbutton)
258 {
259     if (!GTK_WIDGET_HAS_FOCUS(widget))
260         gtk_widget_grab_focus(widget);
261
262     if (scrollbutton->button == 0) {
263         gtk_grab_add(widget);
264         scrollbutton->button = event->button;
265
266         if (widget == scrollbutton->downbutton)
267             scrollbutton->scroll_type = GTK_SCROLL_STEP_FORWARD;
268         else
269             scrollbutton->scroll_type = GTK_SCROLL_STEP_BACKWARD;
270         gtk_vscrollbutton_scroll(scrollbutton);
271         gtk_vscrollbutton_add_timer(scrollbutton);
272     }
273     return TRUE;
274 }
275
276
277 static gint gtk_vscrollbutton_button_release(GtkWidget *widget,
278                                              GdkEventButton *event,
279                                              GtkVScrollbutton *scrollbutton)
280 {
281     if (!GTK_WIDGET_HAS_FOCUS(widget))
282         gtk_widget_grab_focus(widget);
283
284     if (scrollbutton->button == event->button) {
285         gtk_grab_remove(widget);
286
287         scrollbutton->button = 0;
288         gtk_vscrollbutton_remove_timer(scrollbutton);
289         gtk_vscrollbutton_set_sensitivity(scrollbutton->adjustment, scrollbutton);
290     }
291     return TRUE;
292 }
293
294 gint gtk_vscrollbutton_scroll(GtkVScrollbutton *scrollbutton)
295 {
296     gfloat new_value;
297     gint 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 = scrollbutton->adjustment->value;
303     return_val = TRUE;
304
305     switch (scrollbutton->scroll_type) {
306
307     case GTK_SCROLL_STEP_BACKWARD:
308         new_value -= scrollbutton->adjustment->step_increment;
309         if (new_value <= scrollbutton->adjustment->lower) {
310             new_value = scrollbutton->adjustment->lower;
311             return_val = FALSE;
312             scrollbutton->timer = 0;
313         }
314         break;
315
316     case GTK_SCROLL_STEP_FORWARD:
317         new_value += scrollbutton->adjustment->step_increment;
318         if (new_value >=
319             (scrollbutton->adjustment->upper -
320              scrollbutton->adjustment->page_size)) {
321             new_value =
322                 scrollbutton->adjustment->upper -
323                 scrollbutton->adjustment->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 != scrollbutton->adjustment->value) {
335         scrollbutton->adjustment->value = 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                                             (GtkFunction)
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                                             (GtkFunction)
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 gint gtk_real_vscrollbutton_timer(GtkVScrollbutton *scrollbutton)
396 {
397     gint 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                               (GtkFunction) 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         if (!GTK_WIDGET_REALIZED(GTK_WIDGET(scrollbutton))) return;
424         if (scrollbutton->button != 0) return; /* not while something is pressed */
425         
426         gtk_widget_set_sensitive(scrollbutton->upbutton, 
427                                  (adjustment->value > adjustment->lower));
428         gtk_widget_set_sensitive(scrollbutton->downbutton, 
429                                  (adjustment->value <  (adjustment->upper - adjustment->page_size)));
430 }