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 (GtkObject *object);
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_expose (GtkWidget *widget,
193 GdkEventExpose *event);
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 GtkObjectClass *object_class;
283 GtkWidgetClass *widget_class;
285 gobject_class = G_OBJECT_CLASS (class);
286 object_class = (GtkObjectClass*) class;
287 widget_class = (GtkWidgetClass*) class;
289 gobject_class->set_property = gtk_range_set_property;
290 gobject_class->get_property = gtk_range_get_property;
292 object_class->destroy = gtk_range_destroy;
294 widget_class->size_request = gtk_range_size_request;
295 widget_class->size_allocate = gtk_range_size_allocate;
296 widget_class->realize = gtk_range_realize;
297 widget_class->unrealize = gtk_range_unrealize;
298 widget_class->map = gtk_range_map;
299 widget_class->unmap = gtk_range_unmap;
300 widget_class->expose_event = gtk_range_expose;
301 widget_class->button_press_event = gtk_range_button_press;
302 widget_class->button_release_event = gtk_range_button_release;
303 widget_class->motion_notify_event = gtk_range_motion_notify;
304 widget_class->scroll_event = gtk_range_scroll_event;
305 widget_class->enter_notify_event = gtk_range_enter_notify;
306 widget_class->leave_notify_event = gtk_range_leave_notify;
307 widget_class->grab_broken_event = gtk_range_grab_broken;
308 widget_class->grab_notify = gtk_range_grab_notify;
309 widget_class->state_changed = gtk_range_state_changed;
310 widget_class->style_set = gtk_range_style_set;
311 widget_class->key_press_event = gtk_range_key_press;
313 class->move_slider = gtk_range_move_slider;
314 class->change_value = gtk_range_real_change_value;
316 class->slider_detail = "slider";
317 class->stepper_detail = "stepper";
320 * GtkRange::value-changed:
321 * @range: the #GtkRange that received the signal
323 * Emitted when the range value changes.
325 signals[VALUE_CHANGED] =
326 g_signal_new (I_("value-changed"),
327 G_TYPE_FROM_CLASS (gobject_class),
329 G_STRUCT_OFFSET (GtkRangeClass, value_changed),
331 _gtk_marshal_VOID__VOID,
335 * GtkRange::adjust-bounds:
336 * @range: the #GtkRange that received the signal
337 * @value: the value before we clamp
339 * Emitted before clamping a value, to give the application a
340 * chance to adjust the bounds.
342 signals[ADJUST_BOUNDS] =
343 g_signal_new (I_("adjust-bounds"),
344 G_TYPE_FROM_CLASS (gobject_class),
346 G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds),
348 _gtk_marshal_VOID__DOUBLE,
353 * GtkRange::move-slider:
354 * @range: the #GtkRange that received the signal
355 * @step: how to move the slider
357 * Virtual function that moves the slider. Used for keybindings.
359 signals[MOVE_SLIDER] =
360 g_signal_new (I_("move-slider"),
361 G_TYPE_FROM_CLASS (gobject_class),
362 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
363 G_STRUCT_OFFSET (GtkRangeClass, move_slider),
365 _gtk_marshal_VOID__ENUM,
367 GTK_TYPE_SCROLL_TYPE);
370 * GtkRange::change-value:
371 * @range: the #GtkRange that received the signal
372 * @scroll: the type of scroll action that was performed
373 * @value: the new value resulting from the scroll action
374 * @returns: %TRUE to prevent other handlers from being invoked for the
375 * signal, %FALSE to propagate the signal further
377 * The ::change-value signal is emitted when a scroll action is
378 * performed on a range. It allows an application to determine the
379 * type of scroll event that occurred and the resultant new value.
380 * The application can handle the event itself and return %TRUE to
381 * prevent further processing. Or, by returning %FALSE, it can pass
382 * the event to other handlers until the default GTK+ handler is
385 * The value parameter is unrounded. An application that overrides
386 * the ::change-value signal is responsible for clamping the value to
387 * the desired number of decimal digits; the default GTK+ handler
388 * clamps the value based on @range->round_digits.
390 * It is not possible to use delayed update policies in an overridden
391 * ::change-value handler.
395 signals[CHANGE_VALUE] =
396 g_signal_new (I_("change-value"),
397 G_TYPE_FROM_CLASS (gobject_class),
399 G_STRUCT_OFFSET (GtkRangeClass, change_value),
400 _gtk_boolean_handled_accumulator, NULL,
401 _gtk_marshal_BOOLEAN__ENUM_DOUBLE,
403 GTK_TYPE_SCROLL_TYPE,
406 g_object_class_override_property (gobject_class,
410 g_object_class_install_property (gobject_class,
412 g_param_spec_enum ("update-policy",
414 P_("How the range should be updated on the screen"),
415 GTK_TYPE_UPDATE_TYPE,
416 GTK_UPDATE_CONTINUOUS,
417 GTK_PARAM_READWRITE));
419 g_object_class_install_property (gobject_class,
421 g_param_spec_object ("adjustment",
423 P_("The GtkAdjustment that contains the current value of this range object"),
425 GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
427 g_object_class_install_property (gobject_class,
429 g_param_spec_boolean ("inverted",
431 P_("Invert direction slider moves to increase range value"),
433 GTK_PARAM_READWRITE));
435 g_object_class_install_property (gobject_class,
436 PROP_LOWER_STEPPER_SENSITIVITY,
437 g_param_spec_enum ("lower-stepper-sensitivity",
438 P_("Lower stepper sensitivity"),
439 P_("The sensitivity policy for the stepper that points to the adjustment's lower side"),
440 GTK_TYPE_SENSITIVITY_TYPE,
441 GTK_SENSITIVITY_AUTO,
442 GTK_PARAM_READWRITE));
444 g_object_class_install_property (gobject_class,
445 PROP_UPPER_STEPPER_SENSITIVITY,
446 g_param_spec_enum ("upper-stepper-sensitivity",
447 P_("Upper stepper sensitivity"),
448 P_("The sensitivity policy for the stepper that points to the adjustment's upper side"),
449 GTK_TYPE_SENSITIVITY_TYPE,
450 GTK_SENSITIVITY_AUTO,
451 GTK_PARAM_READWRITE));
454 * GtkRange:show-fill-level:
456 * The show-fill-level property controls whether fill level indicator
457 * graphics are displayed on the trough. See
458 * gtk_range_set_show_fill_level().
462 g_object_class_install_property (gobject_class,
463 PROP_SHOW_FILL_LEVEL,
464 g_param_spec_boolean ("show-fill-level",
465 P_("Show Fill Level"),
466 P_("Whether to display a fill level indicator graphics on trough."),
468 GTK_PARAM_READWRITE));
471 * GtkRange:restrict-to-fill-level:
473 * The restrict-to-fill-level property controls whether slider
474 * movement is restricted to an upper boundary set by the
475 * fill level. See gtk_range_set_restrict_to_fill_level().
479 g_object_class_install_property (gobject_class,
480 PROP_RESTRICT_TO_FILL_LEVEL,
481 g_param_spec_boolean ("restrict-to-fill-level",
482 P_("Restrict to Fill Level"),
483 P_("Whether to restrict the upper boundary to the fill level."),
485 GTK_PARAM_READWRITE));
488 * GtkRange:fill-level:
490 * The fill level (e.g. prebuffering of a network stream).
491 * See gtk_range_set_fill_level().
495 g_object_class_install_property (gobject_class,
497 g_param_spec_double ("fill-level",
499 P_("The fill level."),
503 GTK_PARAM_READWRITE));
505 gtk_widget_class_install_style_property (widget_class,
506 g_param_spec_int ("slider-width",
508 P_("Width of scrollbar or scale thumb"),
512 GTK_PARAM_READABLE));
513 gtk_widget_class_install_style_property (widget_class,
514 g_param_spec_int ("trough-border",
516 P_("Spacing between thumb/steppers and outer trough bevel"),
520 GTK_PARAM_READABLE));
521 gtk_widget_class_install_style_property (widget_class,
522 g_param_spec_int ("stepper-size",
524 P_("Length of step buttons at ends"),
528 GTK_PARAM_READABLE));
530 * GtkRange:stepper-spacing:
532 * The spacing between the stepper buttons and thumb. Note that
533 * setting this value to anything > 0 will automatically set the
534 * trough-under-steppers style property to %TRUE as well. Also,
535 * stepper-spacing won't have any effect if there are no steppers.
537 gtk_widget_class_install_style_property (widget_class,
538 g_param_spec_int ("stepper-spacing",
539 P_("Stepper Spacing"),
540 P_("Spacing between step buttons and thumb"),
544 GTK_PARAM_READABLE));
545 gtk_widget_class_install_style_property (widget_class,
546 g_param_spec_int ("arrow-displacement-x",
547 P_("Arrow X Displacement"),
548 P_("How far in the x direction to move the arrow when the button is depressed"),
552 GTK_PARAM_READABLE));
553 gtk_widget_class_install_style_property (widget_class,
554 g_param_spec_int ("arrow-displacement-y",
555 P_("Arrow Y Displacement"),
556 P_("How far in the y direction to move the arrow when the button is depressed"),
560 GTK_PARAM_READABLE));
562 gtk_widget_class_install_style_property (widget_class,
563 g_param_spec_boolean ("activate-slider",
564 P_("Draw slider ACTIVE during drag"),
565 P_("With this option set to TRUE, sliders will be drawn ACTIVE and with shadow IN while they are dragged"),
567 GTK_PARAM_READABLE));
570 * GtkRange:trough-under-steppers:
572 * Whether to draw the trough across the full length of the range or
573 * to exclude the steppers and their spacing. Note that setting the
574 * #GtkRange:stepper-spacing style property to any value > 0 will
575 * automatically enable trough-under-steppers too.
579 gtk_widget_class_install_style_property (widget_class,
580 g_param_spec_boolean ("trough-under-steppers",
581 P_("Trough Under Steppers"),
582 P_("Whether to draw trough for full length of range or exclude the steppers and spacing"),
584 GTK_PARAM_READABLE));
587 * GtkRange:arrow-scaling:
589 * The arrow size proportion relative to the scroll button size.
593 gtk_widget_class_install_style_property (widget_class,
594 g_param_spec_float ("arrow-scaling",
596 P_("Arrow scaling with regard to scroll button size"),
598 GTK_PARAM_READABLE));
600 g_type_class_add_private (class, sizeof (GtkRangePrivate));
604 gtk_range_set_property (GObject *object,
609 GtkRange *range = GTK_RANGE (object);
610 GtkRangePrivate *priv = range->priv;
614 case PROP_ORIENTATION:
615 priv->orientation = g_value_get_enum (value);
617 priv->slider_detail_quark = 0;
618 priv->stepper_detail_quark[0] = 0;
619 priv->stepper_detail_quark[1] = 0;
620 priv->stepper_detail_quark[2] = 0;
621 priv->stepper_detail_quark[3] = 0;
623 gtk_widget_queue_resize (GTK_WIDGET (range));
625 case PROP_UPDATE_POLICY:
626 gtk_range_set_update_policy (range, g_value_get_enum (value));
628 case PROP_ADJUSTMENT:
629 gtk_range_set_adjustment (range, g_value_get_object (value));
632 gtk_range_set_inverted (range, g_value_get_boolean (value));
634 case PROP_LOWER_STEPPER_SENSITIVITY:
635 gtk_range_set_lower_stepper_sensitivity (range, g_value_get_enum (value));
637 case PROP_UPPER_STEPPER_SENSITIVITY:
638 gtk_range_set_upper_stepper_sensitivity (range, g_value_get_enum (value));
640 case PROP_SHOW_FILL_LEVEL:
641 gtk_range_set_show_fill_level (range, g_value_get_boolean (value));
643 case PROP_RESTRICT_TO_FILL_LEVEL:
644 gtk_range_set_restrict_to_fill_level (range, g_value_get_boolean (value));
646 case PROP_FILL_LEVEL:
647 gtk_range_set_fill_level (range, g_value_get_double (value));
650 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
656 gtk_range_get_property (GObject *object,
661 GtkRange *range = GTK_RANGE (object);
662 GtkRangePrivate *priv = range->priv;
666 case PROP_ORIENTATION:
667 g_value_set_enum (value, priv->orientation);
669 case PROP_UPDATE_POLICY:
670 g_value_set_enum (value, priv->update_policy);
672 case PROP_ADJUSTMENT:
673 g_value_set_object (value, priv->adjustment);
676 g_value_set_boolean (value, priv->inverted);
678 case PROP_LOWER_STEPPER_SENSITIVITY:
679 g_value_set_enum (value, gtk_range_get_lower_stepper_sensitivity (range));
681 case PROP_UPPER_STEPPER_SENSITIVITY:
682 g_value_set_enum (value, gtk_range_get_upper_stepper_sensitivity (range));
684 case PROP_SHOW_FILL_LEVEL:
685 g_value_set_boolean (value, gtk_range_get_show_fill_level (range));
687 case PROP_RESTRICT_TO_FILL_LEVEL:
688 g_value_set_boolean (value, gtk_range_get_restrict_to_fill_level (range));
690 case PROP_FILL_LEVEL:
691 g_value_set_double (value, gtk_range_get_fill_level (range));
694 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
700 gtk_range_init (GtkRange *range)
702 GtkRangePrivate *priv;
704 range->priv = G_TYPE_INSTANCE_GET_PRIVATE (range,
709 gtk_widget_set_has_window (GTK_WIDGET (range), FALSE);
711 priv->orientation = GTK_ORIENTATION_HORIZONTAL;
712 priv->adjustment = NULL;
713 priv->update_policy = GTK_UPDATE_CONTINUOUS;
714 priv->inverted = FALSE;
715 priv->flippable = FALSE;
716 priv->min_slider_size = 1;
717 priv->has_stepper_a = FALSE;
718 priv->has_stepper_b = FALSE;
719 priv->has_stepper_c = FALSE;
720 priv->has_stepper_d = FALSE;
721 priv->need_recalc = TRUE;
722 priv->round_digits = -1;
723 priv->mouse_location = MOUSE_OUTSIDE;
726 priv->grab_location = MOUSE_OUTSIDE;
727 priv->grab_button = 0;
728 priv->lower_sensitivity = GTK_SENSITIVITY_AUTO;
729 priv->upper_sensitivity = GTK_SENSITIVITY_AUTO;
730 priv->lower_sensitive = TRUE;
731 priv->upper_sensitive = TRUE;
732 priv->show_fill_level = FALSE;
733 priv->restrict_to_fill_level = TRUE;
734 priv->fill_level = G_MAXDOUBLE;
739 * gtk_range_get_adjustment:
740 * @range: a #GtkRange
742 * Get the #GtkAdjustment which is the "model" object for #GtkRange.
743 * See gtk_range_set_adjustment() for details.
744 * The return value does not have a reference added, so should not
747 * Return value: a #GtkAdjustment
750 gtk_range_get_adjustment (GtkRange *range)
752 GtkRangePrivate *priv;
754 g_return_val_if_fail (GTK_IS_RANGE (range), NULL);
758 if (!priv->adjustment)
759 gtk_range_set_adjustment (range, NULL);
761 return priv->adjustment;
765 * gtk_range_set_update_policy:
766 * @range: a #GtkRange
767 * @policy: update policy
769 * Sets the update policy for the range. #GTK_UPDATE_CONTINUOUS means that
770 * anytime the range slider is moved, the range value will change and the
771 * value_changed signal will be emitted. #GTK_UPDATE_DELAYED means that
772 * the value will be updated after a brief timeout where no slider motion
773 * occurs, so updates are spaced by a short time rather than
774 * continuous. #GTK_UPDATE_DISCONTINUOUS means that the value will only
775 * be updated when the user releases the button and ends the slider
779 gtk_range_set_update_policy (GtkRange *range,
780 GtkUpdateType policy)
782 GtkRangePrivate *priv;
784 g_return_if_fail (GTK_IS_RANGE (range));
788 if (priv->update_policy != policy)
790 priv->update_policy = policy;
791 g_object_notify (G_OBJECT (range), "update-policy");
796 * gtk_range_get_update_policy:
797 * @range: a #GtkRange
799 * Gets the update policy of @range. See gtk_range_set_update_policy().
801 * Return value: the current update policy
804 gtk_range_get_update_policy (GtkRange *range)
806 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_UPDATE_CONTINUOUS);
808 return range->priv->update_policy;
812 * gtk_range_set_adjustment:
813 * @range: a #GtkRange
814 * @adjustment: a #GtkAdjustment
816 * Sets the adjustment to be used as the "model" object for this range
817 * widget. The adjustment indicates the current range value, the
818 * minimum and maximum range values, the step/page increments used
819 * for keybindings and scrolling, and the page size. The page size
820 * is normally 0 for #GtkScale and nonzero for #GtkScrollbar, and
821 * indicates the size of the visible area of the widget being scrolled.
822 * The page size affects the size of the scrollbar slider.
825 gtk_range_set_adjustment (GtkRange *range,
826 GtkAdjustment *adjustment)
828 GtkRangePrivate *priv;
830 g_return_if_fail (GTK_IS_RANGE (range));
835 adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
837 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
839 if (priv->adjustment != adjustment)
841 if (priv->adjustment)
843 g_signal_handlers_disconnect_by_func (priv->adjustment,
844 gtk_range_adjustment_changed,
846 g_signal_handlers_disconnect_by_func (priv->adjustment,
847 gtk_range_adjustment_value_changed,
849 g_object_unref (priv->adjustment);
852 priv->adjustment = adjustment;
853 g_object_ref_sink (adjustment);
855 g_signal_connect (adjustment, "changed",
856 G_CALLBACK (gtk_range_adjustment_changed),
858 g_signal_connect (adjustment, "value-changed",
859 G_CALLBACK (gtk_range_adjustment_value_changed),
862 gtk_range_adjustment_changed (adjustment, range);
863 g_object_notify (G_OBJECT (range), "adjustment");
868 * gtk_range_set_inverted:
869 * @range: a #GtkRange
870 * @setting: %TRUE to invert the range
872 * Ranges normally move from lower to higher values as the
873 * slider moves from top to bottom or left to right. Inverted
874 * ranges have higher values at the top or on the right rather than
875 * on the bottom or left.
878 gtk_range_set_inverted (GtkRange *range,
881 GtkRangePrivate *priv;
883 g_return_if_fail (GTK_IS_RANGE (range));
887 setting = setting != FALSE;
889 if (setting != priv->inverted)
891 priv->inverted = setting;
892 g_object_notify (G_OBJECT (range), "inverted");
893 gtk_widget_queue_resize (GTK_WIDGET (range));
898 * gtk_range_get_inverted:
899 * @range: a #GtkRange
901 * Gets the value set by gtk_range_set_inverted().
903 * Return value: %TRUE if the range is inverted
906 gtk_range_get_inverted (GtkRange *range)
908 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
910 return range->priv->inverted;
914 * gtk_range_set_flippable:
915 * @range: a #GtkRange
916 * @flippable: %TRUE to make the range flippable
918 * If a range is flippable, it will switch its direction if it is
919 * horizontal and its direction is %GTK_TEXT_DIR_RTL.
921 * See gtk_widget_get_direction().
926 gtk_range_set_flippable (GtkRange *range,
929 GtkRangePrivate *priv;
931 g_return_if_fail (GTK_IS_RANGE (range));
935 flippable = flippable ? TRUE : FALSE;
937 if (flippable != priv->flippable)
939 priv->flippable = flippable;
941 gtk_widget_queue_draw (GTK_WIDGET (range));
946 * gtk_range_get_flippable:
947 * @range: a #GtkRange
949 * Gets the value set by gtk_range_set_flippable().
951 * Return value: %TRUE if the range is flippable
956 gtk_range_get_flippable (GtkRange *range)
958 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
960 return range->priv->flippable;
964 * gtk_range_set_slider_size_fixed:
965 * @range: a #GtkRange
966 * @size_fixed: %TRUE to make the slider size constant
968 * Sets whether the range's slider has a fixed size, or a size that
969 * depends on it's adjustment's page size.
971 * This function is useful mainly for #GtkRange subclasses.
976 gtk_range_set_slider_size_fixed (GtkRange *range,
979 GtkRangePrivate *priv;
981 g_return_if_fail (GTK_IS_RANGE (range));
985 if (size_fixed != priv->slider_size_fixed)
987 priv->slider_size_fixed = size_fixed ? TRUE : FALSE;
989 if (priv->adjustment && gtk_widget_get_mapped (GTK_WIDGET (range)))
991 priv->need_recalc = TRUE;
992 gtk_range_calc_layout (range, gtk_adjustment_get_value (priv->adjustment));
993 gtk_widget_queue_draw (GTK_WIDGET (range));
999 * gtk_range_get_slider_size_fixed:
1000 * @range: a #GtkRange
1002 * This function is useful mainly for #GtkRange subclasses.
1004 * See gtk_range_set_slider_size_fixed().
1006 * Return value: whether the range's slider has a fixed size.
1011 gtk_range_get_slider_size_fixed (GtkRange *range)
1013 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1015 return range->priv->slider_size_fixed;
1019 * gtk_range_set_min_slider_size:
1020 * @range: a #GtkRange
1021 * @min_size: The slider's minimum size
1023 * Sets the minimum size of the range's slider.
1025 * This function is useful mainly for #GtkRange subclasses.
1030 gtk_range_set_min_slider_size (GtkRange *range,
1033 GtkRangePrivate *priv;
1035 g_return_if_fail (GTK_IS_RANGE (range));
1036 g_return_if_fail (min_size > 0);
1040 if (min_size != priv->min_slider_size)
1042 priv->min_slider_size = min_size;
1044 priv->need_recalc = TRUE;
1045 gtk_range_calc_layout (range, priv->adjustment->value);
1046 gtk_widget_queue_draw (GTK_WIDGET (range));
1051 * gtk_range_get_min_slider_size:
1052 * @range: a #GtkRange
1054 * This function is useful mainly for #GtkRange subclasses.
1056 * See gtk_range_set_min_slider_size().
1058 * Return value: The minimum size of the range's slider.
1063 gtk_range_get_min_slider_size (GtkRange *range)
1065 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1067 return range->priv->min_slider_size;
1071 * gtk_range_get_range_rect:
1072 * @range: a #GtkRange
1073 * @range_rect: return location for the range rectangle
1075 * This function returns the area that contains the range's trough
1076 * and its steppers, in widget->window coordinates.
1078 * This function is useful mainly for #GtkRange subclasses.
1083 gtk_range_get_range_rect (GtkRange *range,
1084 GdkRectangle *range_rect)
1086 GtkRangePrivate *priv;
1088 g_return_if_fail (GTK_IS_RANGE (range));
1089 g_return_if_fail (range_rect != NULL);
1093 gtk_range_calc_layout (range, priv->adjustment->value);
1095 *range_rect = priv->range_rect;
1099 * gtk_range_get_slider_range:
1100 * @range: a #GtkRange
1101 * @slider_start: (allow-none): return location for the slider's start, or %NULL
1102 * @slider_end: (allow-none): return location for the slider's end, or %NULL
1104 * This function returns sliders range along the long dimension,
1105 * in widget->window coordinates.
1107 * This function is useful mainly for #GtkRange subclasses.
1112 gtk_range_get_slider_range (GtkRange *range,
1116 GtkRangePrivate *priv;
1118 g_return_if_fail (GTK_IS_RANGE (range));
1122 gtk_range_calc_layout (range, priv->adjustment->value);
1125 *slider_start = priv->slider_start;
1128 *slider_end = priv->slider_end;
1132 * gtk_range_set_lower_stepper_sensitivity:
1133 * @range: a #GtkRange
1134 * @sensitivity: the lower stepper's sensitivity policy.
1136 * Sets the sensitivity policy for the stepper that points to the
1137 * 'lower' end of the GtkRange's adjustment.
1142 gtk_range_set_lower_stepper_sensitivity (GtkRange *range,
1143 GtkSensitivityType sensitivity)
1145 GtkRangePrivate *priv;
1147 g_return_if_fail (GTK_IS_RANGE (range));
1151 if (priv->lower_sensitivity != sensitivity)
1153 priv->lower_sensitivity = sensitivity;
1155 priv->need_recalc = TRUE;
1156 gtk_range_calc_layout (range, priv->adjustment->value);
1157 gtk_widget_queue_draw (GTK_WIDGET (range));
1159 g_object_notify (G_OBJECT (range), "lower-stepper-sensitivity");
1164 * gtk_range_get_lower_stepper_sensitivity:
1165 * @range: a #GtkRange
1167 * Gets the sensitivity policy for the stepper that points to the
1168 * 'lower' end of the GtkRange's adjustment.
1170 * Return value: The lower stepper's sensitivity policy.
1175 gtk_range_get_lower_stepper_sensitivity (GtkRange *range)
1177 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
1179 return range->priv->lower_sensitivity;
1183 * gtk_range_set_upper_stepper_sensitivity:
1184 * @range: a #GtkRange
1185 * @sensitivity: the upper stepper's sensitivity policy.
1187 * Sets the sensitivity policy for the stepper that points to the
1188 * 'upper' end of the GtkRange's adjustment.
1193 gtk_range_set_upper_stepper_sensitivity (GtkRange *range,
1194 GtkSensitivityType sensitivity)
1196 GtkRangePrivate *priv;
1198 g_return_if_fail (GTK_IS_RANGE (range));
1202 if (priv->upper_sensitivity != sensitivity)
1204 priv->upper_sensitivity = sensitivity;
1206 priv->need_recalc = TRUE;
1207 gtk_range_calc_layout (range, priv->adjustment->value);
1208 gtk_widget_queue_draw (GTK_WIDGET (range));
1210 g_object_notify (G_OBJECT (range), "upper-stepper-sensitivity");
1215 * gtk_range_get_upper_stepper_sensitivity:
1216 * @range: a #GtkRange
1218 * Gets the sensitivity policy for the stepper that points to the
1219 * 'upper' end of the GtkRange's adjustment.
1221 * Return value: The upper stepper's sensitivity policy.
1226 gtk_range_get_upper_stepper_sensitivity (GtkRange *range)
1228 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
1230 return range->priv->upper_sensitivity;
1234 * gtk_range_set_increments:
1235 * @range: a #GtkRange
1239 * Sets the step and page sizes for the range.
1240 * The step size is used when the user clicks the #GtkScrollbar
1241 * arrows or moves #GtkScale via arrow keys. The page size
1242 * is used for example when moving via Page Up or Page Down keys.
1245 gtk_range_set_increments (GtkRange *range,
1249 GtkRangePrivate *priv;
1251 g_return_if_fail (GTK_IS_RANGE (range));
1255 priv->adjustment->step_increment = step;
1256 priv->adjustment->page_increment = page;
1258 gtk_adjustment_changed (priv->adjustment);
1262 * gtk_range_set_range:
1263 * @range: a #GtkRange
1264 * @min: minimum range value
1265 * @max: maximum range value
1267 * Sets the allowable values in the #GtkRange, and clamps the range
1268 * value to be between @min and @max. (If the range has a non-zero
1269 * page size, it is clamped between @min and @max - page-size.)
1272 gtk_range_set_range (GtkRange *range,
1276 GtkRangePrivate *priv;
1279 g_return_if_fail (GTK_IS_RANGE (range));
1280 g_return_if_fail (min < max);
1284 priv->adjustment->lower = min;
1285 priv->adjustment->upper = max;
1287 value = priv->adjustment->value;
1289 if (priv->restrict_to_fill_level)
1290 value = MIN (value, MAX (priv->adjustment->lower,
1293 gtk_adjustment_set_value (priv->adjustment, value);
1294 gtk_adjustment_changed (priv->adjustment);
1298 * gtk_range_set_value:
1299 * @range: a #GtkRange
1300 * @value: new value of the range
1302 * Sets the current value of the range; if the value is outside the
1303 * minimum or maximum range values, it will be clamped to fit inside
1304 * them. The range emits the #GtkRange::value-changed signal if the
1308 gtk_range_set_value (GtkRange *range,
1311 GtkRangePrivate *priv;
1313 g_return_if_fail (GTK_IS_RANGE (range));
1317 if (priv->restrict_to_fill_level)
1318 value = MIN (value, MAX (priv->adjustment->lower,
1321 gtk_adjustment_set_value (priv->adjustment, value);
1325 * gtk_range_get_value:
1326 * @range: a #GtkRange
1328 * Gets the current value of the range.
1330 * Return value: current value of the range.
1333 gtk_range_get_value (GtkRange *range)
1335 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1337 return range->priv->adjustment->value;
1341 * gtk_range_set_show_fill_level:
1342 * @range: A #GtkRange
1343 * @show_fill_level: Whether a fill level indicator graphics is shown.
1345 * Sets whether a graphical fill level is show on the trough. See
1346 * gtk_range_set_fill_level() for a general description of the fill
1352 gtk_range_set_show_fill_level (GtkRange *range,
1353 gboolean show_fill_level)
1355 GtkRangePrivate *priv;
1357 g_return_if_fail (GTK_IS_RANGE (range));
1361 show_fill_level = show_fill_level ? TRUE : FALSE;
1363 if (show_fill_level != priv->show_fill_level)
1365 priv->show_fill_level = show_fill_level;
1366 g_object_notify (G_OBJECT (range), "show-fill-level");
1367 gtk_widget_queue_draw (GTK_WIDGET (range));
1372 * gtk_range_get_show_fill_level:
1373 * @range: A #GtkRange
1375 * Gets whether the range displays the fill level graphically.
1377 * Return value: %TRUE if @range shows the fill level.
1382 gtk_range_get_show_fill_level (GtkRange *range)
1384 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1386 return range->priv->show_fill_level;
1390 * gtk_range_set_restrict_to_fill_level:
1391 * @range: A #GtkRange
1392 * @restrict_to_fill_level: Whether the fill level restricts slider movement.
1394 * Sets whether the slider is restricted to the fill level. See
1395 * gtk_range_set_fill_level() for a general description of the fill
1401 gtk_range_set_restrict_to_fill_level (GtkRange *range,
1402 gboolean restrict_to_fill_level)
1404 GtkRangePrivate *priv;
1406 g_return_if_fail (GTK_IS_RANGE (range));
1410 restrict_to_fill_level = restrict_to_fill_level ? TRUE : FALSE;
1412 if (restrict_to_fill_level != priv->restrict_to_fill_level)
1414 priv->restrict_to_fill_level = restrict_to_fill_level;
1415 g_object_notify (G_OBJECT (range), "restrict-to-fill-level");
1417 gtk_range_set_value (range, gtk_range_get_value (range));
1422 * gtk_range_get_restrict_to_fill_level:
1423 * @range: A #GtkRange
1425 * Gets whether the range is restricted to the fill level.
1427 * Return value: %TRUE if @range is restricted to the fill level.
1432 gtk_range_get_restrict_to_fill_level (GtkRange *range)
1434 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1436 return range->priv->restrict_to_fill_level;
1440 * gtk_range_set_fill_level:
1441 * @range: a #GtkRange
1442 * @fill_level: the new position of the fill level indicator
1444 * Set the new position of the fill level indicator.
1446 * The "fill level" is probably best described by its most prominent
1447 * use case, which is an indicator for the amount of pre-buffering in
1448 * a streaming media player. In that use case, the value of the range
1449 * would indicate the current play position, and the fill level would
1450 * be the position up to which the file/stream has been downloaded.
1452 * This amount of prebuffering can be displayed on the range's trough
1453 * and is themeable separately from the trough. To enable fill level
1454 * display, use gtk_range_set_show_fill_level(). The range defaults
1455 * to not showing the fill level.
1457 * Additionally, it's possible to restrict the range's slider position
1458 * to values which are smaller than the fill level. This is controller
1459 * by gtk_range_set_restrict_to_fill_level() and is by default
1465 gtk_range_set_fill_level (GtkRange *range,
1468 GtkRangePrivate *priv;
1470 g_return_if_fail (GTK_IS_RANGE (range));
1474 if (fill_level != priv->fill_level)
1476 priv->fill_level = fill_level;
1477 g_object_notify (G_OBJECT (range), "fill-level");
1479 if (priv->show_fill_level)
1480 gtk_widget_queue_draw (GTK_WIDGET (range));
1482 if (priv->restrict_to_fill_level)
1483 gtk_range_set_value (range, gtk_range_get_value (range));
1488 * gtk_range_get_fill_level:
1489 * @range : A #GtkRange
1491 * Gets the current position of the fill level indicator.
1493 * Return value: The current fill level
1498 gtk_range_get_fill_level (GtkRange *range)
1500 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1502 return range->priv->fill_level;
1506 should_invert (GtkRange *range)
1508 GtkRangePrivate *priv = range->priv;
1510 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1512 (priv->inverted && !priv->flippable) ||
1513 (priv->inverted && priv->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
1514 (!priv->inverted && priv->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
1516 return priv->inverted;
1520 gtk_range_destroy (GtkObject *object)
1522 GtkRange *range = GTK_RANGE (object);
1523 GtkRangePrivate *priv = range->priv;
1525 gtk_range_remove_step_timer (range);
1526 gtk_range_remove_update_timer (range);
1528 if (priv->repaint_id)
1529 g_source_remove (priv->repaint_id);
1530 priv->repaint_id = 0;
1532 if (priv->adjustment)
1534 g_signal_handlers_disconnect_by_func (priv->adjustment,
1535 gtk_range_adjustment_changed,
1537 g_signal_handlers_disconnect_by_func (priv->adjustment,
1538 gtk_range_adjustment_value_changed,
1540 g_object_unref (priv->adjustment);
1541 priv->adjustment = NULL;
1546 g_free (priv->marks);
1548 g_free (priv->mark_pos);
1549 priv->mark_pos = NULL;
1553 GTK_OBJECT_CLASS (gtk_range_parent_class)->destroy (object);
1557 gtk_range_size_request (GtkWidget *widget,
1558 GtkRequisition *requisition)
1561 gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing;
1562 GdkRectangle range_rect;
1565 range = GTK_RANGE (widget);
1567 gtk_range_get_props (range,
1568 &slider_width, &stepper_size,
1569 &focus_width, &trough_border,
1570 &stepper_spacing, NULL,
1573 gtk_range_calc_request (range,
1574 slider_width, stepper_size,
1575 focus_width, trough_border, stepper_spacing,
1576 &range_rect, &border, NULL, NULL, NULL, NULL);
1578 requisition->width = range_rect.width + border.left + border.right;
1579 requisition->height = range_rect.height + border.top + border.bottom;
1583 gtk_range_size_allocate (GtkWidget *widget,
1584 GtkAllocation *allocation)
1586 GtkRange *range = GTK_RANGE (widget);
1587 GtkRangePrivate *priv = range->priv;
1589 gtk_widget_set_allocation (widget, allocation);
1591 priv->recalc_marks = TRUE;
1593 priv->need_recalc = TRUE;
1594 gtk_range_calc_layout (range, priv->adjustment->value);
1596 if (gtk_widget_get_realized (widget))
1597 gdk_window_move_resize (priv->event_window,
1598 allocation->x, allocation->y,
1599 allocation->width, allocation->height);
1603 gtk_range_realize (GtkWidget *widget)
1605 GtkAllocation allocation;
1606 GtkRange *range = GTK_RANGE (widget);
1607 GtkRangePrivate *priv = range->priv;
1609 GdkWindowAttr attributes;
1610 gint attributes_mask;
1612 gtk_range_calc_layout (range, priv->adjustment->value);
1614 gtk_widget_set_realized (widget, TRUE);
1616 window = gtk_widget_get_parent_window (widget);
1617 gtk_widget_set_window (widget, window);
1618 g_object_ref (window);
1620 gtk_widget_get_allocation (widget, &allocation);
1622 attributes.window_type = GDK_WINDOW_CHILD;
1623 attributes.x = allocation.x;
1624 attributes.y = allocation.y;
1625 attributes.width = allocation.width;
1626 attributes.height = allocation.height;
1627 attributes.wclass = GDK_INPUT_ONLY;
1628 attributes.event_mask = gtk_widget_get_events (widget);
1629 attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
1630 GDK_BUTTON_RELEASE_MASK |
1631 GDK_ENTER_NOTIFY_MASK |
1632 GDK_LEAVE_NOTIFY_MASK |
1633 GDK_POINTER_MOTION_MASK |
1634 GDK_POINTER_MOTION_HINT_MASK);
1636 attributes_mask = GDK_WA_X | GDK_WA_Y;
1638 priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
1639 &attributes, attributes_mask);
1640 gdk_window_set_user_data (priv->event_window, range);
1642 gtk_widget_style_attach (widget);
1646 gtk_range_unrealize (GtkWidget *widget)
1648 GtkRange *range = GTK_RANGE (widget);
1649 GtkRangePrivate *priv = range->priv;
1651 gtk_range_remove_step_timer (range);
1652 gtk_range_remove_update_timer (range);
1654 gdk_window_set_user_data (priv->event_window, NULL);
1655 gdk_window_destroy (priv->event_window);
1656 priv->event_window = NULL;
1658 GTK_WIDGET_CLASS (gtk_range_parent_class)->unrealize (widget);
1662 gtk_range_map (GtkWidget *widget)
1664 GtkRange *range = GTK_RANGE (widget);
1665 GtkRangePrivate *priv = range->priv;
1667 gdk_window_show (priv->event_window);
1669 GTK_WIDGET_CLASS (gtk_range_parent_class)->map (widget);
1673 gtk_range_unmap (GtkWidget *widget)
1675 GtkRange *range = GTK_RANGE (widget);
1676 GtkRangePrivate *priv = range->priv;
1678 stop_scrolling (range);
1680 gdk_window_hide (priv->event_window);
1682 GTK_WIDGET_CLASS (gtk_range_parent_class)->unmap (widget);
1685 static const gchar *
1686 gtk_range_get_slider_detail (GtkRange *range)
1688 GtkRangePrivate *priv = range->priv;
1689 const gchar *slider_detail;
1691 if (priv->slider_detail_quark)
1692 return g_quark_to_string (priv->slider_detail_quark);
1694 slider_detail = GTK_RANGE_GET_CLASS (range)->slider_detail;
1696 if (slider_detail && slider_detail[0] == 'X')
1698 gchar *detail = g_strdup (slider_detail);
1700 detail[0] = priv->orientation == GTK_ORIENTATION_HORIZONTAL ? 'h' : 'v';
1702 priv->slider_detail_quark = g_quark_from_string (detail);
1706 return g_quark_to_string (priv->slider_detail_quark);
1709 return slider_detail;
1712 static const gchar *
1713 gtk_range_get_stepper_detail (GtkRange *range,
1716 GtkRangePrivate *priv = range->priv;
1717 const gchar *stepper_detail;
1718 gboolean need_orientation;
1720 const gchar *position = NULL;
1722 if (priv->stepper_detail_quark[stepper])
1723 return g_quark_to_string (priv->stepper_detail_quark[stepper]);
1725 stepper_detail = GTK_RANGE_GET_CLASS (range)->stepper_detail;
1730 position = "_start";
1733 if (priv->has_stepper_a)
1734 position = "_start_inner";
1736 position = "_start";
1739 if (priv->has_stepper_d)
1740 position = "_end_inner";
1748 g_assert_not_reached ();
1751 detail = g_strconcat (stepper_detail, position, NULL);
1753 if (detail[0] == 'X')
1754 detail[0] = priv->orientation == GTK_ORIENTATION_HORIZONTAL ? 'h' : 'v';
1756 priv->stepper_detail_quark[stepper] = g_quark_from_string (detail);
1760 return g_quark_to_string (priv->stepper_detail_quark[stepper]);
1764 draw_stepper (GtkRange *range,
1766 GtkArrowType arrow_type,
1768 gboolean prelighted,
1771 GtkRangePrivate *priv = range->priv;
1772 GtkAllocation allocation;
1773 GtkStateType state_type;
1774 GtkShadowType shadow_type;
1776 GtkWidget *widget = GTK_WIDGET (range);
1777 GdkRectangle intersection;
1779 gfloat arrow_scaling;
1789 rect = &priv->stepper_a;
1792 rect = &priv->stepper_b;
1795 rect = &priv->stepper_c;
1798 rect = &priv->stepper_d;
1801 g_assert_not_reached ();
1804 gboolean arrow_sensitive = TRUE;
1806 /* More to get the right clip region than for efficiency */
1807 if (!gdk_rectangle_intersect (area, rect, &intersection))
1810 gtk_widget_get_allocation (widget, &allocation);
1812 intersection.x += allocation.x;
1813 intersection.y += allocation.y;
1815 if ((!priv->inverted && (arrow_type == GTK_ARROW_DOWN ||
1816 arrow_type == GTK_ARROW_RIGHT)) ||
1817 (priv->inverted && (arrow_type == GTK_ARROW_UP ||
1818 arrow_type == GTK_ARROW_LEFT)))
1820 arrow_sensitive = priv->upper_sensitive;
1824 arrow_sensitive = priv->lower_sensitive;
1827 if (!gtk_widget_is_sensitive (GTK_WIDGET (range)) || !arrow_sensitive)
1828 state_type = GTK_STATE_INSENSITIVE;
1830 state_type = GTK_STATE_ACTIVE;
1831 else if (prelighted)
1832 state_type = GTK_STATE_PRELIGHT;
1834 state_type = GTK_STATE_NORMAL;
1836 if (clicked && arrow_sensitive)
1837 shadow_type = GTK_SHADOW_IN;
1839 shadow_type = GTK_SHADOW_OUT;
1841 style = gtk_widget_get_style (widget);
1842 window = gtk_widget_get_window (widget);
1844 gtk_paint_box (style, window,
1845 state_type, shadow_type,
1846 &intersection, widget,
1847 gtk_range_get_stepper_detail (range, stepper),
1848 allocation.x + rect->x,
1849 allocation.y + rect->y,
1853 gtk_widget_style_get (widget, "arrow-scaling", &arrow_scaling, NULL);
1855 arrow_width = rect->width * arrow_scaling;
1856 arrow_height = rect->height * arrow_scaling;
1857 arrow_x = allocation.x + rect->x + (rect->width - arrow_width) / 2;
1858 arrow_y = allocation.y + rect->y + (rect->height - arrow_height) / 2;
1860 if (clicked && arrow_sensitive)
1862 gint arrow_displacement_x;
1863 gint arrow_displacement_y;
1865 gtk_range_get_props (GTK_RANGE (widget),
1866 NULL, NULL, NULL, NULL, NULL, NULL,
1867 &arrow_displacement_x, &arrow_displacement_y);
1869 arrow_x += arrow_displacement_x;
1870 arrow_y += arrow_displacement_y;
1873 gtk_paint_arrow (style, window,
1874 state_type, shadow_type,
1875 &intersection, widget,
1876 gtk_range_get_stepper_detail (range, stepper),
1879 arrow_x, arrow_y, arrow_width, arrow_height);
1883 gtk_range_expose (GtkWidget *widget,
1884 GdkEventExpose *event)
1886 GtkAllocation allocation;
1887 GtkRange *range = GTK_RANGE (widget);
1888 GtkRangePrivate *priv = range->priv;
1891 GtkShadowType shadow_type;
1893 GdkRectangle expose_area; /* Relative to widget->allocation */
1896 gint focus_line_width = 0;
1897 gint focus_padding = 0;
1898 gboolean touchscreen;
1900 g_object_get (gtk_widget_get_settings (widget),
1901 "gtk-touchscreen-mode", &touchscreen,
1904 style = gtk_widget_get_style (widget);
1905 if (gtk_widget_get_can_focus (GTK_WIDGET (range)))
1906 gtk_widget_style_get (GTK_WIDGET (range),
1907 "focus-line-width", &focus_line_width,
1908 "focus-padding", &focus_padding,
1911 window = gtk_widget_get_window (widget);
1913 /* we're now exposing, so there's no need to force early repaints */
1914 if (priv->repaint_id)
1915 g_source_remove (priv->repaint_id);
1916 priv->repaint_id = 0;
1918 gtk_widget_get_allocation (widget, &allocation);
1920 expose_area = event->area;
1921 expose_area.x -= allocation.x;
1922 expose_area.y -= allocation.y;
1924 gtk_range_calc_marks (range);
1925 gtk_range_calc_layout (range, priv->adjustment->value);
1927 sensitive = gtk_widget_is_sensitive (widget);
1929 /* Just to be confusing, we draw the trough for the whole
1930 * range rectangle, not the trough rectangle (the trough
1931 * rectangle is just for hit detection)
1933 /* The gdk_rectangle_intersect is more to get the right
1934 * clip region (limited to range_rect) than for efficiency
1936 if (gdk_rectangle_intersect (&expose_area, &priv->range_rect,
1939 gint x = (allocation.x + priv->range_rect.x +
1940 focus_line_width + focus_padding);
1941 gint y = (allocation.y + priv->range_rect.y +
1942 focus_line_width + focus_padding);
1943 gint width = (priv->range_rect.width -
1944 2 * (focus_line_width + focus_padding));
1945 gint height = (priv->range_rect.height -
1946 2 * (focus_line_width + focus_padding));
1947 gboolean trough_under_steppers;
1949 gint stepper_spacing;
1951 area.x += allocation.x;
1952 area.y += allocation.y;
1954 gtk_widget_style_get (GTK_WIDGET (range),
1955 "trough-under-steppers", &trough_under_steppers,
1956 "stepper-size", &stepper_size,
1957 "stepper-spacing", &stepper_spacing,
1960 if (stepper_spacing > 0)
1961 trough_under_steppers = FALSE;
1963 if (!trough_under_steppers)
1968 if (priv->has_stepper_a)
1969 offset += stepper_size;
1971 if (priv->has_stepper_b)
1972 offset += stepper_size;
1976 if (priv->has_stepper_c)
1977 shorter += stepper_size;
1979 if (priv->has_stepper_d)
1980 shorter += stepper_size;
1982 if (priv->has_stepper_a || priv->has_stepper_b)
1984 offset += stepper_spacing;
1985 shorter += stepper_spacing;
1988 if (priv->has_stepper_c || priv->has_stepper_d)
1990 shorter += stepper_spacing;
1993 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2006 gint trough_change_pos_x = width;
2007 gint trough_change_pos_y = height;
2009 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2010 trough_change_pos_x = (priv->slider.x +
2011 priv->slider.width / 2 -
2012 (x - allocation.x));
2014 trough_change_pos_y = (priv->slider.y +
2015 priv->slider.height / 2 -
2016 (y - allocation.y));
2018 gtk_paint_box (style, window,
2019 sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
2021 &area, GTK_WIDGET (range),
2022 should_invert (range) ? "trough-upper" : "trough-lower",
2024 trough_change_pos_x, trough_change_pos_y);
2026 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2027 trough_change_pos_y = 0;
2029 trough_change_pos_x = 0;
2031 gtk_paint_box (style, window,
2032 sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
2034 &area, GTK_WIDGET (range),
2035 should_invert (range) ? "trough-lower" : "trough-upper",
2036 x + trough_change_pos_x, y + trough_change_pos_y,
2037 width - trough_change_pos_x,
2038 height - trough_change_pos_y);
2041 if (priv->show_fill_level &&
2042 priv->adjustment->upper - priv->adjustment->page_size -
2043 priv->adjustment->lower != 0)
2045 gdouble fill_level = priv->fill_level;
2048 gint fill_width = width;
2049 gint fill_height = height;
2052 fill_level = CLAMP (fill_level, priv->adjustment->lower,
2053 priv->adjustment->upper -
2054 priv->adjustment->page_size);
2056 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2058 fill_x = allocation.x + priv->trough.x;
2059 fill_width = (priv->slider.width +
2060 (fill_level - priv->adjustment->lower) /
2061 (priv->adjustment->upper -
2062 priv->adjustment->lower -
2063 priv->adjustment->page_size) *
2064 (priv->trough.width -
2065 priv->slider.width));
2067 if (should_invert (range))
2068 fill_x += priv->trough.width - fill_width;
2072 fill_y = allocation.y + priv->trough.y;
2073 fill_height = (priv->slider.height +
2074 (fill_level - priv->adjustment->lower) /
2075 (priv->adjustment->upper -
2076 priv->adjustment->lower -
2077 priv->adjustment->page_size) *
2078 (priv->trough.height -
2079 priv->slider.height));
2081 if (should_invert (range))
2082 fill_y += priv->trough.height - fill_height;
2085 if (fill_level < priv->adjustment->upper - priv->adjustment->page_size)
2086 fill_detail = "trough-fill-level-full";
2088 fill_detail = "trough-fill-level";
2090 gtk_paint_box (style, window,
2091 sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
2093 &area, GTK_WIDGET (range), fill_detail,
2095 fill_width, fill_height);
2098 if (sensitive && gtk_widget_has_focus (widget))
2099 gtk_paint_focus (style, window,
2100 gtk_widget_get_state (widget),
2101 &area, widget, "trough",
2102 allocation.x + priv->range_rect.x,
2103 allocation.y + priv->range_rect.y,
2104 priv->range_rect.width,
2105 priv->range_rect.height);
2108 shadow_type = GTK_SHADOW_OUT;
2111 state = GTK_STATE_INSENSITIVE;
2112 else if (!touchscreen && priv->mouse_location == MOUSE_SLIDER)
2113 state = GTK_STATE_PRELIGHT;
2115 state = GTK_STATE_NORMAL;
2117 if (priv->grab_location == MOUSE_SLIDER)
2119 gboolean activate_slider;
2121 gtk_widget_style_get (widget, "activate-slider", &activate_slider, NULL);
2123 if (activate_slider)
2125 state = GTK_STATE_ACTIVE;
2126 shadow_type = GTK_SHADOW_IN;
2130 if (gdk_rectangle_intersect (&expose_area,
2134 area.x += allocation.x;
2135 area.y += allocation.y;
2137 gtk_paint_slider (style,
2143 gtk_range_get_slider_detail (range),
2144 allocation.x + priv->slider.x,
2145 allocation.y + priv->slider.y,
2147 priv->slider.height,
2151 if (priv->has_stepper_a)
2152 draw_stepper (range, STEPPER_A,
2153 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
2154 priv->grab_location == MOUSE_STEPPER_A,
2155 !touchscreen && priv->mouse_location == MOUSE_STEPPER_A,
2158 if (priv->has_stepper_b)
2159 draw_stepper (range, STEPPER_B,
2160 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
2161 priv->grab_location == MOUSE_STEPPER_B,
2162 !touchscreen && priv->mouse_location == MOUSE_STEPPER_B,
2165 if (priv->has_stepper_c)
2166 draw_stepper (range, STEPPER_C,
2167 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
2168 priv->grab_location == MOUSE_STEPPER_C,
2169 !touchscreen && priv->mouse_location == MOUSE_STEPPER_C,
2172 if (priv->has_stepper_d)
2173 draw_stepper (range, STEPPER_D,
2174 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
2175 priv->grab_location == MOUSE_STEPPER_D,
2176 !touchscreen && priv->mouse_location == MOUSE_STEPPER_D,
2183 range_grab_add (GtkRange *range,
2185 MouseLocation location,
2188 GtkRangePrivate *priv = range->priv;
2190 if (device == priv->grab_device)
2193 if (priv->grab_device != NULL)
2195 g_warning ("GtkRange already had a grab device, releasing device grab");
2196 gtk_device_grab_remove (GTK_WIDGET (range), priv->grab_device);
2199 /* we don't actually gdk_grab, since a button is down */
2200 gtk_device_grab_add (GTK_WIDGET (range), device, TRUE);
2202 priv->grab_location = location;
2203 priv->grab_button = button;
2204 priv->grab_device = device;
2206 if (gtk_range_update_mouse_location (range))
2207 gtk_widget_queue_draw (GTK_WIDGET (range));
2211 range_grab_remove (GtkRange *range)
2213 GtkRangePrivate *priv = range->priv;
2214 MouseLocation location;
2216 if (priv->grab_device)
2218 gtk_device_grab_remove (GTK_WIDGET (range),
2220 priv->grab_device = NULL;
2223 location = priv->grab_location;
2224 priv->grab_location = MOUSE_OUTSIDE;
2225 priv->grab_button = 0;
2227 if (gtk_range_update_mouse_location (range) ||
2228 location != MOUSE_OUTSIDE)
2229 gtk_widget_queue_draw (GTK_WIDGET (range));
2232 static GtkScrollType
2233 range_get_scroll_for_grab (GtkRange *range)
2235 GtkRangePrivate *priv = range->priv;
2238 invert = should_invert (range);
2239 switch (priv->grab_location)
2241 /* Backward stepper */
2242 case MOUSE_STEPPER_A:
2243 case MOUSE_STEPPER_C:
2244 switch (priv->grab_button)
2247 return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
2250 return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD;
2253 return invert ? GTK_SCROLL_END : GTK_SCROLL_START;
2258 /* Forward stepper */
2259 case MOUSE_STEPPER_B:
2260 case MOUSE_STEPPER_D:
2261 switch (priv->grab_button)
2264 return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
2267 return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD;
2270 return invert ? GTK_SCROLL_START : GTK_SCROLL_END;
2278 if (priv->trough_click_forward)
2279 return GTK_SCROLL_PAGE_FORWARD;
2281 return GTK_SCROLL_PAGE_BACKWARD;
2291 return GTK_SCROLL_NONE;
2295 coord_to_value (GtkRange *range,
2298 GtkRangePrivate *priv = range->priv;
2305 gint trough_under_steppers;
2307 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2309 trough_length = priv->trough.height;
2310 trough_start = priv->trough.y;
2311 slider_length = priv->slider.height;
2315 trough_length = priv->trough.width;
2316 trough_start = priv->trough.x;
2317 slider_length = priv->slider.width;
2320 gtk_range_get_props (range, NULL, NULL, NULL, &trough_border, NULL,
2321 &trough_under_steppers, NULL, NULL);
2323 if (! trough_under_steppers)
2325 trough_start += trough_border;
2326 trough_length -= 2 * trough_border;
2329 if (trough_length == slider_length)
2332 frac = (MAX (0, coord - trough_start) /
2333 (gdouble) (trough_length - slider_length));
2335 if (should_invert (range))
2338 value = priv->adjustment->lower + frac * (priv->adjustment->upper -
2339 priv->adjustment->lower -
2340 priv->adjustment->page_size);
2346 gtk_range_key_press (GtkWidget *widget,
2350 GtkRange *range = GTK_RANGE (widget);
2351 GtkRangePrivate *priv = range->priv;
2353 device = gdk_event_get_device ((GdkEvent *) event);
2354 device = gdk_device_get_associated_device (device);
2356 if (device == priv->grab_device &&
2357 event->keyval == GDK_Escape &&
2358 priv->grab_location != MOUSE_OUTSIDE)
2360 stop_scrolling (range);
2362 update_slider_position (range,
2363 priv->slide_initial_coordinate,
2364 priv->slide_initial_coordinate);
2369 return GTK_WIDGET_CLASS (gtk_range_parent_class)->key_press_event (widget, event);
2373 gtk_range_button_press (GtkWidget *widget,
2374 GdkEventButton *event)
2376 GtkRange *range = GTK_RANGE (widget);
2377 GtkRangePrivate *priv = range->priv;
2380 if (!gtk_widget_has_focus (widget))
2381 gtk_widget_grab_focus (widget);
2383 /* ignore presses when we're already doing something else. */
2384 if (priv->grab_location != MOUSE_OUTSIDE)
2387 device = gdk_event_get_device ((GdkEvent *) event);
2388 priv->mouse_x = event->x;
2389 priv->mouse_y = event->y;
2391 if (gtk_range_update_mouse_location (range))
2392 gtk_widget_queue_draw (widget);
2394 if (priv->mouse_location == MOUSE_TROUGH &&
2397 /* button 1 steps by page increment, as with button 2 on a stepper
2399 GtkScrollType scroll;
2400 gdouble click_value;
2402 click_value = coord_to_value (range,
2403 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2404 event->y : event->x);
2406 priv->trough_click_forward = click_value > priv->adjustment->value;
2407 range_grab_add (range, device, MOUSE_TROUGH, event->button);
2409 scroll = range_get_scroll_for_grab (range);
2411 gtk_range_add_step_timer (range, scroll);
2415 else if ((priv->mouse_location == MOUSE_STEPPER_A ||
2416 priv->mouse_location == MOUSE_STEPPER_B ||
2417 priv->mouse_location == MOUSE_STEPPER_C ||
2418 priv->mouse_location == MOUSE_STEPPER_D) &&
2419 (event->button == 1 || event->button == 2 || event->button == 3))
2421 GtkAllocation allocation;
2422 GdkRectangle *stepper_area;
2423 GtkScrollType scroll;
2425 range_grab_add (range, device, priv->mouse_location, event->button);
2427 gtk_widget_get_allocation (widget, &allocation);
2428 stepper_area = get_area (range, priv->mouse_location);
2430 gtk_widget_queue_draw_area (widget,
2431 allocation.x + stepper_area->x,
2432 allocation.y + stepper_area->y,
2433 stepper_area->width,
2434 stepper_area->height);
2436 scroll = range_get_scroll_for_grab (range);
2437 if (scroll != GTK_SCROLL_NONE)
2438 gtk_range_add_step_timer (range, scroll);
2442 else if ((priv->mouse_location == MOUSE_TROUGH &&
2443 event->button == 2) ||
2444 priv->mouse_location == MOUSE_SLIDER)
2446 gboolean need_value_update = FALSE;
2447 gboolean activate_slider;
2449 /* Any button can be used to drag the slider, but you can start
2450 * dragging the slider with a trough click using button 2;
2451 * On button 2 press, we warp the slider to mouse position,
2452 * then begin the slider drag.
2454 if (event->button == 2)
2456 gdouble slider_low_value, slider_high_value, new_value;
2459 coord_to_value (range,
2460 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2461 event->y : event->x);
2463 coord_to_value (range,
2464 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2465 event->y - priv->slider.height :
2466 event->x - priv->slider.width);
2468 /* compute new value for warped slider */
2469 new_value = slider_low_value + (slider_high_value - slider_low_value) / 2;
2471 /* recalc slider, so we can set slide_initial_slider_position
2474 priv->need_recalc = TRUE;
2475 gtk_range_calc_layout (range, new_value);
2477 /* defer adjustment updates to update_slider_position() in order
2478 * to keep pixel quantisation
2480 need_value_update = TRUE;
2483 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2485 priv->slide_initial_slider_position = priv->slider.y;
2486 priv->slide_initial_coordinate = event->y;
2490 priv->slide_initial_slider_position = priv->slider.x;
2491 priv->slide_initial_coordinate = event->x;
2494 range_grab_add (range, device, MOUSE_SLIDER, event->button);
2496 gtk_widget_style_get (widget, "activate-slider", &activate_slider, NULL);
2498 /* force a redraw, if the active slider is drawn differently to the
2501 if (activate_slider)
2502 gtk_widget_queue_draw (widget);
2504 if (need_value_update)
2505 update_slider_position (range, event->x, event->y);
2513 /* During a slide, move the slider as required given new mouse position */
2515 update_slider_position (GtkRange *range,
2519 GtkRangePrivate *priv = range->priv;
2529 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2530 delta = mouse_y - priv->slide_initial_coordinate;
2532 delta = mouse_x - priv->slide_initial_coordinate;
2534 c = priv->slide_initial_slider_position + delta;
2536 new_value = coord_to_value (range, c);
2537 next_value = coord_to_value (range, c + 1);
2538 mark_delta = fabs (next_value - new_value);
2540 for (i = 0; i < priv->n_marks; i++)
2542 mark_value = priv->marks[i];
2544 if (fabs (priv->adjustment->value - mark_value) < 3 * mark_delta)
2546 if (fabs (new_value - mark_value) < (priv->slider_end - priv->slider_start) * 0.5 * mark_delta)
2548 new_value = mark_value;
2554 g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value,
2559 stop_scrolling (GtkRange *range)
2561 range_grab_remove (range);
2562 gtk_range_remove_step_timer (range);
2563 /* Flush any pending discontinuous/delayed updates */
2564 gtk_range_update_value (range);
2568 gtk_range_grab_broken (GtkWidget *widget,
2569 GdkEventGrabBroken *event)
2571 GtkRange *range = GTK_RANGE (widget);
2572 GtkRangePrivate *priv = range->priv;
2575 device = gdk_event_get_device ((GdkEvent *) event);
2577 if (device == priv->grab_device &&
2578 priv->grab_location != MOUSE_OUTSIDE)
2580 if (priv->grab_location == MOUSE_SLIDER)
2581 update_slider_position (range, priv->mouse_x, priv->mouse_y);
2583 stop_scrolling (range);
2592 gtk_range_button_release (GtkWidget *widget,
2593 GdkEventButton *event)
2595 GtkRange *range = GTK_RANGE (widget);
2596 GtkRangePrivate *priv = range->priv;
2599 if (event->window == priv->event_window)
2601 priv->mouse_x = event->x;
2602 priv->mouse_y = event->y;
2606 gdk_window_get_device_position (priv->event_window,
2613 device = gdk_event_get_device ((GdkEvent *) event);
2615 if (priv->grab_device == device &&
2616 priv->grab_button == event->button)
2618 if (priv->grab_location == MOUSE_SLIDER)
2619 update_slider_position (range, priv->mouse_x, priv->mouse_y);
2621 stop_scrolling (range);
2630 * _gtk_range_get_wheel_delta:
2631 * @range: a #GtkRange
2632 * @direction: A #GdkScrollDirection
2634 * Returns a good step value for the mouse wheel.
2636 * Return value: A good step value for the mouse wheel.
2641 _gtk_range_get_wheel_delta (GtkRange *range,
2642 GdkScrollDirection direction)
2644 GtkRangePrivate *priv = range->priv;
2645 GtkAdjustment *adj = priv->adjustment;
2648 if (GTK_IS_SCROLLBAR (range))
2649 delta = pow (adj->page_size, 2.0 / 3.0);
2651 delta = adj->step_increment * 2;
2653 if (direction == GDK_SCROLL_UP ||
2654 direction == GDK_SCROLL_LEFT)
2664 gtk_range_scroll_event (GtkWidget *widget,
2665 GdkEventScroll *event)
2667 GtkRange *range = GTK_RANGE (widget);
2668 GtkRangePrivate *priv = range->priv;
2670 if (gtk_widget_get_realized (widget))
2672 GtkAdjustment *adj = priv->adjustment;
2676 delta = _gtk_range_get_wheel_delta (range, event->direction);
2678 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2679 GTK_SCROLL_JUMP, adj->value + delta,
2682 /* Policy DELAYED makes sense with scroll events,
2683 * but DISCONTINUOUS doesn't, so we update immediately
2686 if (priv->update_policy == GTK_UPDATE_DISCONTINUOUS)
2687 gtk_range_update_value (range);
2694 gtk_range_motion_notify (GtkWidget *widget,
2695 GdkEventMotion *event)
2697 GtkRange *range = GTK_RANGE (widget);
2698 GtkRangePrivate *priv = range->priv;
2700 gdk_event_request_motions (event);
2702 priv->mouse_x = event->x;
2703 priv->mouse_y = event->y;
2705 if (gtk_range_update_mouse_location (range))
2706 gtk_widget_queue_draw (widget);
2708 if (priv->grab_location == MOUSE_SLIDER)
2709 update_slider_position (range, event->x, event->y);
2711 /* We handled the event if the mouse was in the range_rect */
2712 return priv->mouse_location != MOUSE_OUTSIDE;
2716 gtk_range_enter_notify (GtkWidget *widget,
2717 GdkEventCrossing *event)
2719 GtkRange *range = GTK_RANGE (widget);
2720 GtkRangePrivate *priv = range->priv;
2722 priv->mouse_x = event->x;
2723 priv->mouse_y = event->y;
2725 if (gtk_range_update_mouse_location (range))
2726 gtk_widget_queue_draw (widget);
2732 gtk_range_leave_notify (GtkWidget *widget,
2733 GdkEventCrossing *event)
2735 GtkRange *range = GTK_RANGE (widget);
2736 GtkRangePrivate *priv = range->priv;
2741 if (gtk_range_update_mouse_location (range))
2742 gtk_widget_queue_draw (widget);
2748 gtk_range_grab_notify (GtkWidget *widget,
2749 gboolean was_grabbed)
2751 GtkRangePrivate *priv = GTK_RANGE (widget)->priv;
2753 if (priv->grab_device &&
2754 gtk_widget_device_is_shadowed (widget, priv->grab_device))
2755 stop_scrolling (GTK_RANGE (widget));
2759 gtk_range_state_changed (GtkWidget *widget,
2760 GtkStateType previous_state)
2762 if (!gtk_widget_is_sensitive (widget))
2763 stop_scrolling (GTK_RANGE (widget));
2766 #define check_rectangle(rectangle1, rectangle2) \
2768 if (rectangle1.x != rectangle2.x) return TRUE; \
2769 if (rectangle1.y != rectangle2.y) return TRUE; \
2770 if (rectangle1.width != rectangle2.width) return TRUE; \
2771 if (rectangle1.height != rectangle2.height) return TRUE; \
2775 layout_changed (GtkRangePrivate *priv1,
2776 GtkRangePrivate *priv2)
2778 check_rectangle (priv1->slider, priv2->slider);
2779 check_rectangle (priv1->trough, priv2->trough);
2780 check_rectangle (priv1->stepper_a, priv2->stepper_a);
2781 check_rectangle (priv1->stepper_d, priv2->stepper_d);
2782 check_rectangle (priv1->stepper_b, priv2->stepper_b);
2783 check_rectangle (priv1->stepper_c, priv2->stepper_c);
2785 if (priv1->upper_sensitive != priv2->upper_sensitive) return TRUE;
2786 if (priv1->lower_sensitive != priv2->lower_sensitive) return TRUE;
2792 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
2795 GtkRange *range = GTK_RANGE (data);
2796 GtkRangePrivate *priv = range->priv;
2797 GtkRangePrivate priv_aux = *priv;
2799 priv->recalc_marks = TRUE;
2800 priv->need_recalc = TRUE;
2801 gtk_range_calc_layout (range, priv->adjustment->value);
2803 /* now check whether the layout changed */
2804 if (layout_changed (priv, &priv_aux))
2805 gtk_widget_queue_draw (GTK_WIDGET (range));
2807 /* Note that we don't round off to priv->round_digits here.
2808 * that's because it's really broken to change a value
2809 * in response to a change signal on that value; round_digits
2810 * is therefore defined to be a filter on what the GtkRange
2811 * can input into the adjustment, not a filter that the GtkRange
2812 * will enforce on the adjustment.
2817 force_repaint (gpointer data)
2819 GtkRange *range = GTK_RANGE (data);
2820 GtkRangePrivate *priv = range->priv;
2821 GtkWidget *widget = GTK_WIDGET (range);
2823 priv->repaint_id = 0;
2824 if (gtk_widget_is_drawable (widget))
2825 gdk_window_process_updates (gtk_widget_get_window (widget), FALSE);
2831 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
2834 GtkRange *range = GTK_RANGE (data);
2835 GtkRangePrivate *priv = range->priv;
2836 GtkRangePrivate priv_aux = *priv;
2838 priv->need_recalc = TRUE;
2839 gtk_range_calc_layout (range, priv->adjustment->value);
2841 /* now check whether the layout changed */
2842 if (layout_changed (priv, &priv_aux) ||
2843 (GTK_IS_SCALE (range) && gtk_scale_get_draw_value (GTK_SCALE (range))))
2845 gtk_widget_queue_draw (GTK_WIDGET (range));
2846 /* setup a timer to ensure the range isn't lagging too much behind the scroll position */
2847 if (!priv->repaint_id)
2848 priv->repaint_id = gdk_threads_add_timeout_full (GDK_PRIORITY_EVENTS, 181, force_repaint, range, NULL);
2851 /* Note that we don't round off to priv->round_digits here.
2852 * that's because it's really broken to change a value
2853 * in response to a change signal on that value; round_digits
2854 * is therefore defined to be a filter on what the GtkRange
2855 * can input into the adjustment, not a filter that the GtkRange
2856 * will enforce on the adjustment.
2859 g_signal_emit (range, signals[VALUE_CHANGED], 0);
2863 gtk_range_style_set (GtkWidget *widget,
2864 GtkStyle *previous_style)
2866 GtkRange *range = GTK_RANGE (widget);
2867 GtkRangePrivate *priv = range->priv;
2869 priv->need_recalc = TRUE;
2871 GTK_WIDGET_CLASS (gtk_range_parent_class)->style_set (widget, previous_style);
2875 apply_marks (GtkRange *range,
2879 GtkRangePrivate *priv = range->priv;
2883 for (i = 0; i < priv->n_marks; i++)
2885 mark = priv->marks[i];
2886 if ((oldval < mark && mark < *newval) ||
2887 (oldval > mark && mark > *newval))
2896 step_back (GtkRange *range)
2898 GtkRangePrivate *priv = range->priv;
2902 newval = priv->adjustment->value - priv->adjustment->step_increment;
2903 apply_marks (range, priv->adjustment->value, &newval);
2904 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2905 GTK_SCROLL_STEP_BACKWARD, newval, &handled);
2909 step_forward (GtkRange *range)
2911 GtkRangePrivate *priv = range->priv;
2915 newval = priv->adjustment->value + priv->adjustment->step_increment;
2916 apply_marks (range, priv->adjustment->value, &newval);
2917 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2918 GTK_SCROLL_STEP_FORWARD, newval, &handled);
2923 page_back (GtkRange *range)
2925 GtkRangePrivate *priv = range->priv;
2929 newval = priv->adjustment->value - priv->adjustment->page_increment;
2930 apply_marks (range, priv->adjustment->value, &newval);
2931 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2932 GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
2936 page_forward (GtkRange *range)
2938 GtkRangePrivate *priv = range->priv;
2942 newval = priv->adjustment->value + priv->adjustment->page_increment;
2943 apply_marks (range, priv->adjustment->value, &newval);
2944 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2945 GTK_SCROLL_PAGE_FORWARD, newval, &handled);
2949 scroll_begin (GtkRange *range)
2951 GtkRangePrivate *priv = range->priv;
2954 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2955 GTK_SCROLL_START, priv->adjustment->lower,
2960 scroll_end (GtkRange *range)
2962 GtkRangePrivate *priv = range->priv;
2966 newval = priv->adjustment->upper - priv->adjustment->page_size;
2967 g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_END, newval,
2972 gtk_range_scroll (GtkRange *range,
2973 GtkScrollType scroll)
2975 GtkRangePrivate *priv = range->priv;
2976 gdouble old_value = priv->adjustment->value;
2980 case GTK_SCROLL_STEP_LEFT:
2981 if (should_invert (range))
2982 step_forward (range);
2987 case GTK_SCROLL_STEP_UP:
2988 if (should_invert (range))
2989 step_forward (range);
2994 case GTK_SCROLL_STEP_RIGHT:
2995 if (should_invert (range))
2998 step_forward (range);
3001 case GTK_SCROLL_STEP_DOWN:
3002 if (should_invert (range))
3005 step_forward (range);
3008 case GTK_SCROLL_STEP_BACKWARD:
3012 case GTK_SCROLL_STEP_FORWARD:
3013 step_forward (range);
3016 case GTK_SCROLL_PAGE_LEFT:
3017 if (should_invert (range))
3018 page_forward (range);
3023 case GTK_SCROLL_PAGE_UP:
3024 if (should_invert (range))
3025 page_forward (range);
3030 case GTK_SCROLL_PAGE_RIGHT:
3031 if (should_invert (range))
3034 page_forward (range);
3037 case GTK_SCROLL_PAGE_DOWN:
3038 if (should_invert (range))
3041 page_forward (range);
3044 case GTK_SCROLL_PAGE_BACKWARD:
3048 case GTK_SCROLL_PAGE_FORWARD:
3049 page_forward (range);
3052 case GTK_SCROLL_START:
3053 scroll_begin (range);
3056 case GTK_SCROLL_END:
3060 case GTK_SCROLL_JUMP:
3061 /* Used by CList, range doesn't use it. */
3064 case GTK_SCROLL_NONE:
3068 return priv->adjustment->value != old_value;
3072 gtk_range_move_slider (GtkRange *range,
3073 GtkScrollType scroll)
3075 GtkRangePrivate *priv = range->priv;
3076 gboolean cursor_only;
3078 g_object_get (gtk_widget_get_settings (GTK_WIDGET (range)),
3079 "gtk-keynav-cursor-only", &cursor_only,
3084 GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (range));
3086 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
3088 if (scroll == GTK_SCROLL_STEP_UP ||
3089 scroll == GTK_SCROLL_STEP_DOWN)
3092 gtk_widget_child_focus (toplevel,
3093 scroll == GTK_SCROLL_STEP_UP ?
3094 GTK_DIR_UP : GTK_DIR_DOWN);
3100 if (scroll == GTK_SCROLL_STEP_LEFT ||
3101 scroll == GTK_SCROLL_STEP_RIGHT)
3104 gtk_widget_child_focus (toplevel,
3105 scroll == GTK_SCROLL_STEP_LEFT ?
3106 GTK_DIR_LEFT : GTK_DIR_RIGHT);
3112 if (! gtk_range_scroll (range, scroll))
3113 gtk_widget_error_bell (GTK_WIDGET (range));
3115 /* Policy DELAYED makes sense with key events,
3116 * but DISCONTINUOUS doesn't, so we update immediately
3119 if (priv->update_policy == GTK_UPDATE_DISCONTINUOUS)
3120 gtk_range_update_value (range);
3124 gtk_range_get_props (GtkRange *range,
3128 gint *trough_border,
3129 gint *stepper_spacing,
3130 gboolean *trough_under_steppers,
3131 gint *arrow_displacement_x,
3132 gint *arrow_displacement_y)
3134 GtkWidget *widget = GTK_WIDGET (range);
3135 gint tmp_slider_width, tmp_stepper_size, tmp_focus_width, tmp_trough_border;
3136 gint tmp_stepper_spacing, tmp_trough_under_steppers;
3137 gint tmp_arrow_displacement_x, tmp_arrow_displacement_y;
3139 gtk_widget_style_get (widget,
3140 "slider-width", &tmp_slider_width,
3141 "trough-border", &tmp_trough_border,
3142 "stepper-size", &tmp_stepper_size,
3143 "stepper-spacing", &tmp_stepper_spacing,
3144 "trough-under-steppers", &tmp_trough_under_steppers,
3145 "arrow-displacement-x", &tmp_arrow_displacement_x,
3146 "arrow-displacement-y", &tmp_arrow_displacement_y,
3149 if (tmp_stepper_spacing > 0)
3150 tmp_trough_under_steppers = FALSE;
3152 if (gtk_widget_get_can_focus (GTK_WIDGET (range)))
3154 gint focus_line_width;
3157 gtk_widget_style_get (GTK_WIDGET (range),
3158 "focus-line-width", &focus_line_width,
3159 "focus-padding", &focus_padding,
3162 tmp_focus_width = focus_line_width + focus_padding;
3166 tmp_focus_width = 0;
3170 *slider_width = tmp_slider_width;
3173 *focus_width = tmp_focus_width;
3176 *trough_border = tmp_trough_border;
3179 *stepper_size = tmp_stepper_size;
3181 if (stepper_spacing)
3182 *stepper_spacing = tmp_stepper_spacing;
3184 if (trough_under_steppers)
3185 *trough_under_steppers = tmp_trough_under_steppers;
3187 if (arrow_displacement_x)
3188 *arrow_displacement_x = tmp_arrow_displacement_x;
3190 if (arrow_displacement_y)
3191 *arrow_displacement_y = tmp_arrow_displacement_y;
3194 #define POINT_IN_RECT(xcoord, ycoord, rect) \
3195 ((xcoord) >= (rect).x && \
3196 (xcoord) < ((rect).x + (rect).width) && \
3197 (ycoord) >= (rect).y && \
3198 (ycoord) < ((rect).y + (rect).height))
3200 /* Update mouse location, return TRUE if it changes */
3202 gtk_range_update_mouse_location (GtkRange *range)
3204 GtkRangePrivate *priv = range->priv;
3205 GtkAllocation allocation;
3208 GtkWidget *widget = GTK_WIDGET (range);
3210 old = priv->mouse_location;
3215 gtk_widget_get_allocation (widget, &allocation);
3217 if (priv->grab_location != MOUSE_OUTSIDE)
3218 priv->mouse_location = priv->grab_location;
3219 else if (POINT_IN_RECT (x, y, priv->stepper_a))
3220 priv->mouse_location = MOUSE_STEPPER_A;
3221 else if (POINT_IN_RECT (x, y, priv->stepper_b))
3222 priv->mouse_location = MOUSE_STEPPER_B;
3223 else if (POINT_IN_RECT (x, y, priv->stepper_c))
3224 priv->mouse_location = MOUSE_STEPPER_C;
3225 else if (POINT_IN_RECT (x, y, priv->stepper_d))
3226 priv->mouse_location = MOUSE_STEPPER_D;
3227 else if (POINT_IN_RECT (x, y, priv->slider))
3228 priv->mouse_location = MOUSE_SLIDER;
3229 else if (POINT_IN_RECT (x, y, priv->trough))
3230 priv->mouse_location = MOUSE_TROUGH;
3231 else if (POINT_IN_RECT (x, y, allocation))
3232 priv->mouse_location = MOUSE_WIDGET;
3234 priv->mouse_location = MOUSE_OUTSIDE;
3236 return old != priv->mouse_location;
3239 /* Clamp rect, border inside widget->allocation, such that we prefer
3240 * to take space from border not rect in all directions, and prefer to
3241 * give space to border over rect in one direction.
3244 clamp_dimensions (GtkWidget *widget,
3247 gboolean border_expands_horizontally)
3249 GtkAllocation allocation;
3250 gint extra, shortage;
3252 g_return_if_fail (rect->x == 0);
3253 g_return_if_fail (rect->y == 0);
3254 g_return_if_fail (rect->width >= 0);
3255 g_return_if_fail (rect->height >= 0);
3257 gtk_widget_get_allocation (widget, &allocation);
3261 extra = allocation.width - border->left - border->right - rect->width;
3264 if (border_expands_horizontally)
3266 border->left += extra / 2;
3267 border->right += extra / 2 + extra % 2;
3271 rect->width += extra;
3275 /* See if we can fit rect, if not kill the border */
3276 shortage = rect->width - allocation.width;
3279 rect->width = allocation.width;
3280 /* lose the border */
3286 /* See if we can fit rect with borders */
3287 shortage = rect->width + border->left + border->right - allocation.width;
3290 /* Shrink borders */
3291 border->left -= shortage / 2;
3292 border->right -= shortage / 2 + shortage % 2;
3298 extra = allocation.height - border->top - border->bottom - rect->height;
3301 if (border_expands_horizontally)
3303 /* don't expand border vertically */
3304 rect->height += extra;
3308 border->top += extra / 2;
3309 border->bottom += extra / 2 + extra % 2;
3313 /* See if we can fit rect, if not kill the border */
3314 shortage = rect->height - allocation.height;
3317 rect->height = allocation.height;
3318 /* lose the border */
3324 /* See if we can fit rect with borders */
3325 shortage = rect->height + border->top + border->bottom - allocation.height;
3328 /* Shrink borders */
3329 border->top -= shortage / 2;
3330 border->bottom -= shortage / 2 + shortage % 2;
3336 gtk_range_calc_request (GtkRange *range,
3341 gint stepper_spacing,
3342 GdkRectangle *range_rect,
3345 gboolean *has_steppers_ab,
3346 gboolean *has_steppers_cd,
3347 gint *slider_length_p)
3349 GtkRangePrivate *priv = range->priv;
3360 if (GTK_RANGE_GET_CLASS (range)->get_range_border)
3361 GTK_RANGE_GET_CLASS (range)->get_range_border (range, border);
3366 if (priv->has_stepper_a)
3368 if (priv->has_stepper_b)
3370 if (priv->has_stepper_c)
3372 if (priv->has_stepper_d)
3375 n_steppers = n_steppers_ab + n_steppers_cd;
3377 slider_length = priv->min_slider_size;
3382 /* We never expand to fill available space in the small dimension
3383 * (i.e. vertical scrollbars are always a fixed width)
3385 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
3387 range_rect->width = (focus_width + trough_border) * 2 + slider_width;
3388 range_rect->height = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length;
3390 if (n_steppers_ab > 0)
3391 range_rect->height += stepper_spacing;
3393 if (n_steppers_cd > 0)
3394 range_rect->height += stepper_spacing;
3398 range_rect->width = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length;
3399 range_rect->height = (focus_width + trough_border) * 2 + slider_width;
3401 if (n_steppers_ab > 0)
3402 range_rect->width += stepper_spacing;
3404 if (n_steppers_cd > 0)
3405 range_rect->width += stepper_spacing;
3409 *n_steppers_p = n_steppers;
3411 if (has_steppers_ab)
3412 *has_steppers_ab = (n_steppers_ab > 0);
3414 if (has_steppers_cd)
3415 *has_steppers_cd = (n_steppers_cd > 0);
3417 if (slider_length_p)
3418 *slider_length_p = slider_length;
3422 gtk_range_calc_layout (GtkRange *range,
3423 gdouble adjustment_value)
3425 GtkRangePrivate *priv = range->priv;
3426 gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing;
3430 gboolean has_steppers_ab;
3431 gboolean has_steppers_cd;
3432 gboolean trough_under_steppers;
3433 GdkRectangle range_rect;
3436 if (!priv->need_recalc)
3439 /* If we have a too-small allocation, we prefer the steppers over
3440 * the trough/slider, probably the steppers are a more useful
3441 * feature in small spaces.
3443 * Also, we prefer to draw the range itself rather than the border
3444 * areas if there's a conflict, since the borders will be decoration
3445 * not controls. Though this depends on subclasses cooperating by
3446 * not drawing on priv->range_rect.
3449 widget = GTK_WIDGET (range);
3451 gtk_range_get_props (range,
3452 &slider_width, &stepper_size,
3453 &focus_width, &trough_border,
3454 &stepper_spacing, &trough_under_steppers,
3457 gtk_range_calc_request (range,
3458 slider_width, stepper_size,
3459 focus_width, trough_border, stepper_spacing,
3460 &range_rect, &border, &n_steppers,
3461 &has_steppers_ab, &has_steppers_cd, &slider_length);
3463 /* We never expand to fill available space in the small dimension
3464 * (i.e. vertical scrollbars are always a fixed width)
3466 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
3468 clamp_dimensions (widget, &range_rect, &border, TRUE);
3472 clamp_dimensions (widget, &range_rect, &border, FALSE);
3475 range_rect.x = border.left;
3476 range_rect.y = border.top;
3478 priv->range_rect = range_rect;
3480 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
3482 gint stepper_width, stepper_height;
3484 /* Steppers are the width of the range, and stepper_size in
3485 * height, or if we don't have enough height, divided equally
3486 * among available space.
3488 stepper_width = range_rect.width - focus_width * 2;
3490 if (trough_under_steppers)
3491 stepper_width -= trough_border * 2;
3493 if (stepper_width < 1)
3494 stepper_width = range_rect.width; /* screw the trough border */
3496 if (n_steppers == 0)
3497 stepper_height = 0; /* avoid divide by n_steppers */
3499 stepper_height = MIN (stepper_size, (range_rect.height / n_steppers));
3503 priv->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers;
3504 priv->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers;
3506 if (priv->has_stepper_a)
3508 priv->stepper_a.width = stepper_width;
3509 priv->stepper_a.height = stepper_height;
3513 priv->stepper_a.width = 0;
3514 priv->stepper_a.height = 0;
3519 priv->stepper_b.x = priv->stepper_a.x;
3520 priv->stepper_b.y = priv->stepper_a.y + priv->stepper_a.height;
3522 if (priv->has_stepper_b)
3524 priv->stepper_b.width = stepper_width;
3525 priv->stepper_b.height = stepper_height;
3529 priv->stepper_b.width = 0;
3530 priv->stepper_b.height = 0;
3535 if (priv->has_stepper_d)
3537 priv->stepper_d.width = stepper_width;
3538 priv->stepper_d.height = stepper_height;
3542 priv->stepper_d.width = 0;
3543 priv->stepper_d.height = 0;
3546 priv->stepper_d.x = priv->stepper_a.x;
3547 priv->stepper_d.y = range_rect.y + range_rect.height - priv->stepper_d.height - focus_width - trough_border * trough_under_steppers;
3551 if (priv->has_stepper_c)
3553 priv->stepper_c.width = stepper_width;
3554 priv->stepper_c.height = stepper_height;
3558 priv->stepper_c.width = 0;
3559 priv->stepper_c.height = 0;
3562 priv->stepper_c.x = priv->stepper_a.x;
3563 priv->stepper_c.y = priv->stepper_d.y - priv->stepper_c.height;
3565 /* Now the trough is the remaining space between steppers B and C,
3566 * if any, minus spacing
3568 priv->trough.x = range_rect.x;
3569 priv->trough.y = priv->stepper_b.y + priv->stepper_b.height + stepper_spacing * has_steppers_ab;
3570 priv->trough.width = range_rect.width;
3571 priv->trough.height = priv->stepper_c.y - priv->trough.y - stepper_spacing * has_steppers_cd;
3573 /* Slider fits into the trough, with stepper_spacing on either side,
3574 * and the size/position based on the adjustment or fixed, depending.
3576 priv->slider.x = priv->trough.x + focus_width + trough_border;
3577 priv->slider.width = priv->trough.width - (focus_width + trough_border) * 2;
3579 /* Compute slider position/length */
3581 gint y, bottom, top, height;
3583 top = priv->trough.y;
3584 bottom = priv->trough.y + priv->trough.height;
3586 if (! trough_under_steppers)
3588 top += trough_border;
3589 bottom -= trough_border;
3592 /* slider height is the fraction (page_size /
3593 * total_adjustment_range) times the trough height in pixels
3596 if (priv->adjustment->upper - priv->adjustment->lower != 0)
3597 height = ((bottom - top) * (priv->adjustment->page_size /
3598 (priv->adjustment->upper - priv->adjustment->lower)));
3600 height = priv->min_slider_size;
3602 if (height < priv->min_slider_size ||
3603 priv->slider_size_fixed)
3604 height = priv->min_slider_size;
3606 height = MIN (height, priv->trough.height);
3610 if (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size != 0)
3611 y += (bottom - top - height) * ((adjustment_value - priv->adjustment->lower) /
3612 (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size));
3614 y = CLAMP (y, top, bottom);
3616 if (should_invert (range))
3617 y = bottom - (y - top + height);
3620 priv->slider.height = height;
3622 /* These are publically exported */
3623 priv->slider_start = priv->slider.y;
3624 priv->slider_end = priv->slider.y + priv->slider.height;
3629 gint stepper_width, stepper_height;
3631 /* Steppers are the height of the range, and stepper_size in
3632 * width, or if we don't have enough width, divided equally
3633 * among available space.
3635 stepper_height = range_rect.height + focus_width * 2;
3637 if (trough_under_steppers)
3638 stepper_height -= trough_border * 2;
3640 if (stepper_height < 1)
3641 stepper_height = range_rect.height; /* screw the trough border */
3643 if (n_steppers == 0)
3644 stepper_width = 0; /* avoid divide by n_steppers */
3646 stepper_width = MIN (stepper_size, (range_rect.width / n_steppers));
3650 priv->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers;
3651 priv->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers;
3653 if (priv->has_stepper_a)
3655 priv->stepper_a.width = stepper_width;
3656 priv->stepper_a.height = stepper_height;
3660 priv->stepper_a.width = 0;
3661 priv->stepper_a.height = 0;
3666 priv->stepper_b.x = priv->stepper_a.x + priv->stepper_a.width;
3667 priv->stepper_b.y = priv->stepper_a.y;
3669 if (priv->has_stepper_b)
3671 priv->stepper_b.width = stepper_width;
3672 priv->stepper_b.height = stepper_height;
3676 priv->stepper_b.width = 0;
3677 priv->stepper_b.height = 0;
3682 if (priv->has_stepper_d)
3684 priv->stepper_d.width = stepper_width;
3685 priv->stepper_d.height = stepper_height;
3689 priv->stepper_d.width = 0;
3690 priv->stepper_d.height = 0;
3693 priv->stepper_d.x = range_rect.x + range_rect.width - priv->stepper_d.width - focus_width - trough_border * trough_under_steppers;
3694 priv->stepper_d.y = priv->stepper_a.y;
3699 if (priv->has_stepper_c)
3701 priv->stepper_c.width = stepper_width;
3702 priv->stepper_c.height = stepper_height;
3706 priv->stepper_c.width = 0;
3707 priv->stepper_c.height = 0;
3710 priv->stepper_c.x = priv->stepper_d.x - priv->stepper_c.width;
3711 priv->stepper_c.y = priv->stepper_a.y;
3713 /* Now the trough is the remaining space between steppers B and C,
3716 priv->trough.x = priv->stepper_b.x + priv->stepper_b.width + stepper_spacing * has_steppers_ab;
3717 priv->trough.y = range_rect.y;
3719 priv->trough.width = priv->stepper_c.x - priv->trough.x - stepper_spacing * has_steppers_cd;
3720 priv->trough.height = range_rect.height;
3722 /* Slider fits into the trough, with stepper_spacing on either side,
3723 * and the size/position based on the adjustment or fixed, depending.
3725 priv->slider.y = priv->trough.y + focus_width + trough_border;
3726 priv->slider.height = priv->trough.height - (focus_width + trough_border) * 2;
3728 /* Compute slider position/length */
3730 gint x, left, right, width;
3732 left = priv->trough.x;
3733 right = priv->trough.x + priv->trough.width;
3735 if (! trough_under_steppers)
3737 left += trough_border;
3738 right -= trough_border;
3741 /* slider width is the fraction (page_size /
3742 * total_adjustment_range) times the trough width in pixels
3745 if (priv->adjustment->upper - priv->adjustment->lower != 0)
3746 width = ((right - left) * (priv->adjustment->page_size /
3747 (priv->adjustment->upper - priv->adjustment->lower)));
3749 width = priv->min_slider_size;
3751 if (width < priv->min_slider_size ||
3752 priv->slider_size_fixed)
3753 width = priv->min_slider_size;
3755 width = MIN (width, priv->trough.width);
3759 if (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size != 0)
3760 x += (right - left - width) * ((adjustment_value - priv->adjustment->lower) /
3761 (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size));
3763 x = CLAMP (x, left, right);
3765 if (should_invert (range))
3766 x = right - (x - left + width);
3769 priv->slider.width = width;
3771 /* These are publically exported */
3772 priv->slider_start = priv->slider.x;
3773 priv->slider_end = priv->slider.x + priv->slider.width;
3777 gtk_range_update_mouse_location (range);
3779 switch (priv->upper_sensitivity)
3781 case GTK_SENSITIVITY_AUTO:
3782 priv->upper_sensitive =
3783 (priv->adjustment->value <
3784 (priv->adjustment->upper - priv->adjustment->page_size));
3787 case GTK_SENSITIVITY_ON:
3788 priv->upper_sensitive = TRUE;
3791 case GTK_SENSITIVITY_OFF:
3792 priv->upper_sensitive = FALSE;
3796 switch (priv->lower_sensitivity)
3798 case GTK_SENSITIVITY_AUTO:
3799 priv->lower_sensitive =
3800 (priv->adjustment->value > priv->adjustment->lower);
3803 case GTK_SENSITIVITY_ON:
3804 priv->lower_sensitive = TRUE;
3807 case GTK_SENSITIVITY_OFF:
3808 priv->lower_sensitive = FALSE;
3813 static GdkRectangle*
3814 get_area (GtkRange *range,
3815 MouseLocation location)
3817 GtkRangePrivate *priv = range->priv;
3821 case MOUSE_STEPPER_A:
3822 return &priv->stepper_a;
3823 case MOUSE_STEPPER_B:
3824 return &priv->stepper_b;
3825 case MOUSE_STEPPER_C:
3826 return &priv->stepper_c;
3827 case MOUSE_STEPPER_D:
3828 return &priv->stepper_d;
3830 return &priv->trough;
3832 return &priv->slider;
3838 g_warning (G_STRLOC": bug");
3843 gtk_range_calc_marks (GtkRange *range)
3845 GtkRangePrivate *priv = range->priv;
3848 if (!priv->recalc_marks)
3851 priv->recalc_marks = FALSE;
3853 for (i = 0; i < priv->n_marks; i++)
3855 priv->need_recalc = TRUE;
3856 gtk_range_calc_layout (range, priv->marks[i]);
3857 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
3858 priv->mark_pos[i] = priv->slider.x + priv->slider.width / 2;
3860 priv->mark_pos[i] = priv->slider.y + priv->slider.height / 2;
3863 priv->need_recalc = TRUE;
3867 gtk_range_real_change_value (GtkRange *range,
3868 GtkScrollType scroll,
3871 GtkRangePrivate *priv = range->priv;
3873 /* potentially adjust the bounds _before_ we clamp */
3874 g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value);
3876 if (priv->restrict_to_fill_level)
3877 value = MIN (value, MAX (priv->adjustment->lower,
3880 value = CLAMP (value, priv->adjustment->lower,
3881 (priv->adjustment->upper - priv->adjustment->page_size));
3883 if (priv->round_digits >= 0)
3888 i = priv->round_digits;
3893 value = floor ((value * power) + 0.5) / power;
3896 if (priv->adjustment->value != value)
3898 priv->need_recalc = TRUE;
3900 gtk_widget_queue_draw (GTK_WIDGET (range));
3902 switch (priv->update_policy)
3904 case GTK_UPDATE_CONTINUOUS:
3905 gtk_adjustment_set_value (priv->adjustment, value);
3908 /* Delayed means we update after a period of inactivity */
3909 case GTK_UPDATE_DELAYED:
3910 gtk_range_reset_update_timer (range);
3913 /* Discontinuous means we update on button release */
3914 case GTK_UPDATE_DISCONTINUOUS:
3915 /* don't emit value_changed signal */
3916 priv->adjustment->value = value;
3917 priv->update_pending = TRUE;
3925 gtk_range_update_value (GtkRange *range)
3927 GtkRangePrivate *priv = range->priv;
3929 gtk_range_remove_update_timer (range);
3931 if (priv->update_pending)
3933 gtk_adjustment_value_changed (priv->adjustment);
3935 priv->update_pending = FALSE;
3939 struct _GtkRangeStepTimer
3946 second_timeout (gpointer data)
3948 GtkRange *range = GTK_RANGE (data);
3949 GtkRangePrivate *priv = range->priv;
3951 gtk_range_scroll (range, priv->timer->step);
3957 initial_timeout (gpointer data)
3959 GtkRange *range = GTK_RANGE (data);
3960 GtkRangePrivate *priv = range->priv;
3961 GtkSettings *settings;
3964 settings = gtk_widget_get_settings (GTK_WIDGET (data));
3965 g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
3967 priv->timer->timeout_id = gdk_threads_add_timeout (timeout * SCROLL_DELAY_FACTOR,
3975 gtk_range_add_step_timer (GtkRange *range,
3978 GtkRangePrivate *priv = range->priv;
3979 GtkSettings *settings;
3982 g_return_if_fail (priv->timer == NULL);
3983 g_return_if_fail (step != GTK_SCROLL_NONE);
3985 settings = gtk_widget_get_settings (GTK_WIDGET (range));
3986 g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
3988 priv->timer = g_new (GtkRangeStepTimer, 1);
3990 priv->timer->timeout_id = gdk_threads_add_timeout (timeout,
3993 priv->timer->step = step;
3995 gtk_range_scroll (range, priv->timer->step);
3999 gtk_range_remove_step_timer (GtkRange *range)
4001 GtkRangePrivate *priv = range->priv;
4005 if (priv->timer->timeout_id != 0)
4006 g_source_remove (priv->timer->timeout_id);
4008 g_free (priv->timer);
4015 update_timeout (gpointer data)
4017 GtkRange *range = GTK_RANGE (data);
4018 GtkRangePrivate *priv = range->priv;
4020 gtk_range_update_value (range);
4021 priv->update_timeout_id = 0;
4028 gtk_range_reset_update_timer (GtkRange *range)
4030 GtkRangePrivate *priv = range->priv;
4032 gtk_range_remove_update_timer (range);
4034 priv->update_timeout_id = gdk_threads_add_timeout (UPDATE_DELAY,
4040 gtk_range_remove_update_timer (GtkRange *range)
4042 GtkRangePrivate *priv = range->priv;
4044 if (priv->update_timeout_id != 0)
4046 g_source_remove (priv->update_timeout_id);
4047 priv->update_timeout_id = 0;
4052 _gtk_range_set_stop_values (GtkRange *range,
4056 GtkRangePrivate *priv = range->priv;
4059 g_free (priv->marks);
4060 priv->marks = g_new (gdouble, n_values);
4062 g_free (priv->mark_pos);
4063 priv->mark_pos = g_new (gint, n_values);
4065 priv->n_marks = n_values;
4067 for (i = 0; i < n_values; i++)
4068 priv->marks[i] = values[i];
4070 priv->recalc_marks = TRUE;
4074 _gtk_range_get_stop_positions (GtkRange *range,
4077 GtkRangePrivate *priv = range->priv;
4079 gtk_range_calc_marks (range);
4082 *values = g_memdup (priv->mark_pos, priv->n_marks * sizeof (gint));
4084 return priv->n_marks;
4088 _gtk_range_set_round_digits (GtkRange *range,
4091 range->priv->round_digits = round_digits;
4095 _gtk_range_set_steppers (GtkRange *range,
4101 range->priv->has_stepper_a = has_a;
4102 range->priv->has_stepper_b = has_b;
4103 range->priv->has_stepper_c = has_c;
4104 range->priv->has_stepper_d = has_d;