claws-mail/src/gtk/gtkvscrollbutton.c
2020-01-30 14:17:08 +00:00

432 lines
13 KiB
C

/* 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 <http://www.gnu.org/licenses/>.
*/
/*
* 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 <glib.h>
#include <gtk/gtk.h>
#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))));
}