X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkrange.c;h=bad90e36e38f40c2d38ca78061d391916c898297;hb=d651bb25cd53dd8dee940fe9d788d0c7d9e0dc18;hp=595a8a9bdc3431bed455acd4b2a2628c42230715;hpb=2f9aee8d7f0d164412a459d26970ea3151f875be;p=~andy%2Fgtk diff --git a/gtk/gtkrange.c b/gtk/gtkrange.c index 595a8a9bd..bad90e36e 100644 --- a/gtk/gtkrange.c +++ b/gtk/gtkrange.c @@ -1,5 +1,6 @@ /* GTK - The GIMP Toolkit * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * Copyright (C) 2001 Red Hat, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -18,108 +19,194 @@ */ /* - * Modified by the GTK+ Team and others 1997-2000. See the AUTHORS + * Modified by the GTK+ Team and others 1997-2004. 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/. */ +#include #include +#include +#include "gtkalias.h" +#include "gtkintl.h" #include "gtkmain.h" +#include "gtkmarshalers.h" #include "gtkrange.h" -#include "gtksignal.h" +#include "gtkintl.h" +#include "gtkscrollbar.h" -#define SCROLL_TIMER_LENGTH 20 -#define SCROLL_INITIAL_DELAY 100 -#define SCROLL_DELAY_LENGTH 300 +#define SCROLL_INITIAL_DELAY 250 /* must hold button this long before ... */ +#define SCROLL_LATER_DELAY 100 /* ... it starts repeating at this rate */ +#define UPDATE_DELAY 300 /* Delay for queued update */ -#define RANGE_CLASS(w) GTK_RANGE_GET_CLASS (w) +enum { + PROP_0, + PROP_UPDATE_POLICY, + PROP_ADJUSTMENT, + PROP_INVERTED +}; enum { - ARG_0, - ARG_UPDATE_POLICY + VALUE_CHANGED, + ADJUST_BOUNDS, + MOVE_SLIDER, + CHANGE_VALUE, + LAST_SIGNAL +}; + +typedef enum { + MOUSE_OUTSIDE, + MOUSE_STEPPER_A, + MOUSE_STEPPER_B, + MOUSE_STEPPER_C, + MOUSE_STEPPER_D, + MOUSE_TROUGH, + MOUSE_SLIDER, + MOUSE_WIDGET /* inside widget but not in any of the above GUI elements */ +} MouseLocation; + +struct _GtkRangeLayout +{ + /* These are in widget->window coordinates */ + GdkRectangle stepper_a; + GdkRectangle stepper_b; + GdkRectangle stepper_c; + GdkRectangle stepper_d; + /* The trough rectangle is the area the thumb can slide in, not the + * entire range_rect + */ + GdkRectangle trough; + GdkRectangle slider; + + /* Layout-related state */ + + MouseLocation mouse_location; + /* last mouse coords we got, or -1 if mouse is outside the range */ + gint mouse_x; + gint mouse_y; + /* "grabbed" mouse location, OUTSIDE for no grab */ + MouseLocation grab_location; + gint grab_button; /* 0 if none */ }; -static void gtk_range_class_init (GtkRangeClass *klass); -static void gtk_range_init (GtkRange *range); -static void gtk_range_set_arg (GtkObject *object, - GtkArg *arg, - guint arg_id); -static void gtk_range_get_arg (GtkObject *object, - GtkArg *arg, - guint arg_id); -static void gtk_range_destroy (GtkObject *object); -static void gtk_range_draw (GtkWidget *widget, - GdkRectangle *area); -static void gtk_range_draw_focus (GtkWidget *widget); -static void gtk_range_unrealize (GtkWidget *widget); -static gint gtk_range_expose (GtkWidget *widget, - GdkEventExpose *event); -static gint gtk_range_button_press (GtkWidget *widget, - GdkEventButton *event); -static gint gtk_range_button_release (GtkWidget *widget, - GdkEventButton *event); -static gint gtk_range_motion_notify (GtkWidget *widget, - GdkEventMotion *event); -static gint gtk_range_key_press (GtkWidget *widget, - GdkEventKey *event); -static gint gtk_range_enter_notify (GtkWidget *widget, - GdkEventCrossing *event); -static gint gtk_range_leave_notify (GtkWidget *widget, - GdkEventCrossing *event); -static gint gtk_range_focus_in (GtkWidget *widget, - GdkEventFocus *event); -static gint gtk_range_focus_out (GtkWidget *widget, - GdkEventFocus *event); -static gint gtk_range_scroll_event (GtkWidget *widget, - GdkEventScroll *event); -static void gtk_range_style_set (GtkWidget *widget, - GtkStyle *previous_style); - -static void gtk_real_range_draw_trough (GtkRange *range); -static void gtk_real_range_draw_slider (GtkRange *range); -static gint gtk_real_range_timer (GtkRange *range); -static gint gtk_range_scroll (GtkRange *range, - gfloat jump_perc); - -static void gtk_range_add_timer (GtkRange *range); -static void gtk_range_remove_timer (GtkRange *range); - -static void gtk_range_adjustment_changed (GtkAdjustment *adjustment, - gpointer data); -static void gtk_range_adjustment_value_changed (GtkAdjustment *adjustment, - gpointer data); - -static void gtk_range_trough_hdims (GtkRange *range, - gint *left, - gint *right); -static void gtk_range_trough_vdims (GtkRange *range, - gint *top, - gint *bottom); + +static void gtk_range_class_init (GtkRangeClass *klass); +static void gtk_range_init (GtkRange *range); +static void gtk_range_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void gtk_range_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); +static void gtk_range_destroy (GtkObject *object); +static void gtk_range_finalize (GObject *object); +static void gtk_range_size_request (GtkWidget *widget, + GtkRequisition *requisition); +static void gtk_range_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static void gtk_range_realize (GtkWidget *widget); +static void gtk_range_unrealize (GtkWidget *widget); +static void gtk_range_map (GtkWidget *widget); +static void gtk_range_unmap (GtkWidget *widget); +static gint gtk_range_expose (GtkWidget *widget, + GdkEventExpose *event); +static gint gtk_range_button_press (GtkWidget *widget, + GdkEventButton *event); +static gint gtk_range_button_release (GtkWidget *widget, + GdkEventButton *event); +static gint gtk_range_motion_notify (GtkWidget *widget, + GdkEventMotion *event); +static gint gtk_range_enter_notify (GtkWidget *widget, + GdkEventCrossing *event); +static gint gtk_range_leave_notify (GtkWidget *widget, + GdkEventCrossing *event); +static void gtk_range_grab_notify (GtkWidget *widget, + gboolean was_grabbed); +static void gtk_range_state_changed (GtkWidget *widget, + GtkStateType previous_state); +static gint gtk_range_scroll_event (GtkWidget *widget, + GdkEventScroll *event); +static void gtk_range_style_set (GtkWidget *widget, + GtkStyle *previous_style); +static void update_slider_position (GtkRange *range, + gint mouse_x, + gint mouse_y); + + +/* Range methods */ + +static void gtk_range_move_slider (GtkRange *range, + GtkScrollType scroll); + +/* Internals */ +static void gtk_range_scroll (GtkRange *range, + GtkScrollType scroll); +static gboolean gtk_range_update_mouse_location (GtkRange *range); +static void gtk_range_calc_layout (GtkRange *range, + gdouble adjustment_value); +static void gtk_range_get_props (GtkRange *range, + gint *slider_width, + gint *stepper_size, + gint *trough_border, + gint *stepper_spacing, + gint *arrow_displacement_x, + gint *arrow_displacement_y); +static void gtk_range_calc_request (GtkRange *range, + gint slider_width, + gint stepper_size, + gint trough_border, + gint stepper_spacing, + GdkRectangle *range_rect, + GtkBorder *border, + gint *n_steppers_p, + gint *slider_length_p); +static void gtk_range_adjustment_value_changed (GtkAdjustment *adjustment, + gpointer data); +static void gtk_range_adjustment_changed (GtkAdjustment *adjustment, + gpointer data); +static void gtk_range_add_step_timer (GtkRange *range, + GtkScrollType step); +static void gtk_range_remove_step_timer (GtkRange *range); +static void gtk_range_reset_update_timer (GtkRange *range); +static void gtk_range_remove_update_timer (GtkRange *range); +static GdkRectangle* get_area (GtkRange *range, + MouseLocation location); +static gboolean gtk_range_real_change_value (GtkRange *range, + GtkScrollType scroll, + gdouble value); +static void gtk_range_update_value (GtkRange *range); + static GtkWidgetClass *parent_class = NULL; +static guint signals[LAST_SIGNAL]; -GtkType +GType gtk_range_get_type (void) { - static GtkType range_type = 0; + static GType range_type = 0; if (!range_type) { - static const GtkTypeInfo range_info = + static const GTypeInfo range_info = { - "GtkRange", - sizeof (GtkRange), sizeof (GtkRangeClass), - (GtkClassInitFunc) gtk_range_class_init, - (GtkObjectInitFunc) gtk_range_init, - /* reserved_1 */ NULL, - /* reserved_2 */ NULL, - (GtkClassInitFunc) NULL, + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_range_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkRange), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_range_init, + NULL, /* value_table */ }; - range_type = gtk_type_unique (GTK_TYPE_WIDGET, &range_info); + range_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkRange", + &range_info, G_TYPE_FLAG_ABSTRACT); } return range_type; @@ -128,93 +215,235 @@ gtk_range_get_type (void) static void gtk_range_class_init (GtkRangeClass *class) { + GObjectClass *gobject_class; GtkObjectClass *object_class; GtkWidgetClass *widget_class; + gobject_class = G_OBJECT_CLASS (class); object_class = (GtkObjectClass*) class; widget_class = (GtkWidgetClass*) class; - parent_class = gtk_type_class (GTK_TYPE_WIDGET); + parent_class = g_type_class_peek_parent (class); - object_class->set_arg = gtk_range_set_arg; - object_class->get_arg = gtk_range_get_arg; + gobject_class->set_property = gtk_range_set_property; + gobject_class->get_property = gtk_range_get_property; + gobject_class->finalize = gtk_range_finalize; object_class->destroy = gtk_range_destroy; - widget_class->draw = gtk_range_draw; - widget_class->draw_focus = gtk_range_draw_focus; - widget_class->unrealize = gtk_range_unrealize; + widget_class->size_request = gtk_range_size_request; + widget_class->size_allocate = gtk_range_size_allocate; + widget_class->realize = gtk_range_realize; + widget_class->unrealize = gtk_range_unrealize; + widget_class->map = gtk_range_map; + widget_class->unmap = gtk_range_unmap; widget_class->expose_event = gtk_range_expose; widget_class->button_press_event = gtk_range_button_press; widget_class->button_release_event = gtk_range_button_release; widget_class->motion_notify_event = gtk_range_motion_notify; widget_class->scroll_event = gtk_range_scroll_event; - widget_class->key_press_event = gtk_range_key_press; widget_class->enter_notify_event = gtk_range_enter_notify; widget_class->leave_notify_event = gtk_range_leave_notify; - widget_class->focus_in_event = gtk_range_focus_in; - widget_class->focus_out_event = gtk_range_focus_out; + widget_class->grab_notify = gtk_range_grab_notify; + widget_class->state_changed = gtk_range_state_changed; widget_class->style_set = gtk_range_style_set; - class->slider_width = 11; - class->stepper_size = 11; - class->stepper_slider_spacing = 1; - class->min_slider_size = 7; - class->trough = 1; - class->slider = 2; - class->step_forw = 3; - class->step_back = 4; - class->draw_background = NULL; - class->clear_background = NULL; - class->draw_trough = gtk_real_range_draw_trough; - class->draw_slider = gtk_real_range_draw_slider; - class->draw_step_forw = NULL; - class->draw_step_back = NULL; - class->trough_click = NULL; - class->trough_keys = NULL; - class->motion = NULL; - class->timer = gtk_real_range_timer; - - gtk_object_add_arg_type ("GtkRange::update_policy", - GTK_TYPE_UPDATE_TYPE, - GTK_ARG_READWRITE, - ARG_UPDATE_POLICY); + class->move_slider = gtk_range_move_slider; + class->change_value = gtk_range_real_change_value; + + class->slider_detail = "slider"; + class->stepper_detail = "stepper"; + + signals[VALUE_CHANGED] = + g_signal_new ("value_changed", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkRangeClass, value_changed), + NULL, NULL, + _gtk_marshal_NONE__NONE, + G_TYPE_NONE, 0); + + signals[ADJUST_BOUNDS] = + g_signal_new ("adjust_bounds", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds), + NULL, NULL, + _gtk_marshal_VOID__DOUBLE, + G_TYPE_NONE, 1, + G_TYPE_DOUBLE); + + signals[MOVE_SLIDER] = + g_signal_new ("move_slider", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkRangeClass, move_slider), + NULL, NULL, + _gtk_marshal_VOID__ENUM, + G_TYPE_NONE, 1, + GTK_TYPE_SCROLL_TYPE); + + /** + * GtkRange::change-value: + * @range: the range that received the signal. + * @scroll: the type of scroll action that was performed. + * @value: the new value resulting from the scroll action. + * @returns: %TRUE to prevent other handlers from being invoked for the + * signal. %FALSE to propagate the signal further. + * + * The ::change-value signal is emitted when a scroll action is + * performed on a range. It allows an application to determine the + * type of scroll event that occurred and the resultant new value. + * The application can handle the event itself and return %TRUE to + * prevent further processing. Or, by returning %FALSE, it can pass + * the event to other handlers until the default GTK+ handler is + * reached. + * + * The value parameter is unrounded. An application that overrides + * the ::change-value signal is responsible for clamping the value to + * the desired number of decimal digits; the default GTK+ handler + * clamps the value based on @range->round_digits. + * + * It is not possible to use delayed update policies in an overridden + * ::change-value handler. + * + * Since: 2.6 + */ + signals[CHANGE_VALUE] = + g_signal_new ("change_value", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GtkRangeClass, change_value), + _gtk_boolean_handled_accumulator, NULL, + _gtk_marshal_BOOLEAN__ENUM_DOUBLE, + G_TYPE_BOOLEAN, 2, + GTK_TYPE_SCROLL_TYPE, + G_TYPE_DOUBLE); + + g_object_class_install_property (gobject_class, + PROP_UPDATE_POLICY, + g_param_spec_enum ("update_policy", + P_("Update policy"), + P_("How the range should be updated on the screen"), + GTK_TYPE_UPDATE_TYPE, + GTK_UPDATE_CONTINUOUS, + G_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_ADJUSTMENT, + g_param_spec_object ("adjustment", + P_("Adjustment"), + P_("The GtkAdjustment that contains the current value of this range object"), + GTK_TYPE_ADJUSTMENT, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property (gobject_class, + PROP_INVERTED, + g_param_spec_boolean ("inverted", + P_("Inverted"), + P_("Invert direction slider moves to increase range value"), + FALSE, + G_PARAM_READWRITE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("slider_width", + P_("Slider Width"), + P_("Width of scrollbar or scale thumb"), + 0, + G_MAXINT, + 14, + G_PARAM_READABLE)); + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("trough_border", + P_("Trough Border"), + P_("Spacing between thumb/steppers and outer trough bevel"), + 0, + G_MAXINT, + 1, + G_PARAM_READABLE)); + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("stepper_size", + P_("Stepper Size"), + P_("Length of step buttons at ends"), + 0, + G_MAXINT, + 14, + G_PARAM_READABLE)); + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("stepper_spacing", + P_("Stepper Spacing"), + P_("Spacing between step buttons and thumb"), + 0, + G_MAXINT, + 0, + G_PARAM_READABLE)); + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("arrow_displacement_x", + P_("Arrow X Displacement"), + P_("How far in the x direction to move the arrow when the button is depressed"), + G_MININT, + G_MAXINT, + 0, + G_PARAM_READABLE)); + gtk_widget_class_install_style_property (widget_class, + g_param_spec_int ("arrow_displacement_y", + P_("Arrow Y Displacement"), + P_("How far in the y direction to move the arrow when the button is depressed"), + G_MININT, + G_MAXINT, + 0, + G_PARAM_READABLE)); } static void -gtk_range_set_arg (GtkObject *object, - GtkArg *arg, - guint arg_id) +gtk_range_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) { GtkRange *range; range = GTK_RANGE (object); - switch (arg_id) + switch (prop_id) { - case ARG_UPDATE_POLICY: - gtk_range_set_update_policy (range, GTK_VALUE_ENUM (*arg)); + case PROP_UPDATE_POLICY: + gtk_range_set_update_policy (range, g_value_get_enum (value)); + break; + case PROP_ADJUSTMENT: + gtk_range_set_adjustment (range, g_value_get_object (value)); + break; + case PROP_INVERTED: + gtk_range_set_inverted (range, g_value_get_boolean (value)); break; default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void -gtk_range_get_arg (GtkObject *object, - GtkArg *arg, - guint arg_id) +gtk_range_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { GtkRange *range; range = GTK_RANGE (object); - switch (arg_id) + switch (prop_id) { - case ARG_UPDATE_POLICY: - GTK_VALUE_ENUM (*arg) = range->policy; + case PROP_UPDATE_POLICY: + g_value_set_enum (value, range->update_policy); + break; + case PROP_ADJUSTMENT: + g_value_set_object (value, range->adjustment); + break; + case PROP_INVERTED: + g_value_set_boolean (value, range->inverted); break; default: - arg->type = GTK_TYPE_INVALID; + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } @@ -222,33 +451,42 @@ gtk_range_get_arg (GtkObject *object, static void gtk_range_init (GtkRange *range) { - range->trough = NULL; - range->slider = NULL; - range->step_forw = NULL; - range->step_back = NULL; - - range->x_click_point = 0; - range->y_click_point = 0; - range->button = 0; - range->digits = -1; - range->policy = GTK_UPDATE_CONTINUOUS; - range->scroll_type = GTK_SCROLL_NONE; - range->in_child = 0; - range->click_child = 0; - range->need_timer = FALSE; - range->timer = 0; - range->flippable = 0; - range->old_value = 0.0; - range->old_lower = 0.0; - range->old_upper = 0.0; - range->old_page_size = 0.0; + GTK_WIDGET_SET_FLAGS (range, GTK_NO_WINDOW); + range->adjustment = NULL; + range->update_policy = GTK_UPDATE_CONTINUOUS; + range->inverted = FALSE; + range->flippable = FALSE; + range->min_slider_size = 1; + range->has_stepper_a = FALSE; + range->has_stepper_b = FALSE; + range->has_stepper_c = FALSE; + range->has_stepper_d = FALSE; + range->need_recalc = TRUE; + range->round_digits = -1; + range->layout = g_new0 (GtkRangeLayout, 1); + range->layout->mouse_location = MOUSE_OUTSIDE; + range->layout->mouse_x = -1; + range->layout->mouse_y = -1; + range->layout->grab_location = MOUSE_OUTSIDE; + range->layout->grab_button = 0; + range->timer = NULL; } +/** + * gtk_range_get_adjustment: + * @range: a #GtkRange + * + * Get the #GtkAdjustment which is the "model" object for #GtkRange. + * See gtk_range_set_adjustment() for details. + * The return value does not have a reference added, so should not + * be unreferenced. + * + * Return value: a #GtkAdjustment + **/ GtkAdjustment* gtk_range_get_adjustment (GtkRange *range) { - g_return_val_if_fail (range != NULL, NULL); g_return_val_if_fail (GTK_IS_RANGE (range), NULL); if (!range->adjustment) @@ -257,21 +495,68 @@ gtk_range_get_adjustment (GtkRange *range) return range->adjustment; } +/** + * gtk_range_set_update_policy: + * @range: a #GtkRange + * @policy: update policy + * + * Sets the update policy for the range. #GTK_UPDATE_CONTINUOUS means that + * anytime the range slider is moved, the range value will change and the + * value_changed signal will be emitted. #GTK_UPDATE_DELAYED means that + * the value will be updated after a brief timeout where no slider motion + * occurs, so updates are spaced by a short time rather than + * continuous. #GTK_UPDATE_DISCONTINUOUS means that the value will only + * be updated when the user releases the button and ends the slider + * drag operation. + * + **/ void gtk_range_set_update_policy (GtkRange *range, GtkUpdateType policy) { - g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); - range->policy = policy; + if (range->update_policy != policy) + { + range->update_policy = policy; + g_object_notify (G_OBJECT (range), "update_policy"); + } +} + +/** + * gtk_range_get_update_policy: + * @range: a #GtkRange + * + * Gets the update policy of @range. See gtk_range_set_update_policy(). + * + * Return value: the current update policy + **/ +GtkUpdateType +gtk_range_get_update_policy (GtkRange *range) +{ + g_return_val_if_fail (GTK_IS_RANGE (range), GTK_UPDATE_CONTINUOUS); + + return range->update_policy; } +/** + * gtk_range_set_adjustment: + * @range: a #GtkRange + * @adjustment: a #GtkAdjustment + * + * Sets the adjustment to be used as the "model" object for this range + * widget. The adjustment indicates the current range value, the + * minimum and maximum range values, the step/page increments used + * for keybindings and scrolling, and the page size. The page size + * is normally 0 for #GtkScale and nonzero for #GtkScrollbar, and + * indicates the size of the visible area of the widget being scrolled. + * The page size affects the size of the scrollbar slider. + * + **/ void gtk_range_set_adjustment (GtkRange *range, GtkAdjustment *adjustment) { - g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); if (!adjustment) @@ -283,1341 +568,1979 @@ gtk_range_set_adjustment (GtkRange *range, { if (range->adjustment) { - gtk_signal_disconnect_by_data (GTK_OBJECT (range->adjustment), - (gpointer) range); - gtk_object_unref (GTK_OBJECT (range->adjustment)); + g_signal_handlers_disconnect_by_func (range->adjustment, + gtk_range_adjustment_changed, + range); + g_signal_handlers_disconnect_by_func (range->adjustment, + gtk_range_adjustment_value_changed, + range); + g_object_unref (range->adjustment); } range->adjustment = adjustment; - gtk_object_ref (GTK_OBJECT (adjustment)); + g_object_ref (adjustment); gtk_object_sink (GTK_OBJECT (adjustment)); - gtk_signal_connect (GTK_OBJECT (adjustment), "changed", - (GtkSignalFunc) gtk_range_adjustment_changed, - (gpointer) range); - gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed", - (GtkSignalFunc) gtk_range_adjustment_value_changed, - (gpointer) range); - - range->old_value = adjustment->value; - range->old_lower = adjustment->lower; - range->old_upper = adjustment->upper; - range->old_page_size = adjustment->page_size; + g_signal_connect (adjustment, "changed", + G_CALLBACK (gtk_range_adjustment_changed), + range); + g_signal_connect (adjustment, "value_changed", + G_CALLBACK (gtk_range_adjustment_value_changed), + range); - gtk_range_adjustment_changed (adjustment, (gpointer) range); + gtk_range_adjustment_changed (adjustment, range); + g_object_notify (G_OBJECT (range), "adjustment"); } } +/** + * gtk_range_set_inverted: + * @range: a #GtkRange + * @setting: %TRUE to invert the range + * + * Ranges normally move from lower to higher values as the + * slider moves from top to bottom or left to right. Inverted + * ranges have higher values at the top or on the right rather than + * on the bottom or left. + * + **/ void -gtk_range_draw_background (GtkRange *range) +gtk_range_set_inverted (GtkRange *range, + gboolean setting) { - g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); + + setting = setting != FALSE; - if (range->trough && RANGE_CLASS (range)->draw_background) - (* RANGE_CLASS (range)->draw_background) (range); + if (setting != range->inverted) + { + range->inverted = setting; + g_object_notify (G_OBJECT (range), "inverted"); + gtk_widget_queue_resize (GTK_WIDGET (range)); + } } -void -gtk_range_clear_background (GtkRange *range) +/** + * gtk_range_get_inverted: + * @range: a #GtkRange + * + * Gets the value set by gtk_range_set_inverted(). + * + * Return value: %TRUE if the range is inverted + **/ +gboolean +gtk_range_get_inverted (GtkRange *range) { - g_return_if_fail (range != NULL); - g_return_if_fail (GTK_IS_RANGE (range)); + g_return_val_if_fail (GTK_IS_RANGE (range), FALSE); - if (range->trough && RANGE_CLASS (range)->clear_background) - (* RANGE_CLASS (range)->clear_background) (range); + return range->inverted; } +/** + * gtk_range_set_increments: + * @range: a #GtkRange + * @step: step size + * @page: page size + * + * Sets the step and page sizes for the range. + * The step size is used when the user clicks the #GtkScrollbar + * arrows or moves #GtkScale via arrow keys. The page size + * is used for example when moving via Page Up or Page Down keys. + * + **/ void -gtk_range_draw_trough (GtkRange *range) +gtk_range_set_increments (GtkRange *range, + gdouble step, + gdouble page) { - g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); - if (range->trough && RANGE_CLASS (range)->draw_trough) - (* RANGE_CLASS (range)->draw_trough) (range); + range->adjustment->step_increment = step; + range->adjustment->page_increment = page; + + gtk_adjustment_changed (range->adjustment); } +/** + * gtk_range_set_range: + * @range: a #GtkRange + * @min: minimum range value + * @max: maximum range value + * + * Sets the allowable values in the #GtkRange, and clamps the range + * value to be between @min and @max. (If the range has a non-zero + * page size, it is clamped between @min and @max - page-size.) + **/ void -gtk_range_draw_slider (GtkRange *range) +gtk_range_set_range (GtkRange *range, + gdouble min, + gdouble max) { - g_return_if_fail (range != NULL); + gdouble value; + g_return_if_fail (GTK_IS_RANGE (range)); + g_return_if_fail (min < max); + + range->adjustment->lower = min; + range->adjustment->upper = max; + + value = CLAMP (range->adjustment->value, + range->adjustment->lower, + (range->adjustment->upper - range->adjustment->page_size)); - if (range->slider && RANGE_CLASS (range)->draw_slider) - (* RANGE_CLASS (range)->draw_slider) (range); + gtk_adjustment_set_value (range->adjustment, value); + gtk_adjustment_changed (range->adjustment); } +/** + * gtk_range_set_value: + * @range: a #GtkRange + * @value: new value of the range + * + * Sets the current value of the range; if the value is outside the + * minimum or maximum range values, it will be clamped to fit inside + * them. The range emits the "value_changed" signal if the value + * changes. + * + **/ void -gtk_range_draw_step_forw (GtkRange *range) +gtk_range_set_value (GtkRange *range, + gdouble value) { - g_return_if_fail (range != NULL); g_return_if_fail (GTK_IS_RANGE (range)); + + value = CLAMP (value, range->adjustment->lower, + (range->adjustment->upper - range->adjustment->page_size)); - if (range->step_forw && RANGE_CLASS (range)->draw_step_forw) - (* RANGE_CLASS (range)->draw_step_forw) (range); + gtk_adjustment_set_value (range->adjustment, value); } -void -gtk_range_draw_step_back (GtkRange *range) +/** + * gtk_range_get_value: + * @range: a #GtkRange + * + * Gets the current value of the range. + * + * Return value: current value of the range. + **/ +gdouble +gtk_range_get_value (GtkRange *range) { - g_return_if_fail (range != NULL); - g_return_if_fail (GTK_IS_RANGE (range)); + g_return_val_if_fail (GTK_IS_RANGE (range), 0.0); - if (range->step_back && RANGE_CLASS (range)->draw_step_back) - (* RANGE_CLASS (range)->draw_step_back) (range); + return range->adjustment->value; } -void -gtk_range_slider_update (GtkRange *range) -{ - g_return_if_fail (range != NULL); - g_return_if_fail (GTK_IS_RANGE (range)); - - if (RANGE_CLASS (range)->slider_update) - (* RANGE_CLASS (range)->slider_update) (range); +static gboolean +should_invert (GtkRange *range) +{ + if (range->orientation == GTK_ORIENTATION_HORIZONTAL) + return + (range->inverted && !range->flippable) || + (range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) || + (!range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL); + else + return range->inverted; } -gint -gtk_range_trough_click (GtkRange *range, - gint x, - gint y, - gfloat *jump_perc) +static void +gtk_range_finalize (GObject *object) { - g_return_val_if_fail (range != NULL, GTK_TROUGH_NONE); - g_return_val_if_fail (GTK_IS_RANGE (range), GTK_TROUGH_NONE); + GtkRange *range = GTK_RANGE (object); - if (RANGE_CLASS (range)->trough_click) - return (* RANGE_CLASS (range)->trough_click) (range, x, y, jump_perc); + g_free (range->layout); - return GTK_TROUGH_NONE; + (* G_OBJECT_CLASS (parent_class)->finalize) (object); } -static GdkRegion * -get_window_region (GdkWindow *window) +static void +gtk_range_destroy (GtkObject *object) { - GdkRectangle rect; + GtkRange *range = GTK_RANGE (object); - gdk_window_get_position (window, &rect.x, &rect.y); - gdk_window_get_size (window, &rect.width, &rect.height); + gtk_range_remove_step_timer (range); + gtk_range_remove_update_timer (range); + + if (range->adjustment) + { + g_signal_handlers_disconnect_by_func (range->adjustment, + gtk_range_adjustment_changed, + range); + g_signal_handlers_disconnect_by_func (range->adjustment, + gtk_range_adjustment_value_changed, + range); + g_object_unref (range->adjustment); + range->adjustment = NULL; + } - return gdk_region_rectangle (&rect); + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } static void -move_and_update_window (GdkWindow *window, gint x, gint y) +gtk_range_size_request (GtkWidget *widget, + GtkRequisition *requisition) { - GdkRegion *old_region; - GdkRegion *new_region; - GdkWindow *parent = gdk_window_get_parent (window); - - old_region = get_window_region (window); - gdk_window_move (window, x, y); - new_region = get_window_region (window); - - gdk_region_subtract (old_region, new_region); - gdk_window_invalidate_region (parent, old_region, TRUE); - gdk_region_destroy (old_region); - gdk_region_destroy (new_region); + GtkRange *range; + gint slider_width, stepper_size, trough_border, stepper_spacing; + GdkRectangle range_rect; + GtkBorder border; - gdk_window_process_updates (parent, TRUE); + range = GTK_RANGE (widget); + + gtk_range_get_props (range, + &slider_width, &stepper_size, &trough_border, &stepper_spacing, + NULL, NULL); + + gtk_range_calc_request (range, + slider_width, stepper_size, trough_border, stepper_spacing, + &range_rect, &border, NULL, NULL); + + requisition->width = range_rect.width + border.left + border.right; + requisition->height = range_rect.height + border.top + border.bottom; } -void -gtk_range_default_hslider_update (GtkRange *range) +static void +gtk_range_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) { - gint left; - gint right; - gint x; + GtkRange *range; - g_return_if_fail (range != NULL); - g_return_if_fail (GTK_IS_RANGE (range)); + range = GTK_RANGE (widget); + + widget->allocation = *allocation; + + range->need_recalc = TRUE; + gtk_range_calc_layout (range, range->adjustment->value); if (GTK_WIDGET_REALIZED (range)) - { - gtk_range_trough_hdims (range, &left, &right); - x = left; + gdk_window_move_resize (range->event_window, + widget->allocation.x, + widget->allocation.y, + widget->allocation.width, + widget->allocation.height); +} - if (range->adjustment->value < range->adjustment->lower) - { - range->adjustment->value = range->adjustment->lower; - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); - } - else if (range->adjustment->value > range->adjustment->upper) - { - range->adjustment->value = range->adjustment->upper; - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); - } +static void +gtk_range_realize (GtkWidget *widget) +{ + GtkRange *range; + GdkWindowAttr attributes; + gint attributes_mask; - if (range->adjustment->lower != (range->adjustment->upper - range->adjustment->page_size)) - x += ((right - left) * (range->adjustment->value - range->adjustment->lower) / - (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size)); + range = GTK_RANGE (widget); - if (x < left) - x = left; - else if (x > right) - x = right; + gtk_range_calc_layout (range, range->adjustment->value); + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); - if (range->flippable && gtk_widget_get_direction (range) == GTK_TEXT_DIR_RTL) - x = right - (x - left); - - move_and_update_window (range->slider, x, GTK_WIDGET (range)->style->ythickness); - } + widget->window = gtk_widget_get_parent_window (widget); + g_object_ref (widget->window); + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_ONLY; + attributes.event_mask = gtk_widget_get_events (widget); + attributes.event_mask |= (GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | + GDK_ENTER_NOTIFY_MASK | + GDK_LEAVE_NOTIFY_MASK | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK); + + attributes_mask = GDK_WA_X | GDK_WA_Y; + + range->event_window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gdk_window_set_user_data (range->event_window, range); + + widget->style = gtk_style_attach (widget->style, widget->window); } -void -gtk_range_default_vslider_update (GtkRange *range) +static void +gtk_range_unrealize (GtkWidget *widget) { - gint top; - gint bottom; - gint y; + GtkRange *range = GTK_RANGE (widget); - g_return_if_fail (range != NULL); - g_return_if_fail (GTK_IS_RANGE (range)); - - if (GTK_WIDGET_REALIZED (range)) - { - gtk_range_trough_vdims (range, &top, &bottom); - y = top; - - if (range->adjustment->value < range->adjustment->lower) - { - range->adjustment->value = range->adjustment->lower; - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); - } - else if (range->adjustment->value > range->adjustment->upper) - { - range->adjustment->value = range->adjustment->upper; - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); - } - - if (range->adjustment->lower != (range->adjustment->upper - range->adjustment->page_size)) - y += ((bottom - top) * (range->adjustment->value - range->adjustment->lower) / - (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size)); + gtk_range_remove_step_timer (range); + gtk_range_remove_update_timer (range); + + gdk_window_set_user_data (range->event_window, NULL); + gdk_window_destroy (range->event_window); + range->event_window = NULL; + + if (GTK_WIDGET_CLASS (parent_class)->unrealize) + (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); +} - if (y < top) - y = top; - else if (y > bottom) - y = bottom; +static void +gtk_range_map (GtkWidget *widget) +{ + GtkRange *range = GTK_RANGE (widget); + + gdk_window_show (range->event_window); - move_and_update_window (range->slider, GTK_WIDGET (range)->style->xthickness, y); - } + GTK_WIDGET_CLASS (parent_class)->map (widget); } -gint -gtk_range_default_htrough_click (GtkRange *range, - gint x, - gint y, - gfloat *jump_perc) +static void +gtk_range_unmap (GtkWidget *widget) { - gint ythickness; - gint trough_width; - gint trough_height; - gint slider_x; - gint slider_length; - gint left, right; + GtkRange *range = GTK_RANGE (widget); + + gdk_window_hide (range->event_window); - g_return_val_if_fail (range != NULL, GTK_TROUGH_NONE); - g_return_val_if_fail (GTK_IS_RANGE (range), GTK_TROUGH_NONE); + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} - ythickness = GTK_WIDGET (range)->style->ythickness; +static void +draw_stepper (GtkRange *range, + GdkRectangle *rect, + GtkArrowType arrow_type, + gboolean clicked, + gboolean prelighted, + GdkRectangle *area) +{ + GtkStateType state_type; + GtkShadowType shadow_type; + GdkRectangle intersection; + GtkWidget *widget = GTK_WIDGET (range); - gtk_range_trough_hdims (range, &left, &right); - gdk_window_get_size (range->slider, &slider_length, NULL); - right += slider_length; + gint arrow_x; + gint arrow_y; + gint arrow_width; + gint arrow_height; - if (range->flippable && gtk_widget_get_direction (range) == GTK_TEXT_DIR_RTL) - x = (right - x) + left; + /* More to get the right clip region than for efficiency */ + if (!gdk_rectangle_intersect (area, rect, &intersection)) + return; - if ((x > left) && (y > ythickness)) + intersection.x += widget->allocation.x; + intersection.y += widget->allocation.y; + + if (!GTK_WIDGET_IS_SENSITIVE (range)) + state_type = GTK_STATE_INSENSITIVE; + else if (clicked) + state_type = GTK_STATE_ACTIVE; + else if (prelighted) + state_type = GTK_STATE_PRELIGHT; + else + state_type = GTK_STATE_NORMAL; + + if (clicked) + shadow_type = GTK_SHADOW_IN; + else + shadow_type = GTK_SHADOW_OUT; + + gtk_paint_box (widget->style, + widget->window, + state_type, shadow_type, + &intersection, widget, + GTK_RANGE_GET_CLASS (range)->stepper_detail, + widget->allocation.x + rect->x, + widget->allocation.y + rect->y, + rect->width, + rect->height); + + arrow_width = rect->width / 2; + arrow_height = rect->height / 2; + arrow_x = widget->allocation.x + rect->x + (rect->width - arrow_width) / 2; + arrow_y = widget->allocation.y + rect->y + (rect->height - arrow_height) / 2; + + if (clicked) { - gdk_window_get_size (range->trough, &trough_width, &trough_height); + gint arrow_displacement_x; + gint arrow_displacement_y; - if ((x < right) && (y < (trough_height - ythickness))) - { - if (jump_perc) - { - *jump_perc = ((gdouble) (x - left)) / ((gdouble) (right - left)); - return GTK_TROUGH_JUMP; - } - - gdk_window_get_position (range->slider, &slider_x, NULL); - - if (x < slider_x) - return GTK_TROUGH_START; - else - return GTK_TROUGH_END; - } + gtk_range_get_props (GTK_RANGE (widget), NULL, NULL, NULL, NULL, + &arrow_displacement_x, &arrow_displacement_y); + + arrow_x += arrow_displacement_x; + arrow_y += arrow_displacement_y; } - - return GTK_TROUGH_NONE; + + gtk_paint_arrow (widget->style, + widget->window, + state_type, shadow_type, + &intersection, widget, + GTK_RANGE_GET_CLASS (range)->stepper_detail, + arrow_type, + TRUE, + arrow_x, arrow_y, arrow_width, arrow_height); } -gint -gtk_range_default_vtrough_click (GtkRange *range, - gint x, - gint y, - gfloat *jump_perc) +static gint +gtk_range_expose (GtkWidget *widget, + GdkEventExpose *event) { - gint xthickness; - gint trough_width; - gint trough_height; - gint slider_y; - gint top, bottom; - gint slider_length; - - g_return_val_if_fail (range != NULL, GTK_TROUGH_NONE); - g_return_val_if_fail (GTK_IS_RANGE (range), GTK_TROUGH_NONE); + GtkRange *range; + gboolean sensitive; + GtkStateType state; + GdkRectangle expose_area; /* Relative to widget->allocation */ + GdkRectangle area; + gint focus_line_width = 0; + gint focus_padding = 0; - xthickness = GTK_WIDGET (range)->style->xthickness; + range = GTK_RANGE (widget); - gtk_range_trough_vdims (range, &top, &bottom); - gdk_window_get_size (range->slider, NULL, &slider_length); - bottom += slider_length; - - if ((x > xthickness) && (y > top)) + if (GTK_WIDGET_CAN_FOCUS (range)) { - gdk_window_get_size (range->trough, &trough_width, &trough_height); + gtk_widget_style_get (GTK_WIDGET (range), + "focus-line-width", &focus_line_width, + "focus-padding", &focus_padding, + NULL); + } + + expose_area = event->area; + expose_area.x -= widget->allocation.x; + expose_area.y -= widget->allocation.y; + + gtk_range_calc_layout (range, range->adjustment->value); - if ((x < (trough_width - xthickness) && (y < bottom))) - { - if (jump_perc) - { - *jump_perc = ((gdouble) (y - top)) / ((gdouble) (bottom - top)); - - return GTK_TROUGH_JUMP; - } - - gdk_window_get_position (range->slider, NULL, &slider_y); - - if (y < slider_y) - return GTK_TROUGH_START; - else - return GTK_TROUGH_END; - } + sensitive = GTK_WIDGET_IS_SENSITIVE (widget); + + /* Just to be confusing, we draw the trough for the whole + * range rectangle, not the trough rectangle (the trough + * rectangle is just for hit detection) + */ + /* The gdk_rectangle_intersect is more to get the right + * clip region (limited to range_rect) than for efficiency + */ + if (gdk_rectangle_intersect (&expose_area, &range->range_rect, + &area)) + { + area.x += widget->allocation.x; + area.y += widget->allocation.y; + + gtk_paint_box (widget->style, + widget->window, + sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE, + GTK_SHADOW_IN, + &area, GTK_WIDGET(range), "trough", + widget->allocation.x + range->range_rect.x + focus_line_width + focus_padding, + widget->allocation.y + range->range_rect.y + focus_line_width + focus_padding, + range->range_rect.width - 2 * (focus_line_width + focus_padding), + range->range_rect.height - 2 * (focus_line_width + focus_padding)); + + + if (sensitive && + GTK_WIDGET_HAS_FOCUS (range)) + gtk_paint_focus (widget->style, widget->window, GTK_WIDGET_STATE (widget), + &area, widget, "trough", + widget->allocation.x + range->range_rect.x, + widget->allocation.y + range->range_rect.y, + range->range_rect.width, + range->range_rect.height); } - return GTK_TROUGH_NONE; + if (!sensitive) + state = GTK_STATE_INSENSITIVE; + else if (range->layout->mouse_location == MOUSE_SLIDER) + state = GTK_STATE_PRELIGHT; + else + state = GTK_STATE_NORMAL; + + if (gdk_rectangle_intersect (&expose_area, + &range->layout->slider, + &area)) + { + area.x += widget->allocation.x; + area.y += widget->allocation.y; + + gtk_paint_slider (widget->style, + widget->window, + state, + GTK_SHADOW_OUT, + &area, + widget, + GTK_RANGE_GET_CLASS (range)->slider_detail, + widget->allocation.x + range->layout->slider.x, + widget->allocation.y + range->layout->slider.y, + range->layout->slider.width, + range->layout->slider.height, + range->orientation); + } + + if (range->has_stepper_a) + draw_stepper (range, &range->layout->stepper_a, + range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT, + range->layout->grab_location == MOUSE_STEPPER_A, + range->layout->mouse_location == MOUSE_STEPPER_A, + &expose_area); + + if (range->has_stepper_b) + draw_stepper (range, &range->layout->stepper_b, + range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT, + range->layout->grab_location == MOUSE_STEPPER_B, + range->layout->mouse_location == MOUSE_STEPPER_B, + &expose_area); + + if (range->has_stepper_c) + draw_stepper (range, &range->layout->stepper_c, + range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT, + range->layout->grab_location == MOUSE_STEPPER_C, + range->layout->mouse_location == MOUSE_STEPPER_C, + &expose_area); + + if (range->has_stepper_d) + draw_stepper (range, &range->layout->stepper_d, + range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT, + range->layout->grab_location == MOUSE_STEPPER_D, + range->layout->mouse_location == MOUSE_STEPPER_D, + &expose_area); + + return FALSE; } -void -gtk_range_default_hmotion (GtkRange *range, - gint xdelta, - gint ydelta) +static void +range_grab_add (GtkRange *range, + MouseLocation location, + gint button) { - gdouble old_value; - gint left, right; - gint slider_x, slider_y; - gint new_pos; - - g_return_if_fail (GTK_IS_RANGE (range)); - g_return_if_fail (GTK_WIDGET_REALIZED (range)); - - range = GTK_RANGE (range); + /* we don't actually gtk_grab, since a button is down */ - gdk_window_get_position (range->slider, &slider_x, &slider_y); - gtk_range_trough_hdims (range, &left, &right); + gtk_grab_add (GTK_WIDGET (range)); + + range->layout->grab_location = location; + range->layout->grab_button = button; + + if (gtk_range_update_mouse_location (range)) + gtk_widget_queue_draw (GTK_WIDGET (range)); +} - if (left == right) - return; +static void +range_grab_remove (GtkRange *range) +{ + gtk_grab_remove (GTK_WIDGET (range)); + + range->layout->grab_location = MOUSE_OUTSIDE; + range->layout->grab_button = 0; - new_pos = slider_x + xdelta; + if (gtk_range_update_mouse_location (range)) + gtk_widget_queue_draw (GTK_WIDGET (range)); +} - if (range->flippable && gtk_widget_get_direction (range) == GTK_TEXT_DIR_RTL) - new_pos = (right - new_pos) + left; +static GtkScrollType +range_get_scroll_for_grab (GtkRange *range) +{ + gboolean invert; - if (new_pos < left) - new_pos = left; - else if (new_pos > right) - new_pos = right; + invert = should_invert (range); + switch (range->layout->grab_location) + { + /* Backward stepper */ + case MOUSE_STEPPER_A: + case MOUSE_STEPPER_C: + switch (range->layout->grab_button) + { + case 1: + return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD; + break; + case 2: + return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD; + break; + case 3: + return invert ? GTK_SCROLL_END : GTK_SCROLL_START; + break; + } + break; - old_value = range->adjustment->value; - range->adjustment->value = ((range->adjustment->upper - - range->adjustment->lower - - range->adjustment->page_size) * - (new_pos - left) / (right - left) + - range->adjustment->lower); + /* Forward stepper */ + case MOUSE_STEPPER_B: + case MOUSE_STEPPER_D: + switch (range->layout->grab_button) + { + case 1: + return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD; + break; + case 2: + return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD; + break; + case 3: + return invert ? GTK_SCROLL_START : GTK_SCROLL_END; + break; + } + break; - if (range->digits >= 0) - { - char buffer[64]; + /* In the trough */ + case MOUSE_TROUGH: + { + if (range->trough_click_forward) + return GTK_SCROLL_PAGE_FORWARD; + else + return GTK_SCROLL_PAGE_BACKWARD; + } + break; - sprintf (buffer, "%0.*f", range->digits, range->adjustment->value); - sscanf (buffer, "%f", &range->adjustment->value); + case MOUSE_OUTSIDE: + case MOUSE_SLIDER: + case MOUSE_WIDGET: + break; } - if (old_value != range->adjustment->value) - { - if (range->policy == GTK_UPDATE_CONTINUOUS) - { - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); - } - else - { - gtk_range_slider_update (range); - gtk_range_clear_background (range); - - if (range->policy == GTK_UPDATE_DELAYED) - { - gtk_range_remove_timer (range); - range->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, - (GtkFunction) RANGE_CLASS (range)->timer, - (gpointer) range); - } - } - } + return GTK_SCROLL_NONE; } -void -gtk_range_default_vmotion (GtkRange *range, - gint xdelta, - gint ydelta) +static gdouble +coord_to_value (GtkRange *range, + gint coord) { - gdouble old_value; - gint top, bottom; - gint slider_x, slider_y; - gint new_pos; - - g_return_if_fail (GTK_IS_RANGE (range)); - g_return_if_fail (GTK_WIDGET_REALIZED (range)); - - range = GTK_RANGE (range); - - gdk_window_get_position (range->slider, &slider_x, &slider_y); - gtk_range_trough_vdims (range, &top, &bottom); - - if (bottom == top) - return; - - new_pos = slider_y + ydelta; + gdouble frac; + gdouble value; + + if (range->orientation == GTK_ORIENTATION_VERTICAL) + if (range->layout->trough.height == range->layout->slider.height) + frac = 1.0; + else + frac = ((coord - range->layout->trough.y) / + (gdouble) (range->layout->trough.height - range->layout->slider.height)); + else + if (range->layout->trough.width == range->layout->slider.width) + frac = 1.0; + else + frac = ((coord - range->layout->trough.x) / + (gdouble) (range->layout->trough.width - range->layout->slider.width)); + + if (should_invert (range)) + frac = 1.0 - frac; + + value = range->adjustment->lower + + frac * (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size); - if (new_pos < top) - new_pos = top; - else if (new_pos > bottom) - new_pos = bottom; + return value; +} - old_value = range->adjustment->value; - range->adjustment->value = ((range->adjustment->upper - - range->adjustment->lower - - range->adjustment->page_size) * - (new_pos - top) / (bottom - top) + - range->adjustment->lower); +static gint +gtk_range_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GtkRange *range = GTK_RANGE (widget); + + if (!GTK_WIDGET_HAS_FOCUS (widget)) + gtk_widget_grab_focus (widget); - if (range->digits >= 0) + /* ignore presses when we're already doing something else. */ + if (range->layout->grab_location != MOUSE_OUTSIDE) + return FALSE; + + range->layout->mouse_x = event->x; + range->layout->mouse_y = event->y; + if (gtk_range_update_mouse_location (range)) + gtk_widget_queue_draw (widget); + + if (range->layout->mouse_location == MOUSE_TROUGH && + event->button == 1) { - char buffer[64]; + /* button 1 steps by page increment, as with button 2 on a stepper + */ + GtkScrollType scroll; + gdouble click_value; + + click_value = coord_to_value (range, + range->orientation == GTK_ORIENTATION_VERTICAL ? + event->y : event->x); + + range->trough_click_forward = click_value > range->adjustment->value; + range_grab_add (range, MOUSE_TROUGH, event->button); + + scroll = range_get_scroll_for_grab (range); + + gtk_range_add_step_timer (range, scroll); - sprintf (buffer, "%0.*f", range->digits, range->adjustment->value); - sscanf (buffer, "%f", &range->adjustment->value); + return TRUE; } - - if (old_value != range->adjustment->value) + else if ((range->layout->mouse_location == MOUSE_STEPPER_A || + range->layout->mouse_location == MOUSE_STEPPER_B || + range->layout->mouse_location == MOUSE_STEPPER_C || + range->layout->mouse_location == MOUSE_STEPPER_D) && + (event->button == 1 || event->button == 2 || event->button == 3)) { - if (range->policy == GTK_UPDATE_CONTINUOUS) - { - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); - } - else - { - gtk_range_slider_update (range); - gtk_range_clear_background (range); - - if (range->policy == GTK_UPDATE_DELAYED) - { - gtk_range_remove_timer (range); - range->timer = gtk_timeout_add (SCROLL_DELAY_LENGTH, - (GtkFunction) RANGE_CLASS (range)->timer, - (gpointer) range); - } - } - } -} + GdkRectangle *stepper_area; + GtkScrollType scroll; + + range_grab_add (range, range->layout->mouse_location, event->button); + stepper_area = get_area (range, range->layout->mouse_location); + gtk_widget_queue_draw_area (widget, + widget->allocation.x + stepper_area->x, + widget->allocation.y + stepper_area->y, + stepper_area->width, + stepper_area->height); -static void -gtk_range_destroy (GtkObject *object) -{ - GtkRange *range; + scroll = range_get_scroll_for_grab (range); + if (scroll != GTK_SCROLL_NONE) + gtk_range_add_step_timer (range, scroll); + + return TRUE; + } + else if ((range->layout->mouse_location == MOUSE_TROUGH && + event->button == 2) || + range->layout->mouse_location == MOUSE_SLIDER) + { + gboolean need_value_update = FALSE; - g_return_if_fail (object != NULL); - g_return_if_fail (GTK_IS_RANGE (object)); + /* Any button can be used to drag the slider, but you can start + * dragging the slider with a trough click using button 2; + * On button 2 press, we warp the slider to mouse position, + * then begin the slider drag. + */ + if (event->button == 2) + { + gdouble slider_low_value, slider_high_value, new_value; + + slider_high_value = + coord_to_value (range, + range->orientation == GTK_ORIENTATION_VERTICAL ? + event->y : event->x); + slider_low_value = + coord_to_value (range, + range->orientation == GTK_ORIENTATION_VERTICAL ? + event->y - range->layout->slider.height : + event->x - range->layout->slider.width); + + /* compute new value for warped slider */ + new_value = slider_low_value + (slider_high_value - slider_low_value) / 2; + + /* recalc slider, so we can set slide_initial_slider_position + * properly + */ + range->need_recalc = TRUE; + gtk_range_calc_layout (range, new_value); + + /* defer adjustment updates to update_slider_position() in order + * to keep pixel quantisation + */ + need_value_update = TRUE; + } + + if (range->orientation == GTK_ORIENTATION_VERTICAL) + { + range->slide_initial_slider_position = range->layout->slider.y; + range->slide_initial_coordinate = event->y; + } + else + { + range->slide_initial_slider_position = range->layout->slider.x; + range->slide_initial_coordinate = event->x; + } - range = GTK_RANGE (object); + if (need_value_update) + update_slider_position (range, event->x, event->y); - gtk_range_remove_timer (range); - if (range->adjustment) - { - if (range->adjustment) - gtk_signal_disconnect_by_data (GTK_OBJECT (range->adjustment), - (gpointer) range); - gtk_object_unref (GTK_OBJECT (range->adjustment)); - range->adjustment = NULL; + range_grab_add (range, MOUSE_SLIDER, event->button); + + return TRUE; } - - (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); + + return FALSE; } +/* During a slide, move the slider as required given new mouse position */ static void -gtk_range_draw (GtkWidget *widget, - GdkRectangle *area) +update_slider_position (GtkRange *range, + gint mouse_x, + gint mouse_y) { - GtkRange *range; + gint delta; + gint c; + gdouble new_value; + gboolean handled; - g_return_if_fail (widget != NULL); - g_return_if_fail (GTK_IS_RANGE (widget)); - g_return_if_fail (area != NULL); + if (range->orientation == GTK_ORIENTATION_VERTICAL) + delta = mouse_y - range->slide_initial_coordinate; + else + delta = mouse_x - range->slide_initial_coordinate; - if (GTK_WIDGET_DRAWABLE (widget)) - { - range = GTK_RANGE (widget); + c = range->slide_initial_slider_position + delta; - gtk_range_draw_background (range); - gtk_range_draw_trough (range); - gtk_range_draw_slider (range); - gtk_range_draw_step_forw (range); - gtk_range_draw_step_back (range); - } + new_value = coord_to_value (range, c); + + g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value, + &handled); } -static void -gtk_range_draw_focus (GtkWidget *widget) +static void stop_scrolling (GtkRange *range) { - g_return_if_fail (widget != NULL); - g_return_if_fail (GTK_IS_RANGE (widget)); - - if (GTK_WIDGET_DRAWABLE (widget)) - gtk_range_draw_trough (GTK_RANGE (widget)); + range_grab_remove (range); + gtk_range_remove_step_timer (range); + /* Flush any pending discontinuous/delayed updates */ + gtk_range_update_value (range); + + /* Just be lazy about this, if we scrolled it will all redraw anyway, + * so no point optimizing the button deactivate case + */ + gtk_widget_queue_draw (GTK_WIDGET (range)); } -static void -gtk_range_unrealize (GtkWidget *widget) +static gint +gtk_range_button_release (GtkWidget *widget, + GdkEventButton *event) { - GtkRange *range; - - g_return_if_fail (widget != NULL); - g_return_if_fail (GTK_IS_RANGE (widget)); - - range = GTK_RANGE (widget); + GtkRange *range = GTK_RANGE (widget); - if (range->slider) + if (event->window == range->event_window) { - gdk_window_set_user_data (range->slider, NULL); - gdk_window_destroy (range->slider); - range->slider = NULL; + range->layout->mouse_x = event->x; + range->layout->mouse_y = event->y; } - if (range->trough) - { - gdk_window_set_user_data (range->trough, NULL); - gdk_window_destroy (range->trough); - range->trough = NULL; - } - if (range->step_forw) + else { - gdk_window_set_user_data (range->step_forw, NULL); - gdk_window_destroy (range->step_forw); - range->step_forw = NULL; + gdk_window_get_pointer (range->event_window, + &range->layout->mouse_x, + &range->layout->mouse_y, + NULL); } - if (range->step_back) + + if (range->layout->grab_button == event->button) { - gdk_window_set_user_data (range->step_back, NULL); - gdk_window_destroy (range->step_back); - range->step_back = NULL; + if (range->layout->grab_location == MOUSE_SLIDER) + update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y); + + stop_scrolling (range); + + return TRUE; } - if (GTK_WIDGET_CLASS (parent_class)->unrealize) - (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); + return FALSE; } -static gint -gtk_range_expose (GtkWidget *widget, - GdkEventExpose *event) +/** + * _gtk_range_get_wheel_delta: + * @range: a #GtkRange + * @direction: A #GdkScrollDirection + * + * Returns a good step value for the mouse wheel. + * + * Return value: A good step value for the mouse wheel. + * + * Since: 2.4 + **/ +gdouble +_gtk_range_get_wheel_delta (GtkRange *range, + GdkScrollDirection direction) { - GtkRange *range; + GtkAdjustment *adj = range->adjustment; + gdouble delta; - g_return_val_if_fail (widget != NULL, FALSE); - g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); + if (GTK_IS_SCROLLBAR (range)) + delta = pow (adj->page_size, 2.0 / 3.0); + else + delta = adj->step_increment * 2; + + if (direction == GDK_SCROLL_UP || + direction == GDK_SCROLL_LEFT) + delta = - delta; + + if (range->inverted) + delta = - delta; - range = GTK_RANGE (widget); + return delta; +} + +static gint +gtk_range_scroll_event (GtkWidget *widget, + GdkEventScroll *event) +{ + GtkRange *range = GTK_RANGE (widget); - /* We should really pass another argument - - *the redrawn area - to all the drawing functions) - */ - if (event->window == range->trough) - { - gtk_range_draw_trough (range); - } - else if (event->window == widget->window) - { - gtk_range_draw_background (range); - } - else if (event->window == range->slider) - { - gtk_range_draw_slider (range); - } - else if (event->window == range->step_forw) - { - gtk_range_draw_step_forw (range); - } - else if (event->window == range->step_back) + if (GTK_WIDGET_REALIZED (range)) { - gtk_range_draw_step_back (range); + GtkAdjustment *adj = GTK_RANGE (range)->adjustment; + gdouble delta; + gboolean handled; + + delta = _gtk_range_get_wheel_delta (range, event->direction); + + g_signal_emit (range, signals[CHANGE_VALUE], 0, + GTK_SCROLL_JUMP, adj->value + delta, + &handled); + + /* Policy DELAYED makes sense with scroll events, + * but DISCONTINUOUS doesn't, so we update immediately + * for DISCONTINUOUS + */ + if (range->update_policy == GTK_UPDATE_DISCONTINUOUS) + gtk_range_update_value (range); } - return FALSE; + + return TRUE; } static gint -gtk_range_button_press (GtkWidget *widget, - GdkEventButton *event) +gtk_range_motion_notify (GtkWidget *widget, + GdkEventMotion *event) { GtkRange *range; - gint trough_part; - gfloat jump_perc; + gint x, y; - g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); + range = GTK_RANGE (widget); - if (!GTK_WIDGET_HAS_FOCUS (widget)) - gtk_widget_grab_focus (widget); + gdk_window_get_pointer (range->event_window, &x, &y, NULL); + + range->layout->mouse_x = x; + range->layout->mouse_y = y; - jump_perc = -1; - range = GTK_RANGE (widget); - if (range->button == 0) - { - gtk_grab_add (widget); + if (gtk_range_update_mouse_location (range)) + gtk_widget_queue_draw (widget); - range->button = event->button; - range->x_click_point = event->x; - range->y_click_point = event->y; + if (range->layout->grab_location == MOUSE_SLIDER) + update_slider_position (range, x, y); - if (event->window == range->trough) - { - range->click_child = RANGE_CLASS (range)->trough; - - if (range->button == 2) - trough_part = gtk_range_trough_click (range, event->x, event->y, &jump_perc); - else - trough_part = gtk_range_trough_click (range, event->x, event->y, NULL); - - range->scroll_type = GTK_SCROLL_NONE; - if (trough_part == GTK_TROUGH_START) - range->scroll_type = GTK_SCROLL_PAGE_BACKWARD; - else if (trough_part == GTK_TROUGH_END) - range->scroll_type = GTK_SCROLL_PAGE_FORWARD; - else if (trough_part == GTK_TROUGH_JUMP && - jump_perc >= 0 && jump_perc <= 1) - range->scroll_type = GTK_SCROLL_JUMP; - - if (range->scroll_type != GTK_SCROLL_NONE) - { - gtk_range_scroll (range, jump_perc); - gtk_range_add_timer (range); - } - } - else if (event->window == range->slider) - { - range->click_child = RANGE_CLASS (range)->slider; - range->scroll_type = GTK_SCROLL_NONE; - } - else if (event->window == range->step_forw) - { - range->click_child = RANGE_CLASS (range)->step_forw; - range->scroll_type = GTK_SCROLL_STEP_FORWARD; + /* We handled the event if the mouse was in the range_rect */ + return range->layout->mouse_location != MOUSE_OUTSIDE; +} - gtk_range_scroll (range, -1); - gtk_range_add_timer (range); - gtk_range_draw_step_forw (range); - } - else if (event->window == range->step_back) - { - range->click_child = RANGE_CLASS (range)->step_back; - range->scroll_type = GTK_SCROLL_STEP_BACKWARD; +static gint +gtk_range_enter_notify (GtkWidget *widget, + GdkEventCrossing *event) +{ + GtkRange *range = GTK_RANGE (widget); - gtk_range_scroll (range, -1); - gtk_range_add_timer (range); - gtk_range_draw_step_back (range); - } - } + range->layout->mouse_x = event->x; + range->layout->mouse_y = event->y; - return FALSE; + if (gtk_range_update_mouse_location (range)) + gtk_widget_queue_draw (widget); + + return TRUE; } static gint -gtk_range_button_release (GtkWidget *widget, - GdkEventButton *event) +gtk_range_leave_notify (GtkWidget *widget, + GdkEventCrossing *event) { - GtkRange *range; + GtkRange *range = GTK_RANGE (widget); - g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); + range->layout->mouse_x = -1; + range->layout->mouse_y = -1; - range = GTK_RANGE (widget); - - if (range->button == event->button) - { - gtk_grab_remove (widget); + if (gtk_range_update_mouse_location (range)) + gtk_widget_queue_draw (widget); + + return TRUE; +} - range->button = 0; - range->x_click_point = -1; - range->y_click_point = -1; +static void +gtk_range_grab_notify (GtkWidget *widget, + gboolean was_grabbed) +{ + if (!was_grabbed) + stop_scrolling (GTK_RANGE (widget)); +} - if (range->click_child == RANGE_CLASS (range)->slider) - { - if (range->policy == GTK_UPDATE_DELAYED) - gtk_range_remove_timer (range); +static void +gtk_range_state_changed (GtkWidget *widget, + GtkStateType previous_state) +{ + if (!GTK_WIDGET_IS_SENSITIVE (widget)) + stop_scrolling (GTK_RANGE (widget)); +} - if ((range->policy != GTK_UPDATE_CONTINUOUS) && - (range->old_value != range->adjustment->value)) - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); - } - else if ((range->click_child == RANGE_CLASS (range)->trough) || - (range->click_child == RANGE_CLASS (range)->step_forw) || - (range->click_child == RANGE_CLASS (range)->step_back)) - { - gtk_range_remove_timer (range); - - if ((range->policy != GTK_UPDATE_CONTINUOUS) && - (range->old_value != range->adjustment->value)) - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); - - if (range->click_child == RANGE_CLASS (range)->step_forw) - { - range->click_child = 0; - gtk_range_draw_step_forw (range); - } - else if (range->click_child == RANGE_CLASS (range)->step_back) - { - range->click_child = 0; - gtk_range_draw_step_back (range); - } - } +static void +gtk_range_adjustment_changed (GtkAdjustment *adjustment, + gpointer data) +{ + GtkRange *range = GTK_RANGE (data); - range->click_child = 0; - } + range->need_recalc = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (range)); - return FALSE; + /* Note that we don't round off to range->round_digits here. + * that's because it's really broken to change a value + * in response to a change signal on that value; round_digits + * is therefore defined to be a filter on what the GtkRange + * can input into the adjustment, not a filter that the GtkRange + * will enforce on the adjustment. + */ } -static gint -gtk_range_scroll_event (GtkWidget *widget, - GdkEventScroll *event) +static void +gtk_range_adjustment_value_changed (GtkAdjustment *adjustment, + gpointer data) { - GtkRange *range; - - g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); + GtkRange *range = GTK_RANGE (data); - range = GTK_RANGE (widget); + range->need_recalc = TRUE; + gtk_widget_queue_draw (GTK_WIDGET (range)); + /* This is so we don't lag the widget being scrolled. */ if (GTK_WIDGET_REALIZED (range)) - { - GtkAdjustment *adj = GTK_RANGE (range)->adjustment; - gfloat new_value = adj->value + ((event->direction == GDK_SCROLL_UP) ? - -adj->page_increment / 2: - adj->page_increment / 2); - new_value = CLAMP (new_value, adj->lower, adj->upper - adj->page_size); - gtk_adjustment_set_value (adj, new_value); - } + gdk_window_process_updates (GTK_WIDGET (range)->window, FALSE); + + /* Note that we don't round off to range->round_digits here. + * that's because it's really broken to change a value + * in response to a change signal on that value; round_digits + * is therefore defined to be a filter on what the GtkRange + * can input into the adjustment, not a filter that the GtkRange + * will enforce on the adjustment. + */ - return TRUE; + g_signal_emit (range, signals[VALUE_CHANGED], 0); } -static gint -gtk_range_motion_notify (GtkWidget *widget, - GdkEventMotion *event) +static void +gtk_range_style_set (GtkWidget *widget, + GtkStyle *previous_style) { - GtkRange *range; + GtkRange *range = GTK_RANGE (widget); - g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); + range->need_recalc = TRUE; - range = GTK_RANGE (widget); + (* GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style); +} - if (range->click_child == RANGE_CLASS (range)->slider) - { - GdkModifierType mods; - gint x, y, mask, x2, y2; +static void +step_back (GtkRange *range) +{ + gdouble newval; + gboolean handled; + + newval = range->adjustment->value - range->adjustment->step_increment; + g_signal_emit (range, signals[CHANGE_VALUE], 0, + GTK_SCROLL_STEP_BACKWARD, newval, &handled); +} - gdk_window_get_pointer (range->trough, &x, &y, &mods); - gdk_window_get_position (range->slider, &x2, &y2); +static void +step_forward (GtkRange *range) +{ + gdouble newval; + gboolean handled; - x -= x2; - y -= y2; + newval = range->adjustment->value + range->adjustment->step_increment; + g_signal_emit (range, signals[CHANGE_VALUE], 0, + GTK_SCROLL_STEP_FORWARD, newval, &handled); +} - switch (range->button) - { - case 1: - mask = GDK_BUTTON1_MASK; - break; - case 2: - mask = GDK_BUTTON2_MASK; - break; - case 3: - mask = GDK_BUTTON3_MASK; - break; - default: - mask = 0; - break; - } - if (mods & mask) - { - if (RANGE_CLASS (range)->motion) - (* RANGE_CLASS (range)->motion) (range, x - range->x_click_point, y - range->y_click_point); - } - } +static void +page_back (GtkRange *range) +{ + gdouble newval; + gboolean handled; - return FALSE; + newval = range->adjustment->value - range->adjustment->page_increment; + g_signal_emit (range, signals[CHANGE_VALUE], 0, + GTK_SCROLL_PAGE_BACKWARD, newval, &handled); } -static gint -gtk_range_key_press (GtkWidget *widget, - GdkEventKey *event) +static void +page_forward (GtkRange *range) { - GtkRange *range; - gint return_val; - GtkScrollType scroll = GTK_SCROLL_NONE; - GtkTroughType pos = GTK_TROUGH_NONE; + gdouble newval; + gboolean handled; - g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); + newval = range->adjustment->value + range->adjustment->page_increment; + g_signal_emit (range, signals[CHANGE_VALUE], 0, + GTK_SCROLL_PAGE_FORWARD, newval, &handled); +} - range = GTK_RANGE (widget); - return_val = FALSE; +static void +scroll_begin (GtkRange *range) +{ + gboolean handled; + g_signal_emit (range, signals[CHANGE_VALUE], 0, + GTK_SCROLL_START, range->adjustment->lower, + &handled); +} - if (RANGE_CLASS (range)->trough_keys) - return_val = (* RANGE_CLASS (range)->trough_keys) (range, event, &scroll, &pos); +static void +scroll_end (GtkRange *range) +{ + gdouble newval; + gboolean handled; - if (return_val) - { - if (scroll != GTK_SCROLL_NONE) - { - range->scroll_type = scroll; - gtk_range_scroll (range, -1); - if (range->old_value != range->adjustment->value) - { - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); - switch (range->scroll_type) - { - case GTK_SCROLL_STEP_BACKWARD: - gtk_range_draw_step_back (range); - break; - case GTK_SCROLL_STEP_FORWARD: - gtk_range_draw_step_forw (range); - break; - } - } - } - if (pos != GTK_TROUGH_NONE) - { - if (pos == GTK_TROUGH_START) - range->adjustment->value = range->adjustment->lower; - else if (pos == GTK_TROUGH_END) - range->adjustment->value = - range->adjustment->upper - range->adjustment->page_size; - - if (range->old_value != range->adjustment->value) - { - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), - "value_changed"); - - gtk_range_slider_update (range); - gtk_range_clear_background (range); - } - } - } - return return_val; + newval = range->adjustment->upper - range->adjustment->page_size; + g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_END, newval, + &handled); } -static gint -gtk_range_enter_notify (GtkWidget *widget, - GdkEventCrossing *event) +static void +gtk_range_scroll (GtkRange *range, + GtkScrollType scroll) { - GtkRange *range; + switch (scroll) + { + case GTK_SCROLL_STEP_LEFT: + if (should_invert (range)) + step_forward (range); + else + step_back (range); + break; + + case GTK_SCROLL_STEP_UP: + if (should_invert (range)) + step_forward (range); + else + step_back (range); + break; + + case GTK_SCROLL_STEP_RIGHT: + if (should_invert (range)) + step_back (range); + else + step_forward (range); + break; + + case GTK_SCROLL_STEP_DOWN: + if (should_invert (range)) + step_back (range); + else + step_forward (range); + break; + + case GTK_SCROLL_STEP_BACKWARD: + step_back (range); + break; + + case GTK_SCROLL_STEP_FORWARD: + step_forward (range); + break; - g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); + case GTK_SCROLL_PAGE_LEFT: + if (should_invert (range)) + page_forward (range); + else + page_back (range); + break; + + case GTK_SCROLL_PAGE_UP: + if (should_invert (range)) + page_forward (range); + else + page_back (range); + break; - range = GTK_RANGE (widget); + case GTK_SCROLL_PAGE_RIGHT: + if (should_invert (range)) + page_back (range); + else + page_forward (range); + break; + + case GTK_SCROLL_PAGE_DOWN: + if (should_invert (range)) + page_back (range); + else + page_forward (range); + break; + + case GTK_SCROLL_PAGE_BACKWARD: + page_back (range); + break; + + case GTK_SCROLL_PAGE_FORWARD: + page_forward (range); + break; - if (event->window == range->trough) - { - range->in_child = RANGE_CLASS (range)->trough; - } - else if (event->window == range->slider) - { - range->in_child = RANGE_CLASS (range)->slider; + case GTK_SCROLL_START: + scroll_begin (range); + break; - if ((range->click_child == 0) || - (range->click_child == RANGE_CLASS (range)->trough)) - gtk_range_draw_slider (range); - } - else if (event->window == range->step_forw) - { - range->in_child = RANGE_CLASS (range)->step_forw; + case GTK_SCROLL_END: + scroll_end (range); + break; - if ((range->click_child == 0) || - (range->click_child == RANGE_CLASS (range)->trough)) - gtk_range_draw_step_forw (range); - } - else if (event->window == range->step_back) - { - range->in_child = RANGE_CLASS (range)->step_back; + case GTK_SCROLL_JUMP: + /* Used by CList, range doesn't use it. */ + break; - if ((range->click_child == 0) || - (range->click_child == RANGE_CLASS (range)->trough)) - gtk_range_draw_step_back (range); + case GTK_SCROLL_NONE: + break; } - - return FALSE; } -static gint -gtk_range_leave_notify (GtkWidget *widget, - GdkEventCrossing *event) +static void +gtk_range_move_slider (GtkRange *range, + GtkScrollType scroll) { - GtkRange *range; - - g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); - - range = GTK_RANGE (widget); + gtk_range_scroll (range, scroll); - range->in_child = 0; + /* Policy DELAYED makes sense with key events, + * but DISCONTINUOUS doesn't, so we update immediately + * for DISCONTINUOUS + */ + if (range->update_policy == GTK_UPDATE_DISCONTINUOUS) + gtk_range_update_value (range); +} - if (event->window == range->trough) - { - } - else if (event->window == range->slider) - { - if ((range->click_child == 0) || - (range->click_child == RANGE_CLASS (range)->trough)) - gtk_range_draw_slider (range); - } - else if (event->window == range->step_forw) - { - if ((range->click_child == 0) || - (range->click_child == RANGE_CLASS (range)->trough)) - gtk_range_draw_step_forw (range); - } - else if (event->window == range->step_back) +static void +gtk_range_get_props (GtkRange *range, + gint *slider_width, + gint *stepper_size, + gint *trough_border, + gint *stepper_spacing, + gint *arrow_displacement_x, + gint *arrow_displacement_y) +{ + GtkWidget *widget = GTK_WIDGET (range); + gint tmp_slider_width, tmp_stepper_size, tmp_trough_border, tmp_stepper_spacing; + gint tmp_arrow_displacement_x, tmp_arrow_displacement_y; + + gtk_widget_style_get (widget, + "slider_width", &tmp_slider_width, + "trough_border", &tmp_trough_border, + "stepper_size", &tmp_stepper_size, + "stepper_spacing", &tmp_stepper_spacing, + "arrow_displacement_x", &tmp_arrow_displacement_x, + "arrow_displacement_y", &tmp_arrow_displacement_y, + NULL); + + if (GTK_WIDGET_CAN_FOCUS (range)) { - if ((range->click_child == 0) || - (range->click_child == RANGE_CLASS (range)->trough)) - gtk_range_draw_step_back (range); + gint focus_line_width; + gint focus_padding; + + gtk_widget_style_get (GTK_WIDGET (range), + "focus-line-width", &focus_line_width, + "focus-padding", &focus_padding, + NULL); + + tmp_trough_border += focus_line_width + focus_padding; } + + if (slider_width) + *slider_width = tmp_slider_width; - return FALSE; -} + if (trough_border) + *trough_border = tmp_trough_border; -static gint -gtk_range_focus_in (GtkWidget *widget, - GdkEventFocus *event) -{ - g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); + if (stepper_size) + *stepper_size = tmp_stepper_size; - GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS); - gtk_widget_draw_focus (widget); + if (stepper_spacing) + *stepper_spacing = tmp_stepper_spacing; - return FALSE; + if (arrow_displacement_x) + *arrow_displacement_x = tmp_arrow_displacement_x; + + if (arrow_displacement_y) + *arrow_displacement_y = tmp_arrow_displacement_y; } -static gint -gtk_range_focus_out (GtkWidget *widget, - GdkEventFocus *event) +#define POINT_IN_RECT(xcoord, ycoord, rect) \ + ((xcoord) >= (rect).x && \ + (xcoord) < ((rect).x + (rect).width) && \ + (ycoord) >= (rect).y && \ + (ycoord) < ((rect).y + (rect).height)) + +/* Update mouse location, return TRUE if it changes */ +static gboolean +gtk_range_update_mouse_location (GtkRange *range) { - g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE); - g_return_val_if_fail (event != NULL, FALSE); + gint x, y; + MouseLocation old; + GtkWidget *widget; - GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS); - gtk_widget_draw_focus (widget); + widget = GTK_WIDGET (range); + + old = range->layout->mouse_location; + + x = range->layout->mouse_x; + y = range->layout->mouse_y; + + if (range->layout->grab_location != MOUSE_OUTSIDE) + range->layout->mouse_location = range->layout->grab_location; + else if (POINT_IN_RECT (x, y, range->layout->stepper_a)) + range->layout->mouse_location = MOUSE_STEPPER_A; + else if (POINT_IN_RECT (x, y, range->layout->stepper_b)) + range->layout->mouse_location = MOUSE_STEPPER_B; + else if (POINT_IN_RECT (x, y, range->layout->stepper_c)) + range->layout->mouse_location = MOUSE_STEPPER_C; + else if (POINT_IN_RECT (x, y, range->layout->stepper_d)) + range->layout->mouse_location = MOUSE_STEPPER_D; + else if (POINT_IN_RECT (x, y, range->layout->slider)) + range->layout->mouse_location = MOUSE_SLIDER; + else if (POINT_IN_RECT (x, y, range->layout->trough)) + range->layout->mouse_location = MOUSE_TROUGH; + else if (POINT_IN_RECT (x, y, widget->allocation)) + range->layout->mouse_location = MOUSE_WIDGET; + else + range->layout->mouse_location = MOUSE_OUTSIDE; - return FALSE; + return old != range->layout->mouse_location; } +/* Clamp rect, border inside widget->allocation, such that we prefer + * to take space from border not rect in all directions, and prefer to + * give space to border over rect in one direction. + */ static void -gtk_real_range_draw_trough (GtkRange *range) +clamp_dimensions (GtkWidget *widget, + GdkRectangle *rect, + GtkBorder *border, + gboolean border_expands_horizontally) { - g_return_if_fail (GTK_IS_RANGE (range)); + gint extra, shortage; + + g_return_if_fail (rect->x == 0); + g_return_if_fail (rect->y == 0); + g_return_if_fail (rect->width >= 0); + g_return_if_fail (rect->height >= 0); - if (range->trough) - { - gtk_paint_box (GTK_WIDGET (range)->style, range->trough, - GTK_STATE_ACTIVE, GTK_SHADOW_IN, - NULL, GTK_WIDGET(range), "trough", - 0, 0, -1, -1); - if (GTK_WIDGET_HAS_FOCUS (range)) - gtk_paint_focus (GTK_WIDGET (range)->style, - range->trough, - NULL, GTK_WIDGET(range), "trough", - 0, 0, -1, -1); + /* Width */ + + extra = widget->allocation.width - border->left - border->right - rect->width; + if (extra > 0) + { + if (border_expands_horizontally) + { + border->left += extra / 2; + border->right += extra / 2 + extra % 2; + } + else + { + rect->width += extra; + } + } + + /* See if we can fit rect, if not kill the border */ + shortage = rect->width - widget->allocation.width; + if (shortage > 0) + { + rect->width = widget->allocation.width; + /* lose the border */ + border->left = 0; + border->right = 0; + } + else + { + /* See if we can fit rect with borders */ + shortage = rect->width + border->left + border->right - + widget->allocation.width; + if (shortage > 0) + { + /* Shrink borders */ + border->left -= shortage / 2; + border->right -= shortage / 2 + shortage % 2; + } } -} -static void -gtk_real_range_draw_slider (GtkRange *range) -{ - GtkStateType state_type; - - g_return_if_fail (GTK_IS_RANGE (range)); - - if (range->slider) + /* Height */ + + extra = widget->allocation.height - border->top - border->bottom - rect->height; + if (extra > 0) { - if ((range->in_child == RANGE_CLASS (range)->slider) || - (range->click_child == RANGE_CLASS (range)->slider)) - state_type = GTK_STATE_PRELIGHT; + if (border_expands_horizontally) + { + /* don't expand border vertically */ + rect->height += extra; + } else - state_type = GTK_STATE_NORMAL; - gtk_paint_box (GTK_WIDGET (range)->style, range->slider, - state_type, GTK_SHADOW_OUT, - NULL, GTK_WIDGET (range), "slider", - 0, 0, -1, -1); + { + border->top += extra / 2; + border->bottom += extra / 2 + extra % 2; + } + } + + /* See if we can fit rect, if not kill the border */ + shortage = rect->height - widget->allocation.height; + if (shortage > 0) + { + rect->height = widget->allocation.height; + /* lose the border */ + border->top = 0; + border->bottom = 0; + } + else + { + /* See if we can fit rect with borders */ + shortage = rect->height + border->top + border->bottom - + widget->allocation.height; + if (shortage > 0) + { + /* Shrink borders */ + border->top -= shortage / 2; + border->bottom -= shortage / 2 + shortage % 2; + } } } -static gint -gtk_real_range_timer (GtkRange *range) +static void +gtk_range_calc_request (GtkRange *range, + gint slider_width, + gint stepper_size, + gint trough_border, + gint stepper_spacing, + GdkRectangle *range_rect, + GtkBorder *border, + gint *n_steppers_p, + gint *slider_length_p) { - gint return_val; + gint slider_length; + gint n_steppers; - GDK_THREADS_ENTER (); + border->left = 0; + border->right = 0; + border->top = 0; + border->bottom = 0; - return_val = TRUE; - if (range->click_child == RANGE_CLASS (range)->slider) + if (GTK_RANGE_GET_CLASS (range)->get_range_border) + (* GTK_RANGE_GET_CLASS (range)->get_range_border) (range, border); + + n_steppers = 0; + if (range->has_stepper_a) + n_steppers += 1; + if (range->has_stepper_b) + n_steppers += 1; + if (range->has_stepper_c) + n_steppers += 1; + if (range->has_stepper_d) + n_steppers += 1; + + slider_length = range->min_slider_size; + + range_rect->x = 0; + range_rect->y = 0; + + /* We never expand to fill available space in the small dimension + * (i.e. vertical scrollbars are always a fixed width) + */ + if (range->orientation == GTK_ORIENTATION_VERTICAL) { - if (range->policy == GTK_UPDATE_DELAYED) - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); - return_val = FALSE; + range_rect->width = trough_border * 2 + slider_width; + range_rect->height = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length; } else { - GdkModifierType mods, mask; - - if (!range->timer) - { - return_val = FALSE; - if (range->need_timer) - range->timer = gtk_timeout_add (SCROLL_TIMER_LENGTH, - (GtkFunction) RANGE_CLASS (range)->timer, - (gpointer) range); - else - { - GDK_THREADS_LEAVE (); - return FALSE; - } - range->need_timer = FALSE; - } - - switch (range->button) - { - case 1: - mask = GDK_BUTTON1_MASK; - break; - case 2: - mask = GDK_BUTTON2_MASK; - break; - case 3: - mask = GDK_BUTTON3_MASK; - break; - default: - mask = 0; - break; - } - - gdk_window_get_pointer (range->slider, NULL, NULL, &mods); - - if (mods & mask) - return_val = gtk_range_scroll (range, -1); + range_rect->width = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length; + range_rect->height = trough_border * 2 + slider_width; } - GDK_THREADS_LEAVE (); + if (n_steppers_p) + *n_steppers_p = n_steppers; - return return_val; + if (slider_length_p) + *slider_length_p = slider_length; } -static gint -gtk_range_scroll (GtkRange *range, - gfloat jump_perc) +static void +gtk_range_calc_layout (GtkRange *range, + gdouble adjustment_value) { - gfloat new_value; - gint return_val; + gint slider_width, stepper_size, trough_border, stepper_spacing; + gint slider_length; + GtkBorder border; + gint n_steppers; + GdkRectangle range_rect; + GtkRangeLayout *layout; + GtkWidget *widget; + + if (!range->need_recalc) + return; - g_return_val_if_fail (GTK_IS_RANGE (range), FALSE); + /* If we have a too-small allocation, we prefer the steppers over + * the trough/slider, probably the steppers are a more useful + * feature in small spaces. + * + * Also, we prefer to draw the range itself rather than the border + * areas if there's a conflict, since the borders will be decoration + * not controls. Though this depends on subclasses cooperating by + * not drawing on range->range_rect. + */ - new_value = range->adjustment->value; - return_val = TRUE; + widget = GTK_WIDGET (range); + layout = range->layout; + + gtk_range_get_props (range, + &slider_width, &stepper_size, &trough_border, &stepper_spacing, + NULL, NULL); - switch (range->scroll_type) + gtk_range_calc_request (range, + slider_width, stepper_size, trough_border, stepper_spacing, + &range_rect, &border, &n_steppers, &slider_length); + + /* We never expand to fill available space in the small dimension + * (i.e. vertical scrollbars are always a fixed width) + */ + if (range->orientation == GTK_ORIENTATION_VERTICAL) { - case GTK_SCROLL_NONE: - break; + clamp_dimensions (widget, &range_rect, &border, TRUE); + } + else + { + clamp_dimensions (widget, &range_rect, &border, FALSE); + } + + range_rect.x = border.left; + range_rect.y = border.top; + + range->range_rect = range_rect; + + if (range->orientation == GTK_ORIENTATION_VERTICAL) + { + gint stepper_width, stepper_height; + + /* Steppers are the width of the range, and stepper_size in + * height, or if we don't have enough height, divided equally + * among available space. + */ + stepper_width = range_rect.width - trough_border * 2; + + if (stepper_width < 1) + stepper_width = range_rect.width; /* screw the trough border */ + + if (n_steppers == 0) + stepper_height = 0; /* avoid divide by n_steppers */ + else + stepper_height = MIN (stepper_size, (range_rect.height / n_steppers)); + + /* Stepper A */ - case GTK_SCROLL_JUMP: - if (jump_perc >= 0 && jump_perc <= 1) - { - new_value = (range->adjustment->lower + - (range->adjustment->upper - range->adjustment->page_size - - range->adjustment->lower) * jump_perc); - } - break; + layout->stepper_a.x = range_rect.x + trough_border; + layout->stepper_a.y = range_rect.y + trough_border; + + if (range->has_stepper_a) + { + layout->stepper_a.width = stepper_width; + layout->stepper_a.height = stepper_height; + } + else + { + layout->stepper_a.width = 0; + layout->stepper_a.height = 0; + } + + /* Stepper B */ - case GTK_SCROLL_STEP_BACKWARD: - new_value -= range->adjustment->step_increment; - if (new_value <= range->adjustment->lower) - { - new_value = range->adjustment->lower; - return_val = FALSE; - range->timer = 0; - } - break; + layout->stepper_b.x = layout->stepper_a.x; + layout->stepper_b.y = layout->stepper_a.y + layout->stepper_a.height; + + if (range->has_stepper_b) + { + layout->stepper_b.width = stepper_width; + layout->stepper_b.height = stepper_height; + } + else + { + layout->stepper_b.width = 0; + layout->stepper_b.height = 0; + } + + /* Stepper D */ + + if (range->has_stepper_d) + { + layout->stepper_d.width = stepper_width; + layout->stepper_d.height = stepper_height; + } + else + { + layout->stepper_d.width = 0; + layout->stepper_d.height = 0; + } + + layout->stepper_d.x = layout->stepper_a.x; + layout->stepper_d.y = range_rect.y + range_rect.height - layout->stepper_d.height - trough_border; - case GTK_SCROLL_STEP_FORWARD: - new_value += range->adjustment->step_increment; - if (new_value >= (range->adjustment->upper - range->adjustment->page_size)) - { - new_value = range->adjustment->upper - range->adjustment->page_size; - return_val = FALSE; - range->timer = 0; - } - break; + /* Stepper C */ - case GTK_SCROLL_PAGE_BACKWARD: - new_value -= range->adjustment->page_increment; - if (new_value <= range->adjustment->lower) - { - new_value = range->adjustment->lower; - return_val = FALSE; - range->timer = 0; - } - break; + if (range->has_stepper_c) + { + layout->stepper_c.width = stepper_width; + layout->stepper_c.height = stepper_height; + } + else + { + layout->stepper_c.width = 0; + layout->stepper_c.height = 0; + } + + layout->stepper_c.x = layout->stepper_a.x; + layout->stepper_c.y = layout->stepper_d.y - layout->stepper_c.height; - case GTK_SCROLL_PAGE_FORWARD: - new_value += range->adjustment->page_increment; - if (new_value >= (range->adjustment->upper - range->adjustment->page_size)) - { - new_value = range->adjustment->upper - range->adjustment->page_size; - return_val = FALSE; - range->timer = 0; - } - break; - } + /* Now the trough is the remaining space between steppers B and C, + * if any + */ + layout->trough.x = range_rect.x; + layout->trough.y = layout->stepper_b.y + layout->stepper_b.height; + layout->trough.width = range_rect.width; + layout->trough.height = layout->stepper_c.y - (layout->stepper_b.y + layout->stepper_b.height); + + /* Slider fits into the trough, with stepper_spacing on either side, + * and the size/position based on the adjustment or fixed, depending. + */ + layout->slider.x = layout->trough.x + trough_border; + layout->slider.width = layout->trough.width - trough_border * 2; - if (new_value != range->adjustment->value) + /* Compute slider position/length */ + { + gint y, bottom, top, height; + + top = layout->trough.y + stepper_spacing; + bottom = layout->trough.y + layout->trough.height - stepper_spacing; + + /* slider height is the fraction (page_size / + * total_adjustment_range) times the trough height in pixels + */ + + if (range->adjustment->upper - range->adjustment->lower != 0) + height = ((bottom - top) * (range->adjustment->page_size / + (range->adjustment->upper - range->adjustment->lower))); + else + height = range->min_slider_size; + + if (height < range->min_slider_size || + range->slider_size_fixed) + height = range->min_slider_size; + + height = MIN (height, (layout->trough.height - stepper_spacing * 2)); + + y = top; + + if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0) + y += (bottom - top - height) * ((adjustment_value - range->adjustment->lower) / + (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size)); + + y = CLAMP (y, top, bottom); + + if (should_invert (range)) + y = bottom - (y - top + height); + + layout->slider.y = y; + layout->slider.height = height; + + /* These are publically exported */ + range->slider_start = layout->slider.y; + range->slider_end = layout->slider.y + layout->slider.height; + } + } + else { - range->adjustment->value = new_value; + gint stepper_width, stepper_height; - if ((range->policy == GTK_UPDATE_CONTINUOUS) || - (!return_val && (range->policy == GTK_UPDATE_DELAYED))) - { - gtk_signal_emit_by_name (GTK_OBJECT (range->adjustment), "value_changed"); - } + /* Steppers are the height of the range, and stepper_size in + * width, or if we don't have enough width, divided equally + * among available space. + */ + stepper_height = range_rect.height - trough_border * 2; + + if (stepper_height < 1) + stepper_height = range_rect.height; /* screw the trough border */ + + if (n_steppers == 0) + stepper_width = 0; /* avoid divide by n_steppers */ else - { - gtk_range_slider_update (range); - gtk_range_clear_background (range); - } - } + stepper_width = MIN (stepper_size, (range_rect.width / n_steppers)); - return return_val; -} + /* Stepper A */ + + layout->stepper_a.x = range_rect.x + trough_border; + layout->stepper_a.y = range_rect.y + trough_border; + + if (range->has_stepper_a) + { + layout->stepper_a.width = stepper_width; + layout->stepper_a.height = stepper_height; + } + else + { + layout->stepper_a.width = 0; + layout->stepper_a.height = 0; + } + /* Stepper B */ + + layout->stepper_b.x = layout->stepper_a.x + layout->stepper_a.width; + layout->stepper_b.y = layout->stepper_a.y; + + if (range->has_stepper_b) + { + layout->stepper_b.width = stepper_width; + layout->stepper_b.height = stepper_height; + } + else + { + layout->stepper_b.width = 0; + layout->stepper_b.height = 0; + } + + /* Stepper D */ + + if (range->has_stepper_d) + { + layout->stepper_d.width = stepper_width; + layout->stepper_d.height = stepper_height; + } + else + { + layout->stepper_d.width = 0; + layout->stepper_d.height = 0; + } -static void -gtk_range_add_timer (GtkRange *range) -{ - g_return_if_fail (GTK_IS_RANGE (range)); + layout->stepper_d.x = range_rect.x + range_rect.width - layout->stepper_d.width - trough_border; + layout->stepper_d.y = layout->stepper_a.y; - if (!range->timer) - { - range->need_timer = TRUE; - range->timer = gtk_timeout_add (SCROLL_INITIAL_DELAY, - (GtkFunction) RANGE_CLASS (range)->timer, - (gpointer) range); + + /* Stepper C */ + + if (range->has_stepper_c) + { + layout->stepper_c.width = stepper_width; + layout->stepper_c.height = stepper_height; + } + else + { + layout->stepper_c.width = 0; + layout->stepper_c.height = 0; + } + + layout->stepper_c.x = layout->stepper_d.x - layout->stepper_c.width; + layout->stepper_c.y = layout->stepper_a.y; + + /* Now the trough is the remaining space between steppers B and C, + * if any + */ + layout->trough.x = layout->stepper_b.x + layout->stepper_b.width; + layout->trough.y = range_rect.y; + + layout->trough.width = layout->stepper_c.x - (layout->stepper_b.x + layout->stepper_b.width); + layout->trough.height = range_rect.height; + + /* Slider fits into the trough, with stepper_spacing on either side, + * and the size/position based on the adjustment or fixed, depending. + */ + layout->slider.y = layout->trough.y + trough_border; + layout->slider.height = layout->trough.height - trough_border * 2; + + /* Compute slider position/length */ + { + gint x, left, right, width; + + left = layout->trough.x + stepper_spacing; + right = layout->trough.x + layout->trough.width - stepper_spacing; + + /* slider width is the fraction (page_size / + * total_adjustment_range) times the trough width in pixels + */ + + if (range->adjustment->upper - range->adjustment->lower != 0) + width = ((right - left) * (range->adjustment->page_size / + (range->adjustment->upper - range->adjustment->lower))); + else + width = range->min_slider_size; + + if (width < range->min_slider_size || + range->slider_size_fixed) + width = range->min_slider_size; + + width = MIN (width, (layout->trough.width - stepper_spacing * 2)); + + x = left; + + if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0) + x += (right - left - width) * ((adjustment_value - range->adjustment->lower) / + (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size)); + + x = CLAMP (x, left, right); + + if (should_invert (range)) + x = right - (x - left + width); + + layout->slider.x = x; + layout->slider.width = width; + + /* These are publically exported */ + range->slider_start = layout->slider.x; + range->slider_end = layout->slider.x + layout->slider.width; + } } + + gtk_range_update_mouse_location (range); } -static void -gtk_range_remove_timer (GtkRange *range) +static GdkRectangle* +get_area (GtkRange *range, + MouseLocation location) { - g_return_if_fail (GTK_IS_RANGE (range)); - - if (range->timer) + switch (location) { - gtk_timeout_remove (range->timer); - range->timer = 0; + case MOUSE_STEPPER_A: + return &range->layout->stepper_a; + case MOUSE_STEPPER_B: + return &range->layout->stepper_b; + case MOUSE_STEPPER_C: + return &range->layout->stepper_c; + case MOUSE_STEPPER_D: + return &range->layout->stepper_d; + case MOUSE_TROUGH: + return &range->layout->trough; + case MOUSE_SLIDER: + return &range->layout->slider; + case MOUSE_WIDGET: + case MOUSE_OUTSIDE: + break; } - range->need_timer = FALSE; + + g_warning (G_STRLOC": bug"); + return NULL; } -static void -gtk_range_adjustment_changed (GtkAdjustment *adjustment, - gpointer data) +static gboolean +gtk_range_real_change_value (GtkRange *range, + GtkScrollType scroll, + gdouble value) { - GtkRange *range; + /* potentially adjust the bounds _before we clamp */ + g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value); - g_return_if_fail (adjustment != NULL); - g_return_if_fail (data != NULL); + value = CLAMP (value, range->adjustment->lower, + (range->adjustment->upper - range->adjustment->page_size)); - range = GTK_RANGE (data); - - if (((range->old_lower != adjustment->lower) || - (range->old_upper != adjustment->upper) || - (range->old_page_size != adjustment->page_size)) && - (range->old_value == adjustment->value)) + if (range->round_digits >= 0) { - if ((adjustment->lower == adjustment->upper) || - (range->old_lower == (range->old_upper - range->old_page_size))) - { - adjustment->value = adjustment->lower; - gtk_signal_emit_by_name (GTK_OBJECT (adjustment), "value_changed"); - } - } + gdouble power; + gint i; - if ((range->old_value != adjustment->value) || - (range->old_lower != adjustment->lower) || - (range->old_upper != adjustment->upper) || - (range->old_page_size != adjustment->page_size)) + i = range->round_digits; + power = 1; + while (i--) + power *= 10; + + value = floor ((value * power) + 0.5) / power; + } + + if (range->adjustment->value != value) { - gtk_range_slider_update (range); - gtk_range_clear_background (range); + range->need_recalc = TRUE; - range->old_value = adjustment->value; - range->old_lower = adjustment->lower; - range->old_upper = adjustment->upper; - range->old_page_size = adjustment->page_size; + gtk_widget_queue_draw (GTK_WIDGET (range)); + + switch (range->update_policy) + { + case GTK_UPDATE_CONTINUOUS: + gtk_adjustment_set_value (range->adjustment, value); + break; + + /* Delayed means we update after a period of inactivity */ + case GTK_UPDATE_DELAYED: + gtk_range_reset_update_timer (range); + /* FALL THRU */ + + /* Discontinuous means we update on button release */ + case GTK_UPDATE_DISCONTINUOUS: + /* don't emit value_changed signal */ + range->adjustment->value = value; + range->update_pending = TRUE; + break; + } } + return FALSE; } static void -gtk_range_adjustment_value_changed (GtkAdjustment *adjustment, - gpointer data) +gtk_range_update_value (GtkRange *range) { - GtkRange *range; - - g_return_if_fail (adjustment != NULL); - g_return_if_fail (data != NULL); - - range = GTK_RANGE (data); - - if (range->old_value != adjustment->value) + gtk_range_remove_update_timer (range); + + if (range->update_pending) { - gtk_range_slider_update (range); - gtk_range_clear_background (range); + gtk_adjustment_value_changed (range->adjustment); - range->old_value = adjustment->value; + range->update_pending = FALSE; } } - -static void -gtk_range_trough_hdims (GtkRange *range, - gint *left, - gint *right) +struct _GtkRangeStepTimer { - gint trough_width; - gint slider_length; - gint tmp_width; - gint tleft; - gint tright; - - g_return_if_fail (range != NULL); + guint timeout_id; + GtkScrollType step; +}; - gdk_window_get_size (range->trough, &trough_width, NULL); - gdk_window_get_size (range->slider, &slider_length, NULL); +static gboolean +second_timeout (gpointer data) +{ + GtkRange *range; - tleft = GTK_WIDGET (range)->style->xthickness; - tright = trough_width - slider_length - GTK_WIDGET (range)->style->xthickness; + GDK_THREADS_ENTER (); + range = GTK_RANGE (data); + gtk_range_scroll (range, range->timer->step); + GDK_THREADS_LEAVE (); + + return TRUE; +} - if (range->step_back) - { - gdk_window_get_size (range->step_back, &tmp_width, NULL); - tleft += (tmp_width + RANGE_CLASS (range)->stepper_slider_spacing); - } +static gboolean +initial_timeout (gpointer data) +{ + GtkRange *range; - if (range->step_forw) - { - gdk_window_get_size (range->step_forw, &tmp_width, NULL); - tright -= (tmp_width + RANGE_CLASS (range)->stepper_slider_spacing); - } + GDK_THREADS_ENTER (); + range = GTK_RANGE (data); + range->timer->timeout_id = + g_timeout_add (SCROLL_LATER_DELAY, + second_timeout, + range); + GDK_THREADS_LEAVE (); - if (left) - *left = tleft; - if (right) - *right = tright; + /* remove self */ + return FALSE; } static void -gtk_range_trough_vdims (GtkRange *range, - gint *top, - gint *bottom) +gtk_range_add_step_timer (GtkRange *range, + GtkScrollType step) { - gint trough_height; - gint slider_length; - gint tmp_height; - gint ttop; - gint tbottom; - - g_return_if_fail (range != NULL); + g_return_if_fail (range->timer == NULL); + g_return_if_fail (step != GTK_SCROLL_NONE); + + range->timer = g_new (GtkRangeStepTimer, 1); - gdk_window_get_size (range->trough, NULL, &trough_height); - gdk_window_get_size (range->slider, NULL, &slider_length); + range->timer->timeout_id = + g_timeout_add (SCROLL_INITIAL_DELAY, + initial_timeout, + range); + range->timer->step = step; - ttop = GTK_WIDGET (range)->style->ythickness; - tbottom = trough_height - slider_length - GTK_WIDGET (range)->style->ythickness; + gtk_range_scroll (range, range->timer->step); +} - if (range->step_back) +static void +gtk_range_remove_step_timer (GtkRange *range) +{ + if (range->timer) { - gdk_window_get_size (range->step_back, NULL, &tmp_height); - ttop += (tmp_height + RANGE_CLASS (range)->stepper_slider_spacing); - } + if (range->timer->timeout_id != 0) + g_source_remove (range->timer->timeout_id); - if (range->step_forw) - { - gdk_window_get_size (range->step_forw, NULL, &tmp_height); - tbottom -= (tmp_height + RANGE_CLASS (range)->stepper_slider_spacing); - } + g_free (range->timer); - if (top) - *top = ttop; - if (bottom) - *bottom = tbottom; + range->timer = NULL; + } } -static void -gtk_range_style_set (GtkWidget *widget, - GtkStyle *previous_style) +static gboolean +update_timeout (gpointer data) { GtkRange *range; - g_return_if_fail (widget != NULL); - g_return_if_fail (GTK_IS_RANGE (widget)); - - range = GTK_RANGE (widget); + GDK_THREADS_ENTER (); + range = GTK_RANGE (data); + gtk_range_update_value (range); + range->update_timeout_id = 0; + GDK_THREADS_LEAVE (); - if (GTK_WIDGET_REALIZED (widget)) - { - if (range->trough) - gtk_style_set_background (widget->style, range->trough, GTK_STATE_ACTIVE); + /* self-remove */ + return FALSE; +} - if (range->slider) - gtk_style_set_background (widget->style, range->slider, GTK_STATE_NORMAL); - - /* The backgrounds of the step_forw and step_back never actually - * get drawn in draw calls, so we call gdk_window_clear() here - * so they get the correct colors. This is a hack. OWT. - */ +static void +gtk_range_reset_update_timer (GtkRange *range) +{ + gtk_range_remove_update_timer (range); - if (range->step_forw) - { - gtk_style_set_background (widget->style, range->step_forw, GTK_STATE_ACTIVE); - gdk_window_clear (range->step_forw); - } + range->update_timeout_id = g_timeout_add (UPDATE_DELAY, + update_timeout, + range); +} - if (range->step_back) - { - gtk_style_set_background (widget->style, range->step_back, GTK_STATE_ACTIVE); - gdk_window_clear (range->step_back); - } +static void +gtk_range_remove_update_timer (GtkRange *range) +{ + if (range->update_timeout_id != 0) + { + g_source_remove (range->update_timeout_id); + range->update_timeout_id = 0; } }