/* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library. If not, see . */ /* * Modified by the GTK+ Team and others 1997-1999. See the AUTHORS * file for a list of people on the GTK+ Team. See the ChangeLog * files for a list of changes. These files are distributed with * GTK+ at ftp://ftp.gtk.org/pub/gtk/. */ /* * Simple composite widget to provide vertical scrolling, based * on GtkRange widget code. * Modified by the Sylpheed Team and others 2003 */ #ifdef HAVE_CONFIG_H # include "config.h" #include "claws-features.h" #endif #include #include #include "utils.h" #include "gtkvscrollbutton.h" #include "gtkutils.h" #define SCROLL_TIMER_LENGTH 20 #define SCROLL_INITIAL_DELAY 100 /* must hold button this long before ... */ #define SCROLL_LATER_DELAY 20 /* ... it starts repeating at this rate */ #define SCROLL_DELAY_LENGTH 300 enum { ARG_0, ARG_ADJUSTMENT }; static void gtk_vscrollbutton_class_init(GtkVScrollbuttonClass * klass); static void gtk_vscrollbutton_init(GtkVScrollbutton * vscrollbutton); GType gtk_vscrollbutton_get_type (void); GtkWidget *gtk_vscrollbutton_new (GtkAdjustment *adjustment); static gint gtk_vscrollbutton_button_release (GtkWidget *widget, GdkEventButton *event, GtkVScrollbutton *scrollbutton); static void gtk_vscrollbutton_set_adjustment (GtkVScrollbutton *scrollbutton, GtkAdjustment *adjustment); static gint gtk_vscrollbutton_button_press (GtkWidget *widget, GdkEventButton *event, GtkVScrollbutton *scrollbutton); static gint gtk_vscrollbutton_button_release (GtkWidget *widget, GdkEventButton *event, GtkVScrollbutton *scrollbutton); gint gtk_vscrollbutton_scroll (GtkVScrollbutton *scrollbutton); static gboolean gtk_vscrollbutton_timer_1st_time(GtkVScrollbutton *scrollbutton); static void gtk_vscrollbutton_add_timer (GtkVScrollbutton *scrollbutton); static void gtk_vscrollbutton_remove_timer (GtkVScrollbutton *scrollbutton); static gboolean gtk_real_vscrollbutton_timer (GtkVScrollbutton *scrollbutton); static void gtk_vscrollbutton_set_sensitivity (GtkAdjustment *adjustment, GtkVScrollbutton *scrollbutton); GType gtk_vscrollbutton_get_type(void) { static GType vscrollbutton_type = 0; if (!vscrollbutton_type) { static const GTypeInfo vscrollbutton_info = { sizeof (GtkVScrollbuttonClass), (GBaseInitFunc) NULL, (GBaseFinalizeFunc) NULL, (GClassInitFunc) gtk_vscrollbutton_class_init, (GClassFinalizeFunc) NULL, NULL, /* class_data */ sizeof (GtkVScrollbutton), 0, /* n_preallocs */ (GInstanceInitFunc) gtk_vscrollbutton_init, (const GTypeValueTable *) NULL /* value table */ }; vscrollbutton_type = g_type_register_static (GTK_TYPE_BOX, "GtkVScrollbutton", &vscrollbutton_info, (GTypeFlags)0); } return vscrollbutton_type; } static void gtk_vscrollbutton_class_init(GtkVScrollbuttonClass *class) { } static GdkCursor *hand_cursor = NULL; static gboolean vscroll_visi_notify(GtkWidget *widget, GdkEventVisibility *event, gpointer data) { gdk_window_set_cursor(gtk_widget_get_window(widget), hand_cursor); return FALSE; } static gboolean vscroll_leave_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer data) { gdk_window_set_cursor(gtk_widget_get_window(widget), NULL); return FALSE; } static gboolean vscroll_enter_notify(GtkWidget *widget, GdkEventCrossing *event, gpointer data) { gdk_window_set_cursor(gtk_widget_get_window(widget), hand_cursor); return FALSE; } static void gtk_vscrollbutton_init(GtkVScrollbutton *scrollbutton) { GtkWidget *arrow; if (!hand_cursor) hand_cursor = gdk_cursor_new_for_display( gtk_widget_get_display(GTK_WIDGET(scrollbutton)),GDK_HAND2); scrollbutton->upbutton = gtk_event_box_new(); scrollbutton->downbutton = gtk_event_box_new(); arrow = gtk_image_new_from_icon_name("pan-up-symbolic", GTK_ICON_SIZE_MENU); gtk_widget_show(arrow); gtk_container_add(GTK_CONTAINER(scrollbutton->upbutton), arrow); gtk_widget_set_size_request(scrollbutton->upbutton, -1, 16); arrow = gtk_image_new_from_icon_name("pan-down-symbolic", GTK_ICON_SIZE_MENU); gtk_widget_show(arrow); gtk_container_add(GTK_CONTAINER(scrollbutton->downbutton), arrow); gtk_widget_set_size_request(scrollbutton->downbutton, -1, 16); gtk_widget_set_can_focus(scrollbutton->upbutton, FALSE); gtk_widget_set_can_focus(scrollbutton->downbutton, FALSE); gtk_widget_show(scrollbutton->downbutton); gtk_widget_show(scrollbutton->upbutton); g_signal_connect(G_OBJECT(scrollbutton->upbutton), "motion-notify-event", G_CALLBACK(vscroll_visi_notify), NULL); g_signal_connect(G_OBJECT(scrollbutton->upbutton), "leave-notify-event", G_CALLBACK(vscroll_leave_notify), NULL); g_signal_connect(G_OBJECT(scrollbutton->upbutton), "enter-notify-event", G_CALLBACK(vscroll_enter_notify), NULL); g_signal_connect(G_OBJECT(scrollbutton->downbutton), "motion-notify-event", G_CALLBACK(vscroll_visi_notify), NULL); g_signal_connect(G_OBJECT(scrollbutton->downbutton), "leave-notify-event", G_CALLBACK(vscroll_leave_notify), NULL); g_signal_connect(G_OBJECT(scrollbutton->downbutton), "enter-notify-event", G_CALLBACK(vscroll_enter_notify), NULL); g_signal_connect(G_OBJECT(scrollbutton->upbutton), "button_press_event", G_CALLBACK(gtk_vscrollbutton_button_press), scrollbutton); g_signal_connect(G_OBJECT(scrollbutton->downbutton), "button_press_event", G_CALLBACK(gtk_vscrollbutton_button_press), scrollbutton); g_signal_connect(G_OBJECT(scrollbutton->upbutton), "button_release_event", G_CALLBACK (gtk_vscrollbutton_button_release), scrollbutton); g_signal_connect(G_OBJECT(scrollbutton->downbutton), "button_release_event", G_CALLBACK (gtk_vscrollbutton_button_release), scrollbutton); gtk_box_pack_start(GTK_BOX(&scrollbutton->vbox), scrollbutton->upbutton, TRUE, TRUE, 0); gtk_box_pack_end(GTK_BOX(&scrollbutton->vbox), scrollbutton->downbutton, TRUE, TRUE, 0); scrollbutton->timer = 0; } GtkWidget *gtk_vscrollbutton_new(GtkAdjustment *adjustment) { GtkWidget *vscrollbutton; vscrollbutton = g_object_new (gtk_vscrollbutton_get_type(), NULL); gtk_vscrollbutton_set_adjustment(GTK_VSCROLLBUTTON(vscrollbutton), adjustment); g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment), "value_changed", G_CALLBACK (gtk_vscrollbutton_set_sensitivity), vscrollbutton); g_signal_connect(G_OBJECT(GTK_VSCROLLBUTTON(vscrollbutton)->adjustment), "changed", G_CALLBACK (gtk_vscrollbutton_set_sensitivity), vscrollbutton); return vscrollbutton; } void gtk_vscrollbutton_set_adjustment(GtkVScrollbutton *scrollbutton, GtkAdjustment *adjustment) { cm_return_if_fail(scrollbutton != NULL); cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton)); if (!adjustment) adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0.0, 0.0, 0.0, 0.0, 0.0, 0.0)); else cm_return_if_fail(GTK_IS_ADJUSTMENT(adjustment)); if (scrollbutton->adjustment != adjustment) { if (scrollbutton->adjustment) { g_signal_handlers_disconnect_matched(scrollbutton->adjustment, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, (gpointer) scrollbutton); g_object_unref(G_OBJECT(scrollbutton->adjustment)); } scrollbutton->adjustment = adjustment; g_object_ref(G_OBJECT(adjustment)); g_object_ref_sink (G_OBJECT(adjustment)); } } static gint gtk_vscrollbutton_button_press(GtkWidget *widget, GdkEventButton *event, GtkVScrollbutton *scrollbutton) { if (!gtk_widget_has_focus(widget)) gtk_widget_grab_focus(widget); if (scrollbutton->button == 0) { gtk_grab_add(widget); scrollbutton->button = event->button; if (widget == scrollbutton->downbutton) scrollbutton->scroll_type = GTK_SCROLL_STEP_FORWARD; else scrollbutton->scroll_type = GTK_SCROLL_STEP_BACKWARD; gtk_vscrollbutton_scroll(scrollbutton); gtk_vscrollbutton_add_timer(scrollbutton); } return TRUE; } static gint gtk_vscrollbutton_button_release(GtkWidget *widget, GdkEventButton *event, GtkVScrollbutton *scrollbutton) { if (!gtk_widget_has_focus(widget)) gtk_widget_grab_focus(widget); if (scrollbutton->button == event->button) { gtk_grab_remove(widget); scrollbutton->button = 0; gtk_vscrollbutton_remove_timer(scrollbutton); gtk_vscrollbutton_set_sensitivity(scrollbutton->adjustment, scrollbutton); } return TRUE; } gboolean gtk_vscrollbutton_scroll(GtkVScrollbutton *scrollbutton) { gfloat bound; gfloat new_value; gfloat page_size; gfloat value; gboolean return_val; cm_return_val_if_fail(scrollbutton != NULL, FALSE); cm_return_val_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton), FALSE); new_value = value = gtk_adjustment_get_value(scrollbutton->adjustment); return_val = TRUE; switch (scrollbutton->scroll_type) { case GTK_SCROLL_STEP_BACKWARD: new_value = value - gtk_adjustment_get_step_increment(scrollbutton->adjustment); bound = gtk_adjustment_get_lower(scrollbutton->adjustment); if (new_value <= bound) { new_value = bound; return_val = FALSE; scrollbutton->timer = 0; } break; case GTK_SCROLL_STEP_FORWARD: new_value = value + gtk_adjustment_get_step_increment(scrollbutton->adjustment); bound = gtk_adjustment_get_upper(scrollbutton->adjustment); page_size = gtk_adjustment_get_page_size(scrollbutton->adjustment); if (new_value >= (bound - page_size)) { new_value = bound - page_size; return_val = FALSE; scrollbutton->timer = 0; } break; default: break; } if (new_value != value) { gtk_adjustment_set_value(scrollbutton->adjustment, new_value); g_signal_emit_by_name(G_OBJECT (scrollbutton->adjustment), "value_changed"); gtk_widget_queue_resize(GTK_WIDGET(scrollbutton)); /* ensure resize */ } return return_val; } static gboolean gtk_vscrollbutton_timer_1st_time(GtkVScrollbutton *scrollbutton) { /* * If the real timeout function succeeds and the timeout is still set, * replace it with a quicker one so successive scrolling goes faster. */ g_object_ref(G_OBJECT(scrollbutton)); if (scrollbutton->timer) { /* We explicitely remove ourselves here in the paranoia * that due to things happening above in the callback * above, we might have been removed, and another added. */ g_source_remove(scrollbutton->timer); scrollbutton->timer = g_timeout_add(SCROLL_LATER_DELAY, (GSourceFunc) gtk_real_vscrollbutton_timer, scrollbutton); } g_object_unref(G_OBJECT(scrollbutton)); return FALSE; /* don't keep calling this function */ } static void gtk_vscrollbutton_add_timer(GtkVScrollbutton *scrollbutton) { cm_return_if_fail(scrollbutton != NULL); cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton)); if (!scrollbutton->timer) { scrollbutton->need_timer = TRUE; scrollbutton->timer = g_timeout_add(SCROLL_INITIAL_DELAY, (GSourceFunc) gtk_vscrollbutton_timer_1st_time, scrollbutton); } } static void gtk_vscrollbutton_remove_timer(GtkVScrollbutton *scrollbutton) { cm_return_if_fail(scrollbutton != NULL); cm_return_if_fail(GTK_IS_VSCROLLBUTTON(scrollbutton)); if (scrollbutton->timer) { g_source_remove(scrollbutton->timer); scrollbutton->timer = 0; } scrollbutton->need_timer = FALSE; } static gboolean gtk_real_vscrollbutton_timer(GtkVScrollbutton *scrollbutton) { gboolean return_val; GDK_THREADS_ENTER(); return_val = TRUE; if (!scrollbutton->timer) { return_val = FALSE; if (scrollbutton->need_timer) scrollbutton->timer = g_timeout_add(SCROLL_TIMER_LENGTH, (GSourceFunc) gtk_real_vscrollbutton_timer, (gpointer) scrollbutton); else { GDK_THREADS_LEAVE(); return FALSE; } scrollbutton->need_timer = FALSE; } GDK_THREADS_LEAVE(); return_val = gtk_vscrollbutton_scroll(scrollbutton); return return_val; } static void gtk_vscrollbutton_set_sensitivity (GtkAdjustment *adjustment, GtkVScrollbutton *scrollbutton) { gfloat value; if (!gtk_widget_get_realized(GTK_WIDGET(scrollbutton))) return; if (scrollbutton->button != 0) return; /* not while something is pressed */ value = gtk_adjustment_get_value(adjustment); gtk_widget_set_sensitive(scrollbutton->upbutton, (value > gtk_adjustment_get_lower(adjustment))); gtk_widget_set_sensitive(scrollbutton->downbutton, (value < (gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size(adjustment)))); }