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-2000. 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 "gtkmarshalers.h"
36 #include "gtkscrollbar.h"
38 #define SCROLL_INITIAL_DELAY 250 /* must hold button this long before ... */
39 #define SCROLL_LATER_DELAY 100 /* ... it starts repeating at this rate */
40 #define UPDATE_DELAY 300 /* Delay for queued update */
64 MOUSE_WIDGET /* inside widget but not in any of the above GUI elements */
67 struct _GtkRangeLayout
69 /* These are in widget->window coordinates */
70 GdkRectangle stepper_a;
71 GdkRectangle stepper_b;
72 GdkRectangle stepper_c;
73 GdkRectangle stepper_d;
74 /* The trough rectangle is the area the thumb can slide in, not the
80 /* Layout-related state */
82 MouseLocation mouse_location;
83 /* last mouse coords we got, or -1 if mouse is outside the range */
86 /* "grabbed" mouse location, OUTSIDE for no grab */
87 MouseLocation grab_location;
88 gint grab_button; /* 0 if none */
92 static void gtk_range_class_init (GtkRangeClass *klass);
93 static void gtk_range_init (GtkRange *range);
94 static void gtk_range_set_property (GObject *object,
98 static void gtk_range_get_property (GObject *object,
102 static void gtk_range_destroy (GtkObject *object);
103 static void gtk_range_finalize (GObject *object);
104 static void gtk_range_size_request (GtkWidget *widget,
105 GtkRequisition *requisition);
106 static void gtk_range_size_allocate (GtkWidget *widget,
107 GtkAllocation *allocation);
108 static void gtk_range_realize (GtkWidget *widget);
109 static void gtk_range_unrealize (GtkWidget *widget);
110 static void gtk_range_map (GtkWidget *widget);
111 static void gtk_range_unmap (GtkWidget *widget);
112 static gint gtk_range_expose (GtkWidget *widget,
113 GdkEventExpose *event);
114 static gint gtk_range_button_press (GtkWidget *widget,
115 GdkEventButton *event);
116 static gint gtk_range_button_release (GtkWidget *widget,
117 GdkEventButton *event);
118 static gint gtk_range_motion_notify (GtkWidget *widget,
119 GdkEventMotion *event);
120 static gint gtk_range_enter_notify (GtkWidget *widget,
121 GdkEventCrossing *event);
122 static gint gtk_range_leave_notify (GtkWidget *widget,
123 GdkEventCrossing *event);
124 static void gtk_range_grab_notify (GtkWidget *widget,
125 gboolean was_grabbed);
126 static void gtk_range_state_changed (GtkWidget *widget,
127 GtkStateType previous_state);
128 static gint gtk_range_scroll_event (GtkWidget *widget,
129 GdkEventScroll *event);
130 static void gtk_range_style_set (GtkWidget *widget,
131 GtkStyle *previous_style);
132 static void update_slider_position (GtkRange *range,
139 static void gtk_range_move_slider (GtkRange *range,
140 GtkScrollType scroll);
143 static void gtk_range_scroll (GtkRange *range,
144 GtkScrollType scroll);
145 static gboolean gtk_range_update_mouse_location (GtkRange *range);
146 static void gtk_range_calc_layout (GtkRange *range,
147 gdouble adjustment_value);
148 static void gtk_range_get_props (GtkRange *range,
152 gint *stepper_spacing,
153 gint *arrow_displacement_x,
154 gint *arrow_displacement_y);
155 static void gtk_range_calc_request (GtkRange *range,
159 gint stepper_spacing,
160 GdkRectangle *range_rect,
163 gint *slider_length_p);
164 static void gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
166 static void gtk_range_adjustment_changed (GtkAdjustment *adjustment,
168 static void gtk_range_add_step_timer (GtkRange *range,
170 static void gtk_range_remove_step_timer (GtkRange *range);
171 static void gtk_range_reset_update_timer (GtkRange *range);
172 static void gtk_range_remove_update_timer (GtkRange *range);
173 static GdkRectangle* get_area (GtkRange *range,
174 MouseLocation location);
175 static void gtk_range_internal_set_value (GtkRange *range,
177 static void gtk_range_update_value (GtkRange *range);
180 static GtkWidgetClass *parent_class = NULL;
181 static guint signals[LAST_SIGNAL];
185 gtk_range_get_type (void)
187 static GType range_type = 0;
191 static const GTypeInfo range_info =
193 sizeof (GtkRangeClass),
194 NULL, /* base_init */
195 NULL, /* base_finalize */
196 (GClassInitFunc) gtk_range_class_init,
197 NULL, /* class_finalize */
198 NULL, /* class_data */
201 (GInstanceInitFunc) gtk_range_init,
202 NULL, /* value_table */
205 range_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkRange",
206 &range_info, G_TYPE_FLAG_ABSTRACT);
213 gtk_range_class_init (GtkRangeClass *class)
215 GObjectClass *gobject_class;
216 GtkObjectClass *object_class;
217 GtkWidgetClass *widget_class;
219 gobject_class = G_OBJECT_CLASS (class);
220 object_class = (GtkObjectClass*) class;
221 widget_class = (GtkWidgetClass*) class;
223 parent_class = g_type_class_peek_parent (class);
225 gobject_class->set_property = gtk_range_set_property;
226 gobject_class->get_property = gtk_range_get_property;
227 gobject_class->finalize = gtk_range_finalize;
228 object_class->destroy = gtk_range_destroy;
230 widget_class->size_request = gtk_range_size_request;
231 widget_class->size_allocate = gtk_range_size_allocate;
232 widget_class->realize = gtk_range_realize;
233 widget_class->unrealize = gtk_range_unrealize;
234 widget_class->map = gtk_range_map;
235 widget_class->unmap = gtk_range_unmap;
236 widget_class->expose_event = gtk_range_expose;
237 widget_class->button_press_event = gtk_range_button_press;
238 widget_class->button_release_event = gtk_range_button_release;
239 widget_class->motion_notify_event = gtk_range_motion_notify;
240 widget_class->scroll_event = gtk_range_scroll_event;
241 widget_class->enter_notify_event = gtk_range_enter_notify;
242 widget_class->leave_notify_event = gtk_range_leave_notify;
243 widget_class->grab_notify = gtk_range_grab_notify;
244 widget_class->state_changed = gtk_range_state_changed;
245 widget_class->style_set = gtk_range_style_set;
247 class->move_slider = gtk_range_move_slider;
249 class->slider_detail = "slider";
250 class->stepper_detail = "stepper";
252 signals[VALUE_CHANGED] =
253 g_signal_new ("value_changed",
254 G_TYPE_FROM_CLASS (gobject_class),
256 G_STRUCT_OFFSET (GtkRangeClass, value_changed),
258 _gtk_marshal_NONE__NONE,
261 signals[ADJUST_BOUNDS] =
262 g_signal_new ("adjust_bounds",
263 G_TYPE_FROM_CLASS (gobject_class),
265 G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds),
267 _gtk_marshal_VOID__DOUBLE,
271 signals[MOVE_SLIDER] =
272 g_signal_new ("move_slider",
273 G_TYPE_FROM_CLASS (gobject_class),
274 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
275 G_STRUCT_OFFSET (GtkRangeClass, move_slider),
277 _gtk_marshal_VOID__ENUM,
279 GTK_TYPE_SCROLL_TYPE);
281 g_object_class_install_property (gobject_class,
283 g_param_spec_enum ("update_policy",
285 P_("How the range should be updated on the screen"),
286 GTK_TYPE_UPDATE_TYPE,
287 GTK_UPDATE_CONTINUOUS,
290 g_object_class_install_property (gobject_class,
292 g_param_spec_object ("adjustment",
294 P_("The GtkAdjustment that contains the current value of this range object"),
296 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
298 g_object_class_install_property (gobject_class,
300 g_param_spec_boolean ("inverted",
302 P_("Invert direction slider moves to increase range value"),
306 gtk_widget_class_install_style_property (widget_class,
307 g_param_spec_int ("slider_width",
309 P_("Width of scrollbar or scale thumb"),
314 gtk_widget_class_install_style_property (widget_class,
315 g_param_spec_int ("trough_border",
317 P_("Spacing between thumb/steppers and outer trough bevel"),
322 gtk_widget_class_install_style_property (widget_class,
323 g_param_spec_int ("stepper_size",
325 P_("Length of step buttons at ends"),
330 gtk_widget_class_install_style_property (widget_class,
331 g_param_spec_int ("stepper_spacing",
332 P_("Stepper Spacing"),
333 P_("Spacing between step buttons and thumb"),
338 gtk_widget_class_install_style_property (widget_class,
339 g_param_spec_int ("arrow_displacement_x",
340 P_("Arrow X Displacement"),
341 P_("How far in the x direction to move the arrow when the button is depressed"),
346 gtk_widget_class_install_style_property (widget_class,
347 g_param_spec_int ("arrow_displacement_y",
348 P_("Arrow Y Displacement"),
349 P_("How far in the y direction to move the arrow when the button is depressed"),
357 gtk_range_set_property (GObject *object,
364 range = GTK_RANGE (object);
368 case PROP_UPDATE_POLICY:
369 gtk_range_set_update_policy (range, g_value_get_enum (value));
371 case PROP_ADJUSTMENT:
372 gtk_range_set_adjustment (range, g_value_get_object (value));
375 gtk_range_set_inverted (range, g_value_get_boolean (value));
378 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
384 gtk_range_get_property (GObject *object,
391 range = GTK_RANGE (object);
395 case PROP_UPDATE_POLICY:
396 g_value_set_enum (value, range->update_policy);
398 case PROP_ADJUSTMENT:
399 g_value_set_object (value, range->adjustment);
402 g_value_set_boolean (value, range->inverted);
405 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
411 gtk_range_init (GtkRange *range)
413 GTK_WIDGET_SET_FLAGS (range, GTK_NO_WINDOW);
415 range->adjustment = NULL;
416 range->update_policy = GTK_UPDATE_CONTINUOUS;
417 range->inverted = FALSE;
418 range->flippable = FALSE;
419 range->min_slider_size = 1;
420 range->has_stepper_a = FALSE;
421 range->has_stepper_b = FALSE;
422 range->has_stepper_c = FALSE;
423 range->has_stepper_d = FALSE;
424 range->need_recalc = TRUE;
425 range->round_digits = -1;
426 range->layout = g_new0 (GtkRangeLayout, 1);
427 range->layout->mouse_location = MOUSE_OUTSIDE;
428 range->layout->mouse_x = -1;
429 range->layout->mouse_y = -1;
430 range->layout->grab_location = MOUSE_OUTSIDE;
431 range->layout->grab_button = 0;
436 * gtk_range_get_adjustment:
437 * @range: a #GtkRange
439 * Get the #GtkAdjustment which is the "model" object for #GtkRange.
440 * See gtk_range_set_adjustment() for details.
441 * The return value does not have a reference added, so should not
444 * Return value: a #GtkAdjustment
447 gtk_range_get_adjustment (GtkRange *range)
449 g_return_val_if_fail (GTK_IS_RANGE (range), NULL);
451 if (!range->adjustment)
452 gtk_range_set_adjustment (range, NULL);
454 return range->adjustment;
458 * gtk_range_set_update_policy:
459 * @range: a #GtkRange
460 * @policy: update policy
462 * Sets the update policy for the range. #GTK_UPDATE_CONTINUOUS means that
463 * anytime the range slider is moved, the range value will change and the
464 * value_changed signal will be emitted. #GTK_UPDATE_DELAYED means that
465 * the value will be updated after a brief timeout where no slider motion
466 * occurs, so updates are spaced by a short time rather than
467 * continuous. #GTK_UPDATE_DISCONTINUOUS means that the value will only
468 * be updated when the user releases the button and ends the slider
473 gtk_range_set_update_policy (GtkRange *range,
474 GtkUpdateType policy)
476 g_return_if_fail (GTK_IS_RANGE (range));
478 if (range->update_policy != policy)
480 range->update_policy = policy;
481 g_object_notify (G_OBJECT (range), "update_policy");
486 * gtk_range_get_update_policy:
487 * @range: a #GtkRange
489 * Gets the update policy of @range. See gtk_range_set_update_policy().
491 * Return value: the current update policy
494 gtk_range_get_update_policy (GtkRange *range)
496 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_UPDATE_CONTINUOUS);
498 return range->update_policy;
502 * gtk_range_set_adjustment:
503 * @range: a #GtkRange
504 * @adjustment: a #GtkAdjustment
506 * Sets the adjustment to be used as the "model" object for this range
507 * widget. The adjustment indicates the current range value, the
508 * minimum and maximum range values, the step/page increments used
509 * for keybindings and scrolling, and the page size. The page size
510 * is normally 0 for #GtkScale and nonzero for #GtkScrollbar, and
511 * indicates the size of the visible area of the widget being scrolled.
512 * The page size affects the size of the scrollbar slider.
516 gtk_range_set_adjustment (GtkRange *range,
517 GtkAdjustment *adjustment)
519 g_return_if_fail (GTK_IS_RANGE (range));
522 adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
524 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
526 if (range->adjustment != adjustment)
528 if (range->adjustment)
530 g_signal_handlers_disconnect_by_func (range->adjustment,
531 gtk_range_adjustment_changed,
533 g_signal_handlers_disconnect_by_func (range->adjustment,
534 gtk_range_adjustment_value_changed,
536 g_object_unref (range->adjustment);
539 range->adjustment = adjustment;
540 g_object_ref (adjustment);
541 gtk_object_sink (GTK_OBJECT (adjustment));
543 g_signal_connect (adjustment, "changed",
544 G_CALLBACK (gtk_range_adjustment_changed),
546 g_signal_connect (adjustment, "value_changed",
547 G_CALLBACK (gtk_range_adjustment_value_changed),
550 gtk_range_adjustment_changed (adjustment, range);
551 g_object_notify (G_OBJECT (range), "adjustment");
556 * gtk_range_set_inverted:
557 * @range: a #GtkRange
558 * @setting: %TRUE to invert the range
560 * Ranges normally move from lower to higher values as the
561 * slider moves from top to bottom or left to right. Inverted
562 * ranges have higher values at the top or on the right rather than
563 * on the bottom or left.
567 gtk_range_set_inverted (GtkRange *range,
570 g_return_if_fail (GTK_IS_RANGE (range));
572 setting = setting != FALSE;
574 if (setting != range->inverted)
576 range->inverted = setting;
577 g_object_notify (G_OBJECT (range), "inverted");
578 gtk_widget_queue_resize (GTK_WIDGET (range));
583 * gtk_range_get_inverted:
584 * @range: a #GtkRange
586 * Gets the value set by gtk_range_set_inverted().
588 * Return value: %TRUE if the range is inverted
591 gtk_range_get_inverted (GtkRange *range)
593 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
595 return range->inverted;
599 * gtk_range_set_increments:
600 * @range: a #GtkRange
604 * Sets the step and page sizes for the range.
605 * The step size is used when the user clicks the #GtkScrollbar
606 * arrows or moves #GtkScale via arrow keys. The page size
607 * is used for example when moving via Page Up or Page Down keys.
611 gtk_range_set_increments (GtkRange *range,
615 g_return_if_fail (GTK_IS_RANGE (range));
617 range->adjustment->step_increment = step;
618 range->adjustment->page_increment = page;
620 gtk_adjustment_changed (range->adjustment);
624 * gtk_range_set_range:
625 * @range: a #GtkRange
626 * @min: minimum range value
627 * @max: maximum range value
629 * Sets the allowable values in the #GtkRange, and clamps the range
630 * value to be between @min and @max. (If the range has a non-zero
631 * page size, it is clamped between @min and @max - page-size.)
634 gtk_range_set_range (GtkRange *range,
640 g_return_if_fail (GTK_IS_RANGE (range));
641 g_return_if_fail (min < max);
643 range->adjustment->lower = min;
644 range->adjustment->upper = max;
646 value = CLAMP (range->adjustment->value,
647 range->adjustment->lower,
648 (range->adjustment->upper - range->adjustment->page_size));
650 gtk_adjustment_set_value (range->adjustment, value);
651 gtk_adjustment_changed (range->adjustment);
655 * gtk_range_set_value:
656 * @range: a #GtkRange
657 * @value: new value of the range
659 * Sets the current value of the range; if the value is outside the
660 * minimum or maximum range values, it will be clamped to fit inside
661 * them. The range emits the "value_changed" signal if the value
666 gtk_range_set_value (GtkRange *range,
669 g_return_if_fail (GTK_IS_RANGE (range));
671 value = CLAMP (value, range->adjustment->lower,
672 (range->adjustment->upper - range->adjustment->page_size));
674 gtk_adjustment_set_value (range->adjustment, value);
678 * gtk_range_get_value:
679 * @range: a #GtkRange
681 * Gets the current value of the range.
683 * Return value: current value of the range.
686 gtk_range_get_value (GtkRange *range)
688 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
690 return range->adjustment->value;
694 should_invert (GtkRange *range)
696 if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
698 (range->inverted && !range->flippable) ||
699 (range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
700 (!range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
702 return range->inverted;
706 gtk_range_finalize (GObject *object)
708 GtkRange *range = GTK_RANGE (object);
710 g_free (range->layout);
712 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
716 gtk_range_destroy (GtkObject *object)
718 GtkRange *range = GTK_RANGE (object);
720 gtk_range_remove_step_timer (range);
721 gtk_range_remove_update_timer (range);
723 if (range->adjustment)
725 g_signal_handlers_disconnect_by_func (range->adjustment,
726 gtk_range_adjustment_changed,
728 g_signal_handlers_disconnect_by_func (range->adjustment,
729 gtk_range_adjustment_value_changed,
731 g_object_unref (range->adjustment);
732 range->adjustment = NULL;
735 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
739 gtk_range_size_request (GtkWidget *widget,
740 GtkRequisition *requisition)
743 gint slider_width, stepper_size, trough_border, stepper_spacing;
744 GdkRectangle range_rect;
747 range = GTK_RANGE (widget);
749 gtk_range_get_props (range,
750 &slider_width, &stepper_size, &trough_border, &stepper_spacing,
753 gtk_range_calc_request (range,
754 slider_width, stepper_size, trough_border, stepper_spacing,
755 &range_rect, &border, NULL, NULL);
757 requisition->width = range_rect.width + border.left + border.right;
758 requisition->height = range_rect.height + border.top + border.bottom;
762 gtk_range_size_allocate (GtkWidget *widget,
763 GtkAllocation *allocation)
767 range = GTK_RANGE (widget);
769 widget->allocation = *allocation;
771 range->need_recalc = TRUE;
772 gtk_range_calc_layout (range, range->adjustment->value);
774 if (GTK_WIDGET_REALIZED (range))
775 gdk_window_move_resize (range->event_window,
776 widget->allocation.x,
777 widget->allocation.y,
778 widget->allocation.width,
779 widget->allocation.height);
783 gtk_range_realize (GtkWidget *widget)
786 GdkWindowAttr attributes;
787 gint attributes_mask;
789 range = GTK_RANGE (widget);
791 gtk_range_calc_layout (range, range->adjustment->value);
793 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
795 widget->window = gtk_widget_get_parent_window (widget);
796 g_object_ref (widget->window);
798 attributes.window_type = GDK_WINDOW_CHILD;
799 attributes.x = widget->allocation.x;
800 attributes.y = widget->allocation.y;
801 attributes.width = widget->allocation.width;
802 attributes.height = widget->allocation.height;
803 attributes.wclass = GDK_INPUT_ONLY;
804 attributes.event_mask = gtk_widget_get_events (widget);
805 attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
806 GDK_BUTTON_RELEASE_MASK |
807 GDK_ENTER_NOTIFY_MASK |
808 GDK_LEAVE_NOTIFY_MASK |
809 GDK_POINTER_MOTION_MASK |
810 GDK_POINTER_MOTION_HINT_MASK);
812 attributes_mask = GDK_WA_X | GDK_WA_Y;
814 range->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
815 &attributes, attributes_mask);
816 gdk_window_set_user_data (range->event_window, range);
818 widget->style = gtk_style_attach (widget->style, widget->window);
822 gtk_range_unrealize (GtkWidget *widget)
824 GtkRange *range = GTK_RANGE (widget);
826 gtk_range_remove_step_timer (range);
827 gtk_range_remove_update_timer (range);
829 gdk_window_set_user_data (range->event_window, NULL);
830 gdk_window_destroy (range->event_window);
831 range->event_window = NULL;
833 if (GTK_WIDGET_CLASS (parent_class)->unrealize)
834 (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
838 gtk_range_map (GtkWidget *widget)
840 GtkRange *range = GTK_RANGE (widget);
842 g_return_if_fail (GTK_IS_RANGE (widget));
844 gdk_window_show (range->event_window);
846 GTK_WIDGET_CLASS (parent_class)->map (widget);
850 gtk_range_unmap (GtkWidget *widget)
852 GtkRange *range = GTK_RANGE (widget);
854 g_return_if_fail (GTK_IS_RANGE (widget));
856 gdk_window_hide (range->event_window);
858 GTK_WIDGET_CLASS (parent_class)->unmap (widget);
862 draw_stepper (GtkRange *range,
864 GtkArrowType arrow_type,
869 GtkStateType state_type;
870 GtkShadowType shadow_type;
871 GdkRectangle intersection;
872 GtkWidget *widget = GTK_WIDGET (range);
879 /* More to get the right clip region than for efficiency */
880 if (!gdk_rectangle_intersect (area, rect, &intersection))
883 intersection.x += widget->allocation.x;
884 intersection.y += widget->allocation.y;
886 if (!GTK_WIDGET_IS_SENSITIVE (range))
887 state_type = GTK_STATE_INSENSITIVE;
889 state_type = GTK_STATE_ACTIVE;
891 state_type = GTK_STATE_PRELIGHT;
893 state_type = GTK_STATE_NORMAL;
896 shadow_type = GTK_SHADOW_IN;
898 shadow_type = GTK_SHADOW_OUT;
900 gtk_paint_box (widget->style,
902 state_type, shadow_type,
903 &intersection, widget,
904 GTK_RANGE_GET_CLASS (range)->stepper_detail,
905 widget->allocation.x + rect->x,
906 widget->allocation.y + rect->y,
910 arrow_width = rect->width / 2;
911 arrow_height = rect->height / 2;
912 arrow_x = widget->allocation.x + rect->x + (rect->width - arrow_width) / 2;
913 arrow_y = widget->allocation.y + rect->y + (rect->height - arrow_height) / 2;
917 gint arrow_displacement_x;
918 gint arrow_displacement_y;
920 gtk_range_get_props (GTK_RANGE (widget), NULL, NULL, NULL, NULL,
921 &arrow_displacement_x, &arrow_displacement_y);
923 arrow_x += arrow_displacement_x;
924 arrow_y += arrow_displacement_y;
927 gtk_paint_arrow (widget->style,
929 state_type, shadow_type,
930 &intersection, widget,
931 GTK_RANGE_GET_CLASS (range)->stepper_detail,
934 arrow_x, arrow_y, arrow_width, arrow_height);
938 gtk_range_expose (GtkWidget *widget,
939 GdkEventExpose *event)
944 GdkRectangle expose_area; /* Relative to widget->allocation */
946 gint focus_line_width = 0;
947 gint focus_padding = 0;
949 range = GTK_RANGE (widget);
951 if (GTK_WIDGET_CAN_FOCUS (range))
953 gtk_widget_style_get (GTK_WIDGET (range),
954 "focus-line-width", &focus_line_width,
955 "focus-padding", &focus_padding,
959 expose_area = event->area;
960 expose_area.x -= widget->allocation.x;
961 expose_area.y -= widget->allocation.y;
963 gtk_range_calc_layout (range, range->adjustment->value);
965 sensitive = GTK_WIDGET_IS_SENSITIVE (widget);
967 /* Just to be confusing, we draw the trough for the whole
968 * range rectangle, not the trough rectangle (the trough
969 * rectangle is just for hit detection)
971 /* The gdk_rectangle_intersect is more to get the right
972 * clip region (limited to range_rect) than for efficiency
974 if (gdk_rectangle_intersect (&expose_area, &range->range_rect,
977 area.x += widget->allocation.x;
978 area.y += widget->allocation.y;
980 gtk_paint_box (widget->style,
982 sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
984 &area, GTK_WIDGET(range), "trough",
985 widget->allocation.x + range->range_rect.x + focus_line_width + focus_padding,
986 widget->allocation.y + range->range_rect.y + focus_line_width + focus_padding,
987 range->range_rect.width - 2 * (focus_line_width + focus_padding),
988 range->range_rect.height - 2 * (focus_line_width + focus_padding));
992 GTK_WIDGET_HAS_FOCUS (range))
993 gtk_paint_focus (widget->style, widget->window, GTK_WIDGET_STATE (widget),
994 &area, widget, "trough",
995 widget->allocation.x + range->range_rect.x,
996 widget->allocation.y + range->range_rect.y,
997 range->range_rect.width,
998 range->range_rect.height);
1002 state = GTK_STATE_INSENSITIVE;
1003 else if (range->layout->mouse_location == MOUSE_SLIDER)
1004 state = GTK_STATE_PRELIGHT;
1006 state = GTK_STATE_NORMAL;
1008 if (gdk_rectangle_intersect (&expose_area,
1009 &range->layout->slider,
1012 area.x += widget->allocation.x;
1013 area.y += widget->allocation.y;
1015 gtk_paint_slider (widget->style,
1021 GTK_RANGE_GET_CLASS (range)->slider_detail,
1022 widget->allocation.x + range->layout->slider.x,
1023 widget->allocation.y + range->layout->slider.y,
1024 range->layout->slider.width,
1025 range->layout->slider.height,
1026 range->orientation);
1029 if (range->has_stepper_a)
1030 draw_stepper (range, &range->layout->stepper_a,
1031 range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
1032 range->layout->grab_location == MOUSE_STEPPER_A,
1033 range->layout->mouse_location == MOUSE_STEPPER_A,
1036 if (range->has_stepper_b)
1037 draw_stepper (range, &range->layout->stepper_b,
1038 range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
1039 range->layout->grab_location == MOUSE_STEPPER_B,
1040 range->layout->mouse_location == MOUSE_STEPPER_B,
1043 if (range->has_stepper_c)
1044 draw_stepper (range, &range->layout->stepper_c,
1045 range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
1046 range->layout->grab_location == MOUSE_STEPPER_C,
1047 range->layout->mouse_location == MOUSE_STEPPER_C,
1050 if (range->has_stepper_d)
1051 draw_stepper (range, &range->layout->stepper_d,
1052 range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
1053 range->layout->grab_location == MOUSE_STEPPER_D,
1054 range->layout->mouse_location == MOUSE_STEPPER_D,
1061 range_grab_add (GtkRange *range,
1062 MouseLocation location,
1065 /* we don't actually gtk_grab, since a button is down */
1067 gtk_grab_add (GTK_WIDGET (range));
1069 range->layout->grab_location = location;
1070 range->layout->grab_button = button;
1072 if (gtk_range_update_mouse_location (range))
1073 gtk_widget_queue_draw (GTK_WIDGET (range));
1077 range_grab_remove (GtkRange *range)
1079 gtk_grab_remove (GTK_WIDGET (range));
1081 range->layout->grab_location = MOUSE_OUTSIDE;
1082 range->layout->grab_button = 0;
1084 if (gtk_range_update_mouse_location (range))
1085 gtk_widget_queue_draw (GTK_WIDGET (range));
1088 static GtkScrollType
1089 range_get_scroll_for_grab (GtkRange *range)
1093 invert = should_invert (range);
1094 switch (range->layout->grab_location)
1096 /* Backward stepper */
1097 case MOUSE_STEPPER_A:
1098 case MOUSE_STEPPER_C:
1099 switch (range->layout->grab_button)
1102 return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
1105 return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD;
1108 return invert ? GTK_SCROLL_END : GTK_SCROLL_START;
1113 /* Forward stepper */
1114 case MOUSE_STEPPER_B:
1115 case MOUSE_STEPPER_D:
1116 switch (range->layout->grab_button)
1119 return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
1122 return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD;
1125 return invert ? GTK_SCROLL_START : GTK_SCROLL_END;
1133 if (range->trough_click_forward)
1134 return GTK_SCROLL_PAGE_FORWARD;
1136 return GTK_SCROLL_PAGE_BACKWARD;
1146 return GTK_SCROLL_NONE;
1150 coord_to_value (GtkRange *range,
1156 if (range->orientation == GTK_ORIENTATION_VERTICAL)
1157 if (range->layout->trough.height == range->layout->slider.height)
1160 frac = ((coord - range->layout->trough.y) /
1161 (gdouble) (range->layout->trough.height - range->layout->slider.height));
1163 if (range->layout->trough.width == range->layout->slider.width)
1166 frac = ((coord - range->layout->trough.x) /
1167 (gdouble) (range->layout->trough.width - range->layout->slider.width));
1169 if (should_invert (range))
1172 value = range->adjustment->lower +
1173 frac * (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size);
1179 gtk_range_button_press (GtkWidget *widget,
1180 GdkEventButton *event)
1182 GtkRange *range = GTK_RANGE (widget);
1184 if (!GTK_WIDGET_HAS_FOCUS (widget))
1185 gtk_widget_grab_focus (widget);
1187 /* ignore presses when we're already doing something else. */
1188 if (range->layout->grab_location != MOUSE_OUTSIDE)
1191 range->layout->mouse_x = event->x;
1192 range->layout->mouse_y = event->y;
1193 if (gtk_range_update_mouse_location (range))
1194 gtk_widget_queue_draw (widget);
1196 if (range->layout->mouse_location == MOUSE_TROUGH &&
1199 /* button 1 steps by page increment, as with button 2 on a stepper
1201 GtkScrollType scroll;
1202 gdouble click_value;
1204 click_value = coord_to_value (range,
1205 range->orientation == GTK_ORIENTATION_VERTICAL ?
1206 event->y : event->x);
1208 range->trough_click_forward = click_value > range->adjustment->value;
1209 range_grab_add (range, MOUSE_TROUGH, event->button);
1211 scroll = range_get_scroll_for_grab (range);
1213 gtk_range_add_step_timer (range, scroll);
1217 else if ((range->layout->mouse_location == MOUSE_STEPPER_A ||
1218 range->layout->mouse_location == MOUSE_STEPPER_B ||
1219 range->layout->mouse_location == MOUSE_STEPPER_C ||
1220 range->layout->mouse_location == MOUSE_STEPPER_D) &&
1221 (event->button == 1 || event->button == 2 || event->button == 3))
1223 GdkRectangle *stepper_area;
1224 GtkScrollType scroll;
1226 range_grab_add (range, range->layout->mouse_location, event->button);
1228 stepper_area = get_area (range, range->layout->mouse_location);
1229 gtk_widget_queue_draw_area (widget,
1230 widget->allocation.x + stepper_area->x,
1231 widget->allocation.y + stepper_area->y,
1232 stepper_area->width,
1233 stepper_area->height);
1235 scroll = range_get_scroll_for_grab (range);
1236 if (scroll != GTK_SCROLL_NONE)
1237 gtk_range_add_step_timer (range, scroll);
1241 else if ((range->layout->mouse_location == MOUSE_TROUGH &&
1242 event->button == 2) ||
1243 range->layout->mouse_location == MOUSE_SLIDER)
1245 gboolean need_value_update = FALSE;
1247 /* Any button can be used to drag the slider, but you can start
1248 * dragging the slider with a trough click using button 2;
1249 * On button 2 press, we warp the slider to mouse position,
1250 * then begin the slider drag.
1252 if (event->button == 2)
1254 gdouble slider_low_value, slider_high_value, new_value;
1257 coord_to_value (range,
1258 range->orientation == GTK_ORIENTATION_VERTICAL ?
1259 event->y : event->x);
1261 coord_to_value (range,
1262 range->orientation == GTK_ORIENTATION_VERTICAL ?
1263 event->y - range->layout->slider.height :
1264 event->x - range->layout->slider.width);
1266 /* compute new value for warped slider */
1267 new_value = slider_low_value + (slider_high_value - slider_low_value) / 2;
1269 /* recalc slider, so we can set slide_initial_slider_position
1272 range->need_recalc = TRUE;
1273 gtk_range_calc_layout (range, new_value);
1275 /* defer adjustment updates to update_slider_position() in order
1276 * to keep pixel quantisation
1278 need_value_update = TRUE;
1281 if (range->orientation == GTK_ORIENTATION_VERTICAL)
1283 range->slide_initial_slider_position = range->layout->slider.y;
1284 range->slide_initial_coordinate = event->y;
1288 range->slide_initial_slider_position = range->layout->slider.x;
1289 range->slide_initial_coordinate = event->x;
1292 if (need_value_update)
1293 update_slider_position (range, event->x, event->y);
1295 range_grab_add (range, MOUSE_SLIDER, event->button);
1303 /* During a slide, move the slider as required given new mouse position */
1305 update_slider_position (GtkRange *range,
1313 if (range->orientation == GTK_ORIENTATION_VERTICAL)
1314 delta = mouse_y - range->slide_initial_coordinate;
1316 delta = mouse_x - range->slide_initial_coordinate;
1318 c = range->slide_initial_slider_position + delta;
1320 new_value = coord_to_value (range, c);
1322 gtk_range_internal_set_value (range, new_value);
1325 static void stop_scrolling (GtkRange *range)
1327 range_grab_remove (range);
1328 gtk_range_remove_step_timer (range);
1329 /* Flush any pending discontinuous/delayed updates */
1330 gtk_range_update_value (range);
1332 /* Just be lazy about this, if we scrolled it will all redraw anyway,
1333 * so no point optimizing the button deactivate case
1335 gtk_widget_queue_draw (GTK_WIDGET (range));
1339 gtk_range_button_release (GtkWidget *widget,
1340 GdkEventButton *event)
1342 GtkRange *range = GTK_RANGE (widget);
1344 if (event->window == range->event_window)
1346 range->layout->mouse_x = event->x;
1347 range->layout->mouse_y = event->y;
1351 gdk_window_get_pointer (range->event_window,
1352 &range->layout->mouse_x,
1353 &range->layout->mouse_y,
1357 if (range->layout->grab_button == event->button)
1359 if (range->layout->grab_location == MOUSE_SLIDER)
1360 update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y);
1362 stop_scrolling (range);
1371 * _gtk_range_get_wheel_delta:
1372 * @range: a #GtkRange
1373 * @direction: A #GdkScrollDirection
1375 * Returns a good step value for the mouse wheel.
1377 * Return value: A good step value for the mouse wheel.
1382 _gtk_range_get_wheel_delta (GtkRange *range,
1383 GdkScrollDirection direction)
1385 GtkAdjustment *adj = range->adjustment;
1388 if (GTK_IS_SCROLLBAR (range))
1389 delta = pow (adj->page_size, 2.0 / 3.0);
1391 delta = adj->step_increment * 2;
1393 if (direction == GDK_SCROLL_UP ||
1394 direction == GDK_SCROLL_LEFT)
1397 if (range->inverted)
1404 gtk_range_scroll_event (GtkWidget *widget,
1405 GdkEventScroll *event)
1407 GtkRange *range = GTK_RANGE (widget);
1409 if (GTK_WIDGET_REALIZED (range))
1411 GtkAdjustment *adj = GTK_RANGE (range)->adjustment;
1414 delta = _gtk_range_get_wheel_delta (range, event->direction);
1415 gtk_range_internal_set_value (range, adj->value + delta);
1417 /* Policy DELAYED makes sense with scroll events,
1418 * but DISCONTINUOUS doesn't, so we update immediately
1421 if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
1422 gtk_range_update_value (range);
1429 gtk_range_motion_notify (GtkWidget *widget,
1430 GdkEventMotion *event)
1435 range = GTK_RANGE (widget);
1437 gdk_window_get_pointer (range->event_window, &x, &y, NULL);
1439 range->layout->mouse_x = x;
1440 range->layout->mouse_y = y;
1442 if (gtk_range_update_mouse_location (range))
1443 gtk_widget_queue_draw (widget);
1445 if (range->layout->grab_location == MOUSE_SLIDER)
1446 update_slider_position (range, x, y);
1448 /* We handled the event if the mouse was in the range_rect */
1449 return range->layout->mouse_location != MOUSE_OUTSIDE;
1453 gtk_range_enter_notify (GtkWidget *widget,
1454 GdkEventCrossing *event)
1456 GtkRange *range = GTK_RANGE (widget);
1458 range->layout->mouse_x = event->x;
1459 range->layout->mouse_y = event->y;
1461 if (gtk_range_update_mouse_location (range))
1462 gtk_widget_queue_draw (widget);
1468 gtk_range_leave_notify (GtkWidget *widget,
1469 GdkEventCrossing *event)
1471 GtkRange *range = GTK_RANGE (widget);
1473 range->layout->mouse_x = -1;
1474 range->layout->mouse_y = -1;
1476 if (gtk_range_update_mouse_location (range))
1477 gtk_widget_queue_draw (widget);
1483 gtk_range_grab_notify (GtkWidget *widget,
1484 gboolean was_grabbed)
1487 stop_scrolling (GTK_RANGE (widget));
1491 gtk_range_state_changed (GtkWidget *widget,
1492 GtkStateType previous_state)
1494 if (!GTK_WIDGET_IS_SENSITIVE (widget))
1495 stop_scrolling (GTK_RANGE (widget));
1499 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
1502 GtkRange *range = GTK_RANGE (data);
1504 range->need_recalc = TRUE;
1505 gtk_widget_queue_draw (GTK_WIDGET (range));
1507 /* Note that we don't round off to range->round_digits here.
1508 * that's because it's really broken to change a value
1509 * in response to a change signal on that value; round_digits
1510 * is therefore defined to be a filter on what the GtkRange
1511 * can input into the adjustment, not a filter that the GtkRange
1512 * will enforce on the adjustment.
1517 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
1520 GtkRange *range = GTK_RANGE (data);
1522 range->need_recalc = TRUE;
1524 gtk_widget_queue_draw (GTK_WIDGET (range));
1525 /* This is so we don't lag the widget being scrolled. */
1526 if (GTK_WIDGET_REALIZED (range))
1527 gdk_window_process_updates (GTK_WIDGET (range)->window, FALSE);
1529 /* Note that we don't round off to range->round_digits here.
1530 * that's because it's really broken to change a value
1531 * in response to a change signal on that value; round_digits
1532 * is therefore defined to be a filter on what the GtkRange
1533 * can input into the adjustment, not a filter that the GtkRange
1534 * will enforce on the adjustment.
1537 g_signal_emit (range, signals[VALUE_CHANGED], 0);
1541 gtk_range_style_set (GtkWidget *widget,
1542 GtkStyle *previous_style)
1544 GtkRange *range = GTK_RANGE (widget);
1546 range->need_recalc = TRUE;
1548 (* GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style);
1552 step_back (GtkRange *range)
1556 newval = range->adjustment->value - range->adjustment->step_increment;
1557 gtk_range_internal_set_value (range, newval);
1561 step_forward (GtkRange *range)
1565 newval = range->adjustment->value + range->adjustment->step_increment;
1566 gtk_range_internal_set_value (range, newval);
1571 page_back (GtkRange *range)
1575 newval = range->adjustment->value - range->adjustment->page_increment;
1576 gtk_range_internal_set_value (range, newval);
1580 page_forward (GtkRange *range)
1584 newval = range->adjustment->value + range->adjustment->page_increment;
1585 gtk_range_internal_set_value (range, newval);
1589 scroll_begin (GtkRange *range)
1591 gtk_range_internal_set_value (range, range->adjustment->lower);
1595 scroll_end (GtkRange *range)
1599 newval = range->adjustment->upper - range->adjustment->page_size;
1600 gtk_range_internal_set_value (range, newval);
1604 gtk_range_scroll (GtkRange *range,
1605 GtkScrollType scroll)
1609 case GTK_SCROLL_STEP_LEFT:
1610 if (should_invert (range))
1611 step_forward (range);
1616 case GTK_SCROLL_STEP_UP:
1617 if (should_invert (range))
1618 step_forward (range);
1623 case GTK_SCROLL_STEP_RIGHT:
1624 if (should_invert (range))
1627 step_forward (range);
1630 case GTK_SCROLL_STEP_DOWN:
1631 if (should_invert (range))
1634 step_forward (range);
1637 case GTK_SCROLL_STEP_BACKWARD:
1641 case GTK_SCROLL_STEP_FORWARD:
1642 step_forward (range);
1645 case GTK_SCROLL_PAGE_LEFT:
1646 if (should_invert (range))
1647 page_forward (range);
1652 case GTK_SCROLL_PAGE_UP:
1653 if (should_invert (range))
1654 page_forward (range);
1659 case GTK_SCROLL_PAGE_RIGHT:
1660 if (should_invert (range))
1663 page_forward (range);
1666 case GTK_SCROLL_PAGE_DOWN:
1667 if (should_invert (range))
1670 page_forward (range);
1673 case GTK_SCROLL_PAGE_BACKWARD:
1677 case GTK_SCROLL_PAGE_FORWARD:
1678 page_forward (range);
1681 case GTK_SCROLL_START:
1682 scroll_begin (range);
1685 case GTK_SCROLL_END:
1689 case GTK_SCROLL_JUMP:
1690 /* Used by CList, range doesn't use it. */
1693 case GTK_SCROLL_NONE:
1699 gtk_range_move_slider (GtkRange *range,
1700 GtkScrollType scroll)
1702 gtk_range_scroll (range, scroll);
1704 /* Policy DELAYED makes sense with key events,
1705 * but DISCONTINUOUS doesn't, so we update immediately
1708 if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
1709 gtk_range_update_value (range);
1713 gtk_range_get_props (GtkRange *range,
1716 gint *trough_border,
1717 gint *stepper_spacing,
1718 gint *arrow_displacement_x,
1719 gint *arrow_displacement_y)
1721 GtkWidget *widget = GTK_WIDGET (range);
1722 gint tmp_slider_width, tmp_stepper_size, tmp_trough_border, tmp_stepper_spacing;
1723 gint tmp_arrow_displacement_x, tmp_arrow_displacement_y;
1725 gtk_widget_style_get (widget,
1726 "slider_width", &tmp_slider_width,
1727 "trough_border", &tmp_trough_border,
1728 "stepper_size", &tmp_stepper_size,
1729 "stepper_spacing", &tmp_stepper_spacing,
1730 "arrow_displacement_x", &tmp_arrow_displacement_x,
1731 "arrow_displacement_y", &tmp_arrow_displacement_y,
1734 if (GTK_WIDGET_CAN_FOCUS (range))
1736 gint focus_line_width;
1739 gtk_widget_style_get (GTK_WIDGET (range),
1740 "focus-line-width", &focus_line_width,
1741 "focus-padding", &focus_padding,
1744 tmp_trough_border += focus_line_width + focus_padding;
1748 *slider_width = tmp_slider_width;
1751 *trough_border = tmp_trough_border;
1754 *stepper_size = tmp_stepper_size;
1756 if (stepper_spacing)
1757 *stepper_spacing = tmp_stepper_spacing;
1759 if (arrow_displacement_x)
1760 *arrow_displacement_x = tmp_arrow_displacement_x;
1762 if (arrow_displacement_y)
1763 *arrow_displacement_y = tmp_arrow_displacement_y;
1766 #define POINT_IN_RECT(xcoord, ycoord, rect) \
1767 ((xcoord) >= (rect).x && \
1768 (xcoord) < ((rect).x + (rect).width) && \
1769 (ycoord) >= (rect).y && \
1770 (ycoord) < ((rect).y + (rect).height))
1772 /* Update mouse location, return TRUE if it changes */
1774 gtk_range_update_mouse_location (GtkRange *range)
1780 widget = GTK_WIDGET (range);
1782 old = range->layout->mouse_location;
1784 x = range->layout->mouse_x;
1785 y = range->layout->mouse_y;
1787 if (range->layout->grab_location != MOUSE_OUTSIDE)
1788 range->layout->mouse_location = range->layout->grab_location;
1789 else if (POINT_IN_RECT (x, y, range->layout->stepper_a))
1790 range->layout->mouse_location = MOUSE_STEPPER_A;
1791 else if (POINT_IN_RECT (x, y, range->layout->stepper_b))
1792 range->layout->mouse_location = MOUSE_STEPPER_B;
1793 else if (POINT_IN_RECT (x, y, range->layout->stepper_c))
1794 range->layout->mouse_location = MOUSE_STEPPER_C;
1795 else if (POINT_IN_RECT (x, y, range->layout->stepper_d))
1796 range->layout->mouse_location = MOUSE_STEPPER_D;
1797 else if (POINT_IN_RECT (x, y, range->layout->slider))
1798 range->layout->mouse_location = MOUSE_SLIDER;
1799 else if (POINT_IN_RECT (x, y, range->layout->trough))
1800 range->layout->mouse_location = MOUSE_TROUGH;
1801 else if (POINT_IN_RECT (x, y, widget->allocation))
1802 range->layout->mouse_location = MOUSE_WIDGET;
1804 range->layout->mouse_location = MOUSE_OUTSIDE;
1806 return old != range->layout->mouse_location;
1809 /* Clamp rect, border inside widget->allocation, such that we prefer
1810 * to take space from border not rect in all directions, and prefer to
1811 * give space to border over rect in one direction.
1814 clamp_dimensions (GtkWidget *widget,
1817 gboolean border_expands_horizontally)
1819 gint extra, shortage;
1821 g_return_if_fail (rect->x == 0);
1822 g_return_if_fail (rect->y == 0);
1823 g_return_if_fail (rect->width >= 0);
1824 g_return_if_fail (rect->height >= 0);
1828 extra = widget->allocation.width - border->left - border->right - rect->width;
1831 if (border_expands_horizontally)
1833 border->left += extra / 2;
1834 border->right += extra / 2 + extra % 2;
1838 rect->width += extra;
1842 /* See if we can fit rect, if not kill the border */
1843 shortage = rect->width - widget->allocation.width;
1846 rect->width = widget->allocation.width;
1847 /* lose the border */
1853 /* See if we can fit rect with borders */
1854 shortage = rect->width + border->left + border->right -
1855 widget->allocation.width;
1858 /* Shrink borders */
1859 border->left -= shortage / 2;
1860 border->right -= shortage / 2 + shortage % 2;
1866 extra = widget->allocation.height - border->top - border->bottom - rect->height;
1869 if (border_expands_horizontally)
1871 /* don't expand border vertically */
1872 rect->height += extra;
1876 border->top += extra / 2;
1877 border->bottom += extra / 2 + extra % 2;
1881 /* See if we can fit rect, if not kill the border */
1882 shortage = rect->height - widget->allocation.height;
1885 rect->height = widget->allocation.height;
1886 /* lose the border */
1892 /* See if we can fit rect with borders */
1893 shortage = rect->height + border->top + border->bottom -
1894 widget->allocation.height;
1897 /* Shrink borders */
1898 border->top -= shortage / 2;
1899 border->bottom -= shortage / 2 + shortage % 2;
1905 gtk_range_calc_request (GtkRange *range,
1909 gint stepper_spacing,
1910 GdkRectangle *range_rect,
1913 gint *slider_length_p)
1923 if (GTK_RANGE_GET_CLASS (range)->get_range_border)
1924 (* GTK_RANGE_GET_CLASS (range)->get_range_border) (range, border);
1927 if (range->has_stepper_a)
1929 if (range->has_stepper_b)
1931 if (range->has_stepper_c)
1933 if (range->has_stepper_d)
1936 slider_length = range->min_slider_size;
1941 /* We never expand to fill available space in the small dimension
1942 * (i.e. vertical scrollbars are always a fixed width)
1944 if (range->orientation == GTK_ORIENTATION_VERTICAL)
1946 range_rect->width = trough_border * 2 + slider_width;
1947 range_rect->height = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length;
1951 range_rect->width = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length;
1952 range_rect->height = trough_border * 2 + slider_width;
1956 *n_steppers_p = n_steppers;
1958 if (slider_length_p)
1959 *slider_length_p = slider_length;
1963 gtk_range_calc_layout (GtkRange *range,
1964 gdouble adjustment_value)
1966 gint slider_width, stepper_size, trough_border, stepper_spacing;
1970 GdkRectangle range_rect;
1971 GtkRangeLayout *layout;
1974 if (!range->need_recalc)
1977 /* If we have a too-small allocation, we prefer the steppers over
1978 * the trough/slider, probably the steppers are a more useful
1979 * feature in small spaces.
1981 * Also, we prefer to draw the range itself rather than the border
1982 * areas if there's a conflict, since the borders will be decoration
1983 * not controls. Though this depends on subclasses cooperating by
1984 * not drawing on range->range_rect.
1987 widget = GTK_WIDGET (range);
1988 layout = range->layout;
1990 gtk_range_get_props (range,
1991 &slider_width, &stepper_size, &trough_border, &stepper_spacing,
1994 gtk_range_calc_request (range,
1995 slider_width, stepper_size, trough_border, stepper_spacing,
1996 &range_rect, &border, &n_steppers, &slider_length);
1998 /* We never expand to fill available space in the small dimension
1999 * (i.e. vertical scrollbars are always a fixed width)
2001 if (range->orientation == GTK_ORIENTATION_VERTICAL)
2003 clamp_dimensions (widget, &range_rect, &border, TRUE);
2007 clamp_dimensions (widget, &range_rect, &border, FALSE);
2010 range_rect.x = border.left;
2011 range_rect.y = border.top;
2013 range->range_rect = range_rect;
2015 if (range->orientation == GTK_ORIENTATION_VERTICAL)
2017 gint stepper_width, stepper_height;
2019 /* Steppers are the width of the range, and stepper_size in
2020 * height, or if we don't have enough height, divided equally
2021 * among available space.
2023 stepper_width = range_rect.width - trough_border * 2;
2025 if (stepper_width < 1)
2026 stepper_width = range_rect.width; /* screw the trough border */
2028 if (n_steppers == 0)
2029 stepper_height = 0; /* avoid divide by n_steppers */
2031 stepper_height = MIN (stepper_size, (range_rect.height / n_steppers));
2035 layout->stepper_a.x = range_rect.x + trough_border;
2036 layout->stepper_a.y = range_rect.y + trough_border;
2038 if (range->has_stepper_a)
2040 layout->stepper_a.width = stepper_width;
2041 layout->stepper_a.height = stepper_height;
2045 layout->stepper_a.width = 0;
2046 layout->stepper_a.height = 0;
2051 layout->stepper_b.x = layout->stepper_a.x;
2052 layout->stepper_b.y = layout->stepper_a.y + layout->stepper_a.height;
2054 if (range->has_stepper_b)
2056 layout->stepper_b.width = stepper_width;
2057 layout->stepper_b.height = stepper_height;
2061 layout->stepper_b.width = 0;
2062 layout->stepper_b.height = 0;
2067 if (range->has_stepper_d)
2069 layout->stepper_d.width = stepper_width;
2070 layout->stepper_d.height = stepper_height;
2074 layout->stepper_d.width = 0;
2075 layout->stepper_d.height = 0;
2078 layout->stepper_d.x = layout->stepper_a.x;
2079 layout->stepper_d.y = range_rect.y + range_rect.height - layout->stepper_d.height - trough_border;
2083 if (range->has_stepper_c)
2085 layout->stepper_c.width = stepper_width;
2086 layout->stepper_c.height = stepper_height;
2090 layout->stepper_c.width = 0;
2091 layout->stepper_c.height = 0;
2094 layout->stepper_c.x = layout->stepper_a.x;
2095 layout->stepper_c.y = layout->stepper_d.y - layout->stepper_c.height;
2097 /* Now the trough is the remaining space between steppers B and C,
2100 layout->trough.x = range_rect.x;
2101 layout->trough.y = layout->stepper_b.y + layout->stepper_b.height;
2102 layout->trough.width = range_rect.width;
2103 layout->trough.height = layout->stepper_c.y - (layout->stepper_b.y + layout->stepper_b.height);
2105 /* Slider fits into the trough, with stepper_spacing on either side,
2106 * and the size/position based on the adjustment or fixed, depending.
2108 layout->slider.x = layout->trough.x + trough_border;
2109 layout->slider.width = layout->trough.width - trough_border * 2;
2111 /* Compute slider position/length */
2113 gint y, bottom, top, height;
2115 top = layout->trough.y + stepper_spacing;
2116 bottom = layout->trough.y + layout->trough.height - stepper_spacing;
2118 /* slider height is the fraction (page_size /
2119 * total_adjustment_range) times the trough height in pixels
2122 if (range->adjustment->upper - range->adjustment->lower != 0)
2123 height = ((bottom - top) * (range->adjustment->page_size /
2124 (range->adjustment->upper - range->adjustment->lower)));
2126 height = range->min_slider_size;
2128 if (height < range->min_slider_size ||
2129 range->slider_size_fixed)
2130 height = range->min_slider_size;
2132 height = MIN (height, (layout->trough.height - stepper_spacing * 2));
2136 if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0)
2137 y += (bottom - top - height) * ((adjustment_value - range->adjustment->lower) /
2138 (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
2140 y = CLAMP (y, top, bottom);
2142 if (should_invert (range))
2143 y = bottom - (y - top + height);
2145 layout->slider.y = y;
2146 layout->slider.height = height;
2148 /* These are publically exported */
2149 range->slider_start = layout->slider.y;
2150 range->slider_end = layout->slider.y + layout->slider.height;
2155 gint stepper_width, stepper_height;
2157 /* Steppers are the height of the range, and stepper_size in
2158 * width, or if we don't have enough width, divided equally
2159 * among available space.
2161 stepper_height = range_rect.height - trough_border * 2;
2163 if (stepper_height < 1)
2164 stepper_height = range_rect.height; /* screw the trough border */
2166 if (n_steppers == 0)
2167 stepper_width = 0; /* avoid divide by n_steppers */
2169 stepper_width = MIN (stepper_size, (range_rect.width / n_steppers));
2173 layout->stepper_a.x = range_rect.x + trough_border;
2174 layout->stepper_a.y = range_rect.y + trough_border;
2176 if (range->has_stepper_a)
2178 layout->stepper_a.width = stepper_width;
2179 layout->stepper_a.height = stepper_height;
2183 layout->stepper_a.width = 0;
2184 layout->stepper_a.height = 0;
2189 layout->stepper_b.x = layout->stepper_a.x + layout->stepper_a.width;
2190 layout->stepper_b.y = layout->stepper_a.y;
2192 if (range->has_stepper_b)
2194 layout->stepper_b.width = stepper_width;
2195 layout->stepper_b.height = stepper_height;
2199 layout->stepper_b.width = 0;
2200 layout->stepper_b.height = 0;
2205 if (range->has_stepper_d)
2207 layout->stepper_d.width = stepper_width;
2208 layout->stepper_d.height = stepper_height;
2212 layout->stepper_d.width = 0;
2213 layout->stepper_d.height = 0;
2216 layout->stepper_d.x = range_rect.x + range_rect.width - layout->stepper_d.width - trough_border;
2217 layout->stepper_d.y = layout->stepper_a.y;
2222 if (range->has_stepper_c)
2224 layout->stepper_c.width = stepper_width;
2225 layout->stepper_c.height = stepper_height;
2229 layout->stepper_c.width = 0;
2230 layout->stepper_c.height = 0;
2233 layout->stepper_c.x = layout->stepper_d.x - layout->stepper_c.width;
2234 layout->stepper_c.y = layout->stepper_a.y;
2236 /* Now the trough is the remaining space between steppers B and C,
2239 layout->trough.x = layout->stepper_b.x + layout->stepper_b.width;
2240 layout->trough.y = range_rect.y;
2242 layout->trough.width = layout->stepper_c.x - (layout->stepper_b.x + layout->stepper_b.width);
2243 layout->trough.height = range_rect.height;
2245 /* Slider fits into the trough, with stepper_spacing on either side,
2246 * and the size/position based on the adjustment or fixed, depending.
2248 layout->slider.y = layout->trough.y + trough_border;
2249 layout->slider.height = layout->trough.height - trough_border * 2;
2251 /* Compute slider position/length */
2253 gint x, left, right, width;
2255 left = layout->trough.x + stepper_spacing;
2256 right = layout->trough.x + layout->trough.width - stepper_spacing;
2258 /* slider width is the fraction (page_size /
2259 * total_adjustment_range) times the trough width in pixels
2262 if (range->adjustment->upper - range->adjustment->lower != 0)
2263 width = ((right - left) * (range->adjustment->page_size /
2264 (range->adjustment->upper - range->adjustment->lower)));
2266 width = range->min_slider_size;
2268 if (width < range->min_slider_size ||
2269 range->slider_size_fixed)
2270 width = range->min_slider_size;
2272 width = MIN (width, (layout->trough.width - stepper_spacing * 2));
2276 if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0)
2277 x += (right - left - width) * ((adjustment_value - range->adjustment->lower) /
2278 (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
2280 x = CLAMP (x, left, right);
2282 if (should_invert (range))
2283 x = right - (x - left + width);
2285 layout->slider.x = x;
2286 layout->slider.width = width;
2288 /* These are publically exported */
2289 range->slider_start = layout->slider.x;
2290 range->slider_end = layout->slider.x + layout->slider.width;
2294 gtk_range_update_mouse_location (range);
2297 static GdkRectangle*
2298 get_area (GtkRange *range,
2299 MouseLocation location)
2303 case MOUSE_STEPPER_A:
2304 return &range->layout->stepper_a;
2305 case MOUSE_STEPPER_B:
2306 return &range->layout->stepper_b;
2307 case MOUSE_STEPPER_C:
2308 return &range->layout->stepper_c;
2309 case MOUSE_STEPPER_D:
2310 return &range->layout->stepper_d;
2312 return &range->layout->trough;
2314 return &range->layout->slider;
2320 g_warning (G_STRLOC": bug");
2325 gtk_range_internal_set_value (GtkRange *range,
2328 /* potentially adjust the bounds _before we clamp */
2329 g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value);
2331 value = CLAMP (value, range->adjustment->lower,
2332 (range->adjustment->upper - range->adjustment->page_size));
2334 if (range->round_digits >= 0)
2338 /* This is just so darn lame. */
2339 g_snprintf (buffer, 128, "%0.*f",
2340 range->round_digits, value);
2341 sscanf (buffer, "%lf", &value);
2344 if (range->adjustment->value != value)
2346 range->need_recalc = TRUE;
2348 gtk_widget_queue_draw (GTK_WIDGET (range));
2350 switch (range->update_policy)
2352 case GTK_UPDATE_CONTINUOUS:
2353 gtk_adjustment_set_value (range->adjustment, value);
2356 /* Delayed means we update after a period of inactivity */
2357 case GTK_UPDATE_DELAYED:
2358 gtk_range_reset_update_timer (range);
2361 /* Discontinuous means we update on button release */
2362 case GTK_UPDATE_DISCONTINUOUS:
2363 /* don't emit value_changed signal */
2364 range->adjustment->value = value;
2365 range->update_pending = TRUE;
2372 gtk_range_update_value (GtkRange *range)
2374 gtk_range_remove_update_timer (range);
2376 if (range->update_pending)
2378 gtk_adjustment_value_changed (range->adjustment);
2380 range->update_pending = FALSE;
2384 struct _GtkRangeStepTimer
2391 second_timeout (gpointer data)
2395 GDK_THREADS_ENTER ();
2396 range = GTK_RANGE (data);
2397 gtk_range_scroll (range, range->timer->step);
2398 GDK_THREADS_LEAVE ();
2404 initial_timeout (gpointer data)
2408 GDK_THREADS_ENTER ();
2409 range = GTK_RANGE (data);
2410 range->timer->timeout_id =
2411 g_timeout_add (SCROLL_LATER_DELAY,
2414 GDK_THREADS_LEAVE ();
2421 gtk_range_add_step_timer (GtkRange *range,
2424 g_return_if_fail (range->timer == NULL);
2425 g_return_if_fail (step != GTK_SCROLL_NONE);
2427 range->timer = g_new (GtkRangeStepTimer, 1);
2429 range->timer->timeout_id =
2430 g_timeout_add (SCROLL_INITIAL_DELAY,
2433 range->timer->step = step;
2435 gtk_range_scroll (range, range->timer->step);
2439 gtk_range_remove_step_timer (GtkRange *range)
2443 if (range->timer->timeout_id != 0)
2444 g_source_remove (range->timer->timeout_id);
2446 g_free (range->timer);
2448 range->timer = NULL;
2453 update_timeout (gpointer data)
2457 GDK_THREADS_ENTER ();
2458 range = GTK_RANGE (data);
2459 gtk_range_update_value (range);
2460 range->update_timeout_id = 0;
2461 GDK_THREADS_LEAVE ();
2468 gtk_range_reset_update_timer (GtkRange *range)
2470 gtk_range_remove_update_timer (range);
2472 range->update_timeout_id = g_timeout_add (UPDATE_DELAY,
2478 gtk_range_remove_update_timer (GtkRange *range)
2480 if (range->update_timeout_id != 0)
2482 g_source_remove (range->update_timeout_id);
2483 range->update_timeout_id = 0;