1098 lines
37 KiB
C
1098 lines
37 KiB
C
/*
|
|
Copyright (C) 2007 Henrik Hedberg <hhedberg@innologies.fi>
|
|
Copyright (C) 2009 Nadav Wiener <nadavwr@yahoo.com>
|
|
Copyright (C) 2009 Christian Dywan <christian@twotoasts.de>
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU Lesser General Public
|
|
License as published by the Free Software Foundation; either
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
See the file COPYING for the full license text.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "katze-scrolled.h"
|
|
|
|
#define DEFAULT_INTERVAL 50
|
|
#define DEFAULT_DECELERATION 0.7
|
|
#define DEFAULT_DRAGGING_STOPPED_DELAY 100
|
|
|
|
/**
|
|
* SECTION:katze-scrolled
|
|
* @short_description: Implements drag scrolling and kinetic scrolling
|
|
* @see_also: #GtkScrolledWindow
|
|
*
|
|
* A scrolled window derived from #GtkScrolledWindow that implements
|
|
* drag scrolling and kinetic scrolling. Can be used as a drop-in replacement
|
|
* for the existing #GtkScrolledWindow.
|
|
*
|
|
* If a direct child of the #KatzeScrolled has its own window
|
|
* (InputOnly is enough for events), it is automatically activated when added
|
|
* as a child. All motion events in that area will be used to scroll.
|
|
*
|
|
* If some descendant widgets capture button press, button release and/ or
|
|
* motion nofity events, the user can not scroll the area by pressing those
|
|
* widgets (unless the widget is activated). #GtkButton is a typical example
|
|
* of that. Usually that is the desired behaviour.
|
|
*
|
|
* Any widget can be registered to provide pointer events for the
|
|
* #KatzeScrolled by using the
|
|
* #katze_scrolled_activate_scrolling function.
|
|
*
|
|
**/
|
|
|
|
G_DEFINE_TYPE (KatzeScrolled, katze_scrolled, GTK_TYPE_SCROLLED_WINDOW);
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
|
|
PROP_DRAG_SCROLLING,
|
|
PROP_KINETIC_SCROLLING
|
|
};
|
|
|
|
static void
|
|
katze_scrolled_set_property (GObject* object,
|
|
guint prop_id,
|
|
const GValue* value,
|
|
GParamSpec* pspec);
|
|
|
|
static void
|
|
katze_scrolled_get_property (GObject* object,
|
|
guint prop_id,
|
|
GValue* value,
|
|
GParamSpec* pspec);
|
|
|
|
static void
|
|
katze_scrolled_dispose (GObject* object);
|
|
|
|
static void
|
|
katze_scrolled_activate_scrolling (KatzeScrolled* scrolled,
|
|
GtkWidget* widget);
|
|
|
|
static void
|
|
katze_scrolled_set_drag_scrolling (KatzeScrolled* scrolled,
|
|
gboolean drag_scrolling);
|
|
|
|
struct _KatzeScrolledPrivate
|
|
{
|
|
/* Settings */
|
|
guint interval;
|
|
gdouble deceleration;
|
|
gboolean drag_scrolling;
|
|
gboolean kinetic_scrolling;
|
|
guint32 dragging_stopped_delay;
|
|
gboolean scrolling_hints;
|
|
|
|
/* Temporary variables */
|
|
gboolean dragged;
|
|
gboolean press_received;
|
|
GdkWindow* synthetic_crossing_event_window;
|
|
|
|
/* Disabling twice happening scrolling adjustment */
|
|
GtkAdjustment* hadjustment;
|
|
GtkWidget* viewport;
|
|
|
|
/* Motion scrolling */
|
|
gint start_x;
|
|
gint start_y;
|
|
gint previous_x;
|
|
gint previous_y;
|
|
gint farest_x;
|
|
gint farest_y;
|
|
guint32 start_time;
|
|
guint32 previous_time;
|
|
guint32 farest_time;
|
|
gboolean going_right;
|
|
gboolean going_down;
|
|
|
|
/* Kinetic scrolling */
|
|
guint scrolling_timeout_id;
|
|
gdouble horizontal_speed;
|
|
gdouble vertical_speed;
|
|
gdouble horizontal_deceleration;
|
|
gdouble vertical_deceleration;
|
|
|
|
/* Internal scrollbars */
|
|
GdkWindow* vertical_scrollbar_window;
|
|
GdkWindow* horizontal_scrollbar_window;
|
|
gint vertical_scrollbar_size;
|
|
gint horizontal_scrollbar_size;
|
|
guint hide_scrollbars_timeout_id;
|
|
GdkGC* hilight_gc;
|
|
GdkGC* shadow_gc;
|
|
};
|
|
|
|
typedef struct _KatzeScrolledState KatzeScrolledState;
|
|
typedef gboolean (*KatzeScrolledEventHandler)(GdkEvent* event,
|
|
KatzeScrolledState* state,
|
|
gpointer user_data);
|
|
|
|
typedef struct
|
|
{
|
|
KatzeScrolledEventHandler event_handler;
|
|
gpointer user_data;
|
|
} EventHandlerData;
|
|
|
|
struct _KatzeScrolledState
|
|
{
|
|
GList* current_event_handler;
|
|
};
|
|
|
|
static GList* event_handlers = NULL;
|
|
|
|
static void
|
|
katze_scrolled_event_handler_func (GdkEvent* event,
|
|
gpointer data);
|
|
|
|
static void
|
|
katze_scrolled_event_handler_append (KatzeScrolledEventHandler event_handler,
|
|
gpointer user_data)
|
|
{
|
|
EventHandlerData* data;
|
|
|
|
data = g_new0 (EventHandlerData, 1);
|
|
data->event_handler = event_handler;
|
|
data->user_data = user_data;
|
|
event_handlers = g_list_append (event_handlers, data);
|
|
|
|
gdk_event_handler_set ((GdkEventFunc)katze_scrolled_event_handler_func, NULL, NULL);
|
|
}
|
|
|
|
static void
|
|
katze_scrolled_event_handler_next (GdkEvent* event,
|
|
KatzeScrolledState* state)
|
|
{
|
|
EventHandlerData* data;
|
|
gboolean stop_propagating;
|
|
|
|
state->current_event_handler = g_list_next (state->current_event_handler);
|
|
if (state->current_event_handler)
|
|
{
|
|
data = (EventHandlerData*)state->current_event_handler->data;
|
|
stop_propagating = data->event_handler (event, state, data->user_data);
|
|
if (!stop_propagating && state->current_event_handler)
|
|
g_critical ("%s: handler returned FALSE without calling %s first",
|
|
G_STRFUNC, G_STRFUNC);
|
|
}
|
|
else
|
|
gtk_main_do_event (event);
|
|
}
|
|
|
|
static void
|
|
katze_scrolled_event_handler_func (GdkEvent* event,
|
|
gpointer user_data)
|
|
{
|
|
KatzeScrolledState* state;
|
|
EventHandlerData* data;
|
|
gboolean stop_propagating;
|
|
|
|
state = g_new0 (KatzeScrolledState, 1);
|
|
state->current_event_handler = g_list_first (event_handlers);
|
|
if (state->current_event_handler)
|
|
{
|
|
data = (EventHandlerData*)state->current_event_handler->data;
|
|
stop_propagating = data->event_handler (event, state, data->user_data);
|
|
if (!stop_propagating && state->current_event_handler)
|
|
g_critical ("%s: handler returned FALSE without calling %s first",
|
|
G_STRFUNC, "katze_scrolled_event_handler_next");
|
|
}
|
|
else
|
|
gtk_main_do_event (event);
|
|
|
|
g_free (state);
|
|
}
|
|
|
|
static GdkWindow* current_gdk_window;
|
|
static KatzeScrolled* current_scrolled_window;
|
|
static GtkWidget* current_widget;
|
|
static gboolean synthetized_crossing_event;
|
|
|
|
static GTree* activated_widgets;
|
|
|
|
static gint
|
|
compare_pointers (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
return a - b;
|
|
}
|
|
|
|
static void
|
|
disable_hadjustment (KatzeScrolled* scrolled)
|
|
{
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
GtkAdjustment* hadjustment;
|
|
GtkWidget* viewport;
|
|
|
|
if ((hadjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled)))
|
|
&& priv->hadjustment != hadjustment)
|
|
{
|
|
priv->hadjustment = hadjustment;
|
|
priv->viewport = NULL;
|
|
viewport = GTK_WIDGET (scrolled);
|
|
while (GTK_IS_BIN (viewport))
|
|
{
|
|
viewport = gtk_bin_get_child (GTK_BIN (viewport));
|
|
if (GTK_IS_VIEWPORT (viewport))
|
|
{
|
|
priv->viewport = viewport;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
g_signal_handlers_block_matched (priv->hadjustment, G_SIGNAL_MATCH_DATA,
|
|
0, 0, 0, 0, priv->viewport);
|
|
}
|
|
|
|
static void
|
|
enable_hadjustment (KatzeScrolled* scrolled)
|
|
{
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
|
|
g_signal_handlers_unblock_matched (priv->hadjustment, G_SIGNAL_MATCH_DATA, 0, 0, 0, 0, priv->viewport);
|
|
}
|
|
|
|
static gboolean
|
|
on_expose_event (GtkWidget* widget,
|
|
GdkEventExpose* event,
|
|
KatzeScrolled* scrolled)
|
|
{
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
gboolean ret = FALSE;
|
|
|
|
if (GTK_WIDGET_DRAWABLE (widget))
|
|
{
|
|
if (event->window == priv->horizontal_scrollbar_window)
|
|
{
|
|
if (priv->horizontal_scrollbar_size)
|
|
{
|
|
gdk_draw_line (event->window, priv->hilight_gc, 0, 0, priv->horizontal_scrollbar_size - 1, 0);
|
|
gdk_draw_line (event->window, priv->hilight_gc, 0, 1, 0, 9);
|
|
gdk_draw_line (event->window, priv->shadow_gc, priv->horizontal_scrollbar_size - 1, 1, priv->horizontal_scrollbar_size - 1, 9);
|
|
gdk_draw_line (event->window, priv->shadow_gc, 0, 9, priv->horizontal_scrollbar_size - 1, 9);
|
|
}
|
|
|
|
ret = TRUE;
|
|
}
|
|
else if (event->window == priv->vertical_scrollbar_window)
|
|
{
|
|
if (priv->vertical_scrollbar_size)
|
|
{
|
|
gdk_draw_line (event->window, priv->hilight_gc, 0, 0, 9, 0);
|
|
gdk_draw_line (event->window, priv->hilight_gc, 0, 1, 0, priv->vertical_scrollbar_size - 1);
|
|
gdk_draw_line (event->window, priv->shadow_gc, 9, 1, 9, priv->vertical_scrollbar_size - 1);
|
|
gdk_draw_line (event->window, priv->shadow_gc, 0, priv->vertical_scrollbar_size - 1, 9, priv->vertical_scrollbar_size - 1);
|
|
}
|
|
|
|
ret = TRUE;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
adjust_scrollbar (KatzeScrolled* scrolled,
|
|
GdkWindow* scrollbar_window,
|
|
GtkAdjustment* adjustment,
|
|
gint* previous_size,
|
|
gboolean horizontal)
|
|
{
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
GtkWidget* widget = GTK_WIDGET (scrolled);
|
|
gint x, y;
|
|
gint size;
|
|
double position;
|
|
GtkWidget* window;
|
|
|
|
if (adjustment->page_size >= adjustment->upper - adjustment->lower)
|
|
{
|
|
*previous_size = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
size = ((double)adjustment->page_size) / (adjustment->upper - adjustment->lower) * (horizontal
|
|
? widget->allocation.height : widget->allocation.width);
|
|
if (size != *previous_size)
|
|
{
|
|
*previous_size = size;
|
|
if (horizontal)
|
|
{
|
|
gdk_window_resize (scrollbar_window, 10, size);
|
|
gdk_window_clear (scrollbar_window);
|
|
gdk_draw_line (scrollbar_window, priv->hilight_gc, 0, 0, 9, 0);
|
|
gdk_draw_line (scrollbar_window, priv->hilight_gc, 0, 1, 0, size - 1);
|
|
gdk_draw_line (scrollbar_window, priv->shadow_gc, 9, 1, 9, size - 1);
|
|
gdk_draw_line (scrollbar_window, priv->shadow_gc, 0, size - 1, 9, size - 1);
|
|
}
|
|
else
|
|
{
|
|
gdk_window_resize (scrollbar_window, size, 10);
|
|
gdk_window_clear (scrollbar_window);
|
|
gdk_draw_line (scrollbar_window, priv->hilight_gc, 0, 0, size - 1, 0);
|
|
gdk_draw_line (scrollbar_window, priv->hilight_gc, 0, 1, 0, 9);
|
|
gdk_draw_line (scrollbar_window, priv->shadow_gc, size - 1, 1, size - 1, 9);
|
|
gdk_draw_line (scrollbar_window, priv->shadow_gc, 0, 9, size - 1, 9);
|
|
}
|
|
}
|
|
|
|
position = (adjustment->value - adjustment->lower) / (adjustment->upper - adjustment->lower);
|
|
window = gtk_widget_get_toplevel (widget);
|
|
if (horizontal)
|
|
{
|
|
gtk_widget_translate_coordinates (widget, window,
|
|
widget->allocation.width - 20, position * widget->allocation.height, &x, &y);
|
|
gdk_window_move (scrollbar_window, x, y);
|
|
}
|
|
else
|
|
{
|
|
gtk_widget_translate_coordinates (widget, window,
|
|
position * widget->allocation.width, widget->allocation.height - 20, &x, &y);
|
|
gdk_window_move (scrollbar_window, x, y);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
hide_scrollbars_timeout (gpointer data)
|
|
{
|
|
KatzeScrolled* scrolled = KATZE_SCROLLED (data);
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
|
|
gdk_threads_enter ();
|
|
gdk_window_hide (priv->vertical_scrollbar_window);
|
|
gdk_window_hide (priv->horizontal_scrollbar_window);
|
|
|
|
priv->hide_scrollbars_timeout_id = 0;
|
|
gdk_threads_leave ();
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gdouble
|
|
calculate_timeout_scroll_values (gdouble old_value,
|
|
gdouble upper_limit,
|
|
gdouble* scrolling_speed_pointer,
|
|
gdouble deceleration,
|
|
gdouble* other_deceleration,
|
|
gdouble normal_deceleration)
|
|
{
|
|
gdouble new_value = old_value;
|
|
|
|
if (*scrolling_speed_pointer > deceleration ||
|
|
*scrolling_speed_pointer < -deceleration)
|
|
{
|
|
if (old_value + *scrolling_speed_pointer <= 0.0)
|
|
{
|
|
new_value = -1.0;
|
|
*scrolling_speed_pointer = 0.0;
|
|
*other_deceleration = normal_deceleration;
|
|
}
|
|
else if (old_value + *scrolling_speed_pointer >= upper_limit)
|
|
{
|
|
new_value = upper_limit;
|
|
*scrolling_speed_pointer = 0.0;
|
|
*other_deceleration = normal_deceleration;
|
|
}
|
|
else
|
|
new_value = old_value + *scrolling_speed_pointer;
|
|
if (*scrolling_speed_pointer > deceleration)
|
|
*scrolling_speed_pointer -= deceleration;
|
|
else if (*scrolling_speed_pointer < -deceleration)
|
|
*scrolling_speed_pointer += deceleration;
|
|
}
|
|
|
|
return new_value;
|
|
}
|
|
|
|
static void
|
|
do_timeout_scroll (KatzeScrolled* scrolled)
|
|
{
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
GtkScrolledWindow* gtk_scrolled = GTK_SCROLLED_WINDOW (scrolled);
|
|
GtkAdjustment* hadjustment;
|
|
GtkAdjustment* vadjustment;
|
|
gdouble hvalue;
|
|
gdouble vvalue;
|
|
|
|
hadjustment = gtk_scrolled_window_get_hadjustment (gtk_scrolled);
|
|
vadjustment = gtk_scrolled_window_get_vadjustment (gtk_scrolled);
|
|
hvalue = calculate_timeout_scroll_values (hadjustment->value,
|
|
hadjustment->upper - hadjustment->page_size,
|
|
&priv->horizontal_speed,
|
|
priv->horizontal_deceleration,
|
|
&priv->vertical_deceleration,
|
|
priv->deceleration);
|
|
vvalue = calculate_timeout_scroll_values (vadjustment->value,
|
|
vadjustment->upper - vadjustment->page_size,
|
|
&priv->vertical_speed,
|
|
priv->vertical_deceleration,
|
|
&priv->horizontal_deceleration,
|
|
priv->deceleration);
|
|
if (vvalue != vadjustment->value)
|
|
{
|
|
if (hvalue != hadjustment->value)
|
|
{
|
|
disable_hadjustment (scrolled);
|
|
gtk_adjustment_set_value (hadjustment, hvalue);
|
|
enable_hadjustment (scrolled);
|
|
}
|
|
gtk_adjustment_set_value (vadjustment, vvalue);
|
|
}
|
|
else if (hvalue != hadjustment->value)
|
|
gtk_adjustment_set_value (hadjustment, hvalue);
|
|
|
|
adjust_scrollbar (scrolled, priv->horizontal_scrollbar_window,
|
|
gtk_scrolled_window_get_hadjustment (gtk_scrolled),
|
|
&priv->horizontal_scrollbar_size, FALSE);
|
|
adjust_scrollbar (scrolled, priv->vertical_scrollbar_window,
|
|
gtk_scrolled_window_get_vadjustment (gtk_scrolled),
|
|
&priv->vertical_scrollbar_size, TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
timeout_scroll (gpointer data)
|
|
{
|
|
gboolean ret = TRUE;
|
|
KatzeScrolled* scrolled = KATZE_SCROLLED (data);
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
|
|
gdk_threads_enter ();
|
|
do_timeout_scroll (scrolled);
|
|
|
|
if (priv->vertical_speed < priv->deceleration &&
|
|
priv->vertical_speed > -priv->deceleration &&
|
|
priv->horizontal_speed < priv->deceleration &&
|
|
priv->horizontal_speed > -priv->deceleration)
|
|
{
|
|
priv->scrolling_timeout_id = 0;
|
|
if (!priv->hide_scrollbars_timeout_id)
|
|
priv->hide_scrollbars_timeout_id = g_timeout_add (500,
|
|
hide_scrollbars_timeout, scrolled);
|
|
|
|
ret = FALSE;
|
|
}
|
|
gdk_threads_leave ();
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gdouble
|
|
calculate_motion_scroll_values (gdouble old_value,
|
|
gdouble upper_limit,
|
|
gint current_coordinate,
|
|
gint previous_coordinate)
|
|
{
|
|
gdouble new_value = old_value;
|
|
gint movement;
|
|
|
|
movement = current_coordinate - previous_coordinate;
|
|
|
|
if (old_value - movement < upper_limit)
|
|
new_value = old_value - movement;
|
|
else
|
|
new_value = upper_limit;
|
|
|
|
return new_value;
|
|
}
|
|
|
|
static void
|
|
do_motion_scroll (KatzeScrolled* scrolled,
|
|
GtkWidget* widget,
|
|
gint x,
|
|
gint y,
|
|
guint32 timestamp)
|
|
{
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
GtkAdjustment* hadjustment;
|
|
GtkAdjustment* vadjustment;
|
|
gdouble hvalue;
|
|
gdouble vvalue;
|
|
|
|
if (priv->dragged || gtk_drag_check_threshold (widget, priv->start_x, priv->start_y, x, y))
|
|
{
|
|
if (timestamp - priv->previous_time > priv->dragging_stopped_delay || !priv->dragged)
|
|
{
|
|
priv->dragged = TRUE;
|
|
priv->going_right = priv->start_x < x;
|
|
priv->going_down = priv->start_y < y;
|
|
priv->start_x = priv->farest_x = x;
|
|
priv->start_y = priv->farest_y = y;
|
|
priv->start_time = priv->farest_time = timestamp;
|
|
}
|
|
else
|
|
{
|
|
if ((priv->going_right && x > priv->farest_x)
|
|
|| (!priv->going_right && x < priv->farest_x))
|
|
{
|
|
priv->farest_x = x;
|
|
priv->farest_time = timestamp;
|
|
}
|
|
if ((priv->going_down && y > priv->farest_y)
|
|
|| (!priv->going_down && y < priv->farest_y))
|
|
{
|
|
priv->farest_y = y;
|
|
priv->farest_time = timestamp;
|
|
}
|
|
if (gtk_drag_check_threshold (widget, priv->farest_x, priv->farest_y, x, y))
|
|
{
|
|
priv->start_x = priv->farest_x;
|
|
priv->farest_x = x;
|
|
priv->start_y = priv->farest_y;
|
|
priv->farest_y = y;
|
|
priv->start_time = priv->farest_time;
|
|
priv->farest_time = timestamp;
|
|
priv->going_right = priv->start_x < x;
|
|
priv->going_down = priv->start_y < y;
|
|
}
|
|
}
|
|
|
|
hadjustment = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled));
|
|
vadjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled));
|
|
hvalue = calculate_motion_scroll_values (hadjustment->value,
|
|
hadjustment->upper - hadjustment->page_size, x, priv->previous_x);
|
|
vvalue = calculate_motion_scroll_values (vadjustment->value,
|
|
vadjustment->upper - vadjustment->page_size, y, priv->previous_y);
|
|
if (vvalue != vadjustment->value)
|
|
{
|
|
if (hvalue != hadjustment->value)
|
|
{
|
|
disable_hadjustment (scrolled);
|
|
gtk_adjustment_set_value (hadjustment, hvalue);
|
|
enable_hadjustment (scrolled);
|
|
}
|
|
gtk_adjustment_set_value (vadjustment, vvalue);
|
|
}
|
|
else if (hvalue != hadjustment->value)
|
|
gtk_adjustment_set_value (hadjustment, hvalue);
|
|
}
|
|
|
|
priv->previous_y = y;
|
|
priv->previous_x = x;
|
|
priv->previous_time = timestamp;
|
|
|
|
adjust_scrollbar (scrolled, priv->horizontal_scrollbar_window,
|
|
gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled)),
|
|
&priv->horizontal_scrollbar_size, FALSE);
|
|
adjust_scrollbar (scrolled, priv->vertical_scrollbar_window,
|
|
gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled)),
|
|
&priv->vertical_scrollbar_size, TRUE);
|
|
}
|
|
|
|
static gboolean
|
|
button_press_event (GtkWidget* widget,
|
|
GdkEventButton* event,
|
|
KatzeScrolled* scrolled)
|
|
{
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
gint x;
|
|
gint y;
|
|
GdkModifierType mask;
|
|
|
|
if (!priv->drag_scrolling)
|
|
return FALSE;
|
|
|
|
if (event->button != 1)
|
|
return FALSE;
|
|
|
|
priv->press_received = TRUE;
|
|
|
|
if (event->time - priv->previous_time < priv->dragging_stopped_delay &&
|
|
gtk_drag_check_threshold (widget, priv->previous_x, priv->previous_y, x, y))
|
|
{
|
|
if (priv->scrolling_timeout_id)
|
|
{
|
|
g_source_remove (priv->scrolling_timeout_id);
|
|
priv->scrolling_timeout_id = 0;
|
|
}
|
|
gdk_window_get_pointer (GTK_WIDGET (scrolled)->window, &x, &y, &mask);
|
|
/* do_motion_scroll (scrolled, widget, x, y, event->time); */
|
|
}
|
|
else
|
|
{
|
|
if (priv->scrolling_timeout_id)
|
|
{
|
|
g_source_remove (priv->scrolling_timeout_id);
|
|
priv->scrolling_timeout_id = 0;
|
|
priv->previous_time = 0;
|
|
}
|
|
else
|
|
{
|
|
priv->dragged = FALSE;
|
|
priv->previous_time = event->time;
|
|
}
|
|
gdk_window_get_pointer (GTK_WIDGET (scrolled)->window, &x, &y, &mask);
|
|
priv->start_x = priv->previous_x = priv->farest_x = x;
|
|
priv->start_y = priv->previous_y = priv->farest_y = y;
|
|
priv->start_time = event->time;
|
|
}
|
|
|
|
if (priv->scrolling_hints && !GTK_SCROLLED_WINDOW (scrolled)->hscrollbar_visible &&
|
|
adjust_scrollbar (scrolled, priv->horizontal_scrollbar_window,
|
|
gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled)),
|
|
&priv->horizontal_scrollbar_size, FALSE))
|
|
{
|
|
gdk_window_raise (priv->horizontal_scrollbar_window);
|
|
gdk_window_show (priv->horizontal_scrollbar_window);
|
|
}
|
|
if (priv->scrolling_hints && !GTK_SCROLLED_WINDOW (scrolled)->vscrollbar_visible &&
|
|
adjust_scrollbar (scrolled, priv->vertical_scrollbar_window,
|
|
gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled)),
|
|
&priv->vertical_scrollbar_size, TRUE))
|
|
{
|
|
gdk_window_raise (priv->vertical_scrollbar_window);
|
|
gdk_window_show (priv->vertical_scrollbar_window);
|
|
}
|
|
if (priv->hide_scrollbars_timeout_id)
|
|
{
|
|
g_source_remove (priv->hide_scrollbars_timeout_id);
|
|
priv->hide_scrollbars_timeout_id = 0;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
button_release_event (GtkWidget* widget,
|
|
GdkEventButton* event,
|
|
KatzeScrolled* scrolled)
|
|
{
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
gint x;
|
|
gint y;
|
|
GdkModifierType mask;
|
|
|
|
gdk_window_get_pointer (GTK_WIDGET (scrolled)->window, &x, &y, &mask);
|
|
if (priv->press_received &&
|
|
gtk_drag_check_threshold (widget, priv->start_x, priv->start_y, x, y)) {
|
|
priv->dragged = TRUE;
|
|
}
|
|
|
|
if (priv->press_received && priv->kinetic_scrolling &&
|
|
event->time - priv->previous_time < priv->dragging_stopped_delay) {
|
|
priv->vertical_speed = (gdouble)(priv->start_y - y) / (event->time - priv->start_time) * priv->interval;
|
|
priv->horizontal_speed = (gdouble)(priv->start_x - x) / (event->time - priv->start_time) * priv->interval;
|
|
if (ABS (priv->vertical_speed) > ABS (priv->horizontal_speed)) {
|
|
priv->vertical_deceleration = priv->deceleration;
|
|
priv->horizontal_deceleration = priv->deceleration * ABS (priv->horizontal_speed / priv->vertical_speed);
|
|
} else {
|
|
priv->horizontal_deceleration = priv->deceleration;
|
|
priv->vertical_deceleration = priv->deceleration * ABS (priv->vertical_speed / priv->horizontal_speed);
|
|
}
|
|
priv->scrolling_timeout_id = g_timeout_add (priv->interval, timeout_scroll, scrolled);
|
|
|
|
do_timeout_scroll (scrolled);
|
|
}
|
|
else if (!priv->hide_scrollbars_timeout_id) {
|
|
priv->hide_scrollbars_timeout_id = g_timeout_add (500, hide_scrollbars_timeout, scrolled);
|
|
}
|
|
priv->previous_x = x;
|
|
priv->previous_y = y;
|
|
priv->previous_time = event->time;
|
|
|
|
priv->press_received = FALSE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
motion_notify_event (GtkWidget* widget,
|
|
GdkEventMotion* event,
|
|
KatzeScrolled* scrolled)
|
|
{
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
gint x;
|
|
gint y;
|
|
GdkModifierType mask;
|
|
|
|
if (priv->press_received)
|
|
{
|
|
gdk_window_get_pointer (GTK_WIDGET (scrolled)->window, &x, &y, &mask);
|
|
do_motion_scroll (scrolled, widget, x, y, event->time);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
katze_scrolled_event_handler (GdkEvent* event,
|
|
KatzeScrolledState* state,
|
|
gpointer user_data)
|
|
{
|
|
gboolean stop_propagating;
|
|
GdkEventCrossing crossing;
|
|
|
|
stop_propagating = FALSE;
|
|
|
|
if (event->type == GDK_BUTTON_PRESS)
|
|
{
|
|
gdk_window_get_user_data (event->button.window, (gpointer)¤t_widget);
|
|
|
|
if ((current_scrolled_window = g_tree_lookup (activated_widgets, current_widget)))
|
|
{
|
|
current_gdk_window = event->button.window;
|
|
stop_propagating = button_press_event (current_widget, &event->button, current_scrolled_window);
|
|
}
|
|
else
|
|
current_gdk_window = NULL;
|
|
}
|
|
else if (event->any.window == current_gdk_window)
|
|
{
|
|
if (event->type == GDK_MOTION_NOTIFY)
|
|
{
|
|
if (current_scrolled_window->priv->dragged)
|
|
stop_propagating = motion_notify_event (current_widget, &event->motion, current_scrolled_window);
|
|
else
|
|
{
|
|
stop_propagating = motion_notify_event (current_widget, &event->motion, current_scrolled_window);
|
|
if (current_scrolled_window->priv->dragged)
|
|
{
|
|
crossing.type = GDK_LEAVE_NOTIFY;
|
|
crossing.window = event->motion.window;
|
|
crossing.send_event = event->motion.send_event;
|
|
crossing.subwindow = GTK_WIDGET (current_scrolled_window)->window;
|
|
crossing.time = event->motion.time;
|
|
crossing.x = event->motion.x;
|
|
crossing.y = event->motion.y;
|
|
crossing.x_root = event->motion.x_root;
|
|
crossing.y_root = event->motion.y_root;
|
|
crossing.mode = GDK_CROSSING_GRAB;
|
|
crossing.detail = GDK_NOTIFY_ANCESTOR;
|
|
crossing.focus = TRUE;
|
|
crossing.state = event->motion.state;
|
|
|
|
gtk_main_do_event ((GdkEvent*)&crossing);
|
|
synthetized_crossing_event = TRUE;
|
|
}
|
|
}
|
|
}
|
|
else if ((event->type == GDK_ENTER_NOTIFY || event->type == GDK_LEAVE_NOTIFY) &&
|
|
synthetized_crossing_event)
|
|
stop_propagating = TRUE;
|
|
else if (event->type == GDK_BUTTON_RELEASE)
|
|
stop_propagating = button_release_event (current_widget, &event->button, current_scrolled_window);
|
|
}
|
|
|
|
if (!stop_propagating)
|
|
katze_scrolled_event_handler_next (event, state);
|
|
|
|
if (event->type == GDK_BUTTON_RELEASE && event->button.window == current_gdk_window)
|
|
{
|
|
crossing.type = GDK_ENTER_NOTIFY;
|
|
crossing.window = event->button.window;
|
|
crossing.send_event = event->button.send_event;
|
|
crossing.subwindow = GTK_WIDGET (current_scrolled_window)->window;
|
|
crossing.time = event->button.time;
|
|
crossing.x = event->button.x;
|
|
crossing.y = event->button.y;
|
|
crossing.x_root = event->button.x_root;
|
|
crossing.y_root = event->button.y_root;
|
|
crossing.mode = GDK_CROSSING_UNGRAB;
|
|
crossing.detail = GDK_NOTIFY_ANCESTOR;
|
|
crossing.focus = TRUE;
|
|
crossing.state = event->button.state;
|
|
|
|
gtk_main_do_event ((GdkEvent*)&crossing);
|
|
synthetized_crossing_event = FALSE;
|
|
}
|
|
|
|
return stop_propagating;
|
|
}
|
|
|
|
static void
|
|
katze_scrolled_add (GtkContainer* container,
|
|
GtkWidget* widget)
|
|
{
|
|
katze_scrolled_activate_scrolling (KATZE_SCROLLED (container), widget);
|
|
|
|
(*GTK_CONTAINER_CLASS (katze_scrolled_parent_class)->add) (container, widget);
|
|
}
|
|
|
|
static void
|
|
katze_scrolled_realize (GtkWidget* widget)
|
|
{
|
|
KatzeScrolled* scrolled = KATZE_SCROLLED (widget);
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
gboolean drag_scrolling;
|
|
GtkSettings* settings = gtk_widget_get_settings (widget);
|
|
GtkPolicyType policy;
|
|
GdkWindowAttr attr;
|
|
GdkColor color;
|
|
|
|
(*GTK_WIDGET_CLASS (katze_scrolled_parent_class)->realize) (widget);
|
|
|
|
g_object_get (settings, "gtk-touchscreen-mode", &drag_scrolling, NULL);
|
|
policy = drag_scrolling ? GTK_POLICY_NEVER : GTK_POLICY_AUTOMATIC;
|
|
g_object_set (scrolled, "drag-scrolling", drag_scrolling,
|
|
"hscrollbar-policy", policy, "vscrollbar-policy", policy, NULL);
|
|
|
|
widget->window = g_object_ref (gtk_widget_get_parent_window (widget));
|
|
|
|
attr.height = attr.width = 10;
|
|
attr.event_mask = GDK_EXPOSURE_MASK;
|
|
attr.wclass = GDK_INPUT_OUTPUT;
|
|
attr.window_type = GDK_WINDOW_CHILD;
|
|
attr.override_redirect = TRUE;
|
|
priv->vertical_scrollbar_window = gdk_window_new (widget->window, &attr, 0);
|
|
priv->horizontal_scrollbar_window = gdk_window_new (widget->window, &attr, 0);
|
|
|
|
gdk_window_set_user_data (priv->vertical_scrollbar_window, widget);
|
|
gdk_window_set_user_data (priv->horizontal_scrollbar_window, widget);
|
|
g_signal_connect (widget, "expose-event",
|
|
G_CALLBACK (on_expose_event), scrolled);
|
|
|
|
color.red = color.green = color.blue = 0x9999;
|
|
gdk_rgb_find_color (gdk_colormap_get_system (), &color);
|
|
gdk_window_set_background (priv->vertical_scrollbar_window, &color);
|
|
gdk_window_set_background (priv->horizontal_scrollbar_window, &color);
|
|
|
|
priv->hilight_gc = gdk_gc_new (widget->window);
|
|
color.red = color.green = color.blue = 0xcccc;
|
|
gdk_gc_set_rgb_fg_color (priv->hilight_gc, &color);
|
|
priv->shadow_gc = gdk_gc_new (widget->window);
|
|
color.red = color.green = color.blue = 0x6666;
|
|
gdk_gc_set_rgb_fg_color (priv->shadow_gc, &color);
|
|
|
|
GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
|
|
}
|
|
|
|
static void
|
|
katze_scrolled_dispose (GObject* object)
|
|
{
|
|
KatzeScrolled* scrolled = KATZE_SCROLLED (object);
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
|
|
if (priv->scrolling_timeout_id)
|
|
{
|
|
g_source_remove (priv->scrolling_timeout_id);
|
|
priv->scrolling_timeout_id = 0;
|
|
}
|
|
if (priv->hide_scrollbars_timeout_id)
|
|
{
|
|
g_source_remove (priv->hide_scrollbars_timeout_id);
|
|
priv->hide_scrollbars_timeout_id = 0;
|
|
}
|
|
|
|
(*G_OBJECT_CLASS (katze_scrolled_parent_class)->dispose) (object);
|
|
}
|
|
|
|
static void
|
|
katze_scrolled_class_init (KatzeScrolledClass* class)
|
|
{
|
|
GObjectClass* gobject_class;
|
|
GtkWidgetClass* widget_class;
|
|
GtkContainerClass* container_class;
|
|
GParamFlags flags = G_PARAM_READWRITE | G_PARAM_CONSTRUCT;
|
|
GtkSettings* gtk_settings;
|
|
|
|
gobject_class = G_OBJECT_CLASS (class);
|
|
widget_class = GTK_WIDGET_CLASS (class);
|
|
container_class = GTK_CONTAINER_CLASS (class);
|
|
|
|
gobject_class->set_property = katze_scrolled_set_property;
|
|
gobject_class->get_property = katze_scrolled_get_property;
|
|
gobject_class->dispose = katze_scrolled_dispose;
|
|
|
|
widget_class->realize = katze_scrolled_realize;
|
|
|
|
container_class->add = katze_scrolled_add;
|
|
|
|
/**
|
|
* KatzeScrolled:drag-scrolling:
|
|
*
|
|
* Whether the widget can be scrolled by dragging its contents.
|
|
*
|
|
* If "gtk-touchscreen-mode" is enabled, drag scrolling is
|
|
* automatically enabled.
|
|
*
|
|
* Since: 0.2.0
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_DRAG_SCROLLING,
|
|
g_param_spec_boolean (
|
|
"drag-scrolling",
|
|
"Drag Scrolling",
|
|
"Whether the widget can be scrolled by dragging its contents",
|
|
FALSE,
|
|
flags));
|
|
|
|
/**
|
|
* KatzeScrolled:kinetic-scrolling:
|
|
*
|
|
* Whether drag scrolling is kinetic, that is releasing the
|
|
* pointer keeps the contents scrolling further relative to
|
|
* the speed with which they were dragged.
|
|
*
|
|
* Since: 0.2.0
|
|
*/
|
|
g_object_class_install_property (gobject_class,
|
|
PROP_KINETIC_SCROLLING,
|
|
g_param_spec_boolean (
|
|
"kinetic-scrolling",
|
|
"Kinetic Scrolling",
|
|
"Whether drag scrolling is kinetic",
|
|
TRUE,
|
|
flags));
|
|
|
|
activated_widgets = g_tree_new ((GCompareFunc)compare_pointers);
|
|
current_gdk_window = NULL;
|
|
|
|
/* Usually touchscreen mode is either always set or it isn't, so it
|
|
should be a safe optimization to not setup events if not needed. */
|
|
if ((gtk_settings = gtk_settings_get_default ()))
|
|
{
|
|
gboolean touchscreen;
|
|
g_object_get (gtk_settings, "gtk-touchscreen-mode", &touchscreen, NULL);
|
|
if (touchscreen)
|
|
katze_scrolled_event_handler_append (katze_scrolled_event_handler, NULL);
|
|
}
|
|
|
|
g_type_class_add_private (class, sizeof (KatzeScrolledPrivate));
|
|
}
|
|
|
|
static void
|
|
katze_scrolled_init (KatzeScrolled* scrolled)
|
|
{
|
|
KatzeScrolledPrivate* priv;
|
|
|
|
scrolled->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE ((scrolled),
|
|
KATZE_TYPE_SCROLLED, KatzeScrolledPrivate);
|
|
|
|
priv->interval = DEFAULT_INTERVAL;
|
|
priv->deceleration = DEFAULT_DECELERATION;
|
|
priv->drag_scrolling = FALSE;
|
|
priv->kinetic_scrolling = TRUE;
|
|
priv->dragging_stopped_delay = DEFAULT_DRAGGING_STOPPED_DELAY;
|
|
}
|
|
|
|
static void
|
|
katze_scrolled_set_property (GObject* object,
|
|
guint prop_id,
|
|
const GValue* value,
|
|
GParamSpec* pspec)
|
|
{
|
|
KatzeScrolled* scrolled = KATZE_SCROLLED (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_DRAG_SCROLLING:
|
|
katze_scrolled_set_drag_scrolling (scrolled, g_value_get_boolean (value));
|
|
break;
|
|
case PROP_KINETIC_SCROLLING:
|
|
scrolled->priv->kinetic_scrolling = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
katze_scrolled_get_property (GObject* object,
|
|
guint prop_id,
|
|
GValue* value,
|
|
GParamSpec* pspec)
|
|
{
|
|
KatzeScrolled* scrolled = KATZE_SCROLLED (object);
|
|
|
|
switch (prop_id)
|
|
{
|
|
case PROP_DRAG_SCROLLING:
|
|
g_value_set_boolean (value, scrolled->priv->drag_scrolling);
|
|
break;
|
|
case PROP_KINETIC_SCROLLING:
|
|
g_value_set_boolean (value, scrolled->priv->kinetic_scrolling);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* katze_scrolled_new:
|
|
* @hadjustment: a horizontal #GtkAdjustment, or %NULL
|
|
* @vadjustment: a vertical #GtkAdjustment, or %NULL
|
|
*
|
|
* Creates a new #KatzeScrolled.
|
|
*
|
|
* Since: 0.2.0
|
|
**/
|
|
|
|
GtkWidget*
|
|
katze_scrolled_new (GtkAdjustment* hadjustment,
|
|
GtkAdjustment* vadjustment)
|
|
{
|
|
if (hadjustment)
|
|
g_return_val_if_fail (GTK_IS_ADJUSTMENT (hadjustment), NULL);
|
|
if (vadjustment)
|
|
g_return_val_if_fail (GTK_IS_ADJUSTMENT (vadjustment), NULL);
|
|
|
|
return gtk_widget_new (KATZE_TYPE_SCROLLED,
|
|
"hadjustment", hadjustment,
|
|
"vadjustment", vadjustment, NULL);
|
|
}
|
|
|
|
/**
|
|
* katze_scrolled_activate_scrolling:
|
|
* @scrolled: a #KatzeScrolled
|
|
* @widget: a #GtkWidget of which area is made active event source for
|
|
* drag and kinetic scrolling.
|
|
*
|
|
* Activates the widget so that pointer motion events inside the widget are
|
|
* used to scroll the #KatzeScrolled. The widget can be a child of the
|
|
* #KatzeScrolled or even a separate widget ("touchpad" style).
|
|
*
|
|
* The direct child of the #KatzeScrolled (typically #GtkViewport) is
|
|
* activated automatically when added. This function has to be used if indirect
|
|
* descendant widgets are stopping propagation of the button press and release
|
|
* as well as motion events (for example GtkButton is doing so) but scrolling
|
|
* should be possible inside their area too.
|
|
*
|
|
* This function adds #GDK_BUTTON_PRESS_MASK, #GDK_BUTTON_RELEASE_MASK,
|
|
* #GDK_POINTER_MOTION_MASK, and #GDK_MOTION_HINT_MAKS into the widgets event mask.
|
|
*/
|
|
|
|
static void
|
|
katze_scrolled_activate_scrolling (KatzeScrolled* scrolled,
|
|
GtkWidget* widget)
|
|
{
|
|
gtk_widget_add_events (widget,
|
|
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK
|
|
| GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK);
|
|
g_tree_insert (activated_widgets, widget, scrolled);
|
|
}
|
|
|
|
static void
|
|
katze_scrolled_set_drag_scrolling (KatzeScrolled* scrolled,
|
|
gboolean drag_scrolling)
|
|
{
|
|
KatzeScrolledPrivate* priv = scrolled->priv;
|
|
|
|
if (priv->drag_scrolling && !drag_scrolling)
|
|
{
|
|
if (priv->scrolling_timeout_id)
|
|
{
|
|
g_source_remove (priv->scrolling_timeout_id);
|
|
priv->scrolling_timeout_id = 0;
|
|
priv->previous_time = 0;
|
|
}
|
|
|
|
gdk_window_hide (priv->vertical_scrollbar_window);
|
|
gdk_window_hide (priv->horizontal_scrollbar_window);
|
|
if (priv->hide_scrollbars_timeout_id)
|
|
{
|
|
g_source_remove (priv->hide_scrollbars_timeout_id);
|
|
priv->hide_scrollbars_timeout_id = 0;
|
|
}
|
|
|
|
priv->press_received = FALSE;
|
|
}
|
|
|
|
priv->drag_scrolling = drag_scrolling;
|
|
}
|