X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkrange.c;h=65c0d746335896b8b0f6e36974e4ab7dc275c33d;hb=94680cbffc1670e1fd0ebb2a3a9895eac08b1b04;hp=5f4c2ef82b678a56414be42b210c879739c9c0f9;hpb=feefa270e3c422ae8b28b1b263cd1a2e83b5dc1d;p=~andy%2Fgtk diff --git a/gtk/gtkrange.c b/gtk/gtkrange.c index 5f4c2ef82..65c0d7463 100644 --- a/gtk/gtkrange.c +++ b/gtk/gtkrange.c @@ -19,34 +19,44 @@ */ /* - * 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 #include "gtkintl.h" #include "gtkmain.h" #include "gtkmarshalers.h" #include "gtkrange.h" -#include "gtkintl.h" +#include "gtkscrollbar.h" +#include "gtkprivate.h" +#include "gtkalias.h" -#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 SCROLL_DELAY_FACTOR 5 /* Scroll repeat multiplier */ +#define UPDATE_DELAY 300 /* Delay for queued update */ enum { PROP_0, PROP_UPDATE_POLICY, PROP_ADJUSTMENT, - PROP_INVERTED + PROP_INVERTED, + PROP_LOWER_STEPPER_SENSITIVITY, + PROP_UPPER_STEPPER_SENSITIVITY, + PROP_SHOW_FILL_LEVEL, + PROP_RESTRICT_TO_FILL_LEVEL, + PROP_FILL_LEVEL }; enum { VALUE_CHANGED, ADJUST_BOUNDS, MOVE_SLIDER, + CHANGE_VALUE, LAST_SIGNAL }; @@ -61,6 +71,8 @@ typedef enum { MOUSE_WIDGET /* inside widget but not in any of the above GUI elements */ } MouseLocation; +#define GTK_RANGE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_RANGE, GtkRangeLayout)) + struct _GtkRangeLayout { /* These are in widget->window coordinates */ @@ -80,14 +92,27 @@ struct _GtkRangeLayout /* 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 */ + guint grab_button : 8; /* 0 if none */ + + /* Stepper sensitivity */ + guint lower_sensitive : 1; + guint upper_sensitive : 1; + + /* Fill level */ + guint show_fill_level : 1; + guint restrict_to_fill_level : 1; + + GtkSensitivityType lower_sensitivity; + GtkSensitivityType upper_sensitivity; + guint repaint_id; + + gdouble fill_level; }; -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, @@ -97,7 +122,6 @@ static void gtk_range_get_property (GObject *object, 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, @@ -106,30 +130,32 @@ 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, +static gboolean gtk_range_expose (GtkWidget *widget, GdkEventExpose *event); -static gint gtk_range_button_press (GtkWidget *widget, +static gboolean gtk_range_button_press (GtkWidget *widget, GdkEventButton *event); -static gint gtk_range_button_release (GtkWidget *widget, +static gboolean gtk_range_button_release (GtkWidget *widget, GdkEventButton *event); -static gint gtk_range_motion_notify (GtkWidget *widget, +static gboolean gtk_range_motion_notify (GtkWidget *widget, GdkEventMotion *event); -static gint gtk_range_enter_notify (GtkWidget *widget, +static gboolean gtk_range_enter_notify (GtkWidget *widget, GdkEventCrossing *event); -static gint gtk_range_leave_notify (GtkWidget *widget, +static gboolean gtk_range_leave_notify (GtkWidget *widget, GdkEventCrossing *event); +static gboolean gtk_range_grab_broken (GtkWidget *widget, + GdkEventGrabBroken *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, +static gboolean 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); - +static void stop_scrolling (GtkRange *range); /* Range methods */ @@ -137,7 +163,7 @@ static void gtk_range_move_slider (GtkRange *range, GtkScrollType scroll); /* Internals */ -static void gtk_range_scroll (GtkRange *range, +static gboolean gtk_range_scroll (GtkRange *range, GtkScrollType scroll); static gboolean gtk_range_update_mouse_location (GtkRange *range); static void gtk_range_calc_layout (GtkRange *range, @@ -145,18 +171,23 @@ static void gtk_range_calc_layout (GtkRange *range, static void gtk_range_get_props (GtkRange *range, gint *slider_width, gint *stepper_size, + gint *focus_width, gint *trough_border, gint *stepper_spacing, + gboolean *trough_under_steppers, gint *arrow_displacement_x, gint *arrow_displacement_y); static void gtk_range_calc_request (GtkRange *range, gint slider_width, gint stepper_size, + gint focus_width, gint trough_border, gint stepper_spacing, GdkRectangle *range_rect, GtkBorder *border, gint *n_steppers_p, + gboolean *has_steppers_ab, + gboolean *has_steppers_cd, gint *slider_length_p); static void gtk_range_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data); @@ -169,42 +200,17 @@ 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 void gtk_range_internal_set_value (GtkRange *range, +static gboolean gtk_range_real_change_value (GtkRange *range, + GtkScrollType scroll, gdouble value); static void gtk_range_update_value (GtkRange *range); +static gboolean gtk_range_key_press (GtkWidget *range, + GdkEventKey *event); -static GtkWidgetClass *parent_class = NULL; static guint signals[LAST_SIGNAL]; - -GType -gtk_range_get_type (void) -{ - static GType range_type = 0; - - if (!range_type) - { - static const GTypeInfo range_info = - { - sizeof (GtkRangeClass), - 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 = g_type_register_static (GTK_TYPE_WIDGET, "GtkRange", - &range_info, G_TYPE_FLAG_ABSTRACT); - } - - return range_type; -} +G_DEFINE_ABSTRACT_TYPE (GtkRange, gtk_range, GTK_TYPE_WIDGET) static void gtk_range_class_init (GtkRangeClass *class) @@ -217,11 +223,8 @@ gtk_range_class_init (GtkRangeClass *class) object_class = (GtkObjectClass*) class; widget_class = (GtkWidgetClass*) class; - parent_class = g_type_class_peek_parent (class); - 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->size_request = gtk_range_size_request; @@ -237,17 +240,26 @@ gtk_range_class_init (GtkRangeClass *class) widget_class->scroll_event = gtk_range_scroll_event; widget_class->enter_notify_event = gtk_range_enter_notify; widget_class->leave_notify_event = gtk_range_leave_notify; + widget_class->grab_broken_event = gtk_range_grab_broken; widget_class->grab_notify = gtk_range_grab_notify; widget_class->state_changed = gtk_range_state_changed; widget_class->style_set = gtk_range_style_set; + widget_class->key_press_event = gtk_range_key_press; class->move_slider = gtk_range_move_slider; + class->change_value = gtk_range_real_change_value; class->slider_detail = "slider"; class->stepper_detail = "stepper"; + /** + * GtkRange::value-changed: + * @range: the #GtkRange + * + * Emitted when the range value changes. + */ signals[VALUE_CHANGED] = - g_signal_new ("value_changed", + g_signal_new (I_("value_changed"), G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkRangeClass, value_changed), @@ -256,7 +268,7 @@ gtk_range_class_init (GtkRangeClass *class) G_TYPE_NONE, 0); signals[ADJUST_BOUNDS] = - g_signal_new ("adjust_bounds", + g_signal_new (I_("adjust_bounds"), G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds), @@ -265,8 +277,15 @@ gtk_range_class_init (GtkRangeClass *class) G_TYPE_NONE, 1, G_TYPE_DOUBLE); + /** + * GtkRange::move-slider: + * @range: the #GtkRange + * @step: how to move the slider + * + * Virtual function that moves the slider. Used for keybindings. + */ signals[MOVE_SLIDER] = - g_signal_new ("move_slider", + g_signal_new (I_("move_slider"), G_TYPE_FROM_CLASS (gobject_class), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET (GtkRangeClass, move_slider), @@ -274,80 +293,236 @@ gtk_range_class_init (GtkRangeClass *class) _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 (I_("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", - _("Update policy"), - _("How the range should be updated on the screen"), + 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)); + GTK_PARAM_READWRITE)); g_object_class_install_property (gobject_class, PROP_ADJUSTMENT, g_param_spec_object ("adjustment", - _("Adjustment"), - _("The GtkAdjustment that contains the current value of this range object"), + P_("Adjustment"), + P_("The GtkAdjustment that contains the current value of this range object"), GTK_TYPE_ADJUSTMENT, - G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (gobject_class, PROP_INVERTED, g_param_spec_boolean ("inverted", - _("Inverted"), - _("Invert direction slider moves to increase range value"), + P_("Inverted"), + P_("Invert direction slider moves to increase range value"), FALSE, - G_PARAM_READWRITE)); - + GTK_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_LOWER_STEPPER_SENSITIVITY, + g_param_spec_enum ("lower-stepper-sensitivity", + P_("Lower stepper sensitivity"), + P_("The sensitivity policy for the stepper that points to the adjustment's lower side"), + GTK_TYPE_SENSITIVITY_TYPE, + GTK_SENSITIVITY_AUTO, + GTK_PARAM_READWRITE)); + + g_object_class_install_property (gobject_class, + PROP_UPPER_STEPPER_SENSITIVITY, + g_param_spec_enum ("upper-stepper-sensitivity", + P_("Upper stepper sensitivity"), + P_("The sensitivity policy for the stepper that points to the adjustment's upper side"), + GTK_TYPE_SENSITIVITY_TYPE, + GTK_SENSITIVITY_AUTO, + GTK_PARAM_READWRITE)); + + /** + * GtkRange:show-fill-level: + * + * The show-fill-level property controls whether fill level indicator + * graphics are displayed on the trough. See + * gtk_range_set_show_fill_level(). + * + * Since: 2.12 + **/ + g_object_class_install_property (gobject_class, + PROP_SHOW_FILL_LEVEL, + g_param_spec_boolean ("show-fill-level", + P_("Show Fill Level"), + P_("Whether to display a fill level indicator graphics on trough."), + FALSE, + GTK_PARAM_READWRITE)); + + /** + * GtkRange:restrict-to-fill-level: + * + * The restrict-to-fill-level property controls whether slider + * movement is restricted to an upper boundary set by the + * fill level. See gtk_range_set_restrict_to_fill_level(). + * + * Since: 2.12 + **/ + g_object_class_install_property (gobject_class, + PROP_RESTRICT_TO_FILL_LEVEL, + g_param_spec_boolean ("restrict-to-fill-level", + P_("Restrict to Fill Level"), + P_("Whether to restrict the upper boundary to the fill level."), + TRUE, + GTK_PARAM_READWRITE)); + + /** + * GtkRange:fill-level: + * + * The fill level (e.g. prebuffering of a network stream). + * See gtk_range_set_fill_level(). + * + * Since: 2.12 + **/ + g_object_class_install_property (gobject_class, + PROP_FILL_LEVEL, + g_param_spec_double ("fill-level", + P_("Fill Level"), + P_("The fill level."), + -G_MAXDOUBLE, + G_MAXDOUBLE, + G_MAXDOUBLE, + GTK_PARAM_READWRITE)); + gtk_widget_class_install_style_property (widget_class, - g_param_spec_int ("slider_width", - _("Slider Width"), - _("Width of scrollbar or scale thumb"), + g_param_spec_int ("slider-width", + P_("Slider Width"), + P_("Width of scrollbar or scale thumb"), 0, G_MAXINT, 14, - G_PARAM_READABLE)); + GTK_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, - g_param_spec_int ("trough_border", - _("Trough Border"), - _("Spacing between thumb/steppers and outer trough bevel"), + 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_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, - g_param_spec_int ("stepper_size", - _("Stepper Size"), - _("Length of step buttons at ends"), + g_param_spec_int ("stepper-size", + P_("Stepper Size"), + P_("Length of step buttons at ends"), 0, G_MAXINT, 14, - G_PARAM_READABLE)); + GTK_PARAM_READABLE)); + /** + * GtkRange:stepper-spacing: + * + * The spacing between the stepper buttons and thumb. Note that + * setting this value to anything > 0 will automatically set the + * trough-under-steppers style property to %TRUE as well. Also, + * stepper-spacing won't have any effect if there are no steppers. + */ gtk_widget_class_install_style_property (widget_class, - g_param_spec_int ("stepper_spacing", - _("Stepper Spacing"), - _("Spacing between step buttons and thumb"), + g_param_spec_int ("stepper-spacing", + P_("Stepper Spacing"), + P_("Spacing between step buttons and thumb"), 0, G_MAXINT, 0, - G_PARAM_READABLE)); + GTK_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, - g_param_spec_int ("arrow_displacement_x", - _("Arrow X Displacement"), - _("How far in the x direction to move the arrow when the button is depressed"), + 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_PARAM_READABLE)); gtk_widget_class_install_style_property (widget_class, - g_param_spec_int ("arrow_displacement_y", - _("Arrow Y Displacement"), - _("How far in the y direction to move the arrow when the button is depressed"), + 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)); + GTK_PARAM_READABLE)); + + gtk_widget_class_install_style_property (widget_class, + g_param_spec_boolean ("activate-slider", + P_("Draw slider ACTIVE during drag"), + P_("With this option set to TRUE, sliders will be drawn ACTIVE and with shadow IN while they are dragged"), + FALSE, + GTK_PARAM_READABLE)); + + /** + * GtkRange:trough-side-details: + * + * When %TRUE, the parts of the trough on the two sides of the + * slider are drawn with different details. + * + * Since: 2.10 + */ + gtk_widget_class_install_style_property (widget_class, + g_param_spec_boolean ("trough-side-details", + P_("Trough Side Details"), + P_("When TRUE, the parts of the trough on the two sides of the slider are drawn with different details"), + FALSE, + GTK_PARAM_READABLE)); + + /** + * GtkRange:trough-under-steppers: + * + * Whether to draw the trough across the full length of the range or + * to exclude the steppers and their spacing. Note that setting the + * #GtkRange:stepper-spacing style property to any value > 0 will + * automatically enable trough-under-steppers too. + * + * Since: 2.10 + */ + gtk_widget_class_install_style_property (widget_class, + g_param_spec_boolean ("trough-under-steppers", + P_("Trough Under Steppers"), + P_("Whether to draw trough for full length of range or exclude the steppers and spacing"), + TRUE, + GTK_PARAM_READABLE)); + + g_type_class_add_private (class, sizeof (GtkRangeLayout)); } static void @@ -371,6 +546,21 @@ gtk_range_set_property (GObject *object, case PROP_INVERTED: gtk_range_set_inverted (range, g_value_get_boolean (value)); break; + case PROP_LOWER_STEPPER_SENSITIVITY: + gtk_range_set_lower_stepper_sensitivity (range, g_value_get_enum (value)); + break; + case PROP_UPPER_STEPPER_SENSITIVITY: + gtk_range_set_upper_stepper_sensitivity (range, g_value_get_enum (value)); + break; + case PROP_SHOW_FILL_LEVEL: + gtk_range_set_show_fill_level (range, g_value_get_boolean (value)); + break; + case PROP_RESTRICT_TO_FILL_LEVEL: + gtk_range_set_restrict_to_fill_level (range, g_value_get_boolean (value)); + break; + case PROP_FILL_LEVEL: + gtk_range_set_fill_level (range, g_value_get_double (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -398,6 +588,21 @@ gtk_range_get_property (GObject *object, case PROP_INVERTED: g_value_set_boolean (value, range->inverted); break; + case PROP_LOWER_STEPPER_SENSITIVITY: + g_value_set_enum (value, gtk_range_get_lower_stepper_sensitivity (range)); + break; + case PROP_UPPER_STEPPER_SENSITIVITY: + g_value_set_enum (value, gtk_range_get_upper_stepper_sensitivity (range)); + break; + case PROP_SHOW_FILL_LEVEL: + g_value_set_boolean (value, gtk_range_get_show_fill_level (range)); + break; + case PROP_RESTRICT_TO_FILL_LEVEL: + g_value_set_boolean (value, gtk_range_get_restrict_to_fill_level (range)); + break; + case PROP_FILL_LEVEL: + g_value_set_double (value, gtk_range_get_fill_level (range)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -420,12 +625,19 @@ gtk_range_init (GtkRange *range) range->has_stepper_d = FALSE; range->need_recalc = TRUE; range->round_digits = -1; - range->layout = g_new0 (GtkRangeLayout, 1); + range->layout = GTK_RANGE_GET_PRIVATE (range); 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->layout->lower_sensitivity = GTK_SENSITIVITY_AUTO; + range->layout->upper_sensitivity = GTK_SENSITIVITY_AUTO; + range->layout->lower_sensitive = TRUE; + range->layout->upper_sensitive = TRUE; + range->layout->show_fill_level = FALSE; + range->layout->restrict_to_fill_level = TRUE; + range->layout->fill_level = G_MAXDOUBLE; range->timer = NULL; } @@ -464,7 +676,6 @@ gtk_range_get_adjustment (GtkRange *range) * 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, @@ -475,7 +686,7 @@ gtk_range_set_update_policy (GtkRange *range, if (range->update_policy != policy) { range->update_policy = policy; - g_object_notify (G_OBJECT (range), "update_policy"); + g_object_notify (G_OBJECT (range), "update-policy"); } } @@ -507,7 +718,6 @@ gtk_range_get_update_policy (GtkRange *range) * 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, @@ -534,8 +744,7 @@ gtk_range_set_adjustment (GtkRange *range, } range->adjustment = adjustment; - g_object_ref (adjustment); - gtk_object_sink (GTK_OBJECT (adjustment)); + g_object_ref_sink (adjustment); g_signal_connect (adjustment, "changed", G_CALLBACK (gtk_range_adjustment_changed), @@ -558,7 +767,6 @@ gtk_range_set_adjustment (GtkRange *range, * 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_set_inverted (GtkRange *range, @@ -592,6 +800,100 @@ gtk_range_get_inverted (GtkRange *range) return range->inverted; } +/** + * gtk_range_set_lower_stepper_sensitivity: + * @range: a #GtkRange + * @sensitivity: the lower stepper's sensitivity policy. + * + * Sets the sensitivity policy for the stepper that points to the + * 'lower' end of the GtkRange's adjustment. + * + * Since: 2.10 + **/ +void +gtk_range_set_lower_stepper_sensitivity (GtkRange *range, + GtkSensitivityType sensitivity) +{ + g_return_if_fail (GTK_IS_RANGE (range)); + + if (range->layout->lower_sensitivity != sensitivity) + { + range->layout->lower_sensitivity = sensitivity; + + range->need_recalc = TRUE; + gtk_range_calc_layout (range, range->adjustment->value); + gtk_widget_queue_draw (GTK_WIDGET (range)); + + g_object_notify (G_OBJECT (range), "lower-stepper-sensitivity"); + } +} + +/** + * gtk_range_get_lower_stepper_sensitivity: + * @range: a #GtkRange + * + * Gets the sensitivity policy for the stepper that points to the + * 'lower' end of the GtkRange's adjustment. + * + * Return value: The lower stepper's sensitivity policy. + * + * Since: 2.10 + **/ +GtkSensitivityType +gtk_range_get_lower_stepper_sensitivity (GtkRange *range) +{ + g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO); + + return range->layout->lower_sensitivity; +} + +/** + * gtk_range_set_upper_stepper_sensitivity: + * @range: a #GtkRange + * @sensitivity: the upper stepper's sensitivity policy. + * + * Sets the sensitivity policy for the stepper that points to the + * 'upper' end of the GtkRange's adjustment. + * + * Since: 2.10 + **/ +void +gtk_range_set_upper_stepper_sensitivity (GtkRange *range, + GtkSensitivityType sensitivity) +{ + g_return_if_fail (GTK_IS_RANGE (range)); + + if (range->layout->upper_sensitivity != sensitivity) + { + range->layout->upper_sensitivity = sensitivity; + + range->need_recalc = TRUE; + gtk_range_calc_layout (range, range->adjustment->value); + gtk_widget_queue_draw (GTK_WIDGET (range)); + + g_object_notify (G_OBJECT (range), "upper-stepper-sensitivity"); + } +} + +/** + * gtk_range_get_upper_stepper_sensitivity: + * @range: a #GtkRange + * + * Gets the sensitivity policy for the stepper that points to the + * 'upper' end of the GtkRange's adjustment. + * + * Return value: The upper stepper's sensitivity policy. + * + * Since: 2.10 + **/ +GtkSensitivityType +gtk_range_get_upper_stepper_sensitivity (GtkRange *range) +{ + g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO); + + return range->layout->upper_sensitivity; +} + /** * gtk_range_set_increments: * @range: a #GtkRange @@ -602,7 +904,6 @@ gtk_range_get_inverted (GtkRange *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_set_increments (GtkRange *range, @@ -640,8 +941,13 @@ gtk_range_set_range (GtkRange *range, range->adjustment->lower = min; range->adjustment->upper = max; - value = CLAMP (range->adjustment->value, - range->adjustment->lower, + value = range->adjustment->value; + + if (range->layout->restrict_to_fill_level) + value = MIN (value, MAX (range->adjustment->lower, + range->layout->fill_level)); + + value = CLAMP (value, range->adjustment->lower, (range->adjustment->upper - range->adjustment->page_size)); gtk_adjustment_set_value (range->adjustment, value); @@ -655,16 +961,19 @@ gtk_range_set_range (GtkRange *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. - * + * them. The range emits the #GtkRange::value-changed signal if the + * value changes. **/ void gtk_range_set_value (GtkRange *range, gdouble value) { g_return_if_fail (GTK_IS_RANGE (range)); - + + if (range->layout->restrict_to_fill_level) + value = MIN (value, MAX (range->adjustment->lower, + range->layout->fill_level)); + value = CLAMP (value, range->adjustment->lower, (range->adjustment->upper - range->adjustment->page_size)); @@ -687,6 +996,159 @@ gtk_range_get_value (GtkRange *range) return range->adjustment->value; } +/** + * gtk_range_set_show_fill_level: + * @range: A #GtkRange + * @show_fill_level: Whether a fill level indicator graphics is shown. + * + * Sets whether a graphical fill level is show on the trough. See + * gtk_range_set_fill_level() for a general description of the fill + * level concept. + * + * Since: 2.12 + **/ +void +gtk_range_set_show_fill_level (GtkRange *range, + gboolean show_fill_level) +{ + g_return_if_fail (GTK_IS_RANGE (range)); + + show_fill_level = show_fill_level ? TRUE : FALSE; + + if (show_fill_level != range->layout->show_fill_level) + { + range->layout->show_fill_level = show_fill_level; + g_object_notify (G_OBJECT (range), "show-fill-level"); + gtk_widget_queue_draw (GTK_WIDGET (range)); + } +} + +/** + * gtk_range_get_show_fill_level: + * @range: A #GtkRange + * + * Gets whether the range displays the fill level graphically. + * + * Return value: %TRUE if @range shows the fill level. + * + * Since: 2.12 + **/ +gboolean +gtk_range_get_show_fill_level (GtkRange *range) +{ + g_return_val_if_fail (GTK_IS_RANGE (range), FALSE); + + return range->layout->show_fill_level; +} + +/** + * gtk_range_set_restrict_to_fill_level: + * @range: A #GtkRange + * @restrict_to_fill_level: Whether the fill level restricts slider movement. + * + * Sets whether the slider is restricted to the fill level. See + * gtk_range_set_fill_level() for a general description of the fill + * level concept. + * + * Since: 2.12 + **/ +void +gtk_range_set_restrict_to_fill_level (GtkRange *range, + gboolean restrict_to_fill_level) +{ + g_return_if_fail (GTK_IS_RANGE (range)); + + restrict_to_fill_level = restrict_to_fill_level ? TRUE : FALSE; + + if (restrict_to_fill_level != range->layout->restrict_to_fill_level) + { + range->layout->restrict_to_fill_level = restrict_to_fill_level; + g_object_notify (G_OBJECT (range), "restrict-to-fill-level"); + + gtk_range_set_value (range, gtk_range_get_value (range)); + } +} + +/** + * gtk_range_get_restrict_to_fill_level: + * @range: A #GtkRange + * + * Gets whether the range is restricted to the fill level. + * + * Return value: %TRUE if @range is restricted to the fill level. + * + * Since: 2.12 + **/ +gboolean +gtk_range_get_restrict_to_fill_level (GtkRange *range) +{ + g_return_val_if_fail (GTK_IS_RANGE (range), FALSE); + + return range->layout->restrict_to_fill_level; +} + +/** + * gtk_range_set_fill_level: + * @range: a #GtkRange + * @fill_level: the new position of the fill level indicator + * + * Set the new position of the fill level indicator. + * + * The "fill level" is probably best described by its most prominent + * use case, which is an indicator for the amount of pre-buffering in + * a streaming media player. In that use case, the value of the range + * would indicate the current play position, and the fill level would + * be the position up to which the file/stream has been downloaded. + * + * This amount of prebuffering can be displayed on the range's trough + * and is themeable separately from the trough. To enable fill level + * display, use gtk_range_set_show_fill_level(). The range defaults + * to not showing the fill level. + * + * Additionally, it's possible to restrict the range's slider position + * to values which are smaller than the fill level. This is controller + * by gtk_range_set_restrict_to_fill_level() and is by default + * enabled. + * + * Since: 2.12 + **/ +void +gtk_range_set_fill_level (GtkRange *range, + gdouble fill_level) +{ + g_return_if_fail (GTK_IS_RANGE (range)); + + if (fill_level != range->layout->fill_level) + { + range->layout->fill_level = fill_level; + g_object_notify (G_OBJECT (range), "fill-level"); + + if (range->layout->show_fill_level) + gtk_widget_queue_draw (GTK_WIDGET (range)); + + if (range->layout->restrict_to_fill_level) + gtk_range_set_value (range, gtk_range_get_value (range)); + } +} + +/** + * gtk_range_get_fill_level: + * @range : A #GtkRange + * + * Gets the current position of the fill level indicator. + * + * Return value: The current fill level + * + * Since: 2.12 + **/ +gdouble +gtk_range_get_fill_level (GtkRange *range) +{ + g_return_val_if_fail (GTK_IS_RANGE (range), 0.0); + + return range->layout->fill_level; +} + static gboolean should_invert (GtkRange *range) { @@ -699,16 +1161,6 @@ should_invert (GtkRange *range) return range->inverted; } -static void -gtk_range_finalize (GObject *object) -{ - GtkRange *range = GTK_RANGE (object); - - g_free (range->layout); - - (* G_OBJECT_CLASS (parent_class)->finalize) (object); -} - static void gtk_range_destroy (GtkObject *object) { @@ -716,7 +1168,11 @@ gtk_range_destroy (GtkObject *object) gtk_range_remove_step_timer (range); gtk_range_remove_update_timer (range); - + + if (range->layout->repaint_id) + g_source_remove (range->layout->repaint_id); + range->layout->repaint_id = 0; + if (range->adjustment) { g_signal_handlers_disconnect_by_func (range->adjustment, @@ -729,7 +1185,7 @@ gtk_range_destroy (GtkObject *object) range->adjustment = NULL; } - (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); + (* GTK_OBJECT_CLASS (gtk_range_parent_class)->destroy) (object); } static void @@ -737,19 +1193,22 @@ gtk_range_size_request (GtkWidget *widget, GtkRequisition *requisition) { GtkRange *range; - gint slider_width, stepper_size, trough_border, stepper_spacing; + gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing; GdkRectangle range_rect; GtkBorder border; range = GTK_RANGE (widget); gtk_range_get_props (range, - &slider_width, &stepper_size, &trough_border, &stepper_spacing, - NULL, NULL); + &slider_width, &stepper_size, + &focus_width, &trough_border, + &stepper_spacing, NULL, + NULL, NULL); gtk_range_calc_request (range, - slider_width, stepper_size, trough_border, stepper_spacing, - &range_rect, &border, NULL, NULL); + slider_width, stepper_size, + focus_width, trough_border, stepper_spacing, + &range_rect, &border, NULL, NULL, NULL, NULL); requisition->width = range_rect.width + border.left + border.right; requisition->height = range_rect.height + border.top + border.bottom; @@ -799,8 +1258,7 @@ gtk_range_realize (GtkWidget *widget) attributes.height = widget->allocation.height; attributes.wclass = GDK_INPUT_ONLY; attributes.event_mask = gtk_widget_get_events (widget); - attributes.event_mask |= (GDK_EXPOSURE_MASK | - GDK_BUTTON_PRESS_MASK | + attributes.event_mask |= (GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK | @@ -828,8 +1286,8 @@ gtk_range_unrealize (GtkWidget *widget) 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 (GTK_WIDGET_CLASS (gtk_range_parent_class)->unrealize) + (* GTK_WIDGET_CLASS (gtk_range_parent_class)->unrealize) (widget); } static void @@ -837,11 +1295,9 @@ gtk_range_map (GtkWidget *widget) { GtkRange *range = GTK_RANGE (widget); - g_return_if_fail (GTK_IS_RANGE (widget)); - gdk_window_show (range->event_window); - GTK_WIDGET_CLASS (parent_class)->map (widget); + GTK_WIDGET_CLASS (gtk_range_parent_class)->map (widget); } static void @@ -849,11 +1305,11 @@ gtk_range_unmap (GtkWidget *widget) { GtkRange *range = GTK_RANGE (widget); - g_return_if_fail (GTK_IS_RANGE (widget)); + stop_scrolling (range); gdk_window_hide (range->event_window); - GTK_WIDGET_CLASS (parent_class)->unmap (widget); + GTK_WIDGET_CLASS (gtk_range_parent_class)->unmap (widget); } static void @@ -874,14 +1330,28 @@ draw_stepper (GtkRange *range, gint arrow_width; gint arrow_height; + gboolean arrow_sensitive = TRUE; + /* More to get the right clip region than for efficiency */ if (!gdk_rectangle_intersect (area, rect, &intersection)) return; intersection.x += widget->allocation.x; intersection.y += widget->allocation.y; - - if (!GTK_WIDGET_IS_SENSITIVE (range)) + + if ((!range->inverted && (arrow_type == GTK_ARROW_DOWN || + arrow_type == GTK_ARROW_RIGHT)) || + (range->inverted && (arrow_type == GTK_ARROW_UP || + arrow_type == GTK_ARROW_LEFT))) + { + arrow_sensitive = range->layout->upper_sensitive; + } + else + { + arrow_sensitive = range->layout->lower_sensitive; + } + + if (!GTK_WIDGET_IS_SENSITIVE (range) || !arrow_sensitive) state_type = GTK_STATE_INSENSITIVE; else if (clicked) state_type = GTK_STATE_ACTIVE; @@ -889,8 +1359,8 @@ draw_stepper (GtkRange *range, state_type = GTK_STATE_PRELIGHT; else state_type = GTK_STATE_NORMAL; - - if (clicked) + + if (clicked && arrow_sensitive) shadow_type = GTK_SHADOW_IN; else shadow_type = GTK_SHADOW_OUT; @@ -910,12 +1380,13 @@ draw_stepper (GtkRange *range, 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) + if (clicked && arrow_sensitive) { gint arrow_displacement_x; gint arrow_displacement_y; - gtk_range_get_props (GTK_RANGE (widget), NULL, NULL, NULL, NULL, + gtk_range_get_props (GTK_RANGE (widget), + NULL, NULL, NULL, NULL, NULL, NULL, &arrow_displacement_x, &arrow_displacement_y); arrow_x += arrow_displacement_x; @@ -932,28 +1403,34 @@ draw_stepper (GtkRange *range, arrow_x, arrow_y, arrow_width, arrow_height); } -static gint +static gboolean gtk_range_expose (GtkWidget *widget, GdkEventExpose *event) { - GtkRange *range; + GtkRange *range = GTK_RANGE (widget); gboolean sensitive; GtkStateType state; + GtkShadowType shadow_type; GdkRectangle expose_area; /* Relative to widget->allocation */ GdkRectangle area; gint focus_line_width = 0; gint focus_padding = 0; + gboolean touchscreen; - range = GTK_RANGE (widget); - + g_object_get (gtk_widget_get_settings (widget), + "gtk-touchscreen-mode", &touchscreen, + NULL); if (GTK_WIDGET_CAN_FOCUS (range)) - { - gtk_widget_style_get (GTK_WIDGET (range), - "focus-line-width", &focus_line_width, - "focus-padding", &focus_padding, - NULL); - } - + gtk_widget_style_get (GTK_WIDGET (range), + "focus-line-width", &focus_line_width, + "focus-padding", &focus_padding, + NULL); + + /* we're now exposing, so there's no need to force early repaints */ + if (range->layout->repaint_id) + g_source_remove (range->layout->repaint_id); + range->layout->repaint_id = 0; + expose_area = event->area; expose_area.x -= widget->allocation.x; expose_area.y -= widget->allocation.y; @@ -972,20 +1449,181 @@ gtk_range_expose (GtkWidget *widget, if (gdk_rectangle_intersect (&expose_area, &range->range_rect, &area)) { + gint x = (widget->allocation.x + range->range_rect.x + + focus_line_width + focus_padding); + gint y = (widget->allocation.y + range->range_rect.y + + focus_line_width + focus_padding); + gint width = (range->range_rect.width - + 2 * (focus_line_width + focus_padding)); + gint height = (range->range_rect.height - + 2 * (focus_line_width + focus_padding)); + gboolean trough_side_details; + gboolean trough_under_steppers; + gint stepper_size; + gint stepper_spacing; + 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)); - - + + gtk_widget_style_get (GTK_WIDGET (range), + "trough-side-details", &trough_side_details, + "trough-under-steppers", &trough_under_steppers, + "stepper-size", &stepper_size, + "stepper-spacing", &stepper_spacing, + NULL); + + if (stepper_spacing > 0) + trough_under_steppers = FALSE; + + if (! trough_under_steppers) + { + gint offset = 0; + gint shorter = 0; + + if (range->has_stepper_a) + offset += stepper_size; + + if (range->has_stepper_b) + offset += stepper_size; + + shorter += offset; + + if (range->has_stepper_c) + shorter += stepper_size; + + if (range->has_stepper_d) + shorter += stepper_size; + + if (range->has_stepper_a || range->has_stepper_b) + { + offset += stepper_spacing; + shorter += stepper_spacing; + } + + if (range->has_stepper_c || range->has_stepper_d) + { + shorter += stepper_spacing; + } + + if (range->orientation == GTK_ORIENTATION_HORIZONTAL) + { + x += offset; + width -= shorter; + } + else + { + y += offset; + height -= shorter; + } + } + + if (! trough_side_details) + { + gtk_paint_box (widget->style, + widget->window, + sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE, + GTK_SHADOW_IN, + &area, GTK_WIDGET(range), "trough", + x, y, + width, height); + } + else + { + gint trough_change_pos_x = width; + gint trough_change_pos_y = height; + + if (range->orientation == GTK_ORIENTATION_HORIZONTAL) + trough_change_pos_x = (range->layout->slider.x + + range->layout->slider.width / 2 - + (x - widget->allocation.x)); + else + trough_change_pos_y = (range->layout->slider.y + + range->layout->slider.height / 2 - + (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), + should_invert (range) ? "trough-upper" : "trough-lower", + x, y, + trough_change_pos_x, trough_change_pos_y); + + if (range->orientation == GTK_ORIENTATION_HORIZONTAL) + trough_change_pos_y = 0; + else + trough_change_pos_x = 0; + + gtk_paint_box (widget->style, + widget->window, + sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE, + GTK_SHADOW_IN, + &area, GTK_WIDGET (range), + should_invert (range) ? "trough-lower" : "trough-upper", + x + trough_change_pos_x, y + trough_change_pos_y, + width - trough_change_pos_x, + height - trough_change_pos_y); + } + + if (range->layout->show_fill_level && + range->adjustment->upper - range->adjustment->page_size - + range->adjustment->lower != 0) + { + gdouble fill_level = range->layout->fill_level; + gint fill_x = x; + gint fill_y = y; + gint fill_width = width; + gint fill_height = height; + gchar *fill_detail; + + fill_level = CLAMP (fill_level, range->adjustment->lower, + range->adjustment->upper - + range->adjustment->page_size); + + if (range->orientation == GTK_ORIENTATION_HORIZONTAL) + { + fill_x = widget->allocation.x + range->layout->trough.x; + fill_width = (range->layout->slider.width + + (fill_level - range->adjustment->lower) / + (range->adjustment->upper - + range->adjustment->lower - + range->adjustment->page_size) * + (range->layout->trough.width - + range->layout->slider.width)); + + if (should_invert (range)) + fill_x += range->layout->trough.width - fill_width; + } + else + { + fill_y = widget->allocation.y + range->layout->trough.y; + fill_height = (range->layout->slider.height + + (fill_level - range->adjustment->lower) / + (range->adjustment->upper - + range->adjustment->lower - + range->adjustment->page_size) * + (range->layout->trough.height - + range->layout->slider.height)); + + if (should_invert (range)) + fill_y += range->layout->trough.height - fill_height; + } + + if (fill_level < range->adjustment->upper - range->adjustment->page_size) + fill_detail = "trough-fill-level-full"; + else + fill_detail = "trough-fill-level"; + + gtk_paint_box (widget->style, + widget->window, + sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE, + GTK_SHADOW_OUT, + &area, GTK_WIDGET (range), fill_detail, + fill_x, fill_y, + fill_width, fill_height); + } + if (sensitive && GTK_WIDGET_HAS_FOCUS (range)) gtk_paint_focus (widget->style, widget->window, GTK_WIDGET_STATE (widget), @@ -996,13 +1634,28 @@ gtk_range_expose (GtkWidget *widget, range->range_rect.height); } + shadow_type = GTK_SHADOW_OUT; + if (!sensitive) state = GTK_STATE_INSENSITIVE; - else if (range->layout->mouse_location == MOUSE_SLIDER) + else if (!touchscreen && range->layout->mouse_location == MOUSE_SLIDER) state = GTK_STATE_PRELIGHT; else state = GTK_STATE_NORMAL; + if (range->layout->grab_location == MOUSE_SLIDER) + { + gboolean activate_slider; + + gtk_widget_style_get (widget, "activate-slider", &activate_slider, NULL); + + if (activate_slider) + { + state = GTK_STATE_ACTIVE; + shadow_type = GTK_SHADOW_IN; + } + } + if (gdk_rectangle_intersect (&expose_area, &range->layout->slider, &area)) @@ -1013,7 +1666,7 @@ gtk_range_expose (GtkWidget *widget, gtk_paint_slider (widget->style, widget->window, state, - GTK_SHADOW_OUT, + shadow_type, &area, widget, GTK_RANGE_GET_CLASS (range)->slider_detail, @@ -1028,28 +1681,28 @@ gtk_range_expose (GtkWidget *widget, 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, + !touchscreen && 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, + !touchscreen && 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, + !touchscreen && 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, + !touchscreen && range->layout->mouse_location == MOUSE_STEPPER_D, &expose_area); return FALSE; @@ -1074,18 +1727,25 @@ range_grab_add (GtkRange *range, static void range_grab_remove (GtkRange *range) { + MouseLocation location; + gtk_grab_remove (GTK_WIDGET (range)); - + + location = range->layout->grab_location; range->layout->grab_location = MOUSE_OUTSIDE; range->layout->grab_button = 0; - if (gtk_range_update_mouse_location (range)) + if (gtk_range_update_mouse_location (range) || + location != MOUSE_OUTSIDE) gtk_widget_queue_draw (GTK_WIDGET (range)); } static GtkScrollType range_get_scroll_for_grab (GtkRange *range) -{ +{ + gboolean invert; + + invert = should_invert (range); switch (range->layout->grab_location) { /* Backward stepper */ @@ -1094,13 +1754,13 @@ range_get_scroll_for_grab (GtkRange *range) switch (range->layout->grab_button) { case 1: - return GTK_SCROLL_STEP_BACKWARD; + return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD; break; case 2: - return GTK_SCROLL_PAGE_BACKWARD; + return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD; break; case 3: - return GTK_SCROLL_START; + return invert ? GTK_SCROLL_END : GTK_SCROLL_START; break; } break; @@ -1111,13 +1771,13 @@ range_get_scroll_for_grab (GtkRange *range) switch (range->layout->grab_button) { case 1: - return GTK_SCROLL_STEP_FORWARD; + return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD; break; case 2: - return GTK_SCROLL_PAGE_FORWARD; + return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD; break; case 3: - return GTK_SCROLL_END; + return invert ? GTK_SCROLL_START : GTK_SCROLL_END; break; } break; @@ -1147,29 +1807,71 @@ coord_to_value (GtkRange *range, { gdouble frac; gdouble value; - + gint trough_length; + gint trough_start; + gint slider_length; + gint trough_border; + gint trough_under_steppers; + 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)); + { + trough_length = range->layout->trough.height; + trough_start = range->layout->trough.y; + slider_length = 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)); + { + trough_length = range->layout->trough.width; + trough_start = range->layout->trough.x; + slider_length = range->layout->slider.width; + } + + gtk_range_get_props (range, NULL, NULL, NULL, &trough_border, NULL, + &trough_under_steppers, NULL, NULL); + + if (! trough_under_steppers) + { + trough_start += trough_border; + trough_length -= 2 * trough_border; + } + + if (trough_length == slider_length) + frac = 1.0; + else + frac = (MAX (0, coord - trough_start) / + (gdouble) (trough_length - slider_length)); if (should_invert (range)) frac = 1.0 - frac; - - value = range->adjustment->lower + - frac * (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size); + + value = range->adjustment->lower + frac * (range->adjustment->upper - + range->adjustment->lower - + range->adjustment->page_size); return value; } +static gboolean +gtk_range_key_press (GtkWidget *widget, + GdkEventKey *event) +{ + GtkRange *range = GTK_RANGE (widget); + + if (event->keyval == GDK_Escape && + range->layout->grab_location != MOUSE_OUTSIDE) + { + stop_scrolling (range); + + update_slider_position (range, + range->slide_initial_coordinate, + range->slide_initial_coordinate); + + return TRUE; + } + + return GTK_WIDGET_CLASS (gtk_range_parent_class)->key_press_event (widget, event); +} + static gint gtk_range_button_press (GtkWidget *widget, GdkEventButton *event) @@ -1238,6 +1940,7 @@ gtk_range_button_press (GtkWidget *widget, range->layout->mouse_location == MOUSE_SLIDER) { gboolean need_value_update = FALSE; + gboolean activate_slider; /* Any button can be used to drag the slider, but you can start * dragging the slider with a trough click using button 2; @@ -1284,11 +1987,19 @@ gtk_range_button_press (GtkWidget *widget, range->slide_initial_coordinate = event->x; } + range_grab_add (range, MOUSE_SLIDER, event->button); + + gtk_widget_style_get (widget, "activate-slider", &activate_slider, NULL); + + /* force a redraw, if the active slider is drawn differently to the + * prelight one + */ + if (activate_slider) + gtk_widget_queue_draw (widget); + if (need_value_update) update_slider_position (range, event->x, event->y); - range_grab_add (range, MOUSE_SLIDER, event->button); - return TRUE; } @@ -1304,7 +2015,8 @@ update_slider_position (GtkRange *range, gint delta; gint c; gdouble new_value; - + gboolean handled; + if (range->orientation == GTK_ORIENTATION_VERTICAL) delta = mouse_y - range->slide_initial_coordinate; else @@ -1314,20 +2026,36 @@ update_slider_position (GtkRange *range, new_value = coord_to_value (range, c); - gtk_range_internal_set_value (range, new_value); + g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value, + &handled); } -static void stop_scrolling (GtkRange *range) +static void +stop_scrolling (GtkRange *range) { range_grab_remove (range); gtk_range_remove_step_timer (range); /* Flush any pending discontinuous/delayed updates */ gtk_range_update_value (range); +} + +static gboolean +gtk_range_grab_broken (GtkWidget *widget, + GdkEventGrabBroken *event) +{ + GtkRange *range = GTK_RANGE (widget); + + if (range->layout->grab_location != MOUSE_OUTSIDE) + { + if (range->layout->grab_location == MOUSE_SLIDER) + update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y); + + stop_scrolling (range); + + return TRUE; + } - /* 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 (widget); + return FALSE; } static gint @@ -1362,7 +2090,40 @@ gtk_range_button_release (GtkWidget *widget, return FALSE; } -static gint +/** + * _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) +{ + GtkAdjustment *adj = range->adjustment; + gdouble delta; + + 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; + + return delta; +} + +static gboolean gtk_range_scroll_event (GtkWidget *widget, GdkEventScroll *event) { @@ -1371,19 +2132,18 @@ gtk_range_scroll_event (GtkWidget *widget, if (GTK_WIDGET_REALIZED (range)) { GtkAdjustment *adj = GTK_RANGE (range)->adjustment; - gdouble increment = ((event->direction == GDK_SCROLL_UP || - event->direction == GDK_SCROLL_LEFT) ? - -adj->page_increment / 2: - adj->page_increment / 2); - - if (range->inverted) - increment = -increment; - - gtk_range_internal_set_value (range, adj->value + increment); + 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 DISCONTINOUS + * for DISCONTINUOUS */ if (range->update_policy == GTK_UPDATE_DISCONTINUOUS) gtk_range_update_value (range); @@ -1392,7 +2152,7 @@ gtk_range_scroll_event (GtkWidget *widget, return TRUE; } -static gint +static gboolean gtk_range_motion_notify (GtkWidget *widget, GdkEventMotion *event) { @@ -1416,7 +2176,7 @@ gtk_range_motion_notify (GtkWidget *widget, return range->layout->mouse_location != MOUSE_OUTSIDE; } -static gint +static gboolean gtk_range_enter_notify (GtkWidget *widget, GdkEventCrossing *event) { @@ -1431,7 +2191,7 @@ gtk_range_enter_notify (GtkWidget *widget, return TRUE; } -static gint +static gboolean gtk_range_leave_notify (GtkWidget *widget, GdkEventCrossing *event) { @@ -1462,14 +2222,45 @@ gtk_range_state_changed (GtkWidget *widget, stop_scrolling (GTK_RANGE (widget)); } +#define check_rectangle(rectangle1, rectangle2) \ + { \ + if (rectangle1.x != rectangle2.x) return TRUE; \ + if (rectangle1.y != rectangle2.y) return TRUE; \ + if (rectangle1.width != rectangle2.width) return TRUE; \ + if (rectangle1.height != rectangle2.height) return TRUE; \ + } + +static gboolean +layout_changed (GtkRangeLayout *layout1, + GtkRangeLayout *layout2) +{ + check_rectangle (layout1->slider, layout2->slider); + check_rectangle (layout1->trough, layout2->trough); + check_rectangle (layout1->stepper_a, layout2->stepper_a); + check_rectangle (layout1->stepper_d, layout2->stepper_d); + check_rectangle (layout1->stepper_b, layout2->stepper_b); + check_rectangle (layout1->stepper_c, layout2->stepper_c); + + if (layout1->upper_sensitive != layout2->upper_sensitive) return TRUE; + if (layout1->lower_sensitive != layout2->lower_sensitive) return TRUE; + + return FALSE; +} + static void gtk_range_adjustment_changed (GtkAdjustment *adjustment, gpointer data) { GtkRange *range = GTK_RANGE (data); + /* create a copy of the layout */ + GtkRangeLayout layout = *range->layout; range->need_recalc = TRUE; - gtk_widget_queue_draw (GTK_WIDGET (range)); + gtk_range_calc_layout (range, range->adjustment->value); + + /* now check whether the layout changed */ + if (layout_changed (range->layout, &layout)) + gtk_widget_queue_draw (GTK_WIDGET (range)); /* Note that we don't round off to range->round_digits here. * that's because it's really broken to change a value @@ -1480,18 +2271,35 @@ gtk_range_adjustment_changed (GtkAdjustment *adjustment, */ } +static gboolean +force_repaint (gpointer data) +{ + GtkRange *range = GTK_RANGE (data); + range->layout->repaint_id = 0; + if (GTK_WIDGET_DRAWABLE (range)) + gdk_window_process_updates (GTK_WIDGET (range)->window, FALSE); + return FALSE; +} + static void gtk_range_adjustment_value_changed (GtkAdjustment *adjustment, gpointer data) { GtkRange *range = GTK_RANGE (data); + /* create a copy of the layout */ + GtkRangeLayout layout = *range->layout; 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)) - gdk_window_process_updates (GTK_WIDGET (range)->window, FALSE); + gtk_range_calc_layout (range, range->adjustment->value); + + /* now check whether the layout changed */ + if (layout_changed (range->layout, &layout)) + { + gtk_widget_queue_draw (GTK_WIDGET (range)); + /* setup a timer to ensure the range isn't lagging too much behind the scroll position */ + if (!range->layout->repaint_id) + range->layout->repaint_id = gdk_threads_add_timeout_full (GDK_PRIORITY_EVENTS, 181, force_repaint, range, NULL); + } /* Note that we don't round off to range->round_digits here. * that's because it's really broken to change a value @@ -1512,26 +2320,29 @@ gtk_range_style_set (GtkWidget *widget, range->need_recalc = TRUE; - (* GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style); + (* GTK_WIDGET_CLASS (gtk_range_parent_class)->style_set) (widget, previous_style); } static void step_back (GtkRange *range) { gdouble newval; + gboolean handled; newval = range->adjustment->value - range->adjustment->step_increment; - gtk_range_internal_set_value (range, newval); + g_signal_emit (range, signals[CHANGE_VALUE], 0, + GTK_SCROLL_STEP_BACKWARD, newval, &handled); } static void step_forward (GtkRange *range) { gdouble newval; + gboolean handled; newval = range->adjustment->value + range->adjustment->step_increment; - - gtk_range_internal_set_value (range, newval); + g_signal_emit (range, signals[CHANGE_VALUE], 0, + GTK_SCROLL_STEP_FORWARD, newval, &handled); } @@ -1539,25 +2350,50 @@ static void page_back (GtkRange *range) { gdouble newval; + gboolean handled; newval = range->adjustment->value - range->adjustment->page_increment; - gtk_range_internal_set_value (range, newval); + g_signal_emit (range, signals[CHANGE_VALUE], 0, + GTK_SCROLL_PAGE_BACKWARD, newval, &handled); } static void page_forward (GtkRange *range) { gdouble newval; + gboolean handled; newval = range->adjustment->value + range->adjustment->page_increment; + g_signal_emit (range, signals[CHANGE_VALUE], 0, + GTK_SCROLL_PAGE_FORWARD, newval, &handled); +} - gtk_range_internal_set_value (range, newval); +static void +scroll_begin (GtkRange *range) +{ + gboolean handled; + g_signal_emit (range, signals[CHANGE_VALUE], 0, + GTK_SCROLL_START, range->adjustment->lower, + &handled); } static void +scroll_end (GtkRange *range) +{ + gdouble newval; + gboolean handled; + + newval = range->adjustment->upper - range->adjustment->page_size; + g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_END, newval, + &handled); +} + +static gboolean gtk_range_scroll (GtkRange *range, GtkScrollType scroll) { + gdouble old_value = range->adjustment->value; + switch (scroll) { case GTK_SCROLL_STEP_LEFT: @@ -1633,13 +2469,11 @@ gtk_range_scroll (GtkRange *range, break; case GTK_SCROLL_START: - gtk_range_internal_set_value (range, - range->adjustment->lower); + scroll_begin (range); break; case GTK_SCROLL_END: - gtk_range_internal_set_value (range, - range->adjustment->upper - range->adjustment->page_size); + scroll_end (range); break; case GTK_SCROLL_JUMP: @@ -1649,17 +2483,56 @@ gtk_range_scroll (GtkRange *range, case GTK_SCROLL_NONE: break; } + + return range->adjustment->value != old_value; } static void gtk_range_move_slider (GtkRange *range, GtkScrollType scroll) { - gtk_range_scroll (range, scroll); + gboolean cursor_only; + + g_object_get (gtk_widget_get_settings (GTK_WIDGET (range)), + "gtk-keynav-cursor-only", &cursor_only, + NULL); + + if (cursor_only) + { + GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (range)); + + if (range->orientation == GTK_ORIENTATION_HORIZONTAL) + { + if (scroll == GTK_SCROLL_STEP_UP || + scroll == GTK_SCROLL_STEP_DOWN) + { + if (toplevel) + gtk_widget_child_focus (toplevel, + scroll == GTK_SCROLL_STEP_UP ? + GTK_DIR_UP : GTK_DIR_DOWN); + return; + } + } + else + { + if (scroll == GTK_SCROLL_STEP_LEFT || + scroll == GTK_SCROLL_STEP_RIGHT) + { + if (toplevel) + gtk_widget_child_focus (toplevel, + scroll == GTK_SCROLL_STEP_LEFT ? + GTK_DIR_LEFT : GTK_DIR_RIGHT); + return; + } + } + } + + if (! gtk_range_scroll (range, scroll)) + gtk_widget_error_bell (GTK_WIDGET (range)); /* Policy DELAYED makes sense with key events, * but DISCONTINUOUS doesn't, so we update immediately - * for DISCONTINOUS + * for DISCONTINUOUS */ if (range->update_policy == GTK_UPDATE_DISCONTINUOUS) gtk_range_update_value (range); @@ -1669,24 +2542,31 @@ static void gtk_range_get_props (GtkRange *range, gint *slider_width, gint *stepper_size, + gint *focus_width, gint *trough_border, gint *stepper_spacing, + gboolean *trough_under_steppers, 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_slider_width, tmp_stepper_size, tmp_focus_width, tmp_trough_border; + gint tmp_stepper_spacing, tmp_trough_under_steppers; 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, + "slider-width", &tmp_slider_width, + "trough-border", &tmp_trough_border, + "stepper-size", &tmp_stepper_size, + "stepper-spacing", &tmp_stepper_spacing, + "trough-under-steppers", &tmp_trough_under_steppers, + "arrow-displacement-x", &tmp_arrow_displacement_x, + "arrow-displacement-y", &tmp_arrow_displacement_y, NULL); - + + if (tmp_stepper_spacing > 0) + tmp_trough_under_steppers = FALSE; + if (GTK_WIDGET_CAN_FOCUS (range)) { gint focus_line_width; @@ -1696,13 +2576,20 @@ gtk_range_get_props (GtkRange *range, "focus-line-width", &focus_line_width, "focus-padding", &focus_padding, NULL); - - tmp_trough_border += focus_line_width + focus_padding; + + tmp_focus_width = focus_line_width + focus_padding; + } + else + { + tmp_focus_width = 0; } if (slider_width) *slider_width = tmp_slider_width; + if (focus_width) + *focus_width = tmp_focus_width; + if (trough_border) *trough_border = tmp_trough_border; @@ -1712,6 +2599,9 @@ gtk_range_get_props (GtkRange *range, if (stepper_spacing) *stepper_spacing = tmp_stepper_spacing; + if (trough_under_steppers) + *trough_under_steppers = tmp_trough_under_steppers; + if (arrow_displacement_x) *arrow_displacement_x = tmp_arrow_displacement_x; @@ -1861,15 +2751,20 @@ static void gtk_range_calc_request (GtkRange *range, gint slider_width, gint stepper_size, + gint focus_width, gint trough_border, gint stepper_spacing, GdkRectangle *range_rect, GtkBorder *border, gint *n_steppers_p, + gboolean *has_steppers_ab, + gboolean *has_steppers_cd, gint *slider_length_p) { gint slider_length; gint n_steppers; + gint n_steppers_ab; + gint n_steppers_cd; border->left = 0; border->right = 0; @@ -1878,16 +2773,20 @@ gtk_range_calc_request (GtkRange *range, if (GTK_RANGE_GET_CLASS (range)->get_range_border) (* GTK_RANGE_GET_CLASS (range)->get_range_border) (range, border); - - n_steppers = 0; + + n_steppers_ab = 0; + n_steppers_cd = 0; + if (range->has_stepper_a) - n_steppers += 1; + n_steppers_ab += 1; if (range->has_stepper_b) - n_steppers += 1; + n_steppers_ab += 1; if (range->has_stepper_c) - n_steppers += 1; + n_steppers_cd += 1; if (range->has_stepper_d) - n_steppers += 1; + n_steppers_cd += 1; + + n_steppers = n_steppers_ab + n_steppers_cd; slider_length = range->min_slider_size; @@ -1899,18 +2798,36 @@ gtk_range_calc_request (GtkRange *range, */ if (range->orientation == GTK_ORIENTATION_VERTICAL) { - range_rect->width = trough_border * 2 + slider_width; - range_rect->height = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length; + range_rect->width = (focus_width + trough_border) * 2 + slider_width; + range_rect->height = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length; + + if (n_steppers_ab > 0) + range_rect->height += stepper_spacing; + + if (n_steppers_cd > 0) + range_rect->height += stepper_spacing; } else { - range_rect->width = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length; - range_rect->height = trough_border * 2 + slider_width; + range_rect->width = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length; + range_rect->height = (focus_width + trough_border) * 2 + slider_width; + + if (n_steppers_ab > 0) + range_rect->width += stepper_spacing; + + if (n_steppers_cd > 0) + range_rect->width += stepper_spacing; } if (n_steppers_p) *n_steppers_p = n_steppers; + if (has_steppers_ab) + *has_steppers_ab = (n_steppers_ab > 0); + + if (has_steppers_cd) + *has_steppers_cd = (n_steppers_cd > 0); + if (slider_length_p) *slider_length_p = slider_length; } @@ -1919,10 +2836,13 @@ static void gtk_range_calc_layout (GtkRange *range, gdouble adjustment_value) { - gint slider_width, stepper_size, trough_border, stepper_spacing; + gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing; gint slider_length; GtkBorder border; gint n_steppers; + gboolean has_steppers_ab; + gboolean has_steppers_cd; + gboolean trough_under_steppers; GdkRectangle range_rect; GtkRangeLayout *layout; GtkWidget *widget; @@ -1944,12 +2864,16 @@ gtk_range_calc_layout (GtkRange *range, layout = range->layout; gtk_range_get_props (range, - &slider_width, &stepper_size, &trough_border, &stepper_spacing, + &slider_width, &stepper_size, + &focus_width, &trough_border, + &stepper_spacing, &trough_under_steppers, NULL, NULL); gtk_range_calc_request (range, - slider_width, stepper_size, trough_border, stepper_spacing, - &range_rect, &border, &n_steppers, &slider_length); + slider_width, stepper_size, + focus_width, trough_border, stepper_spacing, + &range_rect, &border, &n_steppers, + &has_steppers_ab, &has_steppers_cd, &slider_length); /* We never expand to fill available space in the small dimension * (i.e. vertical scrollbars are always a fixed width) @@ -1976,7 +2900,10 @@ gtk_range_calc_layout (GtkRange *range, * height, or if we don't have enough height, divided equally * among available space. */ - stepper_width = range_rect.width - trough_border * 2; + stepper_width = range_rect.width - focus_width * 2; + + if (trough_under_steppers) + stepper_width -= trough_border * 2; if (stepper_width < 1) stepper_width = range_rect.width; /* screw the trough border */ @@ -1988,8 +2915,8 @@ gtk_range_calc_layout (GtkRange *range, /* Stepper A */ - layout->stepper_a.x = range_rect.x + trough_border; - layout->stepper_a.y = range_rect.y + trough_border; + layout->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers; + layout->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers; if (range->has_stepper_a) { @@ -2032,7 +2959,7 @@ gtk_range_calc_layout (GtkRange *range, } layout->stepper_d.x = layout->stepper_a.x; - layout->stepper_d.y = range_rect.y + range_rect.height - layout->stepper_d.height - trough_border; + layout->stepper_d.y = range_rect.y + range_rect.height - layout->stepper_d.height - focus_width - trough_border * trough_under_steppers; /* Stepper C */ @@ -2051,26 +2978,32 @@ gtk_range_calc_layout (GtkRange *range, layout->stepper_c.y = layout->stepper_d.y - layout->stepper_c.height; /* Now the trough is the remaining space between steppers B and C, - * if any + * if any, minus spacing */ layout->trough.x = range_rect.x; - layout->trough.y = layout->stepper_b.y + layout->stepper_b.height; + layout->trough.y = layout->stepper_b.y + layout->stepper_b.height + stepper_spacing * has_steppers_ab; layout->trough.width = range_rect.width; - layout->trough.height = layout->stepper_c.y - (layout->stepper_b.y + layout->stepper_b.height); - + layout->trough.height = layout->stepper_c.y - layout->trough.y - stepper_spacing * has_steppers_cd; + /* 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; + layout->slider.x = layout->trough.x + focus_width + trough_border; + layout->slider.width = layout->trough.width - (focus_width + trough_border) * 2; /* Compute slider position/length */ { gint y, bottom, top, height; - top = layout->trough.y + stepper_spacing; - bottom = layout->trough.y + layout->trough.height - stepper_spacing; - + top = layout->trough.y; + bottom = layout->trough.y + layout->trough.height; + + if (! trough_under_steppers) + { + top += trough_border; + bottom -= trough_border; + } + /* slider height is the fraction (page_size / * total_adjustment_range) times the trough height in pixels */ @@ -2084,8 +3017,8 @@ gtk_range_calc_layout (GtkRange *range, if (height < range->min_slider_size || range->slider_size_fixed) height = range->min_slider_size; - - height = MIN (height, (layout->trough.height - stepper_spacing * 2)); + + height = MIN (height, layout->trough.height); y = top; @@ -2114,7 +3047,10 @@ gtk_range_calc_layout (GtkRange *range, * width, or if we don't have enough width, divided equally * among available space. */ - stepper_height = range_rect.height - trough_border * 2; + stepper_height = range_rect.height + focus_width * 2; + + if (trough_under_steppers) + stepper_height -= trough_border * 2; if (stepper_height < 1) stepper_height = range_rect.height; /* screw the trough border */ @@ -2126,8 +3062,8 @@ gtk_range_calc_layout (GtkRange *range, /* Stepper A */ - layout->stepper_a.x = range_rect.x + trough_border; - layout->stepper_a.y = range_rect.y + trough_border; + layout->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers; + layout->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers; if (range->has_stepper_a) { @@ -2169,7 +3105,7 @@ gtk_range_calc_layout (GtkRange *range, layout->stepper_d.height = 0; } - layout->stepper_d.x = range_rect.x + range_rect.width - layout->stepper_d.width - trough_border; + layout->stepper_d.x = range_rect.x + range_rect.width - layout->stepper_d.width - focus_width - trough_border * trough_under_steppers; layout->stepper_d.y = layout->stepper_a.y; @@ -2192,25 +3128,31 @@ gtk_range_calc_layout (GtkRange *range, /* 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.x = layout->stepper_b.x + layout->stepper_b.width + stepper_spacing * has_steppers_ab; layout->trough.y = range_rect.y; - layout->trough.width = layout->stepper_c.x - (layout->stepper_b.x + layout->stepper_b.width); + layout->trough.width = layout->stepper_c.x - layout->trough.x - stepper_spacing * has_steppers_cd; 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; + layout->slider.y = layout->trough.y + focus_width + trough_border; + layout->slider.height = layout->trough.height - (focus_width + 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; - + left = layout->trough.x; + right = layout->trough.x + layout->trough.width; + + if (! trough_under_steppers) + { + left += trough_border; + right -= trough_border; + } + /* slider width is the fraction (page_size / * total_adjustment_range) times the trough width in pixels */ @@ -2225,7 +3167,7 @@ gtk_range_calc_layout (GtkRange *range, range->slider_size_fixed) width = range->min_slider_size; - width = MIN (width, (layout->trough.width - stepper_spacing * 2)); + width = MIN (width, layout->trough.width); x = left; @@ -2248,6 +3190,39 @@ gtk_range_calc_layout (GtkRange *range, } gtk_range_update_mouse_location (range); + + switch (range->layout->upper_sensitivity) + { + case GTK_SENSITIVITY_AUTO: + range->layout->upper_sensitive = + (range->adjustment->value < + (range->adjustment->upper - range->adjustment->page_size)); + break; + + case GTK_SENSITIVITY_ON: + range->layout->upper_sensitive = TRUE; + break; + + case GTK_SENSITIVITY_OFF: + range->layout->upper_sensitive = FALSE; + break; + } + + switch (range->layout->lower_sensitivity) + { + case GTK_SENSITIVITY_AUTO: + range->layout->lower_sensitive = + (range->adjustment->value > range->adjustment->lower); + break; + + case GTK_SENSITIVITY_ON: + range->layout->lower_sensitive = TRUE; + break; + + case GTK_SENSITIVITY_OFF: + range->layout->lower_sensitive = FALSE; + break; + } } static GdkRectangle* @@ -2277,24 +3252,32 @@ get_area (GtkRange *range, return NULL; } -static void -gtk_range_internal_set_value (GtkRange *range, - gdouble value) +static gboolean +gtk_range_real_change_value (GtkRange *range, + GtkScrollType scroll, + gdouble value) { /* potentially adjust the bounds _before we clamp */ g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value); + if (range->layout->restrict_to_fill_level) + value = MIN (value, MAX (range->adjustment->lower, + range->layout->fill_level)); + value = CLAMP (value, range->adjustment->lower, (range->adjustment->upper - range->adjustment->page_size)); if (range->round_digits >= 0) { - char buffer[128]; + gdouble power; + gint i; - /* This is just so darn lame. */ - g_snprintf (buffer, 128, "%0.*f", - range->round_digits, value); - sscanf (buffer, "%lf", &value); + i = range->round_digits; + power = 1; + while (i--) + power *= 10; + + value = floor ((value * power) + 0.5) / power; } if (range->adjustment->value != value) @@ -2322,6 +3305,7 @@ gtk_range_internal_set_value (GtkRange *range, break; } } + return FALSE; } static void @@ -2348,10 +3332,8 @@ second_timeout (gpointer data) { GtkRange *range; - GDK_THREADS_ENTER (); range = GTK_RANGE (data); gtk_range_scroll (range, range->timer->step); - GDK_THREADS_LEAVE (); return TRUE; } @@ -2359,16 +3341,17 @@ second_timeout (gpointer data) static gboolean initial_timeout (gpointer data) { - GtkRange *range; + GtkRange *range; + GtkSettings *settings; + guint timeout; - GDK_THREADS_ENTER (); - range = GTK_RANGE (data); - range->timer->timeout_id = - g_timeout_add (SCROLL_LATER_DELAY, - second_timeout, - range); - GDK_THREADS_LEAVE (); + settings = gtk_widget_get_settings (GTK_WIDGET (data)); + g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL); + range = GTK_RANGE (data); + range->timer->timeout_id = gdk_threads_add_timeout (timeout * SCROLL_DELAY_FACTOR, + second_timeout, + range); /* remove self */ return FALSE; } @@ -2377,15 +3360,20 @@ static void gtk_range_add_step_timer (GtkRange *range, GtkScrollType step) { + GtkSettings *settings; + guint timeout; + g_return_if_fail (range->timer == NULL); g_return_if_fail (step != GTK_SCROLL_NONE); - + + settings = gtk_widget_get_settings (GTK_WIDGET (range)); + g_object_get (settings, "gtk-timeout-initial", &timeout, NULL); + range->timer = g_new (GtkRangeStepTimer, 1); - range->timer->timeout_id = - g_timeout_add (SCROLL_INITIAL_DELAY, - initial_timeout, - range); + range->timer->timeout_id = gdk_threads_add_timeout (timeout, + initial_timeout, + range); range->timer->step = step; gtk_range_scroll (range, range->timer->step); @@ -2410,11 +3398,9 @@ update_timeout (gpointer data) { GtkRange *range; - GDK_THREADS_ENTER (); range = GTK_RANGE (data); gtk_range_update_value (range); range->update_timeout_id = 0; - GDK_THREADS_LEAVE (); /* self-remove */ return FALSE; @@ -2425,7 +3411,7 @@ gtk_range_reset_update_timer (GtkRange *range) { gtk_range_remove_update_timer (range); - range->update_timeout_id = g_timeout_add (UPDATE_DELAY, + range->update_timeout_id = gdk_threads_add_timeout (UPDATE_DELAY, update_timeout, range); } @@ -2439,3 +3425,6 @@ gtk_range_remove_update_timer (GtkRange *range) range->update_timeout_id = 0; } } + +#define __GTK_RANGE_C__ +#include "gtkaliasdef.c"