1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3 * Copyright (C) 2001 Red Hat, Inc.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
22 * Modified by the GTK+ Team and others 1997-2004. See the AUTHORS
23 * file for a list of people on the GTK+ Team. See the ChangeLog
24 * files for a list of changes. These files are distributed with
25 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
33 #include <gdk/gdkkeysyms.h>
35 #include "gtkmarshalers.h"
36 #include "gtkorientable.h"
39 #include "gtkscrollbar.h"
40 #include "gtkprivate.h"
45 * @Short_description: Base class for widgets which visualize an adjustment
48 * #GtkRange is the common base class for widgets which visualize an
49 * adjustment, e.g #GtkScale or #GtkScrollbar.
51 * Apart from signals for monitoring the parameters of the adjustment,
52 * #GtkRange provides properties and methods for influencing the sensitivity
53 * of the "steppers". It also provides properties and methods for setting a
54 * "fill level" on range widgets. See gtk_range_set_fill_level().
58 #define SCROLL_DELAY_FACTOR 5 /* Scroll repeat multiplier */
59 #define UPDATE_DELAY 300 /* Delay for queued update */
61 typedef struct _GtkRangeStepTimer GtkRangeStepTimer;
71 MOUSE_WIDGET /* inside widget but not in any of the above GUI elements */
74 struct _GtkRangePrivate
76 MouseLocation mouse_location;
77 /* last mouse coords we got, or -1 if mouse is outside the range */
80 MouseLocation grab_location; /* "grabbed" mouse location, OUTSIDE for no grab */
81 guint grab_button : 8; /* 0 if none */
83 GtkRangeStepTimer *timer;
85 GtkAdjustment *adjustment;
86 GtkOrientation orientation;
87 GtkSensitivityType lower_sensitivity;
88 GtkSensitivityType upper_sensitivity;
89 GtkUpdateType update_policy;
91 GdkDevice *grab_device;
92 GdkRectangle range_rect; /* Area of entire stepper + trough assembly in widget->window coords */
93 /* These are in widget->window coordinates */
94 GdkRectangle stepper_a;
95 GdkRectangle stepper_b;
96 GdkRectangle stepper_c;
97 GdkRectangle stepper_d;
98 GdkRectangle trough; /* The trough rectangle is the area the thumb can slide in, not the entire range_rect */
100 GdkWindow *event_window;
102 GQuark slider_detail_quark;
103 GQuark stepper_detail_quark[4];
105 gboolean recalc_marks;
111 gint min_slider_size;
113 gint round_digits; /* Round off value to this many digits, -1 for no rounding */
114 gint slide_initial_slider_position;
115 gint slide_initial_coordinate;
116 gint slider_start; /* Slider range along the long dimension, in widget->window coords */
120 guint update_timeout_id;
122 /* Steppers are: < > ---- < >
125 guint has_stepper_a : 1;
126 guint has_stepper_b : 1;
127 guint has_stepper_c : 1;
128 guint has_stepper_d : 1;
132 guint need_recalc : 1;
133 guint slider_size_fixed : 1;
134 guint trough_click_forward : 1; /* trough click was on the forward side of slider */
135 guint update_pending : 1; /* need to emit value_changed */
137 /* Stepper sensitivity */
138 guint lower_sensitive : 1;
139 guint upper_sensitive : 1;
142 guint show_fill_level : 1;
143 guint restrict_to_fill_level : 1;
153 PROP_LOWER_STEPPER_SENSITIVITY,
154 PROP_UPPER_STEPPER_SENSITIVITY,
155 PROP_SHOW_FILL_LEVEL,
156 PROP_RESTRICT_TO_FILL_LEVEL,
175 static void gtk_range_set_property (GObject *object,
179 static void gtk_range_get_property (GObject *object,
183 static void gtk_range_destroy (GtkWidget *widget);
184 static void gtk_range_size_request (GtkWidget *widget,
185 GtkRequisition *requisition);
186 static void gtk_range_size_allocate (GtkWidget *widget,
187 GtkAllocation *allocation);
188 static void gtk_range_realize (GtkWidget *widget);
189 static void gtk_range_unrealize (GtkWidget *widget);
190 static void gtk_range_map (GtkWidget *widget);
191 static void gtk_range_unmap (GtkWidget *widget);
192 static gboolean gtk_range_draw (GtkWidget *widget,
194 static gboolean gtk_range_button_press (GtkWidget *widget,
195 GdkEventButton *event);
196 static gboolean gtk_range_button_release (GtkWidget *widget,
197 GdkEventButton *event);
198 static gboolean gtk_range_motion_notify (GtkWidget *widget,
199 GdkEventMotion *event);
200 static gboolean gtk_range_enter_notify (GtkWidget *widget,
201 GdkEventCrossing *event);
202 static gboolean gtk_range_leave_notify (GtkWidget *widget,
203 GdkEventCrossing *event);
204 static gboolean gtk_range_grab_broken (GtkWidget *widget,
205 GdkEventGrabBroken *event);
206 static void gtk_range_grab_notify (GtkWidget *widget,
207 gboolean was_grabbed);
208 static void gtk_range_state_changed (GtkWidget *widget,
209 GtkStateType previous_state);
210 static gboolean gtk_range_scroll_event (GtkWidget *widget,
211 GdkEventScroll *event);
212 static void gtk_range_style_set (GtkWidget *widget,
213 GtkStyle *previous_style);
214 static void update_slider_position (GtkRange *range,
217 static void stop_scrolling (GtkRange *range);
221 static void gtk_range_move_slider (GtkRange *range,
222 GtkScrollType scroll);
225 static gboolean gtk_range_scroll (GtkRange *range,
226 GtkScrollType scroll);
227 static gboolean gtk_range_update_mouse_location (GtkRange *range);
228 static void gtk_range_calc_layout (GtkRange *range,
229 gdouble adjustment_value);
230 static void gtk_range_calc_marks (GtkRange *range);
231 static void gtk_range_get_props (GtkRange *range,
236 gint *stepper_spacing,
237 gboolean *trough_under_steppers,
238 gint *arrow_displacement_x,
239 gint *arrow_displacement_y);
240 static void gtk_range_calc_request (GtkRange *range,
245 gint stepper_spacing,
246 GdkRectangle *range_rect,
249 gboolean *has_steppers_ab,
250 gboolean *has_steppers_cd,
251 gint *slider_length_p);
252 static void gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
254 static void gtk_range_adjustment_changed (GtkAdjustment *adjustment,
256 static void gtk_range_add_step_timer (GtkRange *range,
258 static void gtk_range_remove_step_timer (GtkRange *range);
259 static void gtk_range_reset_update_timer (GtkRange *range);
260 static void gtk_range_remove_update_timer (GtkRange *range);
261 static GdkRectangle* get_area (GtkRange *range,
262 MouseLocation location);
263 static gboolean gtk_range_real_change_value (GtkRange *range,
264 GtkScrollType scroll,
266 static void gtk_range_update_value (GtkRange *range);
267 static gboolean gtk_range_key_press (GtkWidget *range,
271 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkRange, gtk_range, GTK_TYPE_WIDGET,
272 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
275 static guint signals[LAST_SIGNAL];
279 gtk_range_class_init (GtkRangeClass *class)
281 GObjectClass *gobject_class;
282 GtkWidgetClass *widget_class;
284 gobject_class = G_OBJECT_CLASS (class);
285 widget_class = (GtkWidgetClass*) class;
287 gobject_class->set_property = gtk_range_set_property;
288 gobject_class->get_property = gtk_range_get_property;
290 widget_class->destroy = gtk_range_destroy;
291 widget_class->size_request = gtk_range_size_request;
292 widget_class->size_allocate = gtk_range_size_allocate;
293 widget_class->realize = gtk_range_realize;
294 widget_class->unrealize = gtk_range_unrealize;
295 widget_class->map = gtk_range_map;
296 widget_class->unmap = gtk_range_unmap;
297 widget_class->draw = gtk_range_draw;
298 widget_class->button_press_event = gtk_range_button_press;
299 widget_class->button_release_event = gtk_range_button_release;
300 widget_class->motion_notify_event = gtk_range_motion_notify;
301 widget_class->scroll_event = gtk_range_scroll_event;
302 widget_class->enter_notify_event = gtk_range_enter_notify;
303 widget_class->leave_notify_event = gtk_range_leave_notify;
304 widget_class->grab_broken_event = gtk_range_grab_broken;
305 widget_class->grab_notify = gtk_range_grab_notify;
306 widget_class->state_changed = gtk_range_state_changed;
307 widget_class->style_set = gtk_range_style_set;
308 widget_class->key_press_event = gtk_range_key_press;
310 class->move_slider = gtk_range_move_slider;
311 class->change_value = gtk_range_real_change_value;
313 class->slider_detail = "slider";
314 class->stepper_detail = "stepper";
317 * GtkRange::value-changed:
318 * @range: the #GtkRange that received the signal
320 * Emitted when the range value changes.
322 signals[VALUE_CHANGED] =
323 g_signal_new (I_("value-changed"),
324 G_TYPE_FROM_CLASS (gobject_class),
326 G_STRUCT_OFFSET (GtkRangeClass, value_changed),
328 _gtk_marshal_VOID__VOID,
332 * GtkRange::adjust-bounds:
333 * @range: the #GtkRange that received the signal
334 * @value: the value before we clamp
336 * Emitted before clamping a value, to give the application a
337 * chance to adjust the bounds.
339 signals[ADJUST_BOUNDS] =
340 g_signal_new (I_("adjust-bounds"),
341 G_TYPE_FROM_CLASS (gobject_class),
343 G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds),
345 _gtk_marshal_VOID__DOUBLE,
350 * GtkRange::move-slider:
351 * @range: the #GtkRange that received the signal
352 * @step: how to move the slider
354 * Virtual function that moves the slider. Used for keybindings.
356 signals[MOVE_SLIDER] =
357 g_signal_new (I_("move-slider"),
358 G_TYPE_FROM_CLASS (gobject_class),
359 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
360 G_STRUCT_OFFSET (GtkRangeClass, move_slider),
362 _gtk_marshal_VOID__ENUM,
364 GTK_TYPE_SCROLL_TYPE);
367 * GtkRange::change-value:
368 * @range: the #GtkRange that received the signal
369 * @scroll: the type of scroll action that was performed
370 * @value: the new value resulting from the scroll action
371 * @returns: %TRUE to prevent other handlers from being invoked for the
372 * signal, %FALSE to propagate the signal further
374 * The ::change-value signal is emitted when a scroll action is
375 * performed on a range. It allows an application to determine the
376 * type of scroll event that occurred and the resultant new value.
377 * The application can handle the event itself and return %TRUE to
378 * prevent further processing. Or, by returning %FALSE, it can pass
379 * the event to other handlers until the default GTK+ handler is
382 * The value parameter is unrounded. An application that overrides
383 * the ::change-value signal is responsible for clamping the value to
384 * the desired number of decimal digits; the default GTK+ handler
385 * clamps the value based on @range->round_digits.
387 * It is not possible to use delayed update policies in an overridden
388 * ::change-value handler.
392 signals[CHANGE_VALUE] =
393 g_signal_new (I_("change-value"),
394 G_TYPE_FROM_CLASS (gobject_class),
396 G_STRUCT_OFFSET (GtkRangeClass, change_value),
397 _gtk_boolean_handled_accumulator, NULL,
398 _gtk_marshal_BOOLEAN__ENUM_DOUBLE,
400 GTK_TYPE_SCROLL_TYPE,
403 g_object_class_override_property (gobject_class,
407 g_object_class_install_property (gobject_class,
409 g_param_spec_enum ("update-policy",
411 P_("How the range should be updated on the screen"),
412 GTK_TYPE_UPDATE_TYPE,
413 GTK_UPDATE_CONTINUOUS,
414 GTK_PARAM_READWRITE));
416 g_object_class_install_property (gobject_class,
418 g_param_spec_object ("adjustment",
420 P_("The GtkAdjustment that contains the current value of this range object"),
422 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
424 g_object_class_install_property (gobject_class,
426 g_param_spec_boolean ("inverted",
428 P_("Invert direction slider moves to increase range value"),
430 GTK_PARAM_READWRITE));
432 g_object_class_install_property (gobject_class,
433 PROP_LOWER_STEPPER_SENSITIVITY,
434 g_param_spec_enum ("lower-stepper-sensitivity",
435 P_("Lower stepper sensitivity"),
436 P_("The sensitivity policy for the stepper that points to the adjustment's lower side"),
437 GTK_TYPE_SENSITIVITY_TYPE,
438 GTK_SENSITIVITY_AUTO,
439 GTK_PARAM_READWRITE));
441 g_object_class_install_property (gobject_class,
442 PROP_UPPER_STEPPER_SENSITIVITY,
443 g_param_spec_enum ("upper-stepper-sensitivity",
444 P_("Upper stepper sensitivity"),
445 P_("The sensitivity policy for the stepper that points to the adjustment's upper side"),
446 GTK_TYPE_SENSITIVITY_TYPE,
447 GTK_SENSITIVITY_AUTO,
448 GTK_PARAM_READWRITE));
451 * GtkRange:show-fill-level:
453 * The show-fill-level property controls whether fill level indicator
454 * graphics are displayed on the trough. See
455 * gtk_range_set_show_fill_level().
459 g_object_class_install_property (gobject_class,
460 PROP_SHOW_FILL_LEVEL,
461 g_param_spec_boolean ("show-fill-level",
462 P_("Show Fill Level"),
463 P_("Whether to display a fill level indicator graphics on trough."),
465 GTK_PARAM_READWRITE));
468 * GtkRange:restrict-to-fill-level:
470 * The restrict-to-fill-level property controls whether slider
471 * movement is restricted to an upper boundary set by the
472 * fill level. See gtk_range_set_restrict_to_fill_level().
476 g_object_class_install_property (gobject_class,
477 PROP_RESTRICT_TO_FILL_LEVEL,
478 g_param_spec_boolean ("restrict-to-fill-level",
479 P_("Restrict to Fill Level"),
480 P_("Whether to restrict the upper boundary to the fill level."),
482 GTK_PARAM_READWRITE));
485 * GtkRange:fill-level:
487 * The fill level (e.g. prebuffering of a network stream).
488 * See gtk_range_set_fill_level().
492 g_object_class_install_property (gobject_class,
494 g_param_spec_double ("fill-level",
496 P_("The fill level."),
500 GTK_PARAM_READWRITE));
502 gtk_widget_class_install_style_property (widget_class,
503 g_param_spec_int ("slider-width",
505 P_("Width of scrollbar or scale thumb"),
509 GTK_PARAM_READABLE));
510 gtk_widget_class_install_style_property (widget_class,
511 g_param_spec_int ("trough-border",
513 P_("Spacing between thumb/steppers and outer trough bevel"),
517 GTK_PARAM_READABLE));
518 gtk_widget_class_install_style_property (widget_class,
519 g_param_spec_int ("stepper-size",
521 P_("Length of step buttons at ends"),
525 GTK_PARAM_READABLE));
527 * GtkRange:stepper-spacing:
529 * The spacing between the stepper buttons and thumb. Note that
530 * setting this value to anything > 0 will automatically set the
531 * trough-under-steppers style property to %TRUE as well. Also,
532 * stepper-spacing won't have any effect if there are no steppers.
534 gtk_widget_class_install_style_property (widget_class,
535 g_param_spec_int ("stepper-spacing",
536 P_("Stepper Spacing"),
537 P_("Spacing between step buttons and thumb"),
541 GTK_PARAM_READABLE));
542 gtk_widget_class_install_style_property (widget_class,
543 g_param_spec_int ("arrow-displacement-x",
544 P_("Arrow X Displacement"),
545 P_("How far in the x direction to move the arrow when the button is depressed"),
549 GTK_PARAM_READABLE));
550 gtk_widget_class_install_style_property (widget_class,
551 g_param_spec_int ("arrow-displacement-y",
552 P_("Arrow Y Displacement"),
553 P_("How far in the y direction to move the arrow when the button is depressed"),
557 GTK_PARAM_READABLE));
560 * GtkRange:trough-under-steppers:
562 * Whether to draw the trough across the full length of the range or
563 * to exclude the steppers and their spacing. Note that setting the
564 * #GtkRange:stepper-spacing style property to any value > 0 will
565 * automatically enable trough-under-steppers too.
569 gtk_widget_class_install_style_property (widget_class,
570 g_param_spec_boolean ("trough-under-steppers",
571 P_("Trough Under Steppers"),
572 P_("Whether to draw trough for full length of range or exclude the steppers and spacing"),
574 GTK_PARAM_READABLE));
577 * GtkRange:arrow-scaling:
579 * The arrow size proportion relative to the scroll button size.
583 gtk_widget_class_install_style_property (widget_class,
584 g_param_spec_float ("arrow-scaling",
586 P_("Arrow scaling with regard to scroll button size"),
588 GTK_PARAM_READABLE));
590 g_type_class_add_private (class, sizeof (GtkRangePrivate));
594 gtk_range_set_property (GObject *object,
599 GtkRange *range = GTK_RANGE (object);
600 GtkRangePrivate *priv = range->priv;
604 case PROP_ORIENTATION:
605 priv->orientation = g_value_get_enum (value);
607 priv->slider_detail_quark = 0;
608 priv->stepper_detail_quark[0] = 0;
609 priv->stepper_detail_quark[1] = 0;
610 priv->stepper_detail_quark[2] = 0;
611 priv->stepper_detail_quark[3] = 0;
613 gtk_widget_queue_resize (GTK_WIDGET (range));
615 case PROP_UPDATE_POLICY:
616 gtk_range_set_update_policy (range, g_value_get_enum (value));
618 case PROP_ADJUSTMENT:
619 gtk_range_set_adjustment (range, g_value_get_object (value));
622 gtk_range_set_inverted (range, g_value_get_boolean (value));
624 case PROP_LOWER_STEPPER_SENSITIVITY:
625 gtk_range_set_lower_stepper_sensitivity (range, g_value_get_enum (value));
627 case PROP_UPPER_STEPPER_SENSITIVITY:
628 gtk_range_set_upper_stepper_sensitivity (range, g_value_get_enum (value));
630 case PROP_SHOW_FILL_LEVEL:
631 gtk_range_set_show_fill_level (range, g_value_get_boolean (value));
633 case PROP_RESTRICT_TO_FILL_LEVEL:
634 gtk_range_set_restrict_to_fill_level (range, g_value_get_boolean (value));
636 case PROP_FILL_LEVEL:
637 gtk_range_set_fill_level (range, g_value_get_double (value));
640 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
646 gtk_range_get_property (GObject *object,
651 GtkRange *range = GTK_RANGE (object);
652 GtkRangePrivate *priv = range->priv;
656 case PROP_ORIENTATION:
657 g_value_set_enum (value, priv->orientation);
659 case PROP_UPDATE_POLICY:
660 g_value_set_enum (value, priv->update_policy);
662 case PROP_ADJUSTMENT:
663 g_value_set_object (value, priv->adjustment);
666 g_value_set_boolean (value, priv->inverted);
668 case PROP_LOWER_STEPPER_SENSITIVITY:
669 g_value_set_enum (value, gtk_range_get_lower_stepper_sensitivity (range));
671 case PROP_UPPER_STEPPER_SENSITIVITY:
672 g_value_set_enum (value, gtk_range_get_upper_stepper_sensitivity (range));
674 case PROP_SHOW_FILL_LEVEL:
675 g_value_set_boolean (value, gtk_range_get_show_fill_level (range));
677 case PROP_RESTRICT_TO_FILL_LEVEL:
678 g_value_set_boolean (value, gtk_range_get_restrict_to_fill_level (range));
680 case PROP_FILL_LEVEL:
681 g_value_set_double (value, gtk_range_get_fill_level (range));
684 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
690 gtk_range_init (GtkRange *range)
692 GtkRangePrivate *priv;
694 range->priv = G_TYPE_INSTANCE_GET_PRIVATE (range,
699 gtk_widget_set_has_window (GTK_WIDGET (range), FALSE);
701 priv->orientation = GTK_ORIENTATION_HORIZONTAL;
702 priv->adjustment = NULL;
703 priv->update_policy = GTK_UPDATE_CONTINUOUS;
704 priv->inverted = FALSE;
705 priv->flippable = FALSE;
706 priv->min_slider_size = 1;
707 priv->has_stepper_a = FALSE;
708 priv->has_stepper_b = FALSE;
709 priv->has_stepper_c = FALSE;
710 priv->has_stepper_d = FALSE;
711 priv->need_recalc = TRUE;
712 priv->round_digits = -1;
713 priv->mouse_location = MOUSE_OUTSIDE;
716 priv->grab_location = MOUSE_OUTSIDE;
717 priv->grab_button = 0;
718 priv->lower_sensitivity = GTK_SENSITIVITY_AUTO;
719 priv->upper_sensitivity = GTK_SENSITIVITY_AUTO;
720 priv->lower_sensitive = TRUE;
721 priv->upper_sensitive = TRUE;
722 priv->show_fill_level = FALSE;
723 priv->restrict_to_fill_level = TRUE;
724 priv->fill_level = G_MAXDOUBLE;
729 * gtk_range_get_adjustment:
730 * @range: a #GtkRange
732 * Get the #GtkAdjustment which is the "model" object for #GtkRange.
733 * See gtk_range_set_adjustment() for details.
734 * The return value does not have a reference added, so should not
737 * Return value: (transfer none): a #GtkAdjustment
740 gtk_range_get_adjustment (GtkRange *range)
742 GtkRangePrivate *priv;
744 g_return_val_if_fail (GTK_IS_RANGE (range), NULL);
748 if (!priv->adjustment)
749 gtk_range_set_adjustment (range, NULL);
751 return priv->adjustment;
755 * gtk_range_set_update_policy:
756 * @range: a #GtkRange
757 * @policy: update policy
759 * Sets the update policy for the range. #GTK_UPDATE_CONTINUOUS means that
760 * anytime the range slider is moved, the range value will change and the
761 * value_changed signal will be emitted. #GTK_UPDATE_DELAYED means that
762 * the value will be updated after a brief timeout where no slider motion
763 * occurs, so updates are spaced by a short time rather than
764 * continuous. #GTK_UPDATE_DISCONTINUOUS means that the value will only
765 * be updated when the user releases the button and ends the slider
769 gtk_range_set_update_policy (GtkRange *range,
770 GtkUpdateType policy)
772 GtkRangePrivate *priv;
774 g_return_if_fail (GTK_IS_RANGE (range));
778 if (priv->update_policy != policy)
780 priv->update_policy = policy;
781 g_object_notify (G_OBJECT (range), "update-policy");
786 * gtk_range_get_update_policy:
787 * @range: a #GtkRange
789 * Gets the update policy of @range. See gtk_range_set_update_policy().
791 * Return value: the current update policy
794 gtk_range_get_update_policy (GtkRange *range)
796 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_UPDATE_CONTINUOUS);
798 return range->priv->update_policy;
802 * gtk_range_set_adjustment:
803 * @range: a #GtkRange
804 * @adjustment: a #GtkAdjustment
806 * Sets the adjustment to be used as the "model" object for this range
807 * widget. The adjustment indicates the current range value, the
808 * minimum and maximum range values, the step/page increments used
809 * for keybindings and scrolling, and the page size. The page size
810 * is normally 0 for #GtkScale and nonzero for #GtkScrollbar, and
811 * indicates the size of the visible area of the widget being scrolled.
812 * The page size affects the size of the scrollbar slider.
815 gtk_range_set_adjustment (GtkRange *range,
816 GtkAdjustment *adjustment)
818 GtkRangePrivate *priv;
820 g_return_if_fail (GTK_IS_RANGE (range));
825 adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
827 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
829 if (priv->adjustment != adjustment)
831 if (priv->adjustment)
833 g_signal_handlers_disconnect_by_func (priv->adjustment,
834 gtk_range_adjustment_changed,
836 g_signal_handlers_disconnect_by_func (priv->adjustment,
837 gtk_range_adjustment_value_changed,
839 g_object_unref (priv->adjustment);
842 priv->adjustment = adjustment;
843 g_object_ref_sink (adjustment);
845 g_signal_connect (adjustment, "changed",
846 G_CALLBACK (gtk_range_adjustment_changed),
848 g_signal_connect (adjustment, "value-changed",
849 G_CALLBACK (gtk_range_adjustment_value_changed),
852 gtk_range_adjustment_changed (adjustment, range);
853 g_object_notify (G_OBJECT (range), "adjustment");
858 * gtk_range_set_inverted:
859 * @range: a #GtkRange
860 * @setting: %TRUE to invert the range
862 * Ranges normally move from lower to higher values as the
863 * slider moves from top to bottom or left to right. Inverted
864 * ranges have higher values at the top or on the right rather than
865 * on the bottom or left.
868 gtk_range_set_inverted (GtkRange *range,
871 GtkRangePrivate *priv;
873 g_return_if_fail (GTK_IS_RANGE (range));
877 setting = setting != FALSE;
879 if (setting != priv->inverted)
881 priv->inverted = setting;
882 g_object_notify (G_OBJECT (range), "inverted");
883 gtk_widget_queue_resize (GTK_WIDGET (range));
888 * gtk_range_get_inverted:
889 * @range: a #GtkRange
891 * Gets the value set by gtk_range_set_inverted().
893 * Return value: %TRUE if the range is inverted
896 gtk_range_get_inverted (GtkRange *range)
898 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
900 return range->priv->inverted;
904 * gtk_range_set_flippable:
905 * @range: a #GtkRange
906 * @flippable: %TRUE to make the range flippable
908 * If a range is flippable, it will switch its direction if it is
909 * horizontal and its direction is %GTK_TEXT_DIR_RTL.
911 * See gtk_widget_get_direction().
916 gtk_range_set_flippable (GtkRange *range,
919 GtkRangePrivate *priv;
921 g_return_if_fail (GTK_IS_RANGE (range));
925 flippable = flippable ? TRUE : FALSE;
927 if (flippable != priv->flippable)
929 priv->flippable = flippable;
931 gtk_widget_queue_draw (GTK_WIDGET (range));
936 * gtk_range_get_flippable:
937 * @range: a #GtkRange
939 * Gets the value set by gtk_range_set_flippable().
941 * Return value: %TRUE if the range is flippable
946 gtk_range_get_flippable (GtkRange *range)
948 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
950 return range->priv->flippable;
954 * gtk_range_set_slider_size_fixed:
955 * @range: a #GtkRange
956 * @size_fixed: %TRUE to make the slider size constant
958 * Sets whether the range's slider has a fixed size, or a size that
959 * depends on it's adjustment's page size.
961 * This function is useful mainly for #GtkRange subclasses.
966 gtk_range_set_slider_size_fixed (GtkRange *range,
969 GtkRangePrivate *priv;
971 g_return_if_fail (GTK_IS_RANGE (range));
975 if (size_fixed != priv->slider_size_fixed)
977 priv->slider_size_fixed = size_fixed ? TRUE : FALSE;
979 if (priv->adjustment && gtk_widget_get_mapped (GTK_WIDGET (range)))
981 priv->need_recalc = TRUE;
982 gtk_range_calc_layout (range, gtk_adjustment_get_value (priv->adjustment));
983 gtk_widget_queue_draw (GTK_WIDGET (range));
989 * gtk_range_get_slider_size_fixed:
990 * @range: a #GtkRange
992 * This function is useful mainly for #GtkRange subclasses.
994 * See gtk_range_set_slider_size_fixed().
996 * Return value: whether the range's slider has a fixed size.
1001 gtk_range_get_slider_size_fixed (GtkRange *range)
1003 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1005 return range->priv->slider_size_fixed;
1009 * gtk_range_set_min_slider_size:
1010 * @range: a #GtkRange
1011 * @min_size: The slider's minimum size
1013 * Sets the minimum size of the range's slider.
1015 * This function is useful mainly for #GtkRange subclasses.
1020 gtk_range_set_min_slider_size (GtkRange *range,
1023 GtkRangePrivate *priv;
1025 g_return_if_fail (GTK_IS_RANGE (range));
1026 g_return_if_fail (min_size > 0);
1030 if (min_size != priv->min_slider_size)
1032 priv->min_slider_size = min_size;
1034 priv->need_recalc = TRUE;
1035 gtk_range_calc_layout (range, priv->adjustment->value);
1036 gtk_widget_queue_draw (GTK_WIDGET (range));
1041 * gtk_range_get_min_slider_size:
1042 * @range: a #GtkRange
1044 * This function is useful mainly for #GtkRange subclasses.
1046 * See gtk_range_set_min_slider_size().
1048 * Return value: The minimum size of the range's slider.
1053 gtk_range_get_min_slider_size (GtkRange *range)
1055 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1057 return range->priv->min_slider_size;
1061 * gtk_range_get_range_rect:
1062 * @range: a #GtkRange
1063 * @range_rect: return location for the range rectangle
1065 * This function returns the area that contains the range's trough
1066 * and its steppers, in widget->window coordinates.
1068 * This function is useful mainly for #GtkRange subclasses.
1073 gtk_range_get_range_rect (GtkRange *range,
1074 GdkRectangle *range_rect)
1076 GtkRangePrivate *priv;
1078 g_return_if_fail (GTK_IS_RANGE (range));
1079 g_return_if_fail (range_rect != NULL);
1083 gtk_range_calc_layout (range, priv->adjustment->value);
1085 *range_rect = priv->range_rect;
1089 * gtk_range_get_slider_range:
1090 * @range: a #GtkRange
1091 * @slider_start: (allow-none): return location for the slider's start, or %NULL
1092 * @slider_end: (allow-none): return location for the slider's end, or %NULL
1094 * This function returns sliders range along the long dimension,
1095 * in widget->window coordinates.
1097 * This function is useful mainly for #GtkRange subclasses.
1102 gtk_range_get_slider_range (GtkRange *range,
1106 GtkRangePrivate *priv;
1108 g_return_if_fail (GTK_IS_RANGE (range));
1112 gtk_range_calc_layout (range, priv->adjustment->value);
1115 *slider_start = priv->slider_start;
1118 *slider_end = priv->slider_end;
1122 * gtk_range_set_lower_stepper_sensitivity:
1123 * @range: a #GtkRange
1124 * @sensitivity: the lower stepper's sensitivity policy.
1126 * Sets the sensitivity policy for the stepper that points to the
1127 * 'lower' end of the GtkRange's adjustment.
1132 gtk_range_set_lower_stepper_sensitivity (GtkRange *range,
1133 GtkSensitivityType sensitivity)
1135 GtkRangePrivate *priv;
1137 g_return_if_fail (GTK_IS_RANGE (range));
1141 if (priv->lower_sensitivity != sensitivity)
1143 priv->lower_sensitivity = sensitivity;
1145 priv->need_recalc = TRUE;
1146 gtk_range_calc_layout (range, priv->adjustment->value);
1147 gtk_widget_queue_draw (GTK_WIDGET (range));
1149 g_object_notify (G_OBJECT (range), "lower-stepper-sensitivity");
1154 * gtk_range_get_lower_stepper_sensitivity:
1155 * @range: a #GtkRange
1157 * Gets the sensitivity policy for the stepper that points to the
1158 * 'lower' end of the GtkRange's adjustment.
1160 * Return value: The lower stepper's sensitivity policy.
1165 gtk_range_get_lower_stepper_sensitivity (GtkRange *range)
1167 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
1169 return range->priv->lower_sensitivity;
1173 * gtk_range_set_upper_stepper_sensitivity:
1174 * @range: a #GtkRange
1175 * @sensitivity: the upper stepper's sensitivity policy.
1177 * Sets the sensitivity policy for the stepper that points to the
1178 * 'upper' end of the GtkRange's adjustment.
1183 gtk_range_set_upper_stepper_sensitivity (GtkRange *range,
1184 GtkSensitivityType sensitivity)
1186 GtkRangePrivate *priv;
1188 g_return_if_fail (GTK_IS_RANGE (range));
1192 if (priv->upper_sensitivity != sensitivity)
1194 priv->upper_sensitivity = sensitivity;
1196 priv->need_recalc = TRUE;
1197 gtk_range_calc_layout (range, priv->adjustment->value);
1198 gtk_widget_queue_draw (GTK_WIDGET (range));
1200 g_object_notify (G_OBJECT (range), "upper-stepper-sensitivity");
1205 * gtk_range_get_upper_stepper_sensitivity:
1206 * @range: a #GtkRange
1208 * Gets the sensitivity policy for the stepper that points to the
1209 * 'upper' end of the GtkRange's adjustment.
1211 * Return value: The upper stepper's sensitivity policy.
1216 gtk_range_get_upper_stepper_sensitivity (GtkRange *range)
1218 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
1220 return range->priv->upper_sensitivity;
1224 * gtk_range_set_increments:
1225 * @range: a #GtkRange
1229 * Sets the step and page sizes for the range.
1230 * The step size is used when the user clicks the #GtkScrollbar
1231 * arrows or moves #GtkScale via arrow keys. The page size
1232 * is used for example when moving via Page Up or Page Down keys.
1235 gtk_range_set_increments (GtkRange *range,
1239 GtkRangePrivate *priv;
1241 g_return_if_fail (GTK_IS_RANGE (range));
1245 priv->adjustment->step_increment = step;
1246 priv->adjustment->page_increment = page;
1248 gtk_adjustment_changed (priv->adjustment);
1252 * gtk_range_set_range:
1253 * @range: a #GtkRange
1254 * @min: minimum range value
1255 * @max: maximum range value
1257 * Sets the allowable values in the #GtkRange, and clamps the range
1258 * value to be between @min and @max. (If the range has a non-zero
1259 * page size, it is clamped between @min and @max - page-size.)
1262 gtk_range_set_range (GtkRange *range,
1266 GtkRangePrivate *priv;
1269 g_return_if_fail (GTK_IS_RANGE (range));
1270 g_return_if_fail (min < max);
1274 priv->adjustment->lower = min;
1275 priv->adjustment->upper = max;
1277 value = priv->adjustment->value;
1279 if (priv->restrict_to_fill_level)
1280 value = MIN (value, MAX (priv->adjustment->lower,
1283 gtk_adjustment_set_value (priv->adjustment, value);
1284 gtk_adjustment_changed (priv->adjustment);
1288 * gtk_range_set_value:
1289 * @range: a #GtkRange
1290 * @value: new value of the range
1292 * Sets the current value of the range; if the value is outside the
1293 * minimum or maximum range values, it will be clamped to fit inside
1294 * them. The range emits the #GtkRange::value-changed signal if the
1298 gtk_range_set_value (GtkRange *range,
1301 GtkRangePrivate *priv;
1303 g_return_if_fail (GTK_IS_RANGE (range));
1307 if (priv->restrict_to_fill_level)
1308 value = MIN (value, MAX (priv->adjustment->lower,
1311 gtk_adjustment_set_value (priv->adjustment, value);
1315 * gtk_range_get_value:
1316 * @range: a #GtkRange
1318 * Gets the current value of the range.
1320 * Return value: current value of the range.
1323 gtk_range_get_value (GtkRange *range)
1325 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1327 return range->priv->adjustment->value;
1331 * gtk_range_set_show_fill_level:
1332 * @range: A #GtkRange
1333 * @show_fill_level: Whether a fill level indicator graphics is shown.
1335 * Sets whether a graphical fill level is show on the trough. See
1336 * gtk_range_set_fill_level() for a general description of the fill
1342 gtk_range_set_show_fill_level (GtkRange *range,
1343 gboolean show_fill_level)
1345 GtkRangePrivate *priv;
1347 g_return_if_fail (GTK_IS_RANGE (range));
1351 show_fill_level = show_fill_level ? TRUE : FALSE;
1353 if (show_fill_level != priv->show_fill_level)
1355 priv->show_fill_level = show_fill_level;
1356 g_object_notify (G_OBJECT (range), "show-fill-level");
1357 gtk_widget_queue_draw (GTK_WIDGET (range));
1362 * gtk_range_get_show_fill_level:
1363 * @range: A #GtkRange
1365 * Gets whether the range displays the fill level graphically.
1367 * Return value: %TRUE if @range shows the fill level.
1372 gtk_range_get_show_fill_level (GtkRange *range)
1374 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1376 return range->priv->show_fill_level;
1380 * gtk_range_set_restrict_to_fill_level:
1381 * @range: A #GtkRange
1382 * @restrict_to_fill_level: Whether the fill level restricts slider movement.
1384 * Sets whether the slider is restricted to the fill level. See
1385 * gtk_range_set_fill_level() for a general description of the fill
1391 gtk_range_set_restrict_to_fill_level (GtkRange *range,
1392 gboolean restrict_to_fill_level)
1394 GtkRangePrivate *priv;
1396 g_return_if_fail (GTK_IS_RANGE (range));
1400 restrict_to_fill_level = restrict_to_fill_level ? TRUE : FALSE;
1402 if (restrict_to_fill_level != priv->restrict_to_fill_level)
1404 priv->restrict_to_fill_level = restrict_to_fill_level;
1405 g_object_notify (G_OBJECT (range), "restrict-to-fill-level");
1407 gtk_range_set_value (range, gtk_range_get_value (range));
1412 * gtk_range_get_restrict_to_fill_level:
1413 * @range: A #GtkRange
1415 * Gets whether the range is restricted to the fill level.
1417 * Return value: %TRUE if @range is restricted to the fill level.
1422 gtk_range_get_restrict_to_fill_level (GtkRange *range)
1424 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1426 return range->priv->restrict_to_fill_level;
1430 * gtk_range_set_fill_level:
1431 * @range: a #GtkRange
1432 * @fill_level: the new position of the fill level indicator
1434 * Set the new position of the fill level indicator.
1436 * The "fill level" is probably best described by its most prominent
1437 * use case, which is an indicator for the amount of pre-buffering in
1438 * a streaming media player. In that use case, the value of the range
1439 * would indicate the current play position, and the fill level would
1440 * be the position up to which the file/stream has been downloaded.
1442 * This amount of prebuffering can be displayed on the range's trough
1443 * and is themeable separately from the trough. To enable fill level
1444 * display, use gtk_range_set_show_fill_level(). The range defaults
1445 * to not showing the fill level.
1447 * Additionally, it's possible to restrict the range's slider position
1448 * to values which are smaller than the fill level. This is controller
1449 * by gtk_range_set_restrict_to_fill_level() and is by default
1455 gtk_range_set_fill_level (GtkRange *range,
1458 GtkRangePrivate *priv;
1460 g_return_if_fail (GTK_IS_RANGE (range));
1464 if (fill_level != priv->fill_level)
1466 priv->fill_level = fill_level;
1467 g_object_notify (G_OBJECT (range), "fill-level");
1469 if (priv->show_fill_level)
1470 gtk_widget_queue_draw (GTK_WIDGET (range));
1472 if (priv->restrict_to_fill_level)
1473 gtk_range_set_value (range, gtk_range_get_value (range));
1478 * gtk_range_get_fill_level:
1479 * @range: A #GtkRange
1481 * Gets the current position of the fill level indicator.
1483 * Return value: The current fill level
1488 gtk_range_get_fill_level (GtkRange *range)
1490 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1492 return range->priv->fill_level;
1496 should_invert (GtkRange *range)
1498 GtkRangePrivate *priv = range->priv;
1500 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1502 (priv->inverted && !priv->flippable) ||
1503 (priv->inverted && priv->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
1504 (!priv->inverted && priv->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
1506 return priv->inverted;
1510 gtk_range_destroy (GtkWidget *widget)
1512 GtkRange *range = GTK_RANGE (widget);
1513 GtkRangePrivate *priv = range->priv;
1515 gtk_range_remove_step_timer (range);
1516 gtk_range_remove_update_timer (range);
1518 if (priv->repaint_id)
1519 g_source_remove (priv->repaint_id);
1520 priv->repaint_id = 0;
1522 if (priv->adjustment)
1524 g_signal_handlers_disconnect_by_func (priv->adjustment,
1525 gtk_range_adjustment_changed,
1527 g_signal_handlers_disconnect_by_func (priv->adjustment,
1528 gtk_range_adjustment_value_changed,
1530 g_object_unref (priv->adjustment);
1531 priv->adjustment = NULL;
1536 g_free (priv->marks);
1538 g_free (priv->mark_pos);
1539 priv->mark_pos = NULL;
1543 GTK_WIDGET_CLASS (gtk_range_parent_class)->destroy (widget);
1547 gtk_range_size_request (GtkWidget *widget,
1548 GtkRequisition *requisition)
1551 gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing;
1552 GdkRectangle range_rect;
1555 range = GTK_RANGE (widget);
1557 gtk_range_get_props (range,
1558 &slider_width, &stepper_size,
1559 &focus_width, &trough_border,
1560 &stepper_spacing, NULL,
1563 gtk_range_calc_request (range,
1564 slider_width, stepper_size,
1565 focus_width, trough_border, stepper_spacing,
1566 &range_rect, &border, NULL, NULL, NULL, NULL);
1568 requisition->width = range_rect.width + border.left + border.right;
1569 requisition->height = range_rect.height + border.top + border.bottom;
1573 gtk_range_size_allocate (GtkWidget *widget,
1574 GtkAllocation *allocation)
1576 GtkRange *range = GTK_RANGE (widget);
1577 GtkRangePrivate *priv = range->priv;
1579 gtk_widget_set_allocation (widget, allocation);
1581 priv->recalc_marks = TRUE;
1583 priv->need_recalc = TRUE;
1584 gtk_range_calc_layout (range, priv->adjustment->value);
1586 if (gtk_widget_get_realized (widget))
1587 gdk_window_move_resize (priv->event_window,
1588 allocation->x, allocation->y,
1589 allocation->width, allocation->height);
1593 gtk_range_realize (GtkWidget *widget)
1595 GtkAllocation allocation;
1596 GtkRange *range = GTK_RANGE (widget);
1597 GtkRangePrivate *priv = range->priv;
1599 GdkWindowAttr attributes;
1600 gint attributes_mask;
1602 gtk_range_calc_layout (range, priv->adjustment->value);
1604 gtk_widget_set_realized (widget, TRUE);
1606 window = gtk_widget_get_parent_window (widget);
1607 gtk_widget_set_window (widget, window);
1608 g_object_ref (window);
1610 gtk_widget_get_allocation (widget, &allocation);
1612 attributes.window_type = GDK_WINDOW_CHILD;
1613 attributes.x = allocation.x;
1614 attributes.y = allocation.y;
1615 attributes.width = allocation.width;
1616 attributes.height = allocation.height;
1617 attributes.wclass = GDK_INPUT_ONLY;
1618 attributes.event_mask = gtk_widget_get_events (widget);
1619 attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
1620 GDK_BUTTON_RELEASE_MASK |
1621 GDK_ENTER_NOTIFY_MASK |
1622 GDK_LEAVE_NOTIFY_MASK |
1623 GDK_POINTER_MOTION_MASK |
1624 GDK_POINTER_MOTION_HINT_MASK);
1626 attributes_mask = GDK_WA_X | GDK_WA_Y;
1628 priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
1629 &attributes, attributes_mask);
1630 gdk_window_set_user_data (priv->event_window, range);
1632 gtk_widget_style_attach (widget);
1636 gtk_range_unrealize (GtkWidget *widget)
1638 GtkRange *range = GTK_RANGE (widget);
1639 GtkRangePrivate *priv = range->priv;
1641 gtk_range_remove_step_timer (range);
1642 gtk_range_remove_update_timer (range);
1644 gdk_window_set_user_data (priv->event_window, NULL);
1645 gdk_window_destroy (priv->event_window);
1646 priv->event_window = NULL;
1648 GTK_WIDGET_CLASS (gtk_range_parent_class)->unrealize (widget);
1652 gtk_range_map (GtkWidget *widget)
1654 GtkRange *range = GTK_RANGE (widget);
1655 GtkRangePrivate *priv = range->priv;
1657 gdk_window_show (priv->event_window);
1659 GTK_WIDGET_CLASS (gtk_range_parent_class)->map (widget);
1663 gtk_range_unmap (GtkWidget *widget)
1665 GtkRange *range = GTK_RANGE (widget);
1666 GtkRangePrivate *priv = range->priv;
1668 stop_scrolling (range);
1670 gdk_window_hide (priv->event_window);
1672 GTK_WIDGET_CLASS (gtk_range_parent_class)->unmap (widget);
1675 static const gchar *
1676 gtk_range_get_slider_detail (GtkRange *range)
1678 GtkRangePrivate *priv = range->priv;
1679 const gchar *slider_detail;
1681 if (priv->slider_detail_quark)
1682 return g_quark_to_string (priv->slider_detail_quark);
1684 slider_detail = GTK_RANGE_GET_CLASS (range)->slider_detail;
1686 if (slider_detail && slider_detail[0] == 'X')
1688 gchar *detail = g_strdup (slider_detail);
1690 detail[0] = priv->orientation == GTK_ORIENTATION_HORIZONTAL ? 'h' : 'v';
1692 priv->slider_detail_quark = g_quark_from_string (detail);
1696 return g_quark_to_string (priv->slider_detail_quark);
1699 return slider_detail;
1702 static const gchar *
1703 gtk_range_get_stepper_detail (GtkRange *range,
1706 GtkRangePrivate *priv = range->priv;
1707 const gchar *stepper_detail;
1709 const gchar *position = NULL;
1711 if (priv->stepper_detail_quark[stepper])
1712 return g_quark_to_string (priv->stepper_detail_quark[stepper]);
1714 stepper_detail = GTK_RANGE_GET_CLASS (range)->stepper_detail;
1719 position = "_start";
1722 if (priv->has_stepper_a)
1723 position = "_start_inner";
1725 position = "_start";
1728 if (priv->has_stepper_d)
1729 position = "_end_inner";
1737 g_assert_not_reached ();
1740 detail = g_strconcat (stepper_detail, position, NULL);
1742 if (detail[0] == 'X')
1743 detail[0] = priv->orientation == GTK_ORIENTATION_HORIZONTAL ? 'h' : 'v';
1745 priv->stepper_detail_quark[stepper] = g_quark_from_string (detail);
1749 return g_quark_to_string (priv->stepper_detail_quark[stepper]);
1753 draw_stepper (GtkRange *range,
1756 GtkArrowType arrow_type,
1758 gboolean prelighted)
1760 GtkRangePrivate *priv = range->priv;
1761 GtkAllocation allocation;
1762 GtkStateType state_type;
1763 GtkShadowType shadow_type;
1765 GtkWidget *widget = GTK_WIDGET (range);
1767 gfloat arrow_scaling;
1777 rect = &priv->stepper_a;
1780 rect = &priv->stepper_b;
1783 rect = &priv->stepper_c;
1786 rect = &priv->stepper_d;
1789 g_assert_not_reached ();
1792 gboolean arrow_sensitive = TRUE;
1794 gtk_widget_get_allocation (widget, &allocation);
1796 if ((!priv->inverted && (arrow_type == GTK_ARROW_DOWN ||
1797 arrow_type == GTK_ARROW_RIGHT)) ||
1798 (priv->inverted && (arrow_type == GTK_ARROW_UP ||
1799 arrow_type == GTK_ARROW_LEFT)))
1801 arrow_sensitive = priv->upper_sensitive;
1805 arrow_sensitive = priv->lower_sensitive;
1808 if (!gtk_widget_is_sensitive (GTK_WIDGET (range)) || !arrow_sensitive)
1809 state_type = GTK_STATE_INSENSITIVE;
1811 state_type = GTK_STATE_ACTIVE;
1812 else if (prelighted)
1813 state_type = GTK_STATE_PRELIGHT;
1815 state_type = GTK_STATE_NORMAL;
1817 if (clicked && arrow_sensitive)
1818 shadow_type = GTK_SHADOW_IN;
1820 shadow_type = GTK_SHADOW_OUT;
1822 style = gtk_widget_get_style (widget);
1823 window = gtk_widget_get_window (widget);
1825 gtk_paint_box (style, cr,
1826 state_type, shadow_type,
1828 gtk_range_get_stepper_detail (range, stepper),
1834 gtk_widget_style_get (widget, "arrow-scaling", &arrow_scaling, NULL);
1836 arrow_width = rect->width * arrow_scaling;
1837 arrow_height = rect->height * arrow_scaling;
1838 arrow_x = rect->x + (rect->width - arrow_width) / 2;
1839 arrow_y = rect->y + (rect->height - arrow_height) / 2;
1841 if (clicked && arrow_sensitive)
1843 gint arrow_displacement_x;
1844 gint arrow_displacement_y;
1846 gtk_range_get_props (GTK_RANGE (widget),
1847 NULL, NULL, NULL, NULL, NULL, NULL,
1848 &arrow_displacement_x, &arrow_displacement_y);
1850 arrow_x += arrow_displacement_x;
1851 arrow_y += arrow_displacement_y;
1854 gtk_paint_arrow (style, cr,
1855 state_type, shadow_type,
1857 gtk_range_get_stepper_detail (range, stepper),
1860 arrow_x, arrow_y, arrow_width, arrow_height);
1864 gtk_range_draw (GtkWidget *widget,
1867 GtkRange *range = GTK_RANGE (widget);
1868 GtkRangePrivate *priv = range->priv;
1871 GtkShadowType shadow_type;
1874 gint focus_line_width = 0;
1875 gint focus_padding = 0;
1876 gboolean touchscreen;
1878 g_object_get (gtk_widget_get_settings (widget),
1879 "gtk-touchscreen-mode", &touchscreen,
1882 style = gtk_widget_get_style (widget);
1883 if (gtk_widget_get_can_focus (GTK_WIDGET (range)))
1884 gtk_widget_style_get (GTK_WIDGET (range),
1885 "focus-line-width", &focus_line_width,
1886 "focus-padding", &focus_padding,
1889 window = gtk_widget_get_window (widget);
1891 /* we're now exposing, so there's no need to force early repaints */
1892 if (priv->repaint_id)
1893 g_source_remove (priv->repaint_id);
1894 priv->repaint_id = 0;
1896 gtk_range_calc_marks (range);
1897 gtk_range_calc_layout (range, priv->adjustment->value);
1899 sensitive = gtk_widget_is_sensitive (widget);
1901 /* Just to be confusing, we draw the trough for the whole
1902 * range rectangle, not the trough rectangle (the trough
1903 * rectangle is just for hit detection)
1906 gdk_cairo_rectangle (cr, &priv->range_rect);
1910 gint x = (priv->range_rect.x +
1911 focus_line_width + focus_padding);
1912 gint y = (priv->range_rect.y +
1913 focus_line_width + focus_padding);
1914 gint width = (priv->range_rect.width -
1915 2 * (focus_line_width + focus_padding));
1916 gint height = (priv->range_rect.height -
1917 2 * (focus_line_width + focus_padding));
1918 gboolean trough_under_steppers;
1920 gint stepper_spacing;
1922 gtk_widget_style_get (GTK_WIDGET (range),
1923 "trough-under-steppers", &trough_under_steppers,
1924 "stepper-size", &stepper_size,
1925 "stepper-spacing", &stepper_spacing,
1928 if (stepper_spacing > 0)
1929 trough_under_steppers = FALSE;
1931 if (!trough_under_steppers)
1936 if (priv->has_stepper_a)
1937 offset += stepper_size;
1939 if (priv->has_stepper_b)
1940 offset += stepper_size;
1944 if (priv->has_stepper_c)
1945 shorter += stepper_size;
1947 if (priv->has_stepper_d)
1948 shorter += stepper_size;
1950 if (priv->has_stepper_a || priv->has_stepper_b)
1952 offset += stepper_spacing;
1953 shorter += stepper_spacing;
1956 if (priv->has_stepper_c || priv->has_stepper_d)
1958 shorter += stepper_spacing;
1961 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1974 gint trough_change_pos_x = width;
1975 gint trough_change_pos_y = height;
1977 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1978 trough_change_pos_x = (priv->slider.x +
1979 priv->slider.width / 2 -
1982 trough_change_pos_y = (priv->slider.y +
1983 priv->slider.height / 2 -
1986 gtk_paint_box (style, cr,
1987 sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
1990 should_invert (range) ? "trough-upper" : "trough-lower",
1992 trough_change_pos_x, trough_change_pos_y);
1994 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1995 trough_change_pos_y = 0;
1997 trough_change_pos_x = 0;
1999 gtk_paint_box (style, cr,
2000 sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
2003 should_invert (range) ? "trough-lower" : "trough-upper",
2004 x + trough_change_pos_x, y + trough_change_pos_y,
2005 width - trough_change_pos_x,
2006 height - trough_change_pos_y);
2009 if (priv->show_fill_level &&
2010 priv->adjustment->upper - priv->adjustment->page_size -
2011 priv->adjustment->lower != 0)
2013 gdouble fill_level = priv->fill_level;
2016 gint fill_width = width;
2017 gint fill_height = height;
2020 fill_level = CLAMP (fill_level, priv->adjustment->lower,
2021 priv->adjustment->upper -
2022 priv->adjustment->page_size);
2024 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2026 fill_x = priv->trough.x;
2027 fill_width = (priv->slider.width +
2028 (fill_level - priv->adjustment->lower) /
2029 (priv->adjustment->upper -
2030 priv->adjustment->lower -
2031 priv->adjustment->page_size) *
2032 (priv->trough.width -
2033 priv->slider.width));
2035 if (should_invert (range))
2036 fill_x += priv->trough.width - fill_width;
2040 fill_y = priv->trough.y;
2041 fill_height = (priv->slider.height +
2042 (fill_level - priv->adjustment->lower) /
2043 (priv->adjustment->upper -
2044 priv->adjustment->lower -
2045 priv->adjustment->page_size) *
2046 (priv->trough.height -
2047 priv->slider.height));
2049 if (should_invert (range))
2050 fill_y += priv->trough.height - fill_height;
2053 if (fill_level < priv->adjustment->upper - priv->adjustment->page_size)
2054 fill_detail = "trough-fill-level-full";
2056 fill_detail = "trough-fill-level";
2058 gtk_paint_box (style, cr,
2059 sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
2061 GTK_WIDGET (range), fill_detail,
2063 fill_width, fill_height);
2066 if (sensitive && gtk_widget_has_focus (widget))
2067 gtk_paint_focus (style, cr,
2068 gtk_widget_get_state (widget),
2072 priv->range_rect.width,
2073 priv->range_rect.height);
2078 shadow_type = GTK_SHADOW_OUT;
2081 state = GTK_STATE_INSENSITIVE;
2082 else if (!touchscreen && priv->mouse_location == MOUSE_SLIDER)
2083 state = GTK_STATE_PRELIGHT;
2085 state = GTK_STATE_NORMAL;
2087 if (priv->grab_location == MOUSE_SLIDER)
2089 state = GTK_STATE_ACTIVE;
2090 shadow_type = GTK_SHADOW_IN;
2094 gdk_cairo_rectangle (cr, &priv->slider);
2098 gtk_paint_slider (style,
2103 gtk_range_get_slider_detail (range),
2107 priv->slider.height,
2113 if (priv->has_stepper_a)
2114 draw_stepper (range, STEPPER_A, cr,
2115 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
2116 priv->grab_location == MOUSE_STEPPER_A,
2117 !touchscreen && priv->mouse_location == MOUSE_STEPPER_A);
2119 if (priv->has_stepper_b)
2120 draw_stepper (range, STEPPER_B, cr,
2121 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
2122 priv->grab_location == MOUSE_STEPPER_B,
2123 !touchscreen && priv->mouse_location == MOUSE_STEPPER_B);
2125 if (priv->has_stepper_c)
2126 draw_stepper (range, STEPPER_C, cr,
2127 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
2128 priv->grab_location == MOUSE_STEPPER_C,
2129 !touchscreen && priv->mouse_location == MOUSE_STEPPER_C);
2131 if (priv->has_stepper_d)
2132 draw_stepper (range, STEPPER_D, cr,
2133 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
2134 priv->grab_location == MOUSE_STEPPER_D,
2135 !touchscreen && priv->mouse_location == MOUSE_STEPPER_D);
2141 range_grab_add (GtkRange *range,
2143 MouseLocation location,
2146 GtkRangePrivate *priv = range->priv;
2148 if (device == priv->grab_device)
2151 if (priv->grab_device != NULL)
2153 g_warning ("GtkRange already had a grab device, releasing device grab");
2154 gtk_device_grab_remove (GTK_WIDGET (range), priv->grab_device);
2157 /* we don't actually gdk_grab, since a button is down */
2158 gtk_device_grab_add (GTK_WIDGET (range), device, TRUE);
2160 priv->grab_location = location;
2161 priv->grab_button = button;
2162 priv->grab_device = device;
2164 if (gtk_range_update_mouse_location (range))
2165 gtk_widget_queue_draw (GTK_WIDGET (range));
2169 range_grab_remove (GtkRange *range)
2171 GtkRangePrivate *priv = range->priv;
2172 MouseLocation location;
2174 if (priv->grab_device)
2176 gtk_device_grab_remove (GTK_WIDGET (range),
2178 priv->grab_device = NULL;
2181 location = priv->grab_location;
2182 priv->grab_location = MOUSE_OUTSIDE;
2183 priv->grab_button = 0;
2185 if (gtk_range_update_mouse_location (range) ||
2186 location != MOUSE_OUTSIDE)
2187 gtk_widget_queue_draw (GTK_WIDGET (range));
2190 static GtkScrollType
2191 range_get_scroll_for_grab (GtkRange *range)
2193 GtkRangePrivate *priv = range->priv;
2196 invert = should_invert (range);
2197 switch (priv->grab_location)
2199 /* Backward stepper */
2200 case MOUSE_STEPPER_A:
2201 case MOUSE_STEPPER_C:
2202 switch (priv->grab_button)
2205 return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
2208 return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD;
2211 return invert ? GTK_SCROLL_END : GTK_SCROLL_START;
2216 /* Forward stepper */
2217 case MOUSE_STEPPER_B:
2218 case MOUSE_STEPPER_D:
2219 switch (priv->grab_button)
2222 return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
2225 return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD;
2228 return invert ? GTK_SCROLL_START : GTK_SCROLL_END;
2236 if (priv->trough_click_forward)
2237 return GTK_SCROLL_PAGE_FORWARD;
2239 return GTK_SCROLL_PAGE_BACKWARD;
2249 return GTK_SCROLL_NONE;
2253 coord_to_value (GtkRange *range,
2256 GtkRangePrivate *priv = range->priv;
2263 gint trough_under_steppers;
2265 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2267 trough_length = priv->trough.height;
2268 trough_start = priv->trough.y;
2269 slider_length = priv->slider.height;
2273 trough_length = priv->trough.width;
2274 trough_start = priv->trough.x;
2275 slider_length = priv->slider.width;
2278 gtk_range_get_props (range, NULL, NULL, NULL, &trough_border, NULL,
2279 &trough_under_steppers, NULL, NULL);
2281 if (! trough_under_steppers)
2283 trough_start += trough_border;
2284 trough_length -= 2 * trough_border;
2287 if (trough_length == slider_length)
2290 frac = (MAX (0, coord - trough_start) /
2291 (gdouble) (trough_length - slider_length));
2293 if (should_invert (range))
2296 value = priv->adjustment->lower + frac * (priv->adjustment->upper -
2297 priv->adjustment->lower -
2298 priv->adjustment->page_size);
2304 gtk_range_key_press (GtkWidget *widget,
2308 GtkRange *range = GTK_RANGE (widget);
2309 GtkRangePrivate *priv = range->priv;
2311 device = gdk_event_get_device ((GdkEvent *) event);
2312 device = gdk_device_get_associated_device (device);
2314 if (device == priv->grab_device &&
2315 event->keyval == GDK_KEY_Escape &&
2316 priv->grab_location != MOUSE_OUTSIDE)
2318 stop_scrolling (range);
2320 update_slider_position (range,
2321 priv->slide_initial_coordinate,
2322 priv->slide_initial_coordinate);
2327 return GTK_WIDGET_CLASS (gtk_range_parent_class)->key_press_event (widget, event);
2331 gtk_range_button_press (GtkWidget *widget,
2332 GdkEventButton *event)
2334 GtkRange *range = GTK_RANGE (widget);
2335 GtkRangePrivate *priv = range->priv;
2338 if (!gtk_widget_has_focus (widget))
2339 gtk_widget_grab_focus (widget);
2341 /* ignore presses when we're already doing something else. */
2342 if (priv->grab_location != MOUSE_OUTSIDE)
2345 device = gdk_event_get_device ((GdkEvent *) event);
2346 priv->mouse_x = event->x;
2347 priv->mouse_y = event->y;
2349 if (gtk_range_update_mouse_location (range))
2350 gtk_widget_queue_draw (widget);
2352 if (priv->mouse_location == MOUSE_TROUGH &&
2355 /* button 1 steps by page increment, as with button 2 on a stepper
2357 GtkScrollType scroll;
2358 gdouble click_value;
2360 click_value = coord_to_value (range,
2361 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2362 event->y : event->x);
2364 priv->trough_click_forward = click_value > priv->adjustment->value;
2365 range_grab_add (range, device, MOUSE_TROUGH, event->button);
2367 scroll = range_get_scroll_for_grab (range);
2369 gtk_range_add_step_timer (range, scroll);
2373 else if ((priv->mouse_location == MOUSE_STEPPER_A ||
2374 priv->mouse_location == MOUSE_STEPPER_B ||
2375 priv->mouse_location == MOUSE_STEPPER_C ||
2376 priv->mouse_location == MOUSE_STEPPER_D) &&
2377 (event->button == 1 || event->button == 2 || event->button == 3))
2379 GtkAllocation allocation;
2380 GdkRectangle *stepper_area;
2381 GtkScrollType scroll;
2383 range_grab_add (range, device, priv->mouse_location, event->button);
2385 gtk_widget_get_allocation (widget, &allocation);
2386 stepper_area = get_area (range, priv->mouse_location);
2388 gtk_widget_queue_draw_area (widget,
2389 allocation.x + stepper_area->x,
2390 allocation.y + stepper_area->y,
2391 stepper_area->width,
2392 stepper_area->height);
2394 scroll = range_get_scroll_for_grab (range);
2395 if (scroll != GTK_SCROLL_NONE)
2396 gtk_range_add_step_timer (range, scroll);
2400 else if ((priv->mouse_location == MOUSE_TROUGH &&
2401 event->button == 2) ||
2402 priv->mouse_location == MOUSE_SLIDER)
2404 gboolean need_value_update = FALSE;
2406 /* Any button can be used to drag the slider, but you can start
2407 * dragging the slider with a trough click using button 2;
2408 * On button 2 press, we warp the slider to mouse position,
2409 * then begin the slider drag.
2411 if (event->button == 2)
2413 gdouble slider_low_value, slider_high_value, new_value;
2416 coord_to_value (range,
2417 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2418 event->y : event->x);
2420 coord_to_value (range,
2421 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2422 event->y - priv->slider.height :
2423 event->x - priv->slider.width);
2425 /* compute new value for warped slider */
2426 new_value = slider_low_value + (slider_high_value - slider_low_value) / 2;
2428 /* recalc slider, so we can set slide_initial_slider_position
2431 priv->need_recalc = TRUE;
2432 gtk_range_calc_layout (range, new_value);
2434 /* defer adjustment updates to update_slider_position() in order
2435 * to keep pixel quantisation
2437 need_value_update = TRUE;
2440 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2442 priv->slide_initial_slider_position = priv->slider.y;
2443 priv->slide_initial_coordinate = event->y;
2447 priv->slide_initial_slider_position = priv->slider.x;
2448 priv->slide_initial_coordinate = event->x;
2451 range_grab_add (range, device, MOUSE_SLIDER, event->button);
2453 gtk_widget_queue_draw (widget);
2455 if (need_value_update)
2456 update_slider_position (range, event->x, event->y);
2464 /* During a slide, move the slider as required given new mouse position */
2466 update_slider_position (GtkRange *range,
2470 GtkRangePrivate *priv = range->priv;
2480 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2481 delta = mouse_y - priv->slide_initial_coordinate;
2483 delta = mouse_x - priv->slide_initial_coordinate;
2485 c = priv->slide_initial_slider_position + delta;
2487 new_value = coord_to_value (range, c);
2488 next_value = coord_to_value (range, c + 1);
2489 mark_delta = fabs (next_value - new_value);
2491 for (i = 0; i < priv->n_marks; i++)
2493 mark_value = priv->marks[i];
2495 if (fabs (priv->adjustment->value - mark_value) < 3 * mark_delta)
2497 if (fabs (new_value - mark_value) < (priv->slider_end - priv->slider_start) * 0.5 * mark_delta)
2499 new_value = mark_value;
2505 g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value,
2510 stop_scrolling (GtkRange *range)
2512 range_grab_remove (range);
2513 gtk_range_remove_step_timer (range);
2514 /* Flush any pending discontinuous/delayed updates */
2515 gtk_range_update_value (range);
2519 gtk_range_grab_broken (GtkWidget *widget,
2520 GdkEventGrabBroken *event)
2522 GtkRange *range = GTK_RANGE (widget);
2523 GtkRangePrivate *priv = range->priv;
2526 device = gdk_event_get_device ((GdkEvent *) event);
2528 if (device == priv->grab_device &&
2529 priv->grab_location != MOUSE_OUTSIDE)
2531 if (priv->grab_location == MOUSE_SLIDER)
2532 update_slider_position (range, priv->mouse_x, priv->mouse_y);
2534 stop_scrolling (range);
2543 gtk_range_button_release (GtkWidget *widget,
2544 GdkEventButton *event)
2546 GtkRange *range = GTK_RANGE (widget);
2547 GtkRangePrivate *priv = range->priv;
2550 if (event->window == priv->event_window)
2552 priv->mouse_x = event->x;
2553 priv->mouse_y = event->y;
2557 gdk_window_get_device_position (priv->event_window,
2564 device = gdk_event_get_device ((GdkEvent *) event);
2566 if (priv->grab_device == device &&
2567 priv->grab_button == event->button)
2569 if (priv->grab_location == MOUSE_SLIDER)
2570 update_slider_position (range, priv->mouse_x, priv->mouse_y);
2572 stop_scrolling (range);
2581 * _gtk_range_get_wheel_delta:
2582 * @range: a #GtkRange
2583 * @direction: A #GdkScrollDirection
2585 * Returns a good step value for the mouse wheel.
2587 * Return value: A good step value for the mouse wheel.
2592 _gtk_range_get_wheel_delta (GtkRange *range,
2593 GdkScrollDirection direction)
2595 GtkRangePrivate *priv = range->priv;
2596 GtkAdjustment *adj = priv->adjustment;
2599 if (GTK_IS_SCROLLBAR (range))
2600 delta = pow (adj->page_size, 2.0 / 3.0);
2602 delta = adj->step_increment * 2;
2604 if (direction == GDK_SCROLL_UP ||
2605 direction == GDK_SCROLL_LEFT)
2615 gtk_range_scroll_event (GtkWidget *widget,
2616 GdkEventScroll *event)
2618 GtkRange *range = GTK_RANGE (widget);
2619 GtkRangePrivate *priv = range->priv;
2621 if (gtk_widget_get_realized (widget))
2623 GtkAdjustment *adj = priv->adjustment;
2627 delta = _gtk_range_get_wheel_delta (range, event->direction);
2629 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2630 GTK_SCROLL_JUMP, adj->value + delta,
2633 /* Policy DELAYED makes sense with scroll events,
2634 * but DISCONTINUOUS doesn't, so we update immediately
2637 if (priv->update_policy == GTK_UPDATE_DISCONTINUOUS)
2638 gtk_range_update_value (range);
2645 gtk_range_motion_notify (GtkWidget *widget,
2646 GdkEventMotion *event)
2648 GtkRange *range = GTK_RANGE (widget);
2649 GtkRangePrivate *priv = range->priv;
2651 gdk_event_request_motions (event);
2653 priv->mouse_x = event->x;
2654 priv->mouse_y = event->y;
2656 if (gtk_range_update_mouse_location (range))
2657 gtk_widget_queue_draw (widget);
2659 if (priv->grab_location == MOUSE_SLIDER)
2660 update_slider_position (range, event->x, event->y);
2662 /* We handled the event if the mouse was in the range_rect */
2663 return priv->mouse_location != MOUSE_OUTSIDE;
2667 gtk_range_enter_notify (GtkWidget *widget,
2668 GdkEventCrossing *event)
2670 GtkRange *range = GTK_RANGE (widget);
2671 GtkRangePrivate *priv = range->priv;
2673 priv->mouse_x = event->x;
2674 priv->mouse_y = event->y;
2676 if (gtk_range_update_mouse_location (range))
2677 gtk_widget_queue_draw (widget);
2683 gtk_range_leave_notify (GtkWidget *widget,
2684 GdkEventCrossing *event)
2686 GtkRange *range = GTK_RANGE (widget);
2687 GtkRangePrivate *priv = range->priv;
2692 if (gtk_range_update_mouse_location (range))
2693 gtk_widget_queue_draw (widget);
2699 gtk_range_grab_notify (GtkWidget *widget,
2700 gboolean was_grabbed)
2702 GtkRangePrivate *priv = GTK_RANGE (widget)->priv;
2704 if (priv->grab_device &&
2705 gtk_widget_device_is_shadowed (widget, priv->grab_device))
2706 stop_scrolling (GTK_RANGE (widget));
2710 gtk_range_state_changed (GtkWidget *widget,
2711 GtkStateType previous_state)
2713 if (!gtk_widget_is_sensitive (widget))
2714 stop_scrolling (GTK_RANGE (widget));
2717 #define check_rectangle(rectangle1, rectangle2) \
2719 if (rectangle1.x != rectangle2.x) return TRUE; \
2720 if (rectangle1.y != rectangle2.y) return TRUE; \
2721 if (rectangle1.width != rectangle2.width) return TRUE; \
2722 if (rectangle1.height != rectangle2.height) return TRUE; \
2726 layout_changed (GtkRangePrivate *priv1,
2727 GtkRangePrivate *priv2)
2729 check_rectangle (priv1->slider, priv2->slider);
2730 check_rectangle (priv1->trough, priv2->trough);
2731 check_rectangle (priv1->stepper_a, priv2->stepper_a);
2732 check_rectangle (priv1->stepper_d, priv2->stepper_d);
2733 check_rectangle (priv1->stepper_b, priv2->stepper_b);
2734 check_rectangle (priv1->stepper_c, priv2->stepper_c);
2736 if (priv1->upper_sensitive != priv2->upper_sensitive) return TRUE;
2737 if (priv1->lower_sensitive != priv2->lower_sensitive) return TRUE;
2743 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
2746 GtkRange *range = GTK_RANGE (data);
2747 GtkRangePrivate *priv = range->priv;
2748 GtkRangePrivate priv_aux = *priv;
2750 priv->recalc_marks = TRUE;
2751 priv->need_recalc = TRUE;
2752 gtk_range_calc_layout (range, priv->adjustment->value);
2754 /* now check whether the layout changed */
2755 if (layout_changed (priv, &priv_aux))
2756 gtk_widget_queue_draw (GTK_WIDGET (range));
2758 /* Note that we don't round off to priv->round_digits here.
2759 * that's because it's really broken to change a value
2760 * in response to a change signal on that value; round_digits
2761 * is therefore defined to be a filter on what the GtkRange
2762 * can input into the adjustment, not a filter that the GtkRange
2763 * will enforce on the adjustment.
2768 force_repaint (gpointer data)
2770 GtkRange *range = GTK_RANGE (data);
2771 GtkRangePrivate *priv = range->priv;
2772 GtkWidget *widget = GTK_WIDGET (range);
2774 priv->repaint_id = 0;
2775 if (gtk_widget_is_drawable (widget))
2776 gdk_window_process_updates (gtk_widget_get_window (widget), FALSE);
2782 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
2785 GtkRange *range = GTK_RANGE (data);
2786 GtkRangePrivate *priv = range->priv;
2787 GtkRangePrivate priv_aux = *priv;
2789 priv->need_recalc = TRUE;
2790 gtk_range_calc_layout (range, priv->adjustment->value);
2792 /* now check whether the layout changed */
2793 if (layout_changed (priv, &priv_aux) ||
2794 (GTK_IS_SCALE (range) && gtk_scale_get_draw_value (GTK_SCALE (range))))
2796 gtk_widget_queue_draw (GTK_WIDGET (range));
2797 /* setup a timer to ensure the range isn't lagging too much behind the scroll position */
2798 if (!priv->repaint_id)
2799 priv->repaint_id = gdk_threads_add_timeout_full (GDK_PRIORITY_EVENTS, 181, force_repaint, range, NULL);
2802 /* Note that we don't round off to priv->round_digits here.
2803 * that's because it's really broken to change a value
2804 * in response to a change signal on that value; round_digits
2805 * is therefore defined to be a filter on what the GtkRange
2806 * can input into the adjustment, not a filter that the GtkRange
2807 * will enforce on the adjustment.
2810 g_signal_emit (range, signals[VALUE_CHANGED], 0);
2814 gtk_range_style_set (GtkWidget *widget,
2815 GtkStyle *previous_style)
2817 GtkRange *range = GTK_RANGE (widget);
2818 GtkRangePrivate *priv = range->priv;
2820 priv->need_recalc = TRUE;
2822 GTK_WIDGET_CLASS (gtk_range_parent_class)->style_set (widget, previous_style);
2826 apply_marks (GtkRange *range,
2830 GtkRangePrivate *priv = range->priv;
2834 for (i = 0; i < priv->n_marks; i++)
2836 mark = priv->marks[i];
2837 if ((oldval < mark && mark < *newval) ||
2838 (oldval > mark && mark > *newval))
2847 step_back (GtkRange *range)
2849 GtkRangePrivate *priv = range->priv;
2853 newval = priv->adjustment->value - priv->adjustment->step_increment;
2854 apply_marks (range, priv->adjustment->value, &newval);
2855 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2856 GTK_SCROLL_STEP_BACKWARD, newval, &handled);
2860 step_forward (GtkRange *range)
2862 GtkRangePrivate *priv = range->priv;
2866 newval = priv->adjustment->value + priv->adjustment->step_increment;
2867 apply_marks (range, priv->adjustment->value, &newval);
2868 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2869 GTK_SCROLL_STEP_FORWARD, newval, &handled);
2874 page_back (GtkRange *range)
2876 GtkRangePrivate *priv = range->priv;
2880 newval = priv->adjustment->value - priv->adjustment->page_increment;
2881 apply_marks (range, priv->adjustment->value, &newval);
2882 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2883 GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
2887 page_forward (GtkRange *range)
2889 GtkRangePrivate *priv = range->priv;
2893 newval = priv->adjustment->value + priv->adjustment->page_increment;
2894 apply_marks (range, priv->adjustment->value, &newval);
2895 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2896 GTK_SCROLL_PAGE_FORWARD, newval, &handled);
2900 scroll_begin (GtkRange *range)
2902 GtkRangePrivate *priv = range->priv;
2905 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2906 GTK_SCROLL_START, priv->adjustment->lower,
2911 scroll_end (GtkRange *range)
2913 GtkRangePrivate *priv = range->priv;
2917 newval = priv->adjustment->upper - priv->adjustment->page_size;
2918 g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_END, newval,
2923 gtk_range_scroll (GtkRange *range,
2924 GtkScrollType scroll)
2926 GtkRangePrivate *priv = range->priv;
2927 gdouble old_value = priv->adjustment->value;
2931 case GTK_SCROLL_STEP_LEFT:
2932 if (should_invert (range))
2933 step_forward (range);
2938 case GTK_SCROLL_STEP_UP:
2939 if (should_invert (range))
2940 step_forward (range);
2945 case GTK_SCROLL_STEP_RIGHT:
2946 if (should_invert (range))
2949 step_forward (range);
2952 case GTK_SCROLL_STEP_DOWN:
2953 if (should_invert (range))
2956 step_forward (range);
2959 case GTK_SCROLL_STEP_BACKWARD:
2963 case GTK_SCROLL_STEP_FORWARD:
2964 step_forward (range);
2967 case GTK_SCROLL_PAGE_LEFT:
2968 if (should_invert (range))
2969 page_forward (range);
2974 case GTK_SCROLL_PAGE_UP:
2975 if (should_invert (range))
2976 page_forward (range);
2981 case GTK_SCROLL_PAGE_RIGHT:
2982 if (should_invert (range))
2985 page_forward (range);
2988 case GTK_SCROLL_PAGE_DOWN:
2989 if (should_invert (range))
2992 page_forward (range);
2995 case GTK_SCROLL_PAGE_BACKWARD:
2999 case GTK_SCROLL_PAGE_FORWARD:
3000 page_forward (range);
3003 case GTK_SCROLL_START:
3004 scroll_begin (range);
3007 case GTK_SCROLL_END:
3011 case GTK_SCROLL_JUMP:
3012 /* Used by CList, range doesn't use it. */
3015 case GTK_SCROLL_NONE:
3019 return priv->adjustment->value != old_value;
3023 gtk_range_move_slider (GtkRange *range,
3024 GtkScrollType scroll)
3026 GtkRangePrivate *priv = range->priv;
3027 gboolean cursor_only;
3029 g_object_get (gtk_widget_get_settings (GTK_WIDGET (range)),
3030 "gtk-keynav-cursor-only", &cursor_only,
3035 GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (range));
3037 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
3039 if (scroll == GTK_SCROLL_STEP_UP ||
3040 scroll == GTK_SCROLL_STEP_DOWN)
3043 gtk_widget_child_focus (toplevel,
3044 scroll == GTK_SCROLL_STEP_UP ?
3045 GTK_DIR_UP : GTK_DIR_DOWN);
3051 if (scroll == GTK_SCROLL_STEP_LEFT ||
3052 scroll == GTK_SCROLL_STEP_RIGHT)
3055 gtk_widget_child_focus (toplevel,
3056 scroll == GTK_SCROLL_STEP_LEFT ?
3057 GTK_DIR_LEFT : GTK_DIR_RIGHT);
3063 if (! gtk_range_scroll (range, scroll))
3064 gtk_widget_error_bell (GTK_WIDGET (range));
3066 /* Policy DELAYED makes sense with key events,
3067 * but DISCONTINUOUS doesn't, so we update immediately
3070 if (priv->update_policy == GTK_UPDATE_DISCONTINUOUS)
3071 gtk_range_update_value (range);
3075 gtk_range_get_props (GtkRange *range,
3079 gint *trough_border,
3080 gint *stepper_spacing,
3081 gboolean *trough_under_steppers,
3082 gint *arrow_displacement_x,
3083 gint *arrow_displacement_y)
3085 GtkWidget *widget = GTK_WIDGET (range);
3086 gint tmp_slider_width, tmp_stepper_size, tmp_focus_width, tmp_trough_border;
3087 gint tmp_stepper_spacing, tmp_trough_under_steppers;
3088 gint tmp_arrow_displacement_x, tmp_arrow_displacement_y;
3090 gtk_widget_style_get (widget,
3091 "slider-width", &tmp_slider_width,
3092 "trough-border", &tmp_trough_border,
3093 "stepper-size", &tmp_stepper_size,
3094 "stepper-spacing", &tmp_stepper_spacing,
3095 "trough-under-steppers", &tmp_trough_under_steppers,
3096 "arrow-displacement-x", &tmp_arrow_displacement_x,
3097 "arrow-displacement-y", &tmp_arrow_displacement_y,
3100 if (tmp_stepper_spacing > 0)
3101 tmp_trough_under_steppers = FALSE;
3103 if (gtk_widget_get_can_focus (GTK_WIDGET (range)))
3105 gint focus_line_width;
3108 gtk_widget_style_get (GTK_WIDGET (range),
3109 "focus-line-width", &focus_line_width,
3110 "focus-padding", &focus_padding,
3113 tmp_focus_width = focus_line_width + focus_padding;
3117 tmp_focus_width = 0;
3121 *slider_width = tmp_slider_width;
3124 *focus_width = tmp_focus_width;
3127 *trough_border = tmp_trough_border;
3130 *stepper_size = tmp_stepper_size;
3132 if (stepper_spacing)
3133 *stepper_spacing = tmp_stepper_spacing;
3135 if (trough_under_steppers)
3136 *trough_under_steppers = tmp_trough_under_steppers;
3138 if (arrow_displacement_x)
3139 *arrow_displacement_x = tmp_arrow_displacement_x;
3141 if (arrow_displacement_y)
3142 *arrow_displacement_y = tmp_arrow_displacement_y;
3145 #define POINT_IN_RECT(xcoord, ycoord, rect) \
3146 ((xcoord) >= (rect).x && \
3147 (xcoord) < ((rect).x + (rect).width) && \
3148 (ycoord) >= (rect).y && \
3149 (ycoord) < ((rect).y + (rect).height))
3151 /* Update mouse location, return TRUE if it changes */
3153 gtk_range_update_mouse_location (GtkRange *range)
3155 GtkRangePrivate *priv = range->priv;
3156 GtkAllocation allocation;
3159 GtkWidget *widget = GTK_WIDGET (range);
3161 old = priv->mouse_location;
3166 gtk_widget_get_allocation (widget, &allocation);
3168 if (priv->grab_location != MOUSE_OUTSIDE)
3169 priv->mouse_location = priv->grab_location;
3170 else if (POINT_IN_RECT (x, y, priv->stepper_a))
3171 priv->mouse_location = MOUSE_STEPPER_A;
3172 else if (POINT_IN_RECT (x, y, priv->stepper_b))
3173 priv->mouse_location = MOUSE_STEPPER_B;
3174 else if (POINT_IN_RECT (x, y, priv->stepper_c))
3175 priv->mouse_location = MOUSE_STEPPER_C;
3176 else if (POINT_IN_RECT (x, y, priv->stepper_d))
3177 priv->mouse_location = MOUSE_STEPPER_D;
3178 else if (POINT_IN_RECT (x, y, priv->slider))
3179 priv->mouse_location = MOUSE_SLIDER;
3180 else if (POINT_IN_RECT (x, y, priv->trough))
3181 priv->mouse_location = MOUSE_TROUGH;
3182 else if (POINT_IN_RECT (x, y, allocation))
3183 priv->mouse_location = MOUSE_WIDGET;
3185 priv->mouse_location = MOUSE_OUTSIDE;
3187 return old != priv->mouse_location;
3190 /* Clamp rect, border inside widget->allocation, such that we prefer
3191 * to take space from border not rect in all directions, and prefer to
3192 * give space to border over rect in one direction.
3195 clamp_dimensions (GtkWidget *widget,
3198 gboolean border_expands_horizontally)
3200 GtkAllocation allocation;
3201 gint extra, shortage;
3203 g_return_if_fail (rect->x == 0);
3204 g_return_if_fail (rect->y == 0);
3205 g_return_if_fail (rect->width >= 0);
3206 g_return_if_fail (rect->height >= 0);
3208 gtk_widget_get_allocation (widget, &allocation);
3212 extra = allocation.width - border->left - border->right - rect->width;
3215 if (border_expands_horizontally)
3217 border->left += extra / 2;
3218 border->right += extra / 2 + extra % 2;
3222 rect->width += extra;
3226 /* See if we can fit rect, if not kill the border */
3227 shortage = rect->width - allocation.width;
3230 rect->width = allocation.width;
3231 /* lose the border */
3237 /* See if we can fit rect with borders */
3238 shortage = rect->width + border->left + border->right - allocation.width;
3241 /* Shrink borders */
3242 border->left -= shortage / 2;
3243 border->right -= shortage / 2 + shortage % 2;
3249 extra = allocation.height - border->top - border->bottom - rect->height;
3252 if (border_expands_horizontally)
3254 /* don't expand border vertically */
3255 rect->height += extra;
3259 border->top += extra / 2;
3260 border->bottom += extra / 2 + extra % 2;
3264 /* See if we can fit rect, if not kill the border */
3265 shortage = rect->height - allocation.height;
3268 rect->height = allocation.height;
3269 /* lose the border */
3275 /* See if we can fit rect with borders */
3276 shortage = rect->height + border->top + border->bottom - allocation.height;
3279 /* Shrink borders */
3280 border->top -= shortage / 2;
3281 border->bottom -= shortage / 2 + shortage % 2;
3287 gtk_range_calc_request (GtkRange *range,
3292 gint stepper_spacing,
3293 GdkRectangle *range_rect,
3296 gboolean *has_steppers_ab,
3297 gboolean *has_steppers_cd,
3298 gint *slider_length_p)
3300 GtkRangePrivate *priv = range->priv;
3311 if (GTK_RANGE_GET_CLASS (range)->get_range_border)
3312 GTK_RANGE_GET_CLASS (range)->get_range_border (range, border);
3317 if (priv->has_stepper_a)
3319 if (priv->has_stepper_b)
3321 if (priv->has_stepper_c)
3323 if (priv->has_stepper_d)
3326 n_steppers = n_steppers_ab + n_steppers_cd;
3328 slider_length = priv->min_slider_size;
3333 /* We never expand to fill available space in the small dimension
3334 * (i.e. vertical scrollbars are always a fixed width)
3336 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
3338 range_rect->width = (focus_width + trough_border) * 2 + slider_width;
3339 range_rect->height = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length;
3341 if (n_steppers_ab > 0)
3342 range_rect->height += stepper_spacing;
3344 if (n_steppers_cd > 0)
3345 range_rect->height += stepper_spacing;
3349 range_rect->width = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length;
3350 range_rect->height = (focus_width + trough_border) * 2 + slider_width;
3352 if (n_steppers_ab > 0)
3353 range_rect->width += stepper_spacing;
3355 if (n_steppers_cd > 0)
3356 range_rect->width += stepper_spacing;
3360 *n_steppers_p = n_steppers;
3362 if (has_steppers_ab)
3363 *has_steppers_ab = (n_steppers_ab > 0);
3365 if (has_steppers_cd)
3366 *has_steppers_cd = (n_steppers_cd > 0);
3368 if (slider_length_p)
3369 *slider_length_p = slider_length;
3373 gtk_range_calc_layout (GtkRange *range,
3374 gdouble adjustment_value)
3376 GtkRangePrivate *priv = range->priv;
3377 gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing;
3381 gboolean has_steppers_ab;
3382 gboolean has_steppers_cd;
3383 gboolean trough_under_steppers;
3384 GdkRectangle range_rect;
3387 if (!priv->need_recalc)
3390 /* If we have a too-small allocation, we prefer the steppers over
3391 * the trough/slider, probably the steppers are a more useful
3392 * feature in small spaces.
3394 * Also, we prefer to draw the range itself rather than the border
3395 * areas if there's a conflict, since the borders will be decoration
3396 * not controls. Though this depends on subclasses cooperating by
3397 * not drawing on priv->range_rect.
3400 widget = GTK_WIDGET (range);
3402 gtk_range_get_props (range,
3403 &slider_width, &stepper_size,
3404 &focus_width, &trough_border,
3405 &stepper_spacing, &trough_under_steppers,
3408 gtk_range_calc_request (range,
3409 slider_width, stepper_size,
3410 focus_width, trough_border, stepper_spacing,
3411 &range_rect, &border, &n_steppers,
3412 &has_steppers_ab, &has_steppers_cd, &slider_length);
3414 /* We never expand to fill available space in the small dimension
3415 * (i.e. vertical scrollbars are always a fixed width)
3417 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
3419 clamp_dimensions (widget, &range_rect, &border, TRUE);
3423 clamp_dimensions (widget, &range_rect, &border, FALSE);
3426 range_rect.x = border.left;
3427 range_rect.y = border.top;
3429 priv->range_rect = range_rect;
3431 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
3433 gint stepper_width, stepper_height;
3435 /* Steppers are the width of the range, and stepper_size in
3436 * height, or if we don't have enough height, divided equally
3437 * among available space.
3439 stepper_width = range_rect.width - focus_width * 2;
3441 if (trough_under_steppers)
3442 stepper_width -= trough_border * 2;
3444 if (stepper_width < 1)
3445 stepper_width = range_rect.width; /* screw the trough border */
3447 if (n_steppers == 0)
3448 stepper_height = 0; /* avoid divide by n_steppers */
3450 stepper_height = MIN (stepper_size, (range_rect.height / n_steppers));
3454 priv->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers;
3455 priv->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers;
3457 if (priv->has_stepper_a)
3459 priv->stepper_a.width = stepper_width;
3460 priv->stepper_a.height = stepper_height;
3464 priv->stepper_a.width = 0;
3465 priv->stepper_a.height = 0;
3470 priv->stepper_b.x = priv->stepper_a.x;
3471 priv->stepper_b.y = priv->stepper_a.y + priv->stepper_a.height;
3473 if (priv->has_stepper_b)
3475 priv->stepper_b.width = stepper_width;
3476 priv->stepper_b.height = stepper_height;
3480 priv->stepper_b.width = 0;
3481 priv->stepper_b.height = 0;
3486 if (priv->has_stepper_d)
3488 priv->stepper_d.width = stepper_width;
3489 priv->stepper_d.height = stepper_height;
3493 priv->stepper_d.width = 0;
3494 priv->stepper_d.height = 0;
3497 priv->stepper_d.x = priv->stepper_a.x;
3498 priv->stepper_d.y = range_rect.y + range_rect.height - priv->stepper_d.height - focus_width - trough_border * trough_under_steppers;
3502 if (priv->has_stepper_c)
3504 priv->stepper_c.width = stepper_width;
3505 priv->stepper_c.height = stepper_height;
3509 priv->stepper_c.width = 0;
3510 priv->stepper_c.height = 0;
3513 priv->stepper_c.x = priv->stepper_a.x;
3514 priv->stepper_c.y = priv->stepper_d.y - priv->stepper_c.height;
3516 /* Now the trough is the remaining space between steppers B and C,
3517 * if any, minus spacing
3519 priv->trough.x = range_rect.x;
3520 priv->trough.y = priv->stepper_b.y + priv->stepper_b.height + stepper_spacing * has_steppers_ab;
3521 priv->trough.width = range_rect.width;
3522 priv->trough.height = priv->stepper_c.y - priv->trough.y - stepper_spacing * has_steppers_cd;
3524 /* Slider fits into the trough, with stepper_spacing on either side,
3525 * and the size/position based on the adjustment or fixed, depending.
3527 priv->slider.x = priv->trough.x + focus_width + trough_border;
3528 priv->slider.width = priv->trough.width - (focus_width + trough_border) * 2;
3530 /* Compute slider position/length */
3532 gint y, bottom, top, height;
3534 top = priv->trough.y;
3535 bottom = priv->trough.y + priv->trough.height;
3537 if (! trough_under_steppers)
3539 top += trough_border;
3540 bottom -= trough_border;
3543 /* slider height is the fraction (page_size /
3544 * total_adjustment_range) times the trough height in pixels
3547 if (priv->adjustment->upper - priv->adjustment->lower != 0)
3548 height = ((bottom - top) * (priv->adjustment->page_size /
3549 (priv->adjustment->upper - priv->adjustment->lower)));
3551 height = priv->min_slider_size;
3553 if (height < priv->min_slider_size ||
3554 priv->slider_size_fixed)
3555 height = priv->min_slider_size;
3557 height = MIN (height, priv->trough.height);
3561 if (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size != 0)
3562 y += (bottom - top - height) * ((adjustment_value - priv->adjustment->lower) /
3563 (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size));
3565 y = CLAMP (y, top, bottom);
3567 if (should_invert (range))
3568 y = bottom - (y - top + height);
3571 priv->slider.height = height;
3573 /* These are publically exported */
3574 priv->slider_start = priv->slider.y;
3575 priv->slider_end = priv->slider.y + priv->slider.height;
3580 gint stepper_width, stepper_height;
3582 /* Steppers are the height of the range, and stepper_size in
3583 * width, or if we don't have enough width, divided equally
3584 * among available space.
3586 stepper_height = range_rect.height + focus_width * 2;
3588 if (trough_under_steppers)
3589 stepper_height -= trough_border * 2;
3591 if (stepper_height < 1)
3592 stepper_height = range_rect.height; /* screw the trough border */
3594 if (n_steppers == 0)
3595 stepper_width = 0; /* avoid divide by n_steppers */
3597 stepper_width = MIN (stepper_size, (range_rect.width / n_steppers));
3601 priv->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers;
3602 priv->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers;
3604 if (priv->has_stepper_a)
3606 priv->stepper_a.width = stepper_width;
3607 priv->stepper_a.height = stepper_height;
3611 priv->stepper_a.width = 0;
3612 priv->stepper_a.height = 0;
3617 priv->stepper_b.x = priv->stepper_a.x + priv->stepper_a.width;
3618 priv->stepper_b.y = priv->stepper_a.y;
3620 if (priv->has_stepper_b)
3622 priv->stepper_b.width = stepper_width;
3623 priv->stepper_b.height = stepper_height;
3627 priv->stepper_b.width = 0;
3628 priv->stepper_b.height = 0;
3633 if (priv->has_stepper_d)
3635 priv->stepper_d.width = stepper_width;
3636 priv->stepper_d.height = stepper_height;
3640 priv->stepper_d.width = 0;
3641 priv->stepper_d.height = 0;
3644 priv->stepper_d.x = range_rect.x + range_rect.width - priv->stepper_d.width - focus_width - trough_border * trough_under_steppers;
3645 priv->stepper_d.y = priv->stepper_a.y;
3650 if (priv->has_stepper_c)
3652 priv->stepper_c.width = stepper_width;
3653 priv->stepper_c.height = stepper_height;
3657 priv->stepper_c.width = 0;
3658 priv->stepper_c.height = 0;
3661 priv->stepper_c.x = priv->stepper_d.x - priv->stepper_c.width;
3662 priv->stepper_c.y = priv->stepper_a.y;
3664 /* Now the trough is the remaining space between steppers B and C,
3667 priv->trough.x = priv->stepper_b.x + priv->stepper_b.width + stepper_spacing * has_steppers_ab;
3668 priv->trough.y = range_rect.y;
3670 priv->trough.width = priv->stepper_c.x - priv->trough.x - stepper_spacing * has_steppers_cd;
3671 priv->trough.height = range_rect.height;
3673 /* Slider fits into the trough, with stepper_spacing on either side,
3674 * and the size/position based on the adjustment or fixed, depending.
3676 priv->slider.y = priv->trough.y + focus_width + trough_border;
3677 priv->slider.height = priv->trough.height - (focus_width + trough_border) * 2;
3679 /* Compute slider position/length */
3681 gint x, left, right, width;
3683 left = priv->trough.x;
3684 right = priv->trough.x + priv->trough.width;
3686 if (! trough_under_steppers)
3688 left += trough_border;
3689 right -= trough_border;
3692 /* slider width is the fraction (page_size /
3693 * total_adjustment_range) times the trough width in pixels
3696 if (priv->adjustment->upper - priv->adjustment->lower != 0)
3697 width = ((right - left) * (priv->adjustment->page_size /
3698 (priv->adjustment->upper - priv->adjustment->lower)));
3700 width = priv->min_slider_size;
3702 if (width < priv->min_slider_size ||
3703 priv->slider_size_fixed)
3704 width = priv->min_slider_size;
3706 width = MIN (width, priv->trough.width);
3710 if (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size != 0)
3711 x += (right - left - width) * ((adjustment_value - priv->adjustment->lower) /
3712 (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size));
3714 x = CLAMP (x, left, right);
3716 if (should_invert (range))
3717 x = right - (x - left + width);
3720 priv->slider.width = width;
3722 /* These are publically exported */
3723 priv->slider_start = priv->slider.x;
3724 priv->slider_end = priv->slider.x + priv->slider.width;
3728 gtk_range_update_mouse_location (range);
3730 switch (priv->upper_sensitivity)
3732 case GTK_SENSITIVITY_AUTO:
3733 priv->upper_sensitive =
3734 (priv->adjustment->value <
3735 (priv->adjustment->upper - priv->adjustment->page_size));
3738 case GTK_SENSITIVITY_ON:
3739 priv->upper_sensitive = TRUE;
3742 case GTK_SENSITIVITY_OFF:
3743 priv->upper_sensitive = FALSE;
3747 switch (priv->lower_sensitivity)
3749 case GTK_SENSITIVITY_AUTO:
3750 priv->lower_sensitive =
3751 (priv->adjustment->value > priv->adjustment->lower);
3754 case GTK_SENSITIVITY_ON:
3755 priv->lower_sensitive = TRUE;
3758 case GTK_SENSITIVITY_OFF:
3759 priv->lower_sensitive = FALSE;
3764 static GdkRectangle*
3765 get_area (GtkRange *range,
3766 MouseLocation location)
3768 GtkRangePrivate *priv = range->priv;
3772 case MOUSE_STEPPER_A:
3773 return &priv->stepper_a;
3774 case MOUSE_STEPPER_B:
3775 return &priv->stepper_b;
3776 case MOUSE_STEPPER_C:
3777 return &priv->stepper_c;
3778 case MOUSE_STEPPER_D:
3779 return &priv->stepper_d;
3781 return &priv->trough;
3783 return &priv->slider;
3789 g_warning (G_STRLOC": bug");
3794 gtk_range_calc_marks (GtkRange *range)
3796 GtkRangePrivate *priv = range->priv;
3799 if (!priv->recalc_marks)
3802 priv->recalc_marks = FALSE;
3804 for (i = 0; i < priv->n_marks; i++)
3806 priv->need_recalc = TRUE;
3807 gtk_range_calc_layout (range, priv->marks[i]);
3808 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
3809 priv->mark_pos[i] = priv->slider.x + priv->slider.width / 2;
3811 priv->mark_pos[i] = priv->slider.y + priv->slider.height / 2;
3814 priv->need_recalc = TRUE;
3818 gtk_range_real_change_value (GtkRange *range,
3819 GtkScrollType scroll,
3822 GtkRangePrivate *priv = range->priv;
3824 /* potentially adjust the bounds _before_ we clamp */
3825 g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value);
3827 if (priv->restrict_to_fill_level)
3828 value = MIN (value, MAX (priv->adjustment->lower,
3831 value = CLAMP (value, priv->adjustment->lower,
3832 (priv->adjustment->upper - priv->adjustment->page_size));
3834 if (priv->round_digits >= 0)
3839 i = priv->round_digits;
3844 value = floor ((value * power) + 0.5) / power;
3847 if (priv->adjustment->value != value)
3849 priv->need_recalc = TRUE;
3851 gtk_widget_queue_draw (GTK_WIDGET (range));
3853 switch (priv->update_policy)
3855 case GTK_UPDATE_CONTINUOUS:
3856 gtk_adjustment_set_value (priv->adjustment, value);
3859 /* Delayed means we update after a period of inactivity */
3860 case GTK_UPDATE_DELAYED:
3861 gtk_range_reset_update_timer (range);
3864 /* Discontinuous means we update on button release */
3865 case GTK_UPDATE_DISCONTINUOUS:
3866 /* don't emit value_changed signal */
3867 priv->adjustment->value = value;
3868 priv->update_pending = TRUE;
3876 gtk_range_update_value (GtkRange *range)
3878 GtkRangePrivate *priv = range->priv;
3880 gtk_range_remove_update_timer (range);
3882 if (priv->update_pending)
3884 gtk_adjustment_value_changed (priv->adjustment);
3886 priv->update_pending = FALSE;
3890 struct _GtkRangeStepTimer
3897 second_timeout (gpointer data)
3899 GtkRange *range = GTK_RANGE (data);
3900 GtkRangePrivate *priv = range->priv;
3902 gtk_range_scroll (range, priv->timer->step);
3908 initial_timeout (gpointer data)
3910 GtkRange *range = GTK_RANGE (data);
3911 GtkRangePrivate *priv = range->priv;
3912 GtkSettings *settings;
3915 settings = gtk_widget_get_settings (GTK_WIDGET (data));
3916 g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
3918 priv->timer->timeout_id = gdk_threads_add_timeout (timeout * SCROLL_DELAY_FACTOR,
3926 gtk_range_add_step_timer (GtkRange *range,
3929 GtkRangePrivate *priv = range->priv;
3930 GtkSettings *settings;
3933 g_return_if_fail (priv->timer == NULL);
3934 g_return_if_fail (step != GTK_SCROLL_NONE);
3936 settings = gtk_widget_get_settings (GTK_WIDGET (range));
3937 g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
3939 priv->timer = g_new (GtkRangeStepTimer, 1);
3941 priv->timer->timeout_id = gdk_threads_add_timeout (timeout,
3944 priv->timer->step = step;
3946 gtk_range_scroll (range, priv->timer->step);
3950 gtk_range_remove_step_timer (GtkRange *range)
3952 GtkRangePrivate *priv = range->priv;
3956 if (priv->timer->timeout_id != 0)
3957 g_source_remove (priv->timer->timeout_id);
3959 g_free (priv->timer);
3966 update_timeout (gpointer data)
3968 GtkRange *range = GTK_RANGE (data);
3969 GtkRangePrivate *priv = range->priv;
3971 gtk_range_update_value (range);
3972 priv->update_timeout_id = 0;
3979 gtk_range_reset_update_timer (GtkRange *range)
3981 GtkRangePrivate *priv = range->priv;
3983 gtk_range_remove_update_timer (range);
3985 priv->update_timeout_id = gdk_threads_add_timeout (UPDATE_DELAY,
3991 gtk_range_remove_update_timer (GtkRange *range)
3993 GtkRangePrivate *priv = range->priv;
3995 if (priv->update_timeout_id != 0)
3997 g_source_remove (priv->update_timeout_id);
3998 priv->update_timeout_id = 0;
4003 _gtk_range_set_stop_values (GtkRange *range,
4007 GtkRangePrivate *priv = range->priv;
4010 g_free (priv->marks);
4011 priv->marks = g_new (gdouble, n_values);
4013 g_free (priv->mark_pos);
4014 priv->mark_pos = g_new (gint, n_values);
4016 priv->n_marks = n_values;
4018 for (i = 0; i < n_values; i++)
4019 priv->marks[i] = values[i];
4021 priv->recalc_marks = TRUE;
4025 _gtk_range_get_stop_positions (GtkRange *range,
4028 GtkRangePrivate *priv = range->priv;
4030 gtk_range_calc_marks (range);
4033 *values = g_memdup (priv->mark_pos, priv->n_marks * sizeof (gint));
4035 return priv->n_marks;
4039 _gtk_range_set_round_digits (GtkRange *range,
4042 range->priv->round_digits = round_digits;
4046 _gtk_range_set_steppers (GtkRange *range,
4052 range->priv->has_stepper_a = has_a;
4053 range->priv->has_stepper_b = has_b;
4054 range->priv->has_stepper_c = has_c;
4055 range->priv->has_stepper_d = has_d;