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));
601 * GtkRange:stepper-position-details:
603 * When %TRUE, the detail string for rendering the steppers will be
604 * suffixed with information about the stepper position.
608 gtk_widget_class_install_style_property (widget_class,
609 g_param_spec_boolean ("stepper-position-details",
610 P_("Stepper Position Details"),
611 P_("When TRUE, the detail string for rendering the steppers is suffixed with position information"),
613 GTK_PARAM_READABLE));
615 g_type_class_add_private (class, sizeof (GtkRangePrivate));
619 gtk_range_set_property (GObject *object,
624 GtkRange *range = GTK_RANGE (object);
625 GtkRangePrivate *priv = range->priv;
629 case PROP_ORIENTATION:
630 priv->orientation = g_value_get_enum (value);
632 priv->slider_detail_quark = 0;
633 priv->stepper_detail_quark[0] = 0;
634 priv->stepper_detail_quark[1] = 0;
635 priv->stepper_detail_quark[2] = 0;
636 priv->stepper_detail_quark[3] = 0;
638 gtk_widget_queue_resize (GTK_WIDGET (range));
640 case PROP_UPDATE_POLICY:
641 gtk_range_set_update_policy (range, g_value_get_enum (value));
643 case PROP_ADJUSTMENT:
644 gtk_range_set_adjustment (range, g_value_get_object (value));
647 gtk_range_set_inverted (range, g_value_get_boolean (value));
649 case PROP_LOWER_STEPPER_SENSITIVITY:
650 gtk_range_set_lower_stepper_sensitivity (range, g_value_get_enum (value));
652 case PROP_UPPER_STEPPER_SENSITIVITY:
653 gtk_range_set_upper_stepper_sensitivity (range, g_value_get_enum (value));
655 case PROP_SHOW_FILL_LEVEL:
656 gtk_range_set_show_fill_level (range, g_value_get_boolean (value));
658 case PROP_RESTRICT_TO_FILL_LEVEL:
659 gtk_range_set_restrict_to_fill_level (range, g_value_get_boolean (value));
661 case PROP_FILL_LEVEL:
662 gtk_range_set_fill_level (range, g_value_get_double (value));
665 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
671 gtk_range_get_property (GObject *object,
676 GtkRange *range = GTK_RANGE (object);
677 GtkRangePrivate *priv = range->priv;
681 case PROP_ORIENTATION:
682 g_value_set_enum (value, priv->orientation);
684 case PROP_UPDATE_POLICY:
685 g_value_set_enum (value, priv->update_policy);
687 case PROP_ADJUSTMENT:
688 g_value_set_object (value, priv->adjustment);
691 g_value_set_boolean (value, priv->inverted);
693 case PROP_LOWER_STEPPER_SENSITIVITY:
694 g_value_set_enum (value, gtk_range_get_lower_stepper_sensitivity (range));
696 case PROP_UPPER_STEPPER_SENSITIVITY:
697 g_value_set_enum (value, gtk_range_get_upper_stepper_sensitivity (range));
699 case PROP_SHOW_FILL_LEVEL:
700 g_value_set_boolean (value, gtk_range_get_show_fill_level (range));
702 case PROP_RESTRICT_TO_FILL_LEVEL:
703 g_value_set_boolean (value, gtk_range_get_restrict_to_fill_level (range));
705 case PROP_FILL_LEVEL:
706 g_value_set_double (value, gtk_range_get_fill_level (range));
709 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
715 gtk_range_init (GtkRange *range)
717 GtkRangePrivate *priv;
719 range->priv = G_TYPE_INSTANCE_GET_PRIVATE (range,
724 gtk_widget_set_has_window (GTK_WIDGET (range), FALSE);
726 priv->orientation = GTK_ORIENTATION_HORIZONTAL;
727 priv->adjustment = NULL;
728 priv->update_policy = GTK_UPDATE_CONTINUOUS;
729 priv->inverted = FALSE;
730 priv->flippable = FALSE;
731 priv->min_slider_size = 1;
732 priv->has_stepper_a = FALSE;
733 priv->has_stepper_b = FALSE;
734 priv->has_stepper_c = FALSE;
735 priv->has_stepper_d = FALSE;
736 priv->need_recalc = TRUE;
737 priv->round_digits = -1;
738 priv->mouse_location = MOUSE_OUTSIDE;
741 priv->grab_location = MOUSE_OUTSIDE;
742 priv->grab_button = 0;
743 priv->lower_sensitivity = GTK_SENSITIVITY_AUTO;
744 priv->upper_sensitivity = GTK_SENSITIVITY_AUTO;
745 priv->lower_sensitive = TRUE;
746 priv->upper_sensitive = TRUE;
747 priv->show_fill_level = FALSE;
748 priv->restrict_to_fill_level = TRUE;
749 priv->fill_level = G_MAXDOUBLE;
754 * gtk_range_get_adjustment:
755 * @range: a #GtkRange
757 * Get the #GtkAdjustment which is the "model" object for #GtkRange.
758 * See gtk_range_set_adjustment() for details.
759 * The return value does not have a reference added, so should not
762 * Return value: a #GtkAdjustment
765 gtk_range_get_adjustment (GtkRange *range)
767 GtkRangePrivate *priv;
769 g_return_val_if_fail (GTK_IS_RANGE (range), NULL);
773 if (!priv->adjustment)
774 gtk_range_set_adjustment (range, NULL);
776 return priv->adjustment;
780 * gtk_range_set_update_policy:
781 * @range: a #GtkRange
782 * @policy: update policy
784 * Sets the update policy for the range. #GTK_UPDATE_CONTINUOUS means that
785 * anytime the range slider is moved, the range value will change and the
786 * value_changed signal will be emitted. #GTK_UPDATE_DELAYED means that
787 * the value will be updated after a brief timeout where no slider motion
788 * occurs, so updates are spaced by a short time rather than
789 * continuous. #GTK_UPDATE_DISCONTINUOUS means that the value will only
790 * be updated when the user releases the button and ends the slider
794 gtk_range_set_update_policy (GtkRange *range,
795 GtkUpdateType policy)
797 GtkRangePrivate *priv;
799 g_return_if_fail (GTK_IS_RANGE (range));
803 if (priv->update_policy != policy)
805 priv->update_policy = policy;
806 g_object_notify (G_OBJECT (range), "update-policy");
811 * gtk_range_get_update_policy:
812 * @range: a #GtkRange
814 * Gets the update policy of @range. See gtk_range_set_update_policy().
816 * Return value: the current update policy
819 gtk_range_get_update_policy (GtkRange *range)
821 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_UPDATE_CONTINUOUS);
823 return range->priv->update_policy;
827 * gtk_range_set_adjustment:
828 * @range: a #GtkRange
829 * @adjustment: a #GtkAdjustment
831 * Sets the adjustment to be used as the "model" object for this range
832 * widget. The adjustment indicates the current range value, the
833 * minimum and maximum range values, the step/page increments used
834 * for keybindings and scrolling, and the page size. The page size
835 * is normally 0 for #GtkScale and nonzero for #GtkScrollbar, and
836 * indicates the size of the visible area of the widget being scrolled.
837 * The page size affects the size of the scrollbar slider.
840 gtk_range_set_adjustment (GtkRange *range,
841 GtkAdjustment *adjustment)
843 GtkRangePrivate *priv;
845 g_return_if_fail (GTK_IS_RANGE (range));
850 adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
852 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
854 if (priv->adjustment != adjustment)
856 if (priv->adjustment)
858 g_signal_handlers_disconnect_by_func (priv->adjustment,
859 gtk_range_adjustment_changed,
861 g_signal_handlers_disconnect_by_func (priv->adjustment,
862 gtk_range_adjustment_value_changed,
864 g_object_unref (priv->adjustment);
867 priv->adjustment = adjustment;
868 g_object_ref_sink (adjustment);
870 g_signal_connect (adjustment, "changed",
871 G_CALLBACK (gtk_range_adjustment_changed),
873 g_signal_connect (adjustment, "value-changed",
874 G_CALLBACK (gtk_range_adjustment_value_changed),
877 gtk_range_adjustment_changed (adjustment, range);
878 g_object_notify (G_OBJECT (range), "adjustment");
883 * gtk_range_set_inverted:
884 * @range: a #GtkRange
885 * @setting: %TRUE to invert the range
887 * Ranges normally move from lower to higher values as the
888 * slider moves from top to bottom or left to right. Inverted
889 * ranges have higher values at the top or on the right rather than
890 * on the bottom or left.
893 gtk_range_set_inverted (GtkRange *range,
896 GtkRangePrivate *priv;
898 g_return_if_fail (GTK_IS_RANGE (range));
902 setting = setting != FALSE;
904 if (setting != priv->inverted)
906 priv->inverted = setting;
907 g_object_notify (G_OBJECT (range), "inverted");
908 gtk_widget_queue_resize (GTK_WIDGET (range));
913 * gtk_range_get_inverted:
914 * @range: a #GtkRange
916 * Gets the value set by gtk_range_set_inverted().
918 * Return value: %TRUE if the range is inverted
921 gtk_range_get_inverted (GtkRange *range)
923 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
925 return range->priv->inverted;
929 * gtk_range_set_flippable:
930 * @range: a #GtkRange
931 * @flippable: %TRUE to make the range flippable
933 * If a range is flippable, it will switch its direction if it is
934 * horizontal and its direction is %GTK_TEXT_DIR_RTL.
936 * See gtk_widget_get_direction().
941 gtk_range_set_flippable (GtkRange *range,
944 GtkRangePrivate *priv;
946 g_return_if_fail (GTK_IS_RANGE (range));
950 flippable = flippable ? TRUE : FALSE;
952 if (flippable != priv->flippable)
954 priv->flippable = flippable;
956 gtk_widget_queue_draw (GTK_WIDGET (range));
961 * gtk_range_get_flippable:
962 * @range: a #GtkRange
964 * Gets the value set by gtk_range_set_flippable().
966 * Return value: %TRUE if the range is flippable
971 gtk_range_get_flippable (GtkRange *range)
973 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
975 return range->priv->flippable;
979 * gtk_range_set_slider_size_fixed:
980 * @range: a #GtkRange
981 * @size_fixed: %TRUE to make the slider size constant
983 * Sets whether the range's slider has a fixed size, or a size that
984 * depends on it's adjustment's page size.
986 * This function is useful mainly for #GtkRange subclasses.
991 gtk_range_set_slider_size_fixed (GtkRange *range,
994 GtkRangePrivate *priv;
996 g_return_if_fail (GTK_IS_RANGE (range));
1000 if (size_fixed != priv->slider_size_fixed)
1002 priv->slider_size_fixed = size_fixed ? TRUE : FALSE;
1004 if (priv->adjustment && gtk_widget_get_mapped (GTK_WIDGET (range)))
1006 priv->need_recalc = TRUE;
1007 gtk_range_calc_layout (range, gtk_adjustment_get_value (priv->adjustment));
1008 gtk_widget_queue_draw (GTK_WIDGET (range));
1014 * gtk_range_get_slider_size_fixed:
1015 * @range: a #GtkRange
1017 * This function is useful mainly for #GtkRange subclasses.
1019 * See gtk_range_set_slider_size_fixed().
1021 * Return value: whether the range's slider has a fixed size.
1026 gtk_range_get_slider_size_fixed (GtkRange *range)
1028 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1030 return range->priv->slider_size_fixed;
1034 * gtk_range_set_min_slider_size:
1035 * @range: a #GtkRange
1036 * @min_size: The slider's minimum size
1038 * Sets the minimum size of the range's slider.
1040 * This function is useful mainly for #GtkRange subclasses.
1045 gtk_range_set_min_slider_size (GtkRange *range,
1048 GtkRangePrivate *priv;
1050 g_return_if_fail (GTK_IS_RANGE (range));
1051 g_return_if_fail (min_size > 0);
1055 if (min_size != priv->min_slider_size)
1057 priv->min_slider_size = min_size;
1059 priv->need_recalc = TRUE;
1060 gtk_range_calc_layout (range, priv->adjustment->value);
1061 gtk_widget_queue_draw (GTK_WIDGET (range));
1066 * gtk_range_get_min_slider_size:
1067 * @range: a #GtkRange
1069 * This function is useful mainly for #GtkRange subclasses.
1071 * See gtk_range_set_min_slider_size().
1073 * Return value: The minimum size of the range's slider.
1078 gtk_range_get_min_slider_size (GtkRange *range)
1080 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1082 return range->priv->min_slider_size;
1086 * gtk_range_get_range_rect:
1087 * @range: a #GtkRange
1088 * @range_rect: return location for the range rectangle
1090 * This function returns the area that contains the range's trough
1091 * and its steppers, in widget->window coordinates.
1093 * This function is useful mainly for #GtkRange subclasses.
1098 gtk_range_get_range_rect (GtkRange *range,
1099 GdkRectangle *range_rect)
1101 GtkRangePrivate *priv;
1103 g_return_if_fail (GTK_IS_RANGE (range));
1104 g_return_if_fail (range_rect != NULL);
1108 gtk_range_calc_layout (range, priv->adjustment->value);
1110 *range_rect = priv->range_rect;
1114 * gtk_range_get_slider_range:
1115 * @range: a #GtkRange
1116 * @slider_start: (allow-none): return location for the slider's start, or %NULL
1117 * @slider_end: (allow-none): return location for the slider's end, or %NULL
1119 * This function returns sliders range along the long dimension,
1120 * in widget->window coordinates.
1122 * This function is useful mainly for #GtkRange subclasses.
1127 gtk_range_get_slider_range (GtkRange *range,
1131 GtkRangePrivate *priv;
1133 g_return_if_fail (GTK_IS_RANGE (range));
1137 gtk_range_calc_layout (range, priv->adjustment->value);
1140 *slider_start = priv->slider_start;
1143 *slider_end = priv->slider_end;
1147 * gtk_range_set_lower_stepper_sensitivity:
1148 * @range: a #GtkRange
1149 * @sensitivity: the lower stepper's sensitivity policy.
1151 * Sets the sensitivity policy for the stepper that points to the
1152 * 'lower' end of the GtkRange's adjustment.
1157 gtk_range_set_lower_stepper_sensitivity (GtkRange *range,
1158 GtkSensitivityType sensitivity)
1160 GtkRangePrivate *priv;
1162 g_return_if_fail (GTK_IS_RANGE (range));
1166 if (priv->lower_sensitivity != sensitivity)
1168 priv->lower_sensitivity = sensitivity;
1170 priv->need_recalc = TRUE;
1171 gtk_range_calc_layout (range, priv->adjustment->value);
1172 gtk_widget_queue_draw (GTK_WIDGET (range));
1174 g_object_notify (G_OBJECT (range), "lower-stepper-sensitivity");
1179 * gtk_range_get_lower_stepper_sensitivity:
1180 * @range: a #GtkRange
1182 * Gets the sensitivity policy for the stepper that points to the
1183 * 'lower' end of the GtkRange's adjustment.
1185 * Return value: The lower stepper's sensitivity policy.
1190 gtk_range_get_lower_stepper_sensitivity (GtkRange *range)
1192 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
1194 return range->priv->lower_sensitivity;
1198 * gtk_range_set_upper_stepper_sensitivity:
1199 * @range: a #GtkRange
1200 * @sensitivity: the upper stepper's sensitivity policy.
1202 * Sets the sensitivity policy for the stepper that points to the
1203 * 'upper' end of the GtkRange's adjustment.
1208 gtk_range_set_upper_stepper_sensitivity (GtkRange *range,
1209 GtkSensitivityType sensitivity)
1211 GtkRangePrivate *priv;
1213 g_return_if_fail (GTK_IS_RANGE (range));
1217 if (priv->upper_sensitivity != sensitivity)
1219 priv->upper_sensitivity = sensitivity;
1221 priv->need_recalc = TRUE;
1222 gtk_range_calc_layout (range, priv->adjustment->value);
1223 gtk_widget_queue_draw (GTK_WIDGET (range));
1225 g_object_notify (G_OBJECT (range), "upper-stepper-sensitivity");
1230 * gtk_range_get_upper_stepper_sensitivity:
1231 * @range: a #GtkRange
1233 * Gets the sensitivity policy for the stepper that points to the
1234 * 'upper' end of the GtkRange's adjustment.
1236 * Return value: The upper stepper's sensitivity policy.
1241 gtk_range_get_upper_stepper_sensitivity (GtkRange *range)
1243 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
1245 return range->priv->upper_sensitivity;
1249 * gtk_range_set_increments:
1250 * @range: a #GtkRange
1254 * Sets the step and page sizes for the range.
1255 * The step size is used when the user clicks the #GtkScrollbar
1256 * arrows or moves #GtkScale via arrow keys. The page size
1257 * is used for example when moving via Page Up or Page Down keys.
1260 gtk_range_set_increments (GtkRange *range,
1264 GtkRangePrivate *priv;
1266 g_return_if_fail (GTK_IS_RANGE (range));
1270 priv->adjustment->step_increment = step;
1271 priv->adjustment->page_increment = page;
1273 gtk_adjustment_changed (priv->adjustment);
1277 * gtk_range_set_range:
1278 * @range: a #GtkRange
1279 * @min: minimum range value
1280 * @max: maximum range value
1282 * Sets the allowable values in the #GtkRange, and clamps the range
1283 * value to be between @min and @max. (If the range has a non-zero
1284 * page size, it is clamped between @min and @max - page-size.)
1287 gtk_range_set_range (GtkRange *range,
1291 GtkRangePrivate *priv;
1294 g_return_if_fail (GTK_IS_RANGE (range));
1295 g_return_if_fail (min < max);
1299 priv->adjustment->lower = min;
1300 priv->adjustment->upper = max;
1302 value = priv->adjustment->value;
1304 if (priv->restrict_to_fill_level)
1305 value = MIN (value, MAX (priv->adjustment->lower,
1308 gtk_adjustment_set_value (priv->adjustment, value);
1309 gtk_adjustment_changed (priv->adjustment);
1313 * gtk_range_set_value:
1314 * @range: a #GtkRange
1315 * @value: new value of the range
1317 * Sets the current value of the range; if the value is outside the
1318 * minimum or maximum range values, it will be clamped to fit inside
1319 * them. The range emits the #GtkRange::value-changed signal if the
1323 gtk_range_set_value (GtkRange *range,
1326 GtkRangePrivate *priv;
1328 g_return_if_fail (GTK_IS_RANGE (range));
1332 if (priv->restrict_to_fill_level)
1333 value = MIN (value, MAX (priv->adjustment->lower,
1336 gtk_adjustment_set_value (priv->adjustment, value);
1340 * gtk_range_get_value:
1341 * @range: a #GtkRange
1343 * Gets the current value of the range.
1345 * Return value: current value of the range.
1348 gtk_range_get_value (GtkRange *range)
1350 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1352 return range->priv->adjustment->value;
1356 * gtk_range_set_show_fill_level:
1357 * @range: A #GtkRange
1358 * @show_fill_level: Whether a fill level indicator graphics is shown.
1360 * Sets whether a graphical fill level is show on the trough. See
1361 * gtk_range_set_fill_level() for a general description of the fill
1367 gtk_range_set_show_fill_level (GtkRange *range,
1368 gboolean show_fill_level)
1370 GtkRangePrivate *priv;
1372 g_return_if_fail (GTK_IS_RANGE (range));
1376 show_fill_level = show_fill_level ? TRUE : FALSE;
1378 if (show_fill_level != priv->show_fill_level)
1380 priv->show_fill_level = show_fill_level;
1381 g_object_notify (G_OBJECT (range), "show-fill-level");
1382 gtk_widget_queue_draw (GTK_WIDGET (range));
1387 * gtk_range_get_show_fill_level:
1388 * @range: A #GtkRange
1390 * Gets whether the range displays the fill level graphically.
1392 * Return value: %TRUE if @range shows the fill level.
1397 gtk_range_get_show_fill_level (GtkRange *range)
1399 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1401 return range->priv->show_fill_level;
1405 * gtk_range_set_restrict_to_fill_level:
1406 * @range: A #GtkRange
1407 * @restrict_to_fill_level: Whether the fill level restricts slider movement.
1409 * Sets whether the slider is restricted to the fill level. See
1410 * gtk_range_set_fill_level() for a general description of the fill
1416 gtk_range_set_restrict_to_fill_level (GtkRange *range,
1417 gboolean restrict_to_fill_level)
1419 GtkRangePrivate *priv;
1421 g_return_if_fail (GTK_IS_RANGE (range));
1425 restrict_to_fill_level = restrict_to_fill_level ? TRUE : FALSE;
1427 if (restrict_to_fill_level != priv->restrict_to_fill_level)
1429 priv->restrict_to_fill_level = restrict_to_fill_level;
1430 g_object_notify (G_OBJECT (range), "restrict-to-fill-level");
1432 gtk_range_set_value (range, gtk_range_get_value (range));
1437 * gtk_range_get_restrict_to_fill_level:
1438 * @range: A #GtkRange
1440 * Gets whether the range is restricted to the fill level.
1442 * Return value: %TRUE if @range is restricted to the fill level.
1447 gtk_range_get_restrict_to_fill_level (GtkRange *range)
1449 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1451 return range->priv->restrict_to_fill_level;
1455 * gtk_range_set_fill_level:
1456 * @range: a #GtkRange
1457 * @fill_level: the new position of the fill level indicator
1459 * Set the new position of the fill level indicator.
1461 * The "fill level" is probably best described by its most prominent
1462 * use case, which is an indicator for the amount of pre-buffering in
1463 * a streaming media player. In that use case, the value of the range
1464 * would indicate the current play position, and the fill level would
1465 * be the position up to which the file/stream has been downloaded.
1467 * This amount of prebuffering can be displayed on the range's trough
1468 * and is themeable separately from the trough. To enable fill level
1469 * display, use gtk_range_set_show_fill_level(). The range defaults
1470 * to not showing the fill level.
1472 * Additionally, it's possible to restrict the range's slider position
1473 * to values which are smaller than the fill level. This is controller
1474 * by gtk_range_set_restrict_to_fill_level() and is by default
1480 gtk_range_set_fill_level (GtkRange *range,
1483 GtkRangePrivate *priv;
1485 g_return_if_fail (GTK_IS_RANGE (range));
1489 if (fill_level != priv->fill_level)
1491 priv->fill_level = fill_level;
1492 g_object_notify (G_OBJECT (range), "fill-level");
1494 if (priv->show_fill_level)
1495 gtk_widget_queue_draw (GTK_WIDGET (range));
1497 if (priv->restrict_to_fill_level)
1498 gtk_range_set_value (range, gtk_range_get_value (range));
1503 * gtk_range_get_fill_level:
1504 * @range : A #GtkRange
1506 * Gets the current position of the fill level indicator.
1508 * Return value: The current fill level
1513 gtk_range_get_fill_level (GtkRange *range)
1515 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1517 return range->priv->fill_level;
1521 should_invert (GtkRange *range)
1523 GtkRangePrivate *priv = range->priv;
1525 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1527 (priv->inverted && !priv->flippable) ||
1528 (priv->inverted && priv->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
1529 (!priv->inverted && priv->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
1531 return priv->inverted;
1535 gtk_range_destroy (GtkObject *object)
1537 GtkRange *range = GTK_RANGE (object);
1538 GtkRangePrivate *priv = range->priv;
1540 gtk_range_remove_step_timer (range);
1541 gtk_range_remove_update_timer (range);
1543 if (priv->repaint_id)
1544 g_source_remove (priv->repaint_id);
1545 priv->repaint_id = 0;
1547 if (priv->adjustment)
1549 g_signal_handlers_disconnect_by_func (priv->adjustment,
1550 gtk_range_adjustment_changed,
1552 g_signal_handlers_disconnect_by_func (priv->adjustment,
1553 gtk_range_adjustment_value_changed,
1555 g_object_unref (priv->adjustment);
1556 priv->adjustment = NULL;
1561 g_free (priv->marks);
1563 g_free (priv->mark_pos);
1564 priv->mark_pos = NULL;
1568 GTK_OBJECT_CLASS (gtk_range_parent_class)->destroy (object);
1572 gtk_range_size_request (GtkWidget *widget,
1573 GtkRequisition *requisition)
1576 gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing;
1577 GdkRectangle range_rect;
1580 range = GTK_RANGE (widget);
1582 gtk_range_get_props (range,
1583 &slider_width, &stepper_size,
1584 &focus_width, &trough_border,
1585 &stepper_spacing, NULL,
1588 gtk_range_calc_request (range,
1589 slider_width, stepper_size,
1590 focus_width, trough_border, stepper_spacing,
1591 &range_rect, &border, NULL, NULL, NULL, NULL);
1593 requisition->width = range_rect.width + border.left + border.right;
1594 requisition->height = range_rect.height + border.top + border.bottom;
1598 gtk_range_size_allocate (GtkWidget *widget,
1599 GtkAllocation *allocation)
1601 GtkRange *range = GTK_RANGE (widget);
1602 GtkRangePrivate *priv = range->priv;
1604 gtk_widget_set_allocation (widget, allocation);
1606 priv->recalc_marks = TRUE;
1608 priv->need_recalc = TRUE;
1609 gtk_range_calc_layout (range, priv->adjustment->value);
1611 if (gtk_widget_get_realized (widget))
1612 gdk_window_move_resize (priv->event_window,
1613 allocation->x, allocation->y,
1614 allocation->width, allocation->height);
1618 gtk_range_realize (GtkWidget *widget)
1620 GtkAllocation allocation;
1621 GtkRange *range = GTK_RANGE (widget);
1622 GtkRangePrivate *priv = range->priv;
1624 GdkWindowAttr attributes;
1625 gint attributes_mask;
1627 gtk_range_calc_layout (range, priv->adjustment->value);
1629 gtk_widget_set_realized (widget, TRUE);
1631 window = gtk_widget_get_parent_window (widget);
1632 gtk_widget_set_window (widget, window);
1633 g_object_ref (window);
1635 gtk_widget_get_allocation (widget, &allocation);
1637 attributes.window_type = GDK_WINDOW_CHILD;
1638 attributes.x = allocation.x;
1639 attributes.y = allocation.y;
1640 attributes.width = allocation.width;
1641 attributes.height = allocation.height;
1642 attributes.wclass = GDK_INPUT_ONLY;
1643 attributes.event_mask = gtk_widget_get_events (widget);
1644 attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
1645 GDK_BUTTON_RELEASE_MASK |
1646 GDK_ENTER_NOTIFY_MASK |
1647 GDK_LEAVE_NOTIFY_MASK |
1648 GDK_POINTER_MOTION_MASK |
1649 GDK_POINTER_MOTION_HINT_MASK);
1651 attributes_mask = GDK_WA_X | GDK_WA_Y;
1653 priv->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
1654 &attributes, attributes_mask);
1655 gdk_window_set_user_data (priv->event_window, range);
1657 gtk_widget_style_attach (widget);
1661 gtk_range_unrealize (GtkWidget *widget)
1663 GtkRange *range = GTK_RANGE (widget);
1664 GtkRangePrivate *priv = range->priv;
1666 gtk_range_remove_step_timer (range);
1667 gtk_range_remove_update_timer (range);
1669 gdk_window_set_user_data (priv->event_window, NULL);
1670 gdk_window_destroy (priv->event_window);
1671 priv->event_window = NULL;
1673 GTK_WIDGET_CLASS (gtk_range_parent_class)->unrealize (widget);
1677 gtk_range_map (GtkWidget *widget)
1679 GtkRange *range = GTK_RANGE (widget);
1680 GtkRangePrivate *priv = range->priv;
1682 gdk_window_show (priv->event_window);
1684 GTK_WIDGET_CLASS (gtk_range_parent_class)->map (widget);
1688 gtk_range_unmap (GtkWidget *widget)
1690 GtkRange *range = GTK_RANGE (widget);
1691 GtkRangePrivate *priv = range->priv;
1693 stop_scrolling (range);
1695 gdk_window_hide (priv->event_window);
1697 GTK_WIDGET_CLASS (gtk_range_parent_class)->unmap (widget);
1700 static const gchar *
1701 gtk_range_get_slider_detail (GtkRange *range)
1703 GtkRangePrivate *priv = range->priv;
1704 const gchar *slider_detail;
1706 if (priv->slider_detail_quark)
1707 return g_quark_to_string (priv->slider_detail_quark);
1709 slider_detail = GTK_RANGE_GET_CLASS (range)->slider_detail;
1711 if (slider_detail && slider_detail[0] == 'X')
1713 gchar *detail = g_strdup (slider_detail);
1715 detail[0] = priv->orientation == GTK_ORIENTATION_HORIZONTAL ? 'h' : 'v';
1717 priv->slider_detail_quark = g_quark_from_string (detail);
1721 return g_quark_to_string (priv->slider_detail_quark);
1724 return slider_detail;
1727 static const gchar *
1728 gtk_range_get_stepper_detail (GtkRange *range,
1731 GtkRangePrivate *priv = range->priv;
1732 const gchar *stepper_detail;
1733 gboolean need_orientation;
1734 gboolean need_position;
1736 if (priv->stepper_detail_quark[stepper])
1737 return g_quark_to_string (priv->stepper_detail_quark[stepper]);
1739 stepper_detail = GTK_RANGE_GET_CLASS (range)->stepper_detail;
1741 need_orientation = stepper_detail && stepper_detail[0] == 'X';
1743 gtk_widget_style_get (GTK_WIDGET (range),
1744 "stepper-position-details", &need_position,
1747 if (need_orientation || need_position)
1750 const gchar *position = NULL;
1757 position = "_start";
1760 if (priv->has_stepper_a)
1761 position = "_start_inner";
1763 position = "_start";
1766 if (priv->has_stepper_d)
1767 position = "_end_inner";
1775 g_assert_not_reached ();
1779 detail = g_strconcat (stepper_detail, position, NULL);
1781 if (need_orientation)
1782 detail[0] = priv->orientation == GTK_ORIENTATION_HORIZONTAL ? 'h' : 'v';
1784 priv->stepper_detail_quark[stepper] = g_quark_from_string (detail);
1788 return g_quark_to_string (priv->stepper_detail_quark[stepper]);
1791 return stepper_detail;
1795 draw_stepper (GtkRange *range,
1797 GtkArrowType arrow_type,
1799 gboolean prelighted,
1802 GtkRangePrivate *priv = range->priv;
1803 GtkAllocation allocation;
1804 GtkStateType state_type;
1805 GtkShadowType shadow_type;
1807 GtkWidget *widget = GTK_WIDGET (range);
1808 GdkRectangle intersection;
1810 gfloat arrow_scaling;
1820 rect = &priv->stepper_a;
1823 rect = &priv->stepper_b;
1826 rect = &priv->stepper_c;
1829 rect = &priv->stepper_d;
1832 g_assert_not_reached ();
1835 gboolean arrow_sensitive = TRUE;
1837 /* More to get the right clip region than for efficiency */
1838 if (!gdk_rectangle_intersect (area, rect, &intersection))
1841 gtk_widget_get_allocation (widget, &allocation);
1843 intersection.x += allocation.x;
1844 intersection.y += allocation.y;
1846 if ((!priv->inverted && (arrow_type == GTK_ARROW_DOWN ||
1847 arrow_type == GTK_ARROW_RIGHT)) ||
1848 (priv->inverted && (arrow_type == GTK_ARROW_UP ||
1849 arrow_type == GTK_ARROW_LEFT)))
1851 arrow_sensitive = priv->upper_sensitive;
1855 arrow_sensitive = priv->lower_sensitive;
1858 if (!gtk_widget_is_sensitive (GTK_WIDGET (range)) || !arrow_sensitive)
1859 state_type = GTK_STATE_INSENSITIVE;
1861 state_type = GTK_STATE_ACTIVE;
1862 else if (prelighted)
1863 state_type = GTK_STATE_PRELIGHT;
1865 state_type = GTK_STATE_NORMAL;
1867 if (clicked && arrow_sensitive)
1868 shadow_type = GTK_SHADOW_IN;
1870 shadow_type = GTK_SHADOW_OUT;
1872 style = gtk_widget_get_style (widget);
1873 window = gtk_widget_get_window (widget);
1875 gtk_paint_box (style, window,
1876 state_type, shadow_type,
1877 &intersection, widget,
1878 gtk_range_get_stepper_detail (range, stepper),
1879 allocation.x + rect->x,
1880 allocation.y + rect->y,
1884 gtk_widget_style_get (widget, "arrow-scaling", &arrow_scaling, NULL);
1886 arrow_width = rect->width * arrow_scaling;
1887 arrow_height = rect->height * arrow_scaling;
1888 arrow_x = allocation.x + rect->x + (rect->width - arrow_width) / 2;
1889 arrow_y = allocation.y + rect->y + (rect->height - arrow_height) / 2;
1891 if (clicked && arrow_sensitive)
1893 gint arrow_displacement_x;
1894 gint arrow_displacement_y;
1896 gtk_range_get_props (GTK_RANGE (widget),
1897 NULL, NULL, NULL, NULL, NULL, NULL,
1898 &arrow_displacement_x, &arrow_displacement_y);
1900 arrow_x += arrow_displacement_x;
1901 arrow_y += arrow_displacement_y;
1904 gtk_paint_arrow (style, window,
1905 state_type, shadow_type,
1906 &intersection, widget,
1907 gtk_range_get_stepper_detail (range, stepper),
1910 arrow_x, arrow_y, arrow_width, arrow_height);
1914 gtk_range_expose (GtkWidget *widget,
1915 GdkEventExpose *event)
1917 GtkAllocation allocation;
1918 GtkRange *range = GTK_RANGE (widget);
1919 GtkRangePrivate *priv = range->priv;
1922 GtkShadowType shadow_type;
1924 GdkRectangle expose_area; /* Relative to widget->allocation */
1927 gint focus_line_width = 0;
1928 gint focus_padding = 0;
1929 gboolean touchscreen;
1931 g_object_get (gtk_widget_get_settings (widget),
1932 "gtk-touchscreen-mode", &touchscreen,
1935 style = gtk_widget_get_style (widget);
1936 if (gtk_widget_get_can_focus (GTK_WIDGET (range)))
1937 gtk_widget_style_get (GTK_WIDGET (range),
1938 "focus-line-width", &focus_line_width,
1939 "focus-padding", &focus_padding,
1942 window = gtk_widget_get_window (widget);
1944 /* we're now exposing, so there's no need to force early repaints */
1945 if (priv->repaint_id)
1946 g_source_remove (priv->repaint_id);
1947 priv->repaint_id = 0;
1949 gtk_widget_get_allocation (widget, &allocation);
1951 expose_area = event->area;
1952 expose_area.x -= allocation.x;
1953 expose_area.y -= allocation.y;
1955 gtk_range_calc_marks (range);
1956 gtk_range_calc_layout (range, priv->adjustment->value);
1958 sensitive = gtk_widget_is_sensitive (widget);
1960 /* Just to be confusing, we draw the trough for the whole
1961 * range rectangle, not the trough rectangle (the trough
1962 * rectangle is just for hit detection)
1964 /* The gdk_rectangle_intersect is more to get the right
1965 * clip region (limited to range_rect) than for efficiency
1967 if (gdk_rectangle_intersect (&expose_area, &priv->range_rect,
1970 gint x = (allocation.x + priv->range_rect.x +
1971 focus_line_width + focus_padding);
1972 gint y = (allocation.y + priv->range_rect.y +
1973 focus_line_width + focus_padding);
1974 gint width = (priv->range_rect.width -
1975 2 * (focus_line_width + focus_padding));
1976 gint height = (priv->range_rect.height -
1977 2 * (focus_line_width + focus_padding));
1978 gboolean trough_under_steppers;
1980 gint stepper_spacing;
1982 area.x += allocation.x;
1983 area.y += allocation.y;
1985 gtk_widget_style_get (GTK_WIDGET (range),
1986 "trough-under-steppers", &trough_under_steppers,
1987 "stepper-size", &stepper_size,
1988 "stepper-spacing", &stepper_spacing,
1991 if (stepper_spacing > 0)
1992 trough_under_steppers = FALSE;
1994 if (!trough_under_steppers)
1999 if (priv->has_stepper_a)
2000 offset += stepper_size;
2002 if (priv->has_stepper_b)
2003 offset += stepper_size;
2007 if (priv->has_stepper_c)
2008 shorter += stepper_size;
2010 if (priv->has_stepper_d)
2011 shorter += stepper_size;
2013 if (priv->has_stepper_a || priv->has_stepper_b)
2015 offset += stepper_spacing;
2016 shorter += stepper_spacing;
2019 if (priv->has_stepper_c || priv->has_stepper_d)
2021 shorter += stepper_spacing;
2024 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2037 gint trough_change_pos_x = width;
2038 gint trough_change_pos_y = height;
2040 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2041 trough_change_pos_x = (priv->slider.x +
2042 priv->slider.width / 2 -
2043 (x - allocation.x));
2045 trough_change_pos_y = (priv->slider.y +
2046 priv->slider.height / 2 -
2047 (y - allocation.y));
2049 gtk_paint_box (style, window,
2050 sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
2052 &area, GTK_WIDGET (range),
2053 should_invert (range) ? "trough-upper" : "trough-lower",
2055 trough_change_pos_x, trough_change_pos_y);
2057 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2058 trough_change_pos_y = 0;
2060 trough_change_pos_x = 0;
2062 gtk_paint_box (style, window,
2063 sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
2065 &area, GTK_WIDGET (range),
2066 should_invert (range) ? "trough-lower" : "trough-upper",
2067 x + trough_change_pos_x, y + trough_change_pos_y,
2068 width - trough_change_pos_x,
2069 height - trough_change_pos_y);
2072 if (priv->show_fill_level &&
2073 priv->adjustment->upper - priv->adjustment->page_size -
2074 priv->adjustment->lower != 0)
2076 gdouble fill_level = priv->fill_level;
2079 gint fill_width = width;
2080 gint fill_height = height;
2083 fill_level = CLAMP (fill_level, priv->adjustment->lower,
2084 priv->adjustment->upper -
2085 priv->adjustment->page_size);
2087 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2089 fill_x = allocation.x + priv->trough.x;
2090 fill_width = (priv->slider.width +
2091 (fill_level - priv->adjustment->lower) /
2092 (priv->adjustment->upper -
2093 priv->adjustment->lower -
2094 priv->adjustment->page_size) *
2095 (priv->trough.width -
2096 priv->slider.width));
2098 if (should_invert (range))
2099 fill_x += priv->trough.width - fill_width;
2103 fill_y = allocation.y + priv->trough.y;
2104 fill_height = (priv->slider.height +
2105 (fill_level - priv->adjustment->lower) /
2106 (priv->adjustment->upper -
2107 priv->adjustment->lower -
2108 priv->adjustment->page_size) *
2109 (priv->trough.height -
2110 priv->slider.height));
2112 if (should_invert (range))
2113 fill_y += priv->trough.height - fill_height;
2116 if (fill_level < priv->adjustment->upper - priv->adjustment->page_size)
2117 fill_detail = "trough-fill-level-full";
2119 fill_detail = "trough-fill-level";
2121 gtk_paint_box (style, window,
2122 sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
2124 &area, GTK_WIDGET (range), fill_detail,
2126 fill_width, fill_height);
2129 if (sensitive && gtk_widget_has_focus (widget))
2130 gtk_paint_focus (style, window,
2131 gtk_widget_get_state (widget),
2132 &area, widget, "trough",
2133 allocation.x + priv->range_rect.x,
2134 allocation.y + priv->range_rect.y,
2135 priv->range_rect.width,
2136 priv->range_rect.height);
2139 shadow_type = GTK_SHADOW_OUT;
2142 state = GTK_STATE_INSENSITIVE;
2143 else if (!touchscreen && priv->mouse_location == MOUSE_SLIDER)
2144 state = GTK_STATE_PRELIGHT;
2146 state = GTK_STATE_NORMAL;
2148 if (priv->grab_location == MOUSE_SLIDER)
2150 gboolean activate_slider;
2152 gtk_widget_style_get (widget, "activate-slider", &activate_slider, NULL);
2154 if (activate_slider)
2156 state = GTK_STATE_ACTIVE;
2157 shadow_type = GTK_SHADOW_IN;
2161 if (gdk_rectangle_intersect (&expose_area,
2165 area.x += allocation.x;
2166 area.y += allocation.y;
2168 gtk_paint_slider (style,
2174 gtk_range_get_slider_detail (range),
2175 allocation.x + priv->slider.x,
2176 allocation.y + priv->slider.y,
2178 priv->slider.height,
2182 if (priv->has_stepper_a)
2183 draw_stepper (range, STEPPER_A,
2184 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
2185 priv->grab_location == MOUSE_STEPPER_A,
2186 !touchscreen && priv->mouse_location == MOUSE_STEPPER_A,
2189 if (priv->has_stepper_b)
2190 draw_stepper (range, STEPPER_B,
2191 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
2192 priv->grab_location == MOUSE_STEPPER_B,
2193 !touchscreen && priv->mouse_location == MOUSE_STEPPER_B,
2196 if (priv->has_stepper_c)
2197 draw_stepper (range, STEPPER_C,
2198 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
2199 priv->grab_location == MOUSE_STEPPER_C,
2200 !touchscreen && priv->mouse_location == MOUSE_STEPPER_C,
2203 if (priv->has_stepper_d)
2204 draw_stepper (range, STEPPER_D,
2205 priv->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
2206 priv->grab_location == MOUSE_STEPPER_D,
2207 !touchscreen && priv->mouse_location == MOUSE_STEPPER_D,
2214 range_grab_add (GtkRange *range,
2216 MouseLocation location,
2219 GtkRangePrivate *priv = range->priv;
2221 if (device == priv->grab_device)
2224 if (priv->grab_device != NULL)
2226 g_warning ("GtkRange already had a grab device, releasing device grab");
2227 gtk_device_grab_remove (GTK_WIDGET (range), priv->grab_device);
2230 /* we don't actually gdk_grab, since a button is down */
2231 gtk_device_grab_add (GTK_WIDGET (range), device, TRUE);
2233 priv->grab_location = location;
2234 priv->grab_button = button;
2235 priv->grab_device = device;
2237 if (gtk_range_update_mouse_location (range))
2238 gtk_widget_queue_draw (GTK_WIDGET (range));
2242 range_grab_remove (GtkRange *range)
2244 GtkRangePrivate *priv = range->priv;
2245 MouseLocation location;
2247 if (priv->grab_device)
2249 gtk_device_grab_remove (GTK_WIDGET (range),
2251 priv->grab_device = NULL;
2254 location = priv->grab_location;
2255 priv->grab_location = MOUSE_OUTSIDE;
2256 priv->grab_button = 0;
2258 if (gtk_range_update_mouse_location (range) ||
2259 location != MOUSE_OUTSIDE)
2260 gtk_widget_queue_draw (GTK_WIDGET (range));
2263 static GtkScrollType
2264 range_get_scroll_for_grab (GtkRange *range)
2266 GtkRangePrivate *priv = range->priv;
2269 invert = should_invert (range);
2270 switch (priv->grab_location)
2272 /* Backward stepper */
2273 case MOUSE_STEPPER_A:
2274 case MOUSE_STEPPER_C:
2275 switch (priv->grab_button)
2278 return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
2281 return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD;
2284 return invert ? GTK_SCROLL_END : GTK_SCROLL_START;
2289 /* Forward stepper */
2290 case MOUSE_STEPPER_B:
2291 case MOUSE_STEPPER_D:
2292 switch (priv->grab_button)
2295 return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
2298 return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD;
2301 return invert ? GTK_SCROLL_START : GTK_SCROLL_END;
2309 if (priv->trough_click_forward)
2310 return GTK_SCROLL_PAGE_FORWARD;
2312 return GTK_SCROLL_PAGE_BACKWARD;
2322 return GTK_SCROLL_NONE;
2326 coord_to_value (GtkRange *range,
2329 GtkRangePrivate *priv = range->priv;
2336 gint trough_under_steppers;
2338 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2340 trough_length = priv->trough.height;
2341 trough_start = priv->trough.y;
2342 slider_length = priv->slider.height;
2346 trough_length = priv->trough.width;
2347 trough_start = priv->trough.x;
2348 slider_length = priv->slider.width;
2351 gtk_range_get_props (range, NULL, NULL, NULL, &trough_border, NULL,
2352 &trough_under_steppers, NULL, NULL);
2354 if (! trough_under_steppers)
2356 trough_start += trough_border;
2357 trough_length -= 2 * trough_border;
2360 if (trough_length == slider_length)
2363 frac = (MAX (0, coord - trough_start) /
2364 (gdouble) (trough_length - slider_length));
2366 if (should_invert (range))
2369 value = priv->adjustment->lower + frac * (priv->adjustment->upper -
2370 priv->adjustment->lower -
2371 priv->adjustment->page_size);
2377 gtk_range_key_press (GtkWidget *widget,
2381 GtkRange *range = GTK_RANGE (widget);
2382 GtkRangePrivate *priv = range->priv;
2384 device = gdk_event_get_device ((GdkEvent *) event);
2385 device = gdk_device_get_associated_device (device);
2387 if (device == priv->grab_device &&
2388 event->keyval == GDK_Escape &&
2389 priv->grab_location != MOUSE_OUTSIDE)
2391 stop_scrolling (range);
2393 update_slider_position (range,
2394 priv->slide_initial_coordinate,
2395 priv->slide_initial_coordinate);
2400 return GTK_WIDGET_CLASS (gtk_range_parent_class)->key_press_event (widget, event);
2404 gtk_range_button_press (GtkWidget *widget,
2405 GdkEventButton *event)
2407 GtkRange *range = GTK_RANGE (widget);
2408 GtkRangePrivate *priv = range->priv;
2411 if (!gtk_widget_has_focus (widget))
2412 gtk_widget_grab_focus (widget);
2414 /* ignore presses when we're already doing something else. */
2415 if (priv->grab_location != MOUSE_OUTSIDE)
2418 device = gdk_event_get_device ((GdkEvent *) event);
2419 priv->mouse_x = event->x;
2420 priv->mouse_y = event->y;
2422 if (gtk_range_update_mouse_location (range))
2423 gtk_widget_queue_draw (widget);
2425 if (priv->mouse_location == MOUSE_TROUGH &&
2428 /* button 1 steps by page increment, as with button 2 on a stepper
2430 GtkScrollType scroll;
2431 gdouble click_value;
2433 click_value = coord_to_value (range,
2434 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2435 event->y : event->x);
2437 priv->trough_click_forward = click_value > priv->adjustment->value;
2438 range_grab_add (range, device, MOUSE_TROUGH, event->button);
2440 scroll = range_get_scroll_for_grab (range);
2442 gtk_range_add_step_timer (range, scroll);
2446 else if ((priv->mouse_location == MOUSE_STEPPER_A ||
2447 priv->mouse_location == MOUSE_STEPPER_B ||
2448 priv->mouse_location == MOUSE_STEPPER_C ||
2449 priv->mouse_location == MOUSE_STEPPER_D) &&
2450 (event->button == 1 || event->button == 2 || event->button == 3))
2452 GtkAllocation allocation;
2453 GdkRectangle *stepper_area;
2454 GtkScrollType scroll;
2456 range_grab_add (range, device, priv->mouse_location, event->button);
2458 gtk_widget_get_allocation (widget, &allocation);
2459 stepper_area = get_area (range, priv->mouse_location);
2461 gtk_widget_queue_draw_area (widget,
2462 allocation.x + stepper_area->x,
2463 allocation.y + stepper_area->y,
2464 stepper_area->width,
2465 stepper_area->height);
2467 scroll = range_get_scroll_for_grab (range);
2468 if (scroll != GTK_SCROLL_NONE)
2469 gtk_range_add_step_timer (range, scroll);
2473 else if ((priv->mouse_location == MOUSE_TROUGH &&
2474 event->button == 2) ||
2475 priv->mouse_location == MOUSE_SLIDER)
2477 gboolean need_value_update = FALSE;
2478 gboolean activate_slider;
2480 /* Any button can be used to drag the slider, but you can start
2481 * dragging the slider with a trough click using button 2;
2482 * On button 2 press, we warp the slider to mouse position,
2483 * then begin the slider drag.
2485 if (event->button == 2)
2487 gdouble slider_low_value, slider_high_value, new_value;
2490 coord_to_value (range,
2491 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2492 event->y : event->x);
2494 coord_to_value (range,
2495 priv->orientation == GTK_ORIENTATION_VERTICAL ?
2496 event->y - priv->slider.height :
2497 event->x - priv->slider.width);
2499 /* compute new value for warped slider */
2500 new_value = slider_low_value + (slider_high_value - slider_low_value) / 2;
2502 /* recalc slider, so we can set slide_initial_slider_position
2505 priv->need_recalc = TRUE;
2506 gtk_range_calc_layout (range, new_value);
2508 /* defer adjustment updates to update_slider_position() in order
2509 * to keep pixel quantisation
2511 need_value_update = TRUE;
2514 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2516 priv->slide_initial_slider_position = priv->slider.y;
2517 priv->slide_initial_coordinate = event->y;
2521 priv->slide_initial_slider_position = priv->slider.x;
2522 priv->slide_initial_coordinate = event->x;
2525 range_grab_add (range, device, MOUSE_SLIDER, event->button);
2527 gtk_widget_style_get (widget, "activate-slider", &activate_slider, NULL);
2529 /* force a redraw, if the active slider is drawn differently to the
2532 if (activate_slider)
2533 gtk_widget_queue_draw (widget);
2535 if (need_value_update)
2536 update_slider_position (range, event->x, event->y);
2544 /* During a slide, move the slider as required given new mouse position */
2546 update_slider_position (GtkRange *range,
2550 GtkRangePrivate *priv = range->priv;
2560 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2561 delta = mouse_y - priv->slide_initial_coordinate;
2563 delta = mouse_x - priv->slide_initial_coordinate;
2565 c = priv->slide_initial_slider_position + delta;
2567 new_value = coord_to_value (range, c);
2568 next_value = coord_to_value (range, c + 1);
2569 mark_delta = fabs (next_value - new_value);
2571 for (i = 0; i < priv->n_marks; i++)
2573 mark_value = priv->marks[i];
2575 if (fabs (priv->adjustment->value - mark_value) < 3 * mark_delta)
2577 if (fabs (new_value - mark_value) < (priv->slider_end - priv->slider_start) * 0.5 * mark_delta)
2579 new_value = mark_value;
2585 g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value,
2590 stop_scrolling (GtkRange *range)
2592 range_grab_remove (range);
2593 gtk_range_remove_step_timer (range);
2594 /* Flush any pending discontinuous/delayed updates */
2595 gtk_range_update_value (range);
2599 gtk_range_grab_broken (GtkWidget *widget,
2600 GdkEventGrabBroken *event)
2602 GtkRange *range = GTK_RANGE (widget);
2603 GtkRangePrivate *priv = range->priv;
2606 device = gdk_event_get_device ((GdkEvent *) event);
2608 if (device == priv->grab_device &&
2609 priv->grab_location != MOUSE_OUTSIDE)
2611 if (priv->grab_location == MOUSE_SLIDER)
2612 update_slider_position (range, priv->mouse_x, priv->mouse_y);
2614 stop_scrolling (range);
2623 gtk_range_button_release (GtkWidget *widget,
2624 GdkEventButton *event)
2626 GtkRange *range = GTK_RANGE (widget);
2627 GtkRangePrivate *priv = range->priv;
2630 if (event->window == priv->event_window)
2632 priv->mouse_x = event->x;
2633 priv->mouse_y = event->y;
2637 gdk_window_get_device_position (priv->event_window,
2644 device = gdk_event_get_device ((GdkEvent *) event);
2646 if (priv->grab_device == device &&
2647 priv->grab_button == event->button)
2649 if (priv->grab_location == MOUSE_SLIDER)
2650 update_slider_position (range, priv->mouse_x, priv->mouse_y);
2652 stop_scrolling (range);
2661 * _gtk_range_get_wheel_delta:
2662 * @range: a #GtkRange
2663 * @direction: A #GdkScrollDirection
2665 * Returns a good step value for the mouse wheel.
2667 * Return value: A good step value for the mouse wheel.
2672 _gtk_range_get_wheel_delta (GtkRange *range,
2673 GdkScrollDirection direction)
2675 GtkRangePrivate *priv = range->priv;
2676 GtkAdjustment *adj = priv->adjustment;
2679 if (GTK_IS_SCROLLBAR (range))
2680 delta = pow (adj->page_size, 2.0 / 3.0);
2682 delta = adj->step_increment * 2;
2684 if (direction == GDK_SCROLL_UP ||
2685 direction == GDK_SCROLL_LEFT)
2695 gtk_range_scroll_event (GtkWidget *widget,
2696 GdkEventScroll *event)
2698 GtkRange *range = GTK_RANGE (widget);
2699 GtkRangePrivate *priv = range->priv;
2701 if (gtk_widget_get_realized (widget))
2703 GtkAdjustment *adj = priv->adjustment;
2707 delta = _gtk_range_get_wheel_delta (range, event->direction);
2709 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2710 GTK_SCROLL_JUMP, adj->value + delta,
2713 /* Policy DELAYED makes sense with scroll events,
2714 * but DISCONTINUOUS doesn't, so we update immediately
2717 if (priv->update_policy == GTK_UPDATE_DISCONTINUOUS)
2718 gtk_range_update_value (range);
2725 gtk_range_motion_notify (GtkWidget *widget,
2726 GdkEventMotion *event)
2728 GtkRange *range = GTK_RANGE (widget);
2729 GtkRangePrivate *priv = range->priv;
2731 gdk_event_request_motions (event);
2733 priv->mouse_x = event->x;
2734 priv->mouse_y = event->y;
2736 if (gtk_range_update_mouse_location (range))
2737 gtk_widget_queue_draw (widget);
2739 if (priv->grab_location == MOUSE_SLIDER)
2740 update_slider_position (range, event->x, event->y);
2742 /* We handled the event if the mouse was in the range_rect */
2743 return priv->mouse_location != MOUSE_OUTSIDE;
2747 gtk_range_enter_notify (GtkWidget *widget,
2748 GdkEventCrossing *event)
2750 GtkRange *range = GTK_RANGE (widget);
2751 GtkRangePrivate *priv = range->priv;
2753 priv->mouse_x = event->x;
2754 priv->mouse_y = event->y;
2756 if (gtk_range_update_mouse_location (range))
2757 gtk_widget_queue_draw (widget);
2763 gtk_range_leave_notify (GtkWidget *widget,
2764 GdkEventCrossing *event)
2766 GtkRange *range = GTK_RANGE (widget);
2767 GtkRangePrivate *priv = range->priv;
2772 if (gtk_range_update_mouse_location (range))
2773 gtk_widget_queue_draw (widget);
2779 gtk_range_grab_notify (GtkWidget *widget,
2780 gboolean was_grabbed)
2782 GtkRangePrivate *priv = GTK_RANGE (widget)->priv;
2784 if (priv->grab_device &&
2785 gtk_widget_device_is_shadowed (widget, priv->grab_device))
2786 stop_scrolling (GTK_RANGE (widget));
2790 gtk_range_state_changed (GtkWidget *widget,
2791 GtkStateType previous_state)
2793 if (!gtk_widget_is_sensitive (widget))
2794 stop_scrolling (GTK_RANGE (widget));
2797 #define check_rectangle(rectangle1, rectangle2) \
2799 if (rectangle1.x != rectangle2.x) return TRUE; \
2800 if (rectangle1.y != rectangle2.y) return TRUE; \
2801 if (rectangle1.width != rectangle2.width) return TRUE; \
2802 if (rectangle1.height != rectangle2.height) return TRUE; \
2806 layout_changed (GtkRangePrivate *priv1,
2807 GtkRangePrivate *priv2)
2809 check_rectangle (priv1->slider, priv2->slider);
2810 check_rectangle (priv1->trough, priv2->trough);
2811 check_rectangle (priv1->stepper_a, priv2->stepper_a);
2812 check_rectangle (priv1->stepper_d, priv2->stepper_d);
2813 check_rectangle (priv1->stepper_b, priv2->stepper_b);
2814 check_rectangle (priv1->stepper_c, priv2->stepper_c);
2816 if (priv1->upper_sensitive != priv2->upper_sensitive) return TRUE;
2817 if (priv1->lower_sensitive != priv2->lower_sensitive) return TRUE;
2823 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
2826 GtkRange *range = GTK_RANGE (data);
2827 GtkRangePrivate *priv = range->priv;
2828 GtkRangePrivate priv_aux = *priv;
2830 priv->recalc_marks = TRUE;
2831 priv->need_recalc = TRUE;
2832 gtk_range_calc_layout (range, priv->adjustment->value);
2834 /* now check whether the layout changed */
2835 if (layout_changed (priv, &priv_aux))
2836 gtk_widget_queue_draw (GTK_WIDGET (range));
2838 /* Note that we don't round off to priv->round_digits here.
2839 * that's because it's really broken to change a value
2840 * in response to a change signal on that value; round_digits
2841 * is therefore defined to be a filter on what the GtkRange
2842 * can input into the adjustment, not a filter that the GtkRange
2843 * will enforce on the adjustment.
2848 force_repaint (gpointer data)
2850 GtkRange *range = GTK_RANGE (data);
2851 GtkRangePrivate *priv = range->priv;
2852 GtkWidget *widget = GTK_WIDGET (range);
2854 priv->repaint_id = 0;
2855 if (gtk_widget_is_drawable (widget))
2856 gdk_window_process_updates (gtk_widget_get_window (widget), FALSE);
2862 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
2865 GtkRange *range = GTK_RANGE (data);
2866 GtkRangePrivate *priv = range->priv;
2867 GtkRangePrivate priv_aux = *priv;
2869 priv->need_recalc = TRUE;
2870 gtk_range_calc_layout (range, priv->adjustment->value);
2872 /* now check whether the layout changed */
2873 if (layout_changed (priv, &priv_aux) ||
2874 (GTK_IS_SCALE (range) && gtk_scale_get_draw_value (GTK_SCALE (range))))
2876 gtk_widget_queue_draw (GTK_WIDGET (range));
2877 /* setup a timer to ensure the range isn't lagging too much behind the scroll position */
2878 if (!priv->repaint_id)
2879 priv->repaint_id = gdk_threads_add_timeout_full (GDK_PRIORITY_EVENTS, 181, force_repaint, range, NULL);
2882 /* Note that we don't round off to priv->round_digits here.
2883 * that's because it's really broken to change a value
2884 * in response to a change signal on that value; round_digits
2885 * is therefore defined to be a filter on what the GtkRange
2886 * can input into the adjustment, not a filter that the GtkRange
2887 * will enforce on the adjustment.
2890 g_signal_emit (range, signals[VALUE_CHANGED], 0);
2894 gtk_range_style_set (GtkWidget *widget,
2895 GtkStyle *previous_style)
2897 GtkRange *range = GTK_RANGE (widget);
2898 GtkRangePrivate *priv = range->priv;
2900 priv->need_recalc = TRUE;
2902 GTK_WIDGET_CLASS (gtk_range_parent_class)->style_set (widget, previous_style);
2906 apply_marks (GtkRange *range,
2910 GtkRangePrivate *priv = range->priv;
2914 for (i = 0; i < priv->n_marks; i++)
2916 mark = priv->marks[i];
2917 if ((oldval < mark && mark < *newval) ||
2918 (oldval > mark && mark > *newval))
2927 step_back (GtkRange *range)
2929 GtkRangePrivate *priv = range->priv;
2933 newval = priv->adjustment->value - priv->adjustment->step_increment;
2934 apply_marks (range, priv->adjustment->value, &newval);
2935 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2936 GTK_SCROLL_STEP_BACKWARD, newval, &handled);
2940 step_forward (GtkRange *range)
2942 GtkRangePrivate *priv = range->priv;
2946 newval = priv->adjustment->value + priv->adjustment->step_increment;
2947 apply_marks (range, priv->adjustment->value, &newval);
2948 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2949 GTK_SCROLL_STEP_FORWARD, newval, &handled);
2954 page_back (GtkRange *range)
2956 GtkRangePrivate *priv = range->priv;
2960 newval = priv->adjustment->value - priv->adjustment->page_increment;
2961 apply_marks (range, priv->adjustment->value, &newval);
2962 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2963 GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
2967 page_forward (GtkRange *range)
2969 GtkRangePrivate *priv = range->priv;
2973 newval = priv->adjustment->value + priv->adjustment->page_increment;
2974 apply_marks (range, priv->adjustment->value, &newval);
2975 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2976 GTK_SCROLL_PAGE_FORWARD, newval, &handled);
2980 scroll_begin (GtkRange *range)
2982 GtkRangePrivate *priv = range->priv;
2985 g_signal_emit (range, signals[CHANGE_VALUE], 0,
2986 GTK_SCROLL_START, priv->adjustment->lower,
2991 scroll_end (GtkRange *range)
2993 GtkRangePrivate *priv = range->priv;
2997 newval = priv->adjustment->upper - priv->adjustment->page_size;
2998 g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_END, newval,
3003 gtk_range_scroll (GtkRange *range,
3004 GtkScrollType scroll)
3006 GtkRangePrivate *priv = range->priv;
3007 gdouble old_value = priv->adjustment->value;
3011 case GTK_SCROLL_STEP_LEFT:
3012 if (should_invert (range))
3013 step_forward (range);
3018 case GTK_SCROLL_STEP_UP:
3019 if (should_invert (range))
3020 step_forward (range);
3025 case GTK_SCROLL_STEP_RIGHT:
3026 if (should_invert (range))
3029 step_forward (range);
3032 case GTK_SCROLL_STEP_DOWN:
3033 if (should_invert (range))
3036 step_forward (range);
3039 case GTK_SCROLL_STEP_BACKWARD:
3043 case GTK_SCROLL_STEP_FORWARD:
3044 step_forward (range);
3047 case GTK_SCROLL_PAGE_LEFT:
3048 if (should_invert (range))
3049 page_forward (range);
3054 case GTK_SCROLL_PAGE_UP:
3055 if (should_invert (range))
3056 page_forward (range);
3061 case GTK_SCROLL_PAGE_RIGHT:
3062 if (should_invert (range))
3065 page_forward (range);
3068 case GTK_SCROLL_PAGE_DOWN:
3069 if (should_invert (range))
3072 page_forward (range);
3075 case GTK_SCROLL_PAGE_BACKWARD:
3079 case GTK_SCROLL_PAGE_FORWARD:
3080 page_forward (range);
3083 case GTK_SCROLL_START:
3084 scroll_begin (range);
3087 case GTK_SCROLL_END:
3091 case GTK_SCROLL_JUMP:
3092 /* Used by CList, range doesn't use it. */
3095 case GTK_SCROLL_NONE:
3099 return priv->adjustment->value != old_value;
3103 gtk_range_move_slider (GtkRange *range,
3104 GtkScrollType scroll)
3106 GtkRangePrivate *priv = range->priv;
3107 gboolean cursor_only;
3109 g_object_get (gtk_widget_get_settings (GTK_WIDGET (range)),
3110 "gtk-keynav-cursor-only", &cursor_only,
3115 GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (range));
3117 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
3119 if (scroll == GTK_SCROLL_STEP_UP ||
3120 scroll == GTK_SCROLL_STEP_DOWN)
3123 gtk_widget_child_focus (toplevel,
3124 scroll == GTK_SCROLL_STEP_UP ?
3125 GTK_DIR_UP : GTK_DIR_DOWN);
3131 if (scroll == GTK_SCROLL_STEP_LEFT ||
3132 scroll == GTK_SCROLL_STEP_RIGHT)
3135 gtk_widget_child_focus (toplevel,
3136 scroll == GTK_SCROLL_STEP_LEFT ?
3137 GTK_DIR_LEFT : GTK_DIR_RIGHT);
3143 if (! gtk_range_scroll (range, scroll))
3144 gtk_widget_error_bell (GTK_WIDGET (range));
3146 /* Policy DELAYED makes sense with key events,
3147 * but DISCONTINUOUS doesn't, so we update immediately
3150 if (priv->update_policy == GTK_UPDATE_DISCONTINUOUS)
3151 gtk_range_update_value (range);
3155 gtk_range_get_props (GtkRange *range,
3159 gint *trough_border,
3160 gint *stepper_spacing,
3161 gboolean *trough_under_steppers,
3162 gint *arrow_displacement_x,
3163 gint *arrow_displacement_y)
3165 GtkWidget *widget = GTK_WIDGET (range);
3166 gint tmp_slider_width, tmp_stepper_size, tmp_focus_width, tmp_trough_border;
3167 gint tmp_stepper_spacing, tmp_trough_under_steppers;
3168 gint tmp_arrow_displacement_x, tmp_arrow_displacement_y;
3170 gtk_widget_style_get (widget,
3171 "slider-width", &tmp_slider_width,
3172 "trough-border", &tmp_trough_border,
3173 "stepper-size", &tmp_stepper_size,
3174 "stepper-spacing", &tmp_stepper_spacing,
3175 "trough-under-steppers", &tmp_trough_under_steppers,
3176 "arrow-displacement-x", &tmp_arrow_displacement_x,
3177 "arrow-displacement-y", &tmp_arrow_displacement_y,
3180 if (tmp_stepper_spacing > 0)
3181 tmp_trough_under_steppers = FALSE;
3183 if (gtk_widget_get_can_focus (GTK_WIDGET (range)))
3185 gint focus_line_width;
3188 gtk_widget_style_get (GTK_WIDGET (range),
3189 "focus-line-width", &focus_line_width,
3190 "focus-padding", &focus_padding,
3193 tmp_focus_width = focus_line_width + focus_padding;
3197 tmp_focus_width = 0;
3201 *slider_width = tmp_slider_width;
3204 *focus_width = tmp_focus_width;
3207 *trough_border = tmp_trough_border;
3210 *stepper_size = tmp_stepper_size;
3212 if (stepper_spacing)
3213 *stepper_spacing = tmp_stepper_spacing;
3215 if (trough_under_steppers)
3216 *trough_under_steppers = tmp_trough_under_steppers;
3218 if (arrow_displacement_x)
3219 *arrow_displacement_x = tmp_arrow_displacement_x;
3221 if (arrow_displacement_y)
3222 *arrow_displacement_y = tmp_arrow_displacement_y;
3225 #define POINT_IN_RECT(xcoord, ycoord, rect) \
3226 ((xcoord) >= (rect).x && \
3227 (xcoord) < ((rect).x + (rect).width) && \
3228 (ycoord) >= (rect).y && \
3229 (ycoord) < ((rect).y + (rect).height))
3231 /* Update mouse location, return TRUE if it changes */
3233 gtk_range_update_mouse_location (GtkRange *range)
3235 GtkRangePrivate *priv = range->priv;
3236 GtkAllocation allocation;
3239 GtkWidget *widget = GTK_WIDGET (range);
3241 old = priv->mouse_location;
3246 gtk_widget_get_allocation (widget, &allocation);
3248 if (priv->grab_location != MOUSE_OUTSIDE)
3249 priv->mouse_location = priv->grab_location;
3250 else if (POINT_IN_RECT (x, y, priv->stepper_a))
3251 priv->mouse_location = MOUSE_STEPPER_A;
3252 else if (POINT_IN_RECT (x, y, priv->stepper_b))
3253 priv->mouse_location = MOUSE_STEPPER_B;
3254 else if (POINT_IN_RECT (x, y, priv->stepper_c))
3255 priv->mouse_location = MOUSE_STEPPER_C;
3256 else if (POINT_IN_RECT (x, y, priv->stepper_d))
3257 priv->mouse_location = MOUSE_STEPPER_D;
3258 else if (POINT_IN_RECT (x, y, priv->slider))
3259 priv->mouse_location = MOUSE_SLIDER;
3260 else if (POINT_IN_RECT (x, y, priv->trough))
3261 priv->mouse_location = MOUSE_TROUGH;
3262 else if (POINT_IN_RECT (x, y, allocation))
3263 priv->mouse_location = MOUSE_WIDGET;
3265 priv->mouse_location = MOUSE_OUTSIDE;
3267 return old != priv->mouse_location;
3270 /* Clamp rect, border inside widget->allocation, such that we prefer
3271 * to take space from border not rect in all directions, and prefer to
3272 * give space to border over rect in one direction.
3275 clamp_dimensions (GtkWidget *widget,
3278 gboolean border_expands_horizontally)
3280 GtkAllocation allocation;
3281 gint extra, shortage;
3283 g_return_if_fail (rect->x == 0);
3284 g_return_if_fail (rect->y == 0);
3285 g_return_if_fail (rect->width >= 0);
3286 g_return_if_fail (rect->height >= 0);
3288 gtk_widget_get_allocation (widget, &allocation);
3292 extra = allocation.width - border->left - border->right - rect->width;
3295 if (border_expands_horizontally)
3297 border->left += extra / 2;
3298 border->right += extra / 2 + extra % 2;
3302 rect->width += extra;
3306 /* See if we can fit rect, if not kill the border */
3307 shortage = rect->width - allocation.width;
3310 rect->width = allocation.width;
3311 /* lose the border */
3317 /* See if we can fit rect with borders */
3318 shortage = rect->width + border->left + border->right - allocation.width;
3321 /* Shrink borders */
3322 border->left -= shortage / 2;
3323 border->right -= shortage / 2 + shortage % 2;
3329 extra = allocation.height - border->top - border->bottom - rect->height;
3332 if (border_expands_horizontally)
3334 /* don't expand border vertically */
3335 rect->height += extra;
3339 border->top += extra / 2;
3340 border->bottom += extra / 2 + extra % 2;
3344 /* See if we can fit rect, if not kill the border */
3345 shortage = rect->height - allocation.height;
3348 rect->height = allocation.height;
3349 /* lose the border */
3355 /* See if we can fit rect with borders */
3356 shortage = rect->height + border->top + border->bottom - allocation.height;
3359 /* Shrink borders */
3360 border->top -= shortage / 2;
3361 border->bottom -= shortage / 2 + shortage % 2;
3367 gtk_range_calc_request (GtkRange *range,
3372 gint stepper_spacing,
3373 GdkRectangle *range_rect,
3376 gboolean *has_steppers_ab,
3377 gboolean *has_steppers_cd,
3378 gint *slider_length_p)
3380 GtkRangePrivate *priv = range->priv;
3391 if (GTK_RANGE_GET_CLASS (range)->get_range_border)
3392 GTK_RANGE_GET_CLASS (range)->get_range_border (range, border);
3397 if (priv->has_stepper_a)
3399 if (priv->has_stepper_b)
3401 if (priv->has_stepper_c)
3403 if (priv->has_stepper_d)
3406 n_steppers = n_steppers_ab + n_steppers_cd;
3408 slider_length = priv->min_slider_size;
3413 /* We never expand to fill available space in the small dimension
3414 * (i.e. vertical scrollbars are always a fixed width)
3416 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
3418 range_rect->width = (focus_width + trough_border) * 2 + slider_width;
3419 range_rect->height = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length;
3421 if (n_steppers_ab > 0)
3422 range_rect->height += stepper_spacing;
3424 if (n_steppers_cd > 0)
3425 range_rect->height += stepper_spacing;
3429 range_rect->width = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length;
3430 range_rect->height = (focus_width + trough_border) * 2 + slider_width;
3432 if (n_steppers_ab > 0)
3433 range_rect->width += stepper_spacing;
3435 if (n_steppers_cd > 0)
3436 range_rect->width += stepper_spacing;
3440 *n_steppers_p = n_steppers;
3442 if (has_steppers_ab)
3443 *has_steppers_ab = (n_steppers_ab > 0);
3445 if (has_steppers_cd)
3446 *has_steppers_cd = (n_steppers_cd > 0);
3448 if (slider_length_p)
3449 *slider_length_p = slider_length;
3453 gtk_range_calc_layout (GtkRange *range,
3454 gdouble adjustment_value)
3456 GtkRangePrivate *priv = range->priv;
3457 gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing;
3461 gboolean has_steppers_ab;
3462 gboolean has_steppers_cd;
3463 gboolean trough_under_steppers;
3464 GdkRectangle range_rect;
3467 if (!priv->need_recalc)
3470 /* If we have a too-small allocation, we prefer the steppers over
3471 * the trough/slider, probably the steppers are a more useful
3472 * feature in small spaces.
3474 * Also, we prefer to draw the range itself rather than the border
3475 * areas if there's a conflict, since the borders will be decoration
3476 * not controls. Though this depends on subclasses cooperating by
3477 * not drawing on priv->range_rect.
3480 widget = GTK_WIDGET (range);
3482 gtk_range_get_props (range,
3483 &slider_width, &stepper_size,
3484 &focus_width, &trough_border,
3485 &stepper_spacing, &trough_under_steppers,
3488 gtk_range_calc_request (range,
3489 slider_width, stepper_size,
3490 focus_width, trough_border, stepper_spacing,
3491 &range_rect, &border, &n_steppers,
3492 &has_steppers_ab, &has_steppers_cd, &slider_length);
3494 /* We never expand to fill available space in the small dimension
3495 * (i.e. vertical scrollbars are always a fixed width)
3497 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
3499 clamp_dimensions (widget, &range_rect, &border, TRUE);
3503 clamp_dimensions (widget, &range_rect, &border, FALSE);
3506 range_rect.x = border.left;
3507 range_rect.y = border.top;
3509 priv->range_rect = range_rect;
3511 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
3513 gint stepper_width, stepper_height;
3515 /* Steppers are the width of the range, and stepper_size in
3516 * height, or if we don't have enough height, divided equally
3517 * among available space.
3519 stepper_width = range_rect.width - focus_width * 2;
3521 if (trough_under_steppers)
3522 stepper_width -= trough_border * 2;
3524 if (stepper_width < 1)
3525 stepper_width = range_rect.width; /* screw the trough border */
3527 if (n_steppers == 0)
3528 stepper_height = 0; /* avoid divide by n_steppers */
3530 stepper_height = MIN (stepper_size, (range_rect.height / n_steppers));
3534 priv->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers;
3535 priv->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers;
3537 if (priv->has_stepper_a)
3539 priv->stepper_a.width = stepper_width;
3540 priv->stepper_a.height = stepper_height;
3544 priv->stepper_a.width = 0;
3545 priv->stepper_a.height = 0;
3550 priv->stepper_b.x = priv->stepper_a.x;
3551 priv->stepper_b.y = priv->stepper_a.y + priv->stepper_a.height;
3553 if (priv->has_stepper_b)
3555 priv->stepper_b.width = stepper_width;
3556 priv->stepper_b.height = stepper_height;
3560 priv->stepper_b.width = 0;
3561 priv->stepper_b.height = 0;
3566 if (priv->has_stepper_d)
3568 priv->stepper_d.width = stepper_width;
3569 priv->stepper_d.height = stepper_height;
3573 priv->stepper_d.width = 0;
3574 priv->stepper_d.height = 0;
3577 priv->stepper_d.x = priv->stepper_a.x;
3578 priv->stepper_d.y = range_rect.y + range_rect.height - priv->stepper_d.height - focus_width - trough_border * trough_under_steppers;
3582 if (priv->has_stepper_c)
3584 priv->stepper_c.width = stepper_width;
3585 priv->stepper_c.height = stepper_height;
3589 priv->stepper_c.width = 0;
3590 priv->stepper_c.height = 0;
3593 priv->stepper_c.x = priv->stepper_a.x;
3594 priv->stepper_c.y = priv->stepper_d.y - priv->stepper_c.height;
3596 /* Now the trough is the remaining space between steppers B and C,
3597 * if any, minus spacing
3599 priv->trough.x = range_rect.x;
3600 priv->trough.y = priv->stepper_b.y + priv->stepper_b.height + stepper_spacing * has_steppers_ab;
3601 priv->trough.width = range_rect.width;
3602 priv->trough.height = priv->stepper_c.y - priv->trough.y - stepper_spacing * has_steppers_cd;
3604 /* Slider fits into the trough, with stepper_spacing on either side,
3605 * and the size/position based on the adjustment or fixed, depending.
3607 priv->slider.x = priv->trough.x + focus_width + trough_border;
3608 priv->slider.width = priv->trough.width - (focus_width + trough_border) * 2;
3610 /* Compute slider position/length */
3612 gint y, bottom, top, height;
3614 top = priv->trough.y;
3615 bottom = priv->trough.y + priv->trough.height;
3617 if (! trough_under_steppers)
3619 top += trough_border;
3620 bottom -= trough_border;
3623 /* slider height is the fraction (page_size /
3624 * total_adjustment_range) times the trough height in pixels
3627 if (priv->adjustment->upper - priv->adjustment->lower != 0)
3628 height = ((bottom - top) * (priv->adjustment->page_size /
3629 (priv->adjustment->upper - priv->adjustment->lower)));
3631 height = priv->min_slider_size;
3633 if (height < priv->min_slider_size ||
3634 priv->slider_size_fixed)
3635 height = priv->min_slider_size;
3637 height = MIN (height, priv->trough.height);
3641 if (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size != 0)
3642 y += (bottom - top - height) * ((adjustment_value - priv->adjustment->lower) /
3643 (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size));
3645 y = CLAMP (y, top, bottom);
3647 if (should_invert (range))
3648 y = bottom - (y - top + height);
3651 priv->slider.height = height;
3653 /* These are publically exported */
3654 priv->slider_start = priv->slider.y;
3655 priv->slider_end = priv->slider.y + priv->slider.height;
3660 gint stepper_width, stepper_height;
3662 /* Steppers are the height of the range, and stepper_size in
3663 * width, or if we don't have enough width, divided equally
3664 * among available space.
3666 stepper_height = range_rect.height + focus_width * 2;
3668 if (trough_under_steppers)
3669 stepper_height -= trough_border * 2;
3671 if (stepper_height < 1)
3672 stepper_height = range_rect.height; /* screw the trough border */
3674 if (n_steppers == 0)
3675 stepper_width = 0; /* avoid divide by n_steppers */
3677 stepper_width = MIN (stepper_size, (range_rect.width / n_steppers));
3681 priv->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers;
3682 priv->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers;
3684 if (priv->has_stepper_a)
3686 priv->stepper_a.width = stepper_width;
3687 priv->stepper_a.height = stepper_height;
3691 priv->stepper_a.width = 0;
3692 priv->stepper_a.height = 0;
3697 priv->stepper_b.x = priv->stepper_a.x + priv->stepper_a.width;
3698 priv->stepper_b.y = priv->stepper_a.y;
3700 if (priv->has_stepper_b)
3702 priv->stepper_b.width = stepper_width;
3703 priv->stepper_b.height = stepper_height;
3707 priv->stepper_b.width = 0;
3708 priv->stepper_b.height = 0;
3713 if (priv->has_stepper_d)
3715 priv->stepper_d.width = stepper_width;
3716 priv->stepper_d.height = stepper_height;
3720 priv->stepper_d.width = 0;
3721 priv->stepper_d.height = 0;
3724 priv->stepper_d.x = range_rect.x + range_rect.width - priv->stepper_d.width - focus_width - trough_border * trough_under_steppers;
3725 priv->stepper_d.y = priv->stepper_a.y;
3730 if (priv->has_stepper_c)
3732 priv->stepper_c.width = stepper_width;
3733 priv->stepper_c.height = stepper_height;
3737 priv->stepper_c.width = 0;
3738 priv->stepper_c.height = 0;
3741 priv->stepper_c.x = priv->stepper_d.x - priv->stepper_c.width;
3742 priv->stepper_c.y = priv->stepper_a.y;
3744 /* Now the trough is the remaining space between steppers B and C,
3747 priv->trough.x = priv->stepper_b.x + priv->stepper_b.width + stepper_spacing * has_steppers_ab;
3748 priv->trough.y = range_rect.y;
3750 priv->trough.width = priv->stepper_c.x - priv->trough.x - stepper_spacing * has_steppers_cd;
3751 priv->trough.height = range_rect.height;
3753 /* Slider fits into the trough, with stepper_spacing on either side,
3754 * and the size/position based on the adjustment or fixed, depending.
3756 priv->slider.y = priv->trough.y + focus_width + trough_border;
3757 priv->slider.height = priv->trough.height - (focus_width + trough_border) * 2;
3759 /* Compute slider position/length */
3761 gint x, left, right, width;
3763 left = priv->trough.x;
3764 right = priv->trough.x + priv->trough.width;
3766 if (! trough_under_steppers)
3768 left += trough_border;
3769 right -= trough_border;
3772 /* slider width is the fraction (page_size /
3773 * total_adjustment_range) times the trough width in pixels
3776 if (priv->adjustment->upper - priv->adjustment->lower != 0)
3777 width = ((right - left) * (priv->adjustment->page_size /
3778 (priv->adjustment->upper - priv->adjustment->lower)));
3780 width = priv->min_slider_size;
3782 if (width < priv->min_slider_size ||
3783 priv->slider_size_fixed)
3784 width = priv->min_slider_size;
3786 width = MIN (width, priv->trough.width);
3790 if (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size != 0)
3791 x += (right - left - width) * ((adjustment_value - priv->adjustment->lower) /
3792 (priv->adjustment->upper - priv->adjustment->lower - priv->adjustment->page_size));
3794 x = CLAMP (x, left, right);
3796 if (should_invert (range))
3797 x = right - (x - left + width);
3800 priv->slider.width = width;
3802 /* These are publically exported */
3803 priv->slider_start = priv->slider.x;
3804 priv->slider_end = priv->slider.x + priv->slider.width;
3808 gtk_range_update_mouse_location (range);
3810 switch (priv->upper_sensitivity)
3812 case GTK_SENSITIVITY_AUTO:
3813 priv->upper_sensitive =
3814 (priv->adjustment->value <
3815 (priv->adjustment->upper - priv->adjustment->page_size));
3818 case GTK_SENSITIVITY_ON:
3819 priv->upper_sensitive = TRUE;
3822 case GTK_SENSITIVITY_OFF:
3823 priv->upper_sensitive = FALSE;
3827 switch (priv->lower_sensitivity)
3829 case GTK_SENSITIVITY_AUTO:
3830 priv->lower_sensitive =
3831 (priv->adjustment->value > priv->adjustment->lower);
3834 case GTK_SENSITIVITY_ON:
3835 priv->lower_sensitive = TRUE;
3838 case GTK_SENSITIVITY_OFF:
3839 priv->lower_sensitive = FALSE;
3844 static GdkRectangle*
3845 get_area (GtkRange *range,
3846 MouseLocation location)
3848 GtkRangePrivate *priv = range->priv;
3852 case MOUSE_STEPPER_A:
3853 return &priv->stepper_a;
3854 case MOUSE_STEPPER_B:
3855 return &priv->stepper_b;
3856 case MOUSE_STEPPER_C:
3857 return &priv->stepper_c;
3858 case MOUSE_STEPPER_D:
3859 return &priv->stepper_d;
3861 return &priv->trough;
3863 return &priv->slider;
3869 g_warning (G_STRLOC": bug");
3874 gtk_range_calc_marks (GtkRange *range)
3876 GtkRangePrivate *priv = range->priv;
3879 if (!priv->recalc_marks)
3882 priv->recalc_marks = FALSE;
3884 for (i = 0; i < priv->n_marks; i++)
3886 priv->need_recalc = TRUE;
3887 gtk_range_calc_layout (range, priv->marks[i]);
3888 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
3889 priv->mark_pos[i] = priv->slider.x + priv->slider.width / 2;
3891 priv->mark_pos[i] = priv->slider.y + priv->slider.height / 2;
3894 priv->need_recalc = TRUE;
3898 gtk_range_real_change_value (GtkRange *range,
3899 GtkScrollType scroll,
3902 GtkRangePrivate *priv = range->priv;
3904 /* potentially adjust the bounds _before_ we clamp */
3905 g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value);
3907 if (priv->restrict_to_fill_level)
3908 value = MIN (value, MAX (priv->adjustment->lower,
3911 value = CLAMP (value, priv->adjustment->lower,
3912 (priv->adjustment->upper - priv->adjustment->page_size));
3914 if (priv->round_digits >= 0)
3919 i = priv->round_digits;
3924 value = floor ((value * power) + 0.5) / power;
3927 if (priv->adjustment->value != value)
3929 priv->need_recalc = TRUE;
3931 gtk_widget_queue_draw (GTK_WIDGET (range));
3933 switch (priv->update_policy)
3935 case GTK_UPDATE_CONTINUOUS:
3936 gtk_adjustment_set_value (priv->adjustment, value);
3939 /* Delayed means we update after a period of inactivity */
3940 case GTK_UPDATE_DELAYED:
3941 gtk_range_reset_update_timer (range);
3944 /* Discontinuous means we update on button release */
3945 case GTK_UPDATE_DISCONTINUOUS:
3946 /* don't emit value_changed signal */
3947 priv->adjustment->value = value;
3948 priv->update_pending = TRUE;
3956 gtk_range_update_value (GtkRange *range)
3958 GtkRangePrivate *priv = range->priv;
3960 gtk_range_remove_update_timer (range);
3962 if (priv->update_pending)
3964 gtk_adjustment_value_changed (priv->adjustment);
3966 priv->update_pending = FALSE;
3970 struct _GtkRangeStepTimer
3977 second_timeout (gpointer data)
3979 GtkRange *range = GTK_RANGE (data);
3980 GtkRangePrivate *priv = range->priv;
3982 gtk_range_scroll (range, priv->timer->step);
3988 initial_timeout (gpointer data)
3990 GtkRange *range = GTK_RANGE (data);
3991 GtkRangePrivate *priv = range->priv;
3992 GtkSettings *settings;
3995 settings = gtk_widget_get_settings (GTK_WIDGET (data));
3996 g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
3998 priv->timer->timeout_id = gdk_threads_add_timeout (timeout * SCROLL_DELAY_FACTOR,
4006 gtk_range_add_step_timer (GtkRange *range,
4009 GtkRangePrivate *priv = range->priv;
4010 GtkSettings *settings;
4013 g_return_if_fail (priv->timer == NULL);
4014 g_return_if_fail (step != GTK_SCROLL_NONE);
4016 settings = gtk_widget_get_settings (GTK_WIDGET (range));
4017 g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
4019 priv->timer = g_new (GtkRangeStepTimer, 1);
4021 priv->timer->timeout_id = gdk_threads_add_timeout (timeout,
4024 priv->timer->step = step;
4026 gtk_range_scroll (range, priv->timer->step);
4030 gtk_range_remove_step_timer (GtkRange *range)
4032 GtkRangePrivate *priv = range->priv;
4036 if (priv->timer->timeout_id != 0)
4037 g_source_remove (priv->timer->timeout_id);
4039 g_free (priv->timer);
4046 update_timeout (gpointer data)
4048 GtkRange *range = GTK_RANGE (data);
4049 GtkRangePrivate *priv = range->priv;
4051 gtk_range_update_value (range);
4052 priv->update_timeout_id = 0;
4059 gtk_range_reset_update_timer (GtkRange *range)
4061 GtkRangePrivate *priv = range->priv;
4063 gtk_range_remove_update_timer (range);
4065 priv->update_timeout_id = gdk_threads_add_timeout (UPDATE_DELAY,
4071 gtk_range_remove_update_timer (GtkRange *range)
4073 GtkRangePrivate *priv = range->priv;
4075 if (priv->update_timeout_id != 0)
4077 g_source_remove (priv->update_timeout_id);
4078 priv->update_timeout_id = 0;
4083 _gtk_range_set_stop_values (GtkRange *range,
4087 GtkRangePrivate *priv = range->priv;
4090 g_free (priv->marks);
4091 priv->marks = g_new (gdouble, n_values);
4093 g_free (priv->mark_pos);
4094 priv->mark_pos = g_new (gint, n_values);
4096 priv->n_marks = n_values;
4098 for (i = 0; i < n_values; i++)
4099 priv->marks[i] = values[i];
4101 priv->recalc_marks = TRUE;
4105 _gtk_range_get_stop_positions (GtkRange *range,
4108 GtkRangePrivate *priv = range->priv;
4110 gtk_range_calc_marks (range);
4113 *values = g_memdup (priv->mark_pos, priv->n_marks * sizeof (gint));
4115 return priv->n_marks;
4119 _gtk_range_set_round_digits (GtkRange *range,
4122 range->priv->round_digits = round_digits;
4126 _gtk_range_set_steppers (GtkRange *range,
4132 range->priv->has_stepper_a = has_a;
4133 range->priv->has_stepper_b = has_b;
4134 range->priv->has_stepper_c = has_c;
4135 range->priv->has_stepper_d = has_d;