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/.
32 #include "gtkmarshalers.h"
35 #include "gtkscrollbar.h"
37 #define SCROLL_INITIAL_DELAY 250 /* must hold button this long before ... */
38 #define SCROLL_LATER_DELAY 100 /* ... it starts repeating at this rate */
39 #define UPDATE_DELAY 300 /* Delay for queued update */
63 MOUSE_WIDGET /* inside widget but not in any of the above GUI elements */
66 struct _GtkRangeLayout
68 /* These are in widget->window coordinates */
69 GdkRectangle stepper_a;
70 GdkRectangle stepper_b;
71 GdkRectangle stepper_c;
72 GdkRectangle stepper_d;
73 /* The trough rectangle is the area the thumb can slide in, not the
79 /* Layout-related state */
81 MouseLocation mouse_location;
82 /* last mouse coords we got, or -1 if mouse is outside the range */
85 /* "grabbed" mouse location, OUTSIDE for no grab */
86 MouseLocation grab_location;
87 gint grab_button; /* 0 if none */
91 static void gtk_range_class_init (GtkRangeClass *klass);
92 static void gtk_range_init (GtkRange *range);
93 static void gtk_range_set_property (GObject *object,
97 static void gtk_range_get_property (GObject *object,
101 static void gtk_range_destroy (GtkObject *object);
102 static void gtk_range_finalize (GObject *object);
103 static void gtk_range_size_request (GtkWidget *widget,
104 GtkRequisition *requisition);
105 static void gtk_range_size_allocate (GtkWidget *widget,
106 GtkAllocation *allocation);
107 static void gtk_range_realize (GtkWidget *widget);
108 static void gtk_range_unrealize (GtkWidget *widget);
109 static void gtk_range_map (GtkWidget *widget);
110 static void gtk_range_unmap (GtkWidget *widget);
111 static gint gtk_range_expose (GtkWidget *widget,
112 GdkEventExpose *event);
113 static gint gtk_range_button_press (GtkWidget *widget,
114 GdkEventButton *event);
115 static gint gtk_range_button_release (GtkWidget *widget,
116 GdkEventButton *event);
117 static gint gtk_range_motion_notify (GtkWidget *widget,
118 GdkEventMotion *event);
119 static gint gtk_range_enter_notify (GtkWidget *widget,
120 GdkEventCrossing *event);
121 static gint gtk_range_leave_notify (GtkWidget *widget,
122 GdkEventCrossing *event);
123 static void gtk_range_grab_notify (GtkWidget *widget,
124 gboolean was_grabbed);
125 static void gtk_range_state_changed (GtkWidget *widget,
126 GtkStateType previous_state);
127 static gint gtk_range_scroll_event (GtkWidget *widget,
128 GdkEventScroll *event);
129 static void gtk_range_style_set (GtkWidget *widget,
130 GtkStyle *previous_style);
131 static void update_slider_position (GtkRange *range,
138 static void gtk_range_move_slider (GtkRange *range,
139 GtkScrollType scroll);
142 static void gtk_range_scroll (GtkRange *range,
143 GtkScrollType scroll);
144 static gboolean gtk_range_update_mouse_location (GtkRange *range);
145 static void gtk_range_calc_layout (GtkRange *range,
146 gdouble adjustment_value);
147 static void gtk_range_get_props (GtkRange *range,
151 gint *stepper_spacing,
152 gint *arrow_displacement_x,
153 gint *arrow_displacement_y);
154 static void gtk_range_calc_request (GtkRange *range,
158 gint stepper_spacing,
159 GdkRectangle *range_rect,
162 gint *slider_length_p);
163 static void gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
165 static void gtk_range_adjustment_changed (GtkAdjustment *adjustment,
167 static void gtk_range_add_step_timer (GtkRange *range,
169 static void gtk_range_remove_step_timer (GtkRange *range);
170 static void gtk_range_reset_update_timer (GtkRange *range);
171 static void gtk_range_remove_update_timer (GtkRange *range);
172 static GdkRectangle* get_area (GtkRange *range,
173 MouseLocation location);
174 static void gtk_range_internal_set_value (GtkRange *range,
176 static void gtk_range_update_value (GtkRange *range);
179 static GtkWidgetClass *parent_class = NULL;
180 static guint signals[LAST_SIGNAL];
184 gtk_range_get_type (void)
186 static GType range_type = 0;
190 static const GTypeInfo range_info =
192 sizeof (GtkRangeClass),
193 NULL, /* base_init */
194 NULL, /* base_finalize */
195 (GClassInitFunc) gtk_range_class_init,
196 NULL, /* class_finalize */
197 NULL, /* class_data */
200 (GInstanceInitFunc) gtk_range_init,
201 NULL, /* value_table */
204 range_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkRange",
205 &range_info, G_TYPE_FLAG_ABSTRACT);
212 gtk_range_class_init (GtkRangeClass *class)
214 GObjectClass *gobject_class;
215 GtkObjectClass *object_class;
216 GtkWidgetClass *widget_class;
218 gobject_class = G_OBJECT_CLASS (class);
219 object_class = (GtkObjectClass*) class;
220 widget_class = (GtkWidgetClass*) class;
222 parent_class = g_type_class_peek_parent (class);
224 gobject_class->set_property = gtk_range_set_property;
225 gobject_class->get_property = gtk_range_get_property;
226 gobject_class->finalize = gtk_range_finalize;
227 object_class->destroy = gtk_range_destroy;
229 widget_class->size_request = gtk_range_size_request;
230 widget_class->size_allocate = gtk_range_size_allocate;
231 widget_class->realize = gtk_range_realize;
232 widget_class->unrealize = gtk_range_unrealize;
233 widget_class->map = gtk_range_map;
234 widget_class->unmap = gtk_range_unmap;
235 widget_class->expose_event = gtk_range_expose;
236 widget_class->button_press_event = gtk_range_button_press;
237 widget_class->button_release_event = gtk_range_button_release;
238 widget_class->motion_notify_event = gtk_range_motion_notify;
239 widget_class->scroll_event = gtk_range_scroll_event;
240 widget_class->enter_notify_event = gtk_range_enter_notify;
241 widget_class->leave_notify_event = gtk_range_leave_notify;
242 widget_class->grab_notify = gtk_range_grab_notify;
243 widget_class->state_changed = gtk_range_state_changed;
244 widget_class->style_set = gtk_range_style_set;
246 class->move_slider = gtk_range_move_slider;
248 class->slider_detail = "slider";
249 class->stepper_detail = "stepper";
251 signals[VALUE_CHANGED] =
252 g_signal_new ("value_changed",
253 G_TYPE_FROM_CLASS (gobject_class),
255 G_STRUCT_OFFSET (GtkRangeClass, value_changed),
257 _gtk_marshal_NONE__NONE,
260 signals[ADJUST_BOUNDS] =
261 g_signal_new ("adjust_bounds",
262 G_TYPE_FROM_CLASS (gobject_class),
264 G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds),
266 _gtk_marshal_VOID__DOUBLE,
270 signals[MOVE_SLIDER] =
271 g_signal_new ("move_slider",
272 G_TYPE_FROM_CLASS (gobject_class),
273 G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
274 G_STRUCT_OFFSET (GtkRangeClass, move_slider),
276 _gtk_marshal_VOID__ENUM,
278 GTK_TYPE_SCROLL_TYPE);
280 g_object_class_install_property (gobject_class,
282 g_param_spec_enum ("update_policy",
284 P_("How the range should be updated on the screen"),
285 GTK_TYPE_UPDATE_TYPE,
286 GTK_UPDATE_CONTINUOUS,
289 g_object_class_install_property (gobject_class,
291 g_param_spec_object ("adjustment",
293 P_("The GtkAdjustment that contains the current value of this range object"),
295 G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
297 g_object_class_install_property (gobject_class,
299 g_param_spec_boolean ("inverted",
301 P_("Invert direction slider moves to increase range value"),
305 gtk_widget_class_install_style_property (widget_class,
306 g_param_spec_int ("slider_width",
308 P_("Width of scrollbar or scale thumb"),
313 gtk_widget_class_install_style_property (widget_class,
314 g_param_spec_int ("trough_border",
316 P_("Spacing between thumb/steppers and outer trough bevel"),
321 gtk_widget_class_install_style_property (widget_class,
322 g_param_spec_int ("stepper_size",
324 P_("Length of step buttons at ends"),
329 gtk_widget_class_install_style_property (widget_class,
330 g_param_spec_int ("stepper_spacing",
331 P_("Stepper Spacing"),
332 P_("Spacing between step buttons and thumb"),
337 gtk_widget_class_install_style_property (widget_class,
338 g_param_spec_int ("arrow_displacement_x",
339 P_("Arrow X Displacement"),
340 P_("How far in the x direction to move the arrow when the button is depressed"),
345 gtk_widget_class_install_style_property (widget_class,
346 g_param_spec_int ("arrow_displacement_y",
347 P_("Arrow Y Displacement"),
348 P_("How far in the y direction to move the arrow when the button is depressed"),
356 gtk_range_set_property (GObject *object,
363 range = GTK_RANGE (object);
367 case PROP_UPDATE_POLICY:
368 gtk_range_set_update_policy (range, g_value_get_enum (value));
370 case PROP_ADJUSTMENT:
371 gtk_range_set_adjustment (range, g_value_get_object (value));
374 gtk_range_set_inverted (range, g_value_get_boolean (value));
377 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
383 gtk_range_get_property (GObject *object,
390 range = GTK_RANGE (object);
394 case PROP_UPDATE_POLICY:
395 g_value_set_enum (value, range->update_policy);
397 case PROP_ADJUSTMENT:
398 g_value_set_object (value, range->adjustment);
401 g_value_set_boolean (value, range->inverted);
404 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
410 gtk_range_init (GtkRange *range)
412 GTK_WIDGET_SET_FLAGS (range, GTK_NO_WINDOW);
414 range->adjustment = NULL;
415 range->update_policy = GTK_UPDATE_CONTINUOUS;
416 range->inverted = FALSE;
417 range->flippable = FALSE;
418 range->min_slider_size = 1;
419 range->has_stepper_a = FALSE;
420 range->has_stepper_b = FALSE;
421 range->has_stepper_c = FALSE;
422 range->has_stepper_d = FALSE;
423 range->need_recalc = TRUE;
424 range->round_digits = -1;
425 range->layout = g_new0 (GtkRangeLayout, 1);
426 range->layout->mouse_location = MOUSE_OUTSIDE;
427 range->layout->mouse_x = -1;
428 range->layout->mouse_y = -1;
429 range->layout->grab_location = MOUSE_OUTSIDE;
430 range->layout->grab_button = 0;
435 * gtk_range_get_adjustment:
436 * @range: a #GtkRange
438 * Get the #GtkAdjustment which is the "model" object for #GtkRange.
439 * See gtk_range_set_adjustment() for details.
440 * The return value does not have a reference added, so should not
443 * Return value: a #GtkAdjustment
446 gtk_range_get_adjustment (GtkRange *range)
448 g_return_val_if_fail (GTK_IS_RANGE (range), NULL);
450 if (!range->adjustment)
451 gtk_range_set_adjustment (range, NULL);
453 return range->adjustment;
457 * gtk_range_set_update_policy:
458 * @range: a #GtkRange
459 * @policy: update policy
461 * Sets the update policy for the range. #GTK_UPDATE_CONTINUOUS means that
462 * anytime the range slider is moved, the range value will change and the
463 * value_changed signal will be emitted. #GTK_UPDATE_DELAYED means that
464 * the value will be updated after a brief timeout where no slider motion
465 * occurs, so updates are spaced by a short time rather than
466 * continuous. #GTK_UPDATE_DISCONTINUOUS means that the value will only
467 * be updated when the user releases the button and ends the slider
472 gtk_range_set_update_policy (GtkRange *range,
473 GtkUpdateType policy)
475 g_return_if_fail (GTK_IS_RANGE (range));
477 if (range->update_policy != policy)
479 range->update_policy = policy;
480 g_object_notify (G_OBJECT (range), "update_policy");
485 * gtk_range_get_update_policy:
486 * @range: a #GtkRange
488 * Gets the update policy of @range. See gtk_range_set_update_policy().
490 * Return value: the current update policy
493 gtk_range_get_update_policy (GtkRange *range)
495 g_return_val_if_fail (GTK_IS_RANGE (range), GTK_UPDATE_CONTINUOUS);
497 return range->update_policy;
501 * gtk_range_set_adjustment:
502 * @range: a #GtkRange
503 * @adjustment: a #GtkAdjustment
505 * Sets the adjustment to be used as the "model" object for this range
506 * widget. The adjustment indicates the current range value, the
507 * minimum and maximum range values, the step/page increments used
508 * for keybindings and scrolling, and the page size. The page size
509 * is normally 0 for #GtkScale and nonzero for #GtkScrollbar, and
510 * indicates the size of the visible area of the widget being scrolled.
511 * The page size affects the size of the scrollbar slider.
515 gtk_range_set_adjustment (GtkRange *range,
516 GtkAdjustment *adjustment)
518 g_return_if_fail (GTK_IS_RANGE (range));
521 adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
523 g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
525 if (range->adjustment != adjustment)
527 if (range->adjustment)
529 g_signal_handlers_disconnect_by_func (range->adjustment,
530 gtk_range_adjustment_changed,
532 g_signal_handlers_disconnect_by_func (range->adjustment,
533 gtk_range_adjustment_value_changed,
535 g_object_unref (range->adjustment);
538 range->adjustment = adjustment;
539 g_object_ref (adjustment);
540 gtk_object_sink (GTK_OBJECT (adjustment));
542 g_signal_connect (adjustment, "changed",
543 G_CALLBACK (gtk_range_adjustment_changed),
545 g_signal_connect (adjustment, "value_changed",
546 G_CALLBACK (gtk_range_adjustment_value_changed),
549 gtk_range_adjustment_changed (adjustment, range);
550 g_object_notify (G_OBJECT (range), "adjustment");
555 * gtk_range_set_inverted:
556 * @range: a #GtkRange
557 * @setting: %TRUE to invert the range
559 * Ranges normally move from lower to higher values as the
560 * slider moves from top to bottom or left to right. Inverted
561 * ranges have higher values at the top or on the right rather than
562 * on the bottom or left.
566 gtk_range_set_inverted (GtkRange *range,
569 g_return_if_fail (GTK_IS_RANGE (range));
571 setting = setting != FALSE;
573 if (setting != range->inverted)
575 range->inverted = setting;
576 g_object_notify (G_OBJECT (range), "inverted");
577 gtk_widget_queue_resize (GTK_WIDGET (range));
582 * gtk_range_get_inverted:
583 * @range: a #GtkRange
585 * Gets the value set by gtk_range_set_inverted().
587 * Return value: %TRUE if the range is inverted
590 gtk_range_get_inverted (GtkRange *range)
592 g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
594 return range->inverted;
598 * gtk_range_set_increments:
599 * @range: a #GtkRange
603 * Sets the step and page sizes for the range.
604 * The step size is used when the user clicks the #GtkScrollbar
605 * arrows or moves #GtkScale via arrow keys. The page size
606 * is used for example when moving via Page Up or Page Down keys.
610 gtk_range_set_increments (GtkRange *range,
614 g_return_if_fail (GTK_IS_RANGE (range));
616 range->adjustment->step_increment = step;
617 range->adjustment->page_increment = page;
619 gtk_adjustment_changed (range->adjustment);
623 * gtk_range_set_range:
624 * @range: a #GtkRange
625 * @min: minimum range value
626 * @max: maximum range value
628 * Sets the allowable values in the #GtkRange, and clamps the range
629 * value to be between @min and @max. (If the range has a non-zero
630 * page size, it is clamped between @min and @max - page-size.)
633 gtk_range_set_range (GtkRange *range,
639 g_return_if_fail (GTK_IS_RANGE (range));
640 g_return_if_fail (min < max);
642 range->adjustment->lower = min;
643 range->adjustment->upper = max;
645 value = CLAMP (range->adjustment->value,
646 range->adjustment->lower,
647 (range->adjustment->upper - range->adjustment->page_size));
649 gtk_adjustment_set_value (range->adjustment, value);
650 gtk_adjustment_changed (range->adjustment);
654 * gtk_range_set_value:
655 * @range: a #GtkRange
656 * @value: new value of the range
658 * Sets the current value of the range; if the value is outside the
659 * minimum or maximum range values, it will be clamped to fit inside
660 * them. The range emits the "value_changed" signal if the value
665 gtk_range_set_value (GtkRange *range,
668 g_return_if_fail (GTK_IS_RANGE (range));
670 value = CLAMP (value, range->adjustment->lower,
671 (range->adjustment->upper - range->adjustment->page_size));
673 gtk_adjustment_set_value (range->adjustment, value);
677 * gtk_range_get_value:
678 * @range: a #GtkRange
680 * Gets the current value of the range.
682 * Return value: current value of the range.
685 gtk_range_get_value (GtkRange *range)
687 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
689 return range->adjustment->value;
693 should_invert (GtkRange *range)
695 if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
697 (range->inverted && !range->flippable) ||
698 (range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
699 (!range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
701 return range->inverted;
705 gtk_range_finalize (GObject *object)
707 GtkRange *range = GTK_RANGE (object);
709 g_free (range->layout);
711 (* G_OBJECT_CLASS (parent_class)->finalize) (object);
715 gtk_range_destroy (GtkObject *object)
717 GtkRange *range = GTK_RANGE (object);
719 gtk_range_remove_step_timer (range);
720 gtk_range_remove_update_timer (range);
722 if (range->adjustment)
724 g_signal_handlers_disconnect_by_func (range->adjustment,
725 gtk_range_adjustment_changed,
727 g_signal_handlers_disconnect_by_func (range->adjustment,
728 gtk_range_adjustment_value_changed,
730 g_object_unref (range->adjustment);
731 range->adjustment = NULL;
734 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
738 gtk_range_size_request (GtkWidget *widget,
739 GtkRequisition *requisition)
742 gint slider_width, stepper_size, trough_border, stepper_spacing;
743 GdkRectangle range_rect;
746 range = GTK_RANGE (widget);
748 gtk_range_get_props (range,
749 &slider_width, &stepper_size, &trough_border, &stepper_spacing,
752 gtk_range_calc_request (range,
753 slider_width, stepper_size, trough_border, stepper_spacing,
754 &range_rect, &border, NULL, NULL);
756 requisition->width = range_rect.width + border.left + border.right;
757 requisition->height = range_rect.height + border.top + border.bottom;
761 gtk_range_size_allocate (GtkWidget *widget,
762 GtkAllocation *allocation)
766 range = GTK_RANGE (widget);
768 widget->allocation = *allocation;
770 range->need_recalc = TRUE;
771 gtk_range_calc_layout (range, range->adjustment->value);
773 if (GTK_WIDGET_REALIZED (range))
774 gdk_window_move_resize (range->event_window,
775 widget->allocation.x,
776 widget->allocation.y,
777 widget->allocation.width,
778 widget->allocation.height);
782 gtk_range_realize (GtkWidget *widget)
785 GdkWindowAttr attributes;
786 gint attributes_mask;
788 range = GTK_RANGE (widget);
790 gtk_range_calc_layout (range, range->adjustment->value);
792 GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
794 widget->window = gtk_widget_get_parent_window (widget);
795 g_object_ref (widget->window);
797 attributes.window_type = GDK_WINDOW_CHILD;
798 attributes.x = widget->allocation.x;
799 attributes.y = widget->allocation.y;
800 attributes.width = widget->allocation.width;
801 attributes.height = widget->allocation.height;
802 attributes.wclass = GDK_INPUT_ONLY;
803 attributes.event_mask = gtk_widget_get_events (widget);
804 attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
805 GDK_BUTTON_RELEASE_MASK |
806 GDK_ENTER_NOTIFY_MASK |
807 GDK_LEAVE_NOTIFY_MASK |
808 GDK_POINTER_MOTION_MASK |
809 GDK_POINTER_MOTION_HINT_MASK);
811 attributes_mask = GDK_WA_X | GDK_WA_Y;
813 range->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
814 &attributes, attributes_mask);
815 gdk_window_set_user_data (range->event_window, range);
817 widget->style = gtk_style_attach (widget->style, widget->window);
821 gtk_range_unrealize (GtkWidget *widget)
823 GtkRange *range = GTK_RANGE (widget);
825 gtk_range_remove_step_timer (range);
826 gtk_range_remove_update_timer (range);
828 gdk_window_set_user_data (range->event_window, NULL);
829 gdk_window_destroy (range->event_window);
830 range->event_window = NULL;
832 if (GTK_WIDGET_CLASS (parent_class)->unrealize)
833 (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
837 gtk_range_map (GtkWidget *widget)
839 GtkRange *range = GTK_RANGE (widget);
841 g_return_if_fail (GTK_IS_RANGE (widget));
843 gdk_window_show (range->event_window);
845 GTK_WIDGET_CLASS (parent_class)->map (widget);
849 gtk_range_unmap (GtkWidget *widget)
851 GtkRange *range = GTK_RANGE (widget);
853 g_return_if_fail (GTK_IS_RANGE (widget));
855 gdk_window_hide (range->event_window);
857 GTK_WIDGET_CLASS (parent_class)->unmap (widget);
861 draw_stepper (GtkRange *range,
863 GtkArrowType arrow_type,
868 GtkStateType state_type;
869 GtkShadowType shadow_type;
870 GdkRectangle intersection;
871 GtkWidget *widget = GTK_WIDGET (range);
878 /* More to get the right clip region than for efficiency */
879 if (!gdk_rectangle_intersect (area, rect, &intersection))
882 intersection.x += widget->allocation.x;
883 intersection.y += widget->allocation.y;
885 if (!GTK_WIDGET_IS_SENSITIVE (range))
886 state_type = GTK_STATE_INSENSITIVE;
888 state_type = GTK_STATE_ACTIVE;
890 state_type = GTK_STATE_PRELIGHT;
892 state_type = GTK_STATE_NORMAL;
895 shadow_type = GTK_SHADOW_IN;
897 shadow_type = GTK_SHADOW_OUT;
899 gtk_paint_box (widget->style,
901 state_type, shadow_type,
902 &intersection, widget,
903 GTK_RANGE_GET_CLASS (range)->stepper_detail,
904 widget->allocation.x + rect->x,
905 widget->allocation.y + rect->y,
909 arrow_width = rect->width / 2;
910 arrow_height = rect->height / 2;
911 arrow_x = widget->allocation.x + rect->x + (rect->width - arrow_width) / 2;
912 arrow_y = widget->allocation.y + rect->y + (rect->height - arrow_height) / 2;
916 gint arrow_displacement_x;
917 gint arrow_displacement_y;
919 gtk_range_get_props (GTK_RANGE (widget), NULL, NULL, NULL, NULL,
920 &arrow_displacement_x, &arrow_displacement_y);
922 arrow_x += arrow_displacement_x;
923 arrow_y += arrow_displacement_y;
926 gtk_paint_arrow (widget->style,
928 state_type, shadow_type,
929 &intersection, widget,
930 GTK_RANGE_GET_CLASS (range)->stepper_detail,
933 arrow_x, arrow_y, arrow_width, arrow_height);
937 gtk_range_expose (GtkWidget *widget,
938 GdkEventExpose *event)
943 GdkRectangle expose_area; /* Relative to widget->allocation */
945 gint focus_line_width = 0;
946 gint focus_padding = 0;
948 range = GTK_RANGE (widget);
950 if (GTK_WIDGET_CAN_FOCUS (range))
952 gtk_widget_style_get (GTK_WIDGET (range),
953 "focus-line-width", &focus_line_width,
954 "focus-padding", &focus_padding,
958 expose_area = event->area;
959 expose_area.x -= widget->allocation.x;
960 expose_area.y -= widget->allocation.y;
962 gtk_range_calc_layout (range, range->adjustment->value);
964 sensitive = GTK_WIDGET_IS_SENSITIVE (widget);
966 /* Just to be confusing, we draw the trough for the whole
967 * range rectangle, not the trough rectangle (the trough
968 * rectangle is just for hit detection)
970 /* The gdk_rectangle_intersect is more to get the right
971 * clip region (limited to range_rect) than for efficiency
973 if (gdk_rectangle_intersect (&expose_area, &range->range_rect,
976 area.x += widget->allocation.x;
977 area.y += widget->allocation.y;
979 gtk_paint_box (widget->style,
981 sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
983 &area, GTK_WIDGET(range), "trough",
984 widget->allocation.x + range->range_rect.x + focus_line_width + focus_padding,
985 widget->allocation.y + range->range_rect.y + focus_line_width + focus_padding,
986 range->range_rect.width - 2 * (focus_line_width + focus_padding),
987 range->range_rect.height - 2 * (focus_line_width + focus_padding));
991 GTK_WIDGET_HAS_FOCUS (range))
992 gtk_paint_focus (widget->style, widget->window, GTK_WIDGET_STATE (widget),
993 &area, widget, "trough",
994 widget->allocation.x + range->range_rect.x,
995 widget->allocation.y + range->range_rect.y,
996 range->range_rect.width,
997 range->range_rect.height);
1001 state = GTK_STATE_INSENSITIVE;
1002 else if (range->layout->mouse_location == MOUSE_SLIDER)
1003 state = GTK_STATE_PRELIGHT;
1005 state = GTK_STATE_NORMAL;
1007 if (gdk_rectangle_intersect (&expose_area,
1008 &range->layout->slider,
1011 area.x += widget->allocation.x;
1012 area.y += widget->allocation.y;
1014 gtk_paint_slider (widget->style,
1020 GTK_RANGE_GET_CLASS (range)->slider_detail,
1021 widget->allocation.x + range->layout->slider.x,
1022 widget->allocation.y + range->layout->slider.y,
1023 range->layout->slider.width,
1024 range->layout->slider.height,
1025 range->orientation);
1028 if (range->has_stepper_a)
1029 draw_stepper (range, &range->layout->stepper_a,
1030 range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
1031 range->layout->grab_location == MOUSE_STEPPER_A,
1032 range->layout->mouse_location == MOUSE_STEPPER_A,
1035 if (range->has_stepper_b)
1036 draw_stepper (range, &range->layout->stepper_b,
1037 range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
1038 range->layout->grab_location == MOUSE_STEPPER_B,
1039 range->layout->mouse_location == MOUSE_STEPPER_B,
1042 if (range->has_stepper_c)
1043 draw_stepper (range, &range->layout->stepper_c,
1044 range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
1045 range->layout->grab_location == MOUSE_STEPPER_C,
1046 range->layout->mouse_location == MOUSE_STEPPER_C,
1049 if (range->has_stepper_d)
1050 draw_stepper (range, &range->layout->stepper_d,
1051 range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
1052 range->layout->grab_location == MOUSE_STEPPER_D,
1053 range->layout->mouse_location == MOUSE_STEPPER_D,
1060 range_grab_add (GtkRange *range,
1061 MouseLocation location,
1064 /* we don't actually gtk_grab, since a button is down */
1066 gtk_grab_add (GTK_WIDGET (range));
1068 range->layout->grab_location = location;
1069 range->layout->grab_button = button;
1071 if (gtk_range_update_mouse_location (range))
1072 gtk_widget_queue_draw (GTK_WIDGET (range));
1076 range_grab_remove (GtkRange *range)
1078 gtk_grab_remove (GTK_WIDGET (range));
1080 range->layout->grab_location = MOUSE_OUTSIDE;
1081 range->layout->grab_button = 0;
1083 if (gtk_range_update_mouse_location (range))
1084 gtk_widget_queue_draw (GTK_WIDGET (range));
1087 static GtkScrollType
1088 range_get_scroll_for_grab (GtkRange *range)
1092 invert = should_invert (range);
1093 switch (range->layout->grab_location)
1095 /* Backward stepper */
1096 case MOUSE_STEPPER_A:
1097 case MOUSE_STEPPER_C:
1098 switch (range->layout->grab_button)
1101 return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
1104 return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD;
1107 return invert ? GTK_SCROLL_END : GTK_SCROLL_START;
1112 /* Forward stepper */
1113 case MOUSE_STEPPER_B:
1114 case MOUSE_STEPPER_D:
1115 switch (range->layout->grab_button)
1118 return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
1121 return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD;
1124 return invert ? GTK_SCROLL_START : GTK_SCROLL_END;
1132 if (range->trough_click_forward)
1133 return GTK_SCROLL_PAGE_FORWARD;
1135 return GTK_SCROLL_PAGE_BACKWARD;
1145 return GTK_SCROLL_NONE;
1149 coord_to_value (GtkRange *range,
1155 if (range->orientation == GTK_ORIENTATION_VERTICAL)
1156 if (range->layout->trough.height == range->layout->slider.height)
1159 frac = ((coord - range->layout->trough.y) /
1160 (gdouble) (range->layout->trough.height - range->layout->slider.height));
1162 if (range->layout->trough.width == range->layout->slider.width)
1165 frac = ((coord - range->layout->trough.x) /
1166 (gdouble) (range->layout->trough.width - range->layout->slider.width));
1168 if (should_invert (range))
1171 value = range->adjustment->lower +
1172 frac * (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size);
1178 gtk_range_button_press (GtkWidget *widget,
1179 GdkEventButton *event)
1181 GtkRange *range = GTK_RANGE (widget);
1183 if (!GTK_WIDGET_HAS_FOCUS (widget))
1184 gtk_widget_grab_focus (widget);
1186 /* ignore presses when we're already doing something else. */
1187 if (range->layout->grab_location != MOUSE_OUTSIDE)
1190 range->layout->mouse_x = event->x;
1191 range->layout->mouse_y = event->y;
1192 if (gtk_range_update_mouse_location (range))
1193 gtk_widget_queue_draw (widget);
1195 if (range->layout->mouse_location == MOUSE_TROUGH &&
1198 /* button 1 steps by page increment, as with button 2 on a stepper
1200 GtkScrollType scroll;
1201 gdouble click_value;
1203 click_value = coord_to_value (range,
1204 range->orientation == GTK_ORIENTATION_VERTICAL ?
1205 event->y : event->x);
1207 range->trough_click_forward = click_value > range->adjustment->value;
1208 range_grab_add (range, MOUSE_TROUGH, event->button);
1210 scroll = range_get_scroll_for_grab (range);
1212 gtk_range_add_step_timer (range, scroll);
1216 else if ((range->layout->mouse_location == MOUSE_STEPPER_A ||
1217 range->layout->mouse_location == MOUSE_STEPPER_B ||
1218 range->layout->mouse_location == MOUSE_STEPPER_C ||
1219 range->layout->mouse_location == MOUSE_STEPPER_D) &&
1220 (event->button == 1 || event->button == 2 || event->button == 3))
1222 GdkRectangle *stepper_area;
1223 GtkScrollType scroll;
1225 range_grab_add (range, range->layout->mouse_location, event->button);
1227 stepper_area = get_area (range, range->layout->mouse_location);
1228 gtk_widget_queue_draw_area (widget,
1229 widget->allocation.x + stepper_area->x,
1230 widget->allocation.y + stepper_area->y,
1231 stepper_area->width,
1232 stepper_area->height);
1234 scroll = range_get_scroll_for_grab (range);
1235 if (scroll != GTK_SCROLL_NONE)
1236 gtk_range_add_step_timer (range, scroll);
1240 else if ((range->layout->mouse_location == MOUSE_TROUGH &&
1241 event->button == 2) ||
1242 range->layout->mouse_location == MOUSE_SLIDER)
1244 gboolean need_value_update = FALSE;
1246 /* Any button can be used to drag the slider, but you can start
1247 * dragging the slider with a trough click using button 2;
1248 * On button 2 press, we warp the slider to mouse position,
1249 * then begin the slider drag.
1251 if (event->button == 2)
1253 gdouble slider_low_value, slider_high_value, new_value;
1256 coord_to_value (range,
1257 range->orientation == GTK_ORIENTATION_VERTICAL ?
1258 event->y : event->x);
1260 coord_to_value (range,
1261 range->orientation == GTK_ORIENTATION_VERTICAL ?
1262 event->y - range->layout->slider.height :
1263 event->x - range->layout->slider.width);
1265 /* compute new value for warped slider */
1266 new_value = slider_low_value + (slider_high_value - slider_low_value) / 2;
1268 /* recalc slider, so we can set slide_initial_slider_position
1271 range->need_recalc = TRUE;
1272 gtk_range_calc_layout (range, new_value);
1274 /* defer adjustment updates to update_slider_position() in order
1275 * to keep pixel quantisation
1277 need_value_update = TRUE;
1280 if (range->orientation == GTK_ORIENTATION_VERTICAL)
1282 range->slide_initial_slider_position = range->layout->slider.y;
1283 range->slide_initial_coordinate = event->y;
1287 range->slide_initial_slider_position = range->layout->slider.x;
1288 range->slide_initial_coordinate = event->x;
1291 if (need_value_update)
1292 update_slider_position (range, event->x, event->y);
1294 range_grab_add (range, MOUSE_SLIDER, event->button);
1302 /* During a slide, move the slider as required given new mouse position */
1304 update_slider_position (GtkRange *range,
1312 if (range->orientation == GTK_ORIENTATION_VERTICAL)
1313 delta = mouse_y - range->slide_initial_coordinate;
1315 delta = mouse_x - range->slide_initial_coordinate;
1317 c = range->slide_initial_slider_position + delta;
1319 new_value = coord_to_value (range, c);
1321 gtk_range_internal_set_value (range, new_value);
1324 static void stop_scrolling (GtkRange *range)
1326 range_grab_remove (range);
1327 gtk_range_remove_step_timer (range);
1328 /* Flush any pending discontinuous/delayed updates */
1329 gtk_range_update_value (range);
1331 /* Just be lazy about this, if we scrolled it will all redraw anyway,
1332 * so no point optimizing the button deactivate case
1334 gtk_widget_queue_draw (GTK_WIDGET (range));
1338 gtk_range_button_release (GtkWidget *widget,
1339 GdkEventButton *event)
1341 GtkRange *range = GTK_RANGE (widget);
1343 if (event->window == range->event_window)
1345 range->layout->mouse_x = event->x;
1346 range->layout->mouse_y = event->y;
1350 gdk_window_get_pointer (range->event_window,
1351 &range->layout->mouse_x,
1352 &range->layout->mouse_y,
1356 if (range->layout->grab_button == event->button)
1358 if (range->layout->grab_location == MOUSE_SLIDER)
1359 update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y);
1361 stop_scrolling (range);
1370 * _gtk_range_get_wheel_delta:
1371 * @range: a #GtkRange
1372 * @direction: A #GdkScrollDirection
1374 * Returns a good step value for the mouse wheel.
1376 * Return value: A good step value for the mouse wheel.
1381 _gtk_range_get_wheel_delta (GtkRange *range,
1382 GdkScrollDirection direction)
1384 GtkAdjustment *adj = range->adjustment;
1387 if (GTK_IS_SCROLLBAR (range))
1388 delta = pow (adj->page_size, 2.0 / 3.0);
1390 delta = adj->step_increment * 2;
1392 if (direction == GDK_SCROLL_UP ||
1393 direction == GDK_SCROLL_LEFT)
1396 if (range->inverted)
1403 gtk_range_scroll_event (GtkWidget *widget,
1404 GdkEventScroll *event)
1406 GtkRange *range = GTK_RANGE (widget);
1408 if (GTK_WIDGET_REALIZED (range))
1410 GtkAdjustment *adj = GTK_RANGE (range)->adjustment;
1413 delta = _gtk_range_get_wheel_delta (range, event->direction);
1414 gtk_range_internal_set_value (range, adj->value + delta);
1416 /* Policy DELAYED makes sense with scroll events,
1417 * but DISCONTINUOUS doesn't, so we update immediately
1420 if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
1421 gtk_range_update_value (range);
1428 gtk_range_motion_notify (GtkWidget *widget,
1429 GdkEventMotion *event)
1434 range = GTK_RANGE (widget);
1436 gdk_window_get_pointer (range->event_window, &x, &y, NULL);
1438 range->layout->mouse_x = x;
1439 range->layout->mouse_y = y;
1441 if (gtk_range_update_mouse_location (range))
1442 gtk_widget_queue_draw (widget);
1444 if (range->layout->grab_location == MOUSE_SLIDER)
1445 update_slider_position (range, x, y);
1447 /* We handled the event if the mouse was in the range_rect */
1448 return range->layout->mouse_location != MOUSE_OUTSIDE;
1452 gtk_range_enter_notify (GtkWidget *widget,
1453 GdkEventCrossing *event)
1455 GtkRange *range = GTK_RANGE (widget);
1457 range->layout->mouse_x = event->x;
1458 range->layout->mouse_y = event->y;
1460 if (gtk_range_update_mouse_location (range))
1461 gtk_widget_queue_draw (widget);
1467 gtk_range_leave_notify (GtkWidget *widget,
1468 GdkEventCrossing *event)
1470 GtkRange *range = GTK_RANGE (widget);
1472 range->layout->mouse_x = -1;
1473 range->layout->mouse_y = -1;
1475 if (gtk_range_update_mouse_location (range))
1476 gtk_widget_queue_draw (widget);
1482 gtk_range_grab_notify (GtkWidget *widget,
1483 gboolean was_grabbed)
1486 stop_scrolling (GTK_RANGE (widget));
1490 gtk_range_state_changed (GtkWidget *widget,
1491 GtkStateType previous_state)
1493 if (!GTK_WIDGET_IS_SENSITIVE (widget))
1494 stop_scrolling (GTK_RANGE (widget));
1498 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
1501 GtkRange *range = GTK_RANGE (data);
1503 range->need_recalc = TRUE;
1504 gtk_widget_queue_draw (GTK_WIDGET (range));
1506 /* Note that we don't round off to range->round_digits here.
1507 * that's because it's really broken to change a value
1508 * in response to a change signal on that value; round_digits
1509 * is therefore defined to be a filter on what the GtkRange
1510 * can input into the adjustment, not a filter that the GtkRange
1511 * will enforce on the adjustment.
1516 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
1519 GtkRange *range = GTK_RANGE (data);
1521 range->need_recalc = TRUE;
1523 gtk_widget_queue_draw (GTK_WIDGET (range));
1524 /* This is so we don't lag the widget being scrolled. */
1525 if (GTK_WIDGET_REALIZED (range))
1526 gdk_window_process_updates (GTK_WIDGET (range)->window, FALSE);
1528 /* Note that we don't round off to range->round_digits here.
1529 * that's because it's really broken to change a value
1530 * in response to a change signal on that value; round_digits
1531 * is therefore defined to be a filter on what the GtkRange
1532 * can input into the adjustment, not a filter that the GtkRange
1533 * will enforce on the adjustment.
1536 g_signal_emit (range, signals[VALUE_CHANGED], 0);
1540 gtk_range_style_set (GtkWidget *widget,
1541 GtkStyle *previous_style)
1543 GtkRange *range = GTK_RANGE (widget);
1545 range->need_recalc = TRUE;
1547 (* GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style);
1551 step_back (GtkRange *range)
1555 newval = range->adjustment->value - range->adjustment->step_increment;
1556 gtk_range_internal_set_value (range, newval);
1560 step_forward (GtkRange *range)
1564 newval = range->adjustment->value + range->adjustment->step_increment;
1565 gtk_range_internal_set_value (range, newval);
1570 page_back (GtkRange *range)
1574 newval = range->adjustment->value - range->adjustment->page_increment;
1575 gtk_range_internal_set_value (range, newval);
1579 page_forward (GtkRange *range)
1583 newval = range->adjustment->value + range->adjustment->page_increment;
1584 gtk_range_internal_set_value (range, newval);
1588 scroll_begin (GtkRange *range)
1590 gtk_range_internal_set_value (range, range->adjustment->lower);
1594 scroll_end (GtkRange *range)
1598 newval = range->adjustment->upper - range->adjustment->page_size;
1599 gtk_range_internal_set_value (range, newval);
1603 gtk_range_scroll (GtkRange *range,
1604 GtkScrollType scroll)
1608 case GTK_SCROLL_STEP_LEFT:
1609 if (should_invert (range))
1610 step_forward (range);
1615 case GTK_SCROLL_STEP_UP:
1616 if (should_invert (range))
1617 step_forward (range);
1622 case GTK_SCROLL_STEP_RIGHT:
1623 if (should_invert (range))
1626 step_forward (range);
1629 case GTK_SCROLL_STEP_DOWN:
1630 if (should_invert (range))
1633 step_forward (range);
1636 case GTK_SCROLL_STEP_BACKWARD:
1640 case GTK_SCROLL_STEP_FORWARD:
1641 step_forward (range);
1644 case GTK_SCROLL_PAGE_LEFT:
1645 if (should_invert (range))
1646 page_forward (range);
1651 case GTK_SCROLL_PAGE_UP:
1652 if (should_invert (range))
1653 page_forward (range);
1658 case GTK_SCROLL_PAGE_RIGHT:
1659 if (should_invert (range))
1662 page_forward (range);
1665 case GTK_SCROLL_PAGE_DOWN:
1666 if (should_invert (range))
1669 page_forward (range);
1672 case GTK_SCROLL_PAGE_BACKWARD:
1676 case GTK_SCROLL_PAGE_FORWARD:
1677 page_forward (range);
1680 case GTK_SCROLL_START:
1681 scroll_begin (range);
1684 case GTK_SCROLL_END:
1688 case GTK_SCROLL_JUMP:
1689 /* Used by CList, range doesn't use it. */
1692 case GTK_SCROLL_NONE:
1698 gtk_range_move_slider (GtkRange *range,
1699 GtkScrollType scroll)
1701 gtk_range_scroll (range, scroll);
1703 /* Policy DELAYED makes sense with key events,
1704 * but DISCONTINUOUS doesn't, so we update immediately
1707 if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
1708 gtk_range_update_value (range);
1712 gtk_range_get_props (GtkRange *range,
1715 gint *trough_border,
1716 gint *stepper_spacing,
1717 gint *arrow_displacement_x,
1718 gint *arrow_displacement_y)
1720 GtkWidget *widget = GTK_WIDGET (range);
1721 gint tmp_slider_width, tmp_stepper_size, tmp_trough_border, tmp_stepper_spacing;
1722 gint tmp_arrow_displacement_x, tmp_arrow_displacement_y;
1724 gtk_widget_style_get (widget,
1725 "slider_width", &tmp_slider_width,
1726 "trough_border", &tmp_trough_border,
1727 "stepper_size", &tmp_stepper_size,
1728 "stepper_spacing", &tmp_stepper_spacing,
1729 "arrow_displacement_x", &tmp_arrow_displacement_x,
1730 "arrow_displacement_y", &tmp_arrow_displacement_y,
1733 if (GTK_WIDGET_CAN_FOCUS (range))
1735 gint focus_line_width;
1738 gtk_widget_style_get (GTK_WIDGET (range),
1739 "focus-line-width", &focus_line_width,
1740 "focus-padding", &focus_padding,
1743 tmp_trough_border += focus_line_width + focus_padding;
1747 *slider_width = tmp_slider_width;
1750 *trough_border = tmp_trough_border;
1753 *stepper_size = tmp_stepper_size;
1755 if (stepper_spacing)
1756 *stepper_spacing = tmp_stepper_spacing;
1758 if (arrow_displacement_x)
1759 *arrow_displacement_x = tmp_arrow_displacement_x;
1761 if (arrow_displacement_y)
1762 *arrow_displacement_y = tmp_arrow_displacement_y;
1765 #define POINT_IN_RECT(xcoord, ycoord, rect) \
1766 ((xcoord) >= (rect).x && \
1767 (xcoord) < ((rect).x + (rect).width) && \
1768 (ycoord) >= (rect).y && \
1769 (ycoord) < ((rect).y + (rect).height))
1771 /* Update mouse location, return TRUE if it changes */
1773 gtk_range_update_mouse_location (GtkRange *range)
1779 widget = GTK_WIDGET (range);
1781 old = range->layout->mouse_location;
1783 x = range->layout->mouse_x;
1784 y = range->layout->mouse_y;
1786 if (range->layout->grab_location != MOUSE_OUTSIDE)
1787 range->layout->mouse_location = range->layout->grab_location;
1788 else if (POINT_IN_RECT (x, y, range->layout->stepper_a))
1789 range->layout->mouse_location = MOUSE_STEPPER_A;
1790 else if (POINT_IN_RECT (x, y, range->layout->stepper_b))
1791 range->layout->mouse_location = MOUSE_STEPPER_B;
1792 else if (POINT_IN_RECT (x, y, range->layout->stepper_c))
1793 range->layout->mouse_location = MOUSE_STEPPER_C;
1794 else if (POINT_IN_RECT (x, y, range->layout->stepper_d))
1795 range->layout->mouse_location = MOUSE_STEPPER_D;
1796 else if (POINT_IN_RECT (x, y, range->layout->slider))
1797 range->layout->mouse_location = MOUSE_SLIDER;
1798 else if (POINT_IN_RECT (x, y, range->layout->trough))
1799 range->layout->mouse_location = MOUSE_TROUGH;
1800 else if (POINT_IN_RECT (x, y, widget->allocation))
1801 range->layout->mouse_location = MOUSE_WIDGET;
1803 range->layout->mouse_location = MOUSE_OUTSIDE;
1805 return old != range->layout->mouse_location;
1808 /* Clamp rect, border inside widget->allocation, such that we prefer
1809 * to take space from border not rect in all directions, and prefer to
1810 * give space to border over rect in one direction.
1813 clamp_dimensions (GtkWidget *widget,
1816 gboolean border_expands_horizontally)
1818 gint extra, shortage;
1820 g_return_if_fail (rect->x == 0);
1821 g_return_if_fail (rect->y == 0);
1822 g_return_if_fail (rect->width >= 0);
1823 g_return_if_fail (rect->height >= 0);
1827 extra = widget->allocation.width - border->left - border->right - rect->width;
1830 if (border_expands_horizontally)
1832 border->left += extra / 2;
1833 border->right += extra / 2 + extra % 2;
1837 rect->width += extra;
1841 /* See if we can fit rect, if not kill the border */
1842 shortage = rect->width - widget->allocation.width;
1845 rect->width = widget->allocation.width;
1846 /* lose the border */
1852 /* See if we can fit rect with borders */
1853 shortage = rect->width + border->left + border->right -
1854 widget->allocation.width;
1857 /* Shrink borders */
1858 border->left -= shortage / 2;
1859 border->right -= shortage / 2 + shortage % 2;
1865 extra = widget->allocation.height - border->top - border->bottom - rect->height;
1868 if (border_expands_horizontally)
1870 /* don't expand border vertically */
1871 rect->height += extra;
1875 border->top += extra / 2;
1876 border->bottom += extra / 2 + extra % 2;
1880 /* See if we can fit rect, if not kill the border */
1881 shortage = rect->height - widget->allocation.height;
1884 rect->height = widget->allocation.height;
1885 /* lose the border */
1891 /* See if we can fit rect with borders */
1892 shortage = rect->height + border->top + border->bottom -
1893 widget->allocation.height;
1896 /* Shrink borders */
1897 border->top -= shortage / 2;
1898 border->bottom -= shortage / 2 + shortage % 2;
1904 gtk_range_calc_request (GtkRange *range,
1908 gint stepper_spacing,
1909 GdkRectangle *range_rect,
1912 gint *slider_length_p)
1922 if (GTK_RANGE_GET_CLASS (range)->get_range_border)
1923 (* GTK_RANGE_GET_CLASS (range)->get_range_border) (range, border);
1926 if (range->has_stepper_a)
1928 if (range->has_stepper_b)
1930 if (range->has_stepper_c)
1932 if (range->has_stepper_d)
1935 slider_length = range->min_slider_size;
1940 /* We never expand to fill available space in the small dimension
1941 * (i.e. vertical scrollbars are always a fixed width)
1943 if (range->orientation == GTK_ORIENTATION_VERTICAL)
1945 range_rect->width = trough_border * 2 + slider_width;
1946 range_rect->height = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length;
1950 range_rect->width = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length;
1951 range_rect->height = trough_border * 2 + slider_width;
1955 *n_steppers_p = n_steppers;
1957 if (slider_length_p)
1958 *slider_length_p = slider_length;
1962 gtk_range_calc_layout (GtkRange *range,
1963 gdouble adjustment_value)
1965 gint slider_width, stepper_size, trough_border, stepper_spacing;
1969 GdkRectangle range_rect;
1970 GtkRangeLayout *layout;
1973 if (!range->need_recalc)
1976 /* If we have a too-small allocation, we prefer the steppers over
1977 * the trough/slider, probably the steppers are a more useful
1978 * feature in small spaces.
1980 * Also, we prefer to draw the range itself rather than the border
1981 * areas if there's a conflict, since the borders will be decoration
1982 * not controls. Though this depends on subclasses cooperating by
1983 * not drawing on range->range_rect.
1986 widget = GTK_WIDGET (range);
1987 layout = range->layout;
1989 gtk_range_get_props (range,
1990 &slider_width, &stepper_size, &trough_border, &stepper_spacing,
1993 gtk_range_calc_request (range,
1994 slider_width, stepper_size, trough_border, stepper_spacing,
1995 &range_rect, &border, &n_steppers, &slider_length);
1997 /* We never expand to fill available space in the small dimension
1998 * (i.e. vertical scrollbars are always a fixed width)
2000 if (range->orientation == GTK_ORIENTATION_VERTICAL)
2002 clamp_dimensions (widget, &range_rect, &border, TRUE);
2006 clamp_dimensions (widget, &range_rect, &border, FALSE);
2009 range_rect.x = border.left;
2010 range_rect.y = border.top;
2012 range->range_rect = range_rect;
2014 if (range->orientation == GTK_ORIENTATION_VERTICAL)
2016 gint stepper_width, stepper_height;
2018 /* Steppers are the width of the range, and stepper_size in
2019 * height, or if we don't have enough height, divided equally
2020 * among available space.
2022 stepper_width = range_rect.width - trough_border * 2;
2024 if (stepper_width < 1)
2025 stepper_width = range_rect.width; /* screw the trough border */
2027 if (n_steppers == 0)
2028 stepper_height = 0; /* avoid divide by n_steppers */
2030 stepper_height = MIN (stepper_size, (range_rect.height / n_steppers));
2034 layout->stepper_a.x = range_rect.x + trough_border;
2035 layout->stepper_a.y = range_rect.y + trough_border;
2037 if (range->has_stepper_a)
2039 layout->stepper_a.width = stepper_width;
2040 layout->stepper_a.height = stepper_height;
2044 layout->stepper_a.width = 0;
2045 layout->stepper_a.height = 0;
2050 layout->stepper_b.x = layout->stepper_a.x;
2051 layout->stepper_b.y = layout->stepper_a.y + layout->stepper_a.height;
2053 if (range->has_stepper_b)
2055 layout->stepper_b.width = stepper_width;
2056 layout->stepper_b.height = stepper_height;
2060 layout->stepper_b.width = 0;
2061 layout->stepper_b.height = 0;
2066 if (range->has_stepper_d)
2068 layout->stepper_d.width = stepper_width;
2069 layout->stepper_d.height = stepper_height;
2073 layout->stepper_d.width = 0;
2074 layout->stepper_d.height = 0;
2077 layout->stepper_d.x = layout->stepper_a.x;
2078 layout->stepper_d.y = range_rect.y + range_rect.height - layout->stepper_d.height - trough_border;
2082 if (range->has_stepper_c)
2084 layout->stepper_c.width = stepper_width;
2085 layout->stepper_c.height = stepper_height;
2089 layout->stepper_c.width = 0;
2090 layout->stepper_c.height = 0;
2093 layout->stepper_c.x = layout->stepper_a.x;
2094 layout->stepper_c.y = layout->stepper_d.y - layout->stepper_c.height;
2096 /* Now the trough is the remaining space between steppers B and C,
2099 layout->trough.x = range_rect.x;
2100 layout->trough.y = layout->stepper_b.y + layout->stepper_b.height;
2101 layout->trough.width = range_rect.width;
2102 layout->trough.height = layout->stepper_c.y - (layout->stepper_b.y + layout->stepper_b.height);
2104 /* Slider fits into the trough, with stepper_spacing on either side,
2105 * and the size/position based on the adjustment or fixed, depending.
2107 layout->slider.x = layout->trough.x + trough_border;
2108 layout->slider.width = layout->trough.width - trough_border * 2;
2110 /* Compute slider position/length */
2112 gint y, bottom, top, height;
2114 top = layout->trough.y + stepper_spacing;
2115 bottom = layout->trough.y + layout->trough.height - stepper_spacing;
2117 /* slider height is the fraction (page_size /
2118 * total_adjustment_range) times the trough height in pixels
2121 if (range->adjustment->upper - range->adjustment->lower != 0)
2122 height = ((bottom - top) * (range->adjustment->page_size /
2123 (range->adjustment->upper - range->adjustment->lower)));
2125 height = range->min_slider_size;
2127 if (height < range->min_slider_size ||
2128 range->slider_size_fixed)
2129 height = range->min_slider_size;
2131 height = MIN (height, (layout->trough.height - stepper_spacing * 2));
2135 if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0)
2136 y += (bottom - top - height) * ((adjustment_value - range->adjustment->lower) /
2137 (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
2139 y = CLAMP (y, top, bottom);
2141 if (should_invert (range))
2142 y = bottom - (y - top + height);
2144 layout->slider.y = y;
2145 layout->slider.height = height;
2147 /* These are publically exported */
2148 range->slider_start = layout->slider.y;
2149 range->slider_end = layout->slider.y + layout->slider.height;
2154 gint stepper_width, stepper_height;
2156 /* Steppers are the height of the range, and stepper_size in
2157 * width, or if we don't have enough width, divided equally
2158 * among available space.
2160 stepper_height = range_rect.height - trough_border * 2;
2162 if (stepper_height < 1)
2163 stepper_height = range_rect.height; /* screw the trough border */
2165 if (n_steppers == 0)
2166 stepper_width = 0; /* avoid divide by n_steppers */
2168 stepper_width = MIN (stepper_size, (range_rect.width / n_steppers));
2172 layout->stepper_a.x = range_rect.x + trough_border;
2173 layout->stepper_a.y = range_rect.y + trough_border;
2175 if (range->has_stepper_a)
2177 layout->stepper_a.width = stepper_width;
2178 layout->stepper_a.height = stepper_height;
2182 layout->stepper_a.width = 0;
2183 layout->stepper_a.height = 0;
2188 layout->stepper_b.x = layout->stepper_a.x + layout->stepper_a.width;
2189 layout->stepper_b.y = layout->stepper_a.y;
2191 if (range->has_stepper_b)
2193 layout->stepper_b.width = stepper_width;
2194 layout->stepper_b.height = stepper_height;
2198 layout->stepper_b.width = 0;
2199 layout->stepper_b.height = 0;
2204 if (range->has_stepper_d)
2206 layout->stepper_d.width = stepper_width;
2207 layout->stepper_d.height = stepper_height;
2211 layout->stepper_d.width = 0;
2212 layout->stepper_d.height = 0;
2215 layout->stepper_d.x = range_rect.x + range_rect.width - layout->stepper_d.width - trough_border;
2216 layout->stepper_d.y = layout->stepper_a.y;
2221 if (range->has_stepper_c)
2223 layout->stepper_c.width = stepper_width;
2224 layout->stepper_c.height = stepper_height;
2228 layout->stepper_c.width = 0;
2229 layout->stepper_c.height = 0;
2232 layout->stepper_c.x = layout->stepper_d.x - layout->stepper_c.width;
2233 layout->stepper_c.y = layout->stepper_a.y;
2235 /* Now the trough is the remaining space between steppers B and C,
2238 layout->trough.x = layout->stepper_b.x + layout->stepper_b.width;
2239 layout->trough.y = range_rect.y;
2241 layout->trough.width = layout->stepper_c.x - (layout->stepper_b.x + layout->stepper_b.width);
2242 layout->trough.height = range_rect.height;
2244 /* Slider fits into the trough, with stepper_spacing on either side,
2245 * and the size/position based on the adjustment or fixed, depending.
2247 layout->slider.y = layout->trough.y + trough_border;
2248 layout->slider.height = layout->trough.height - trough_border * 2;
2250 /* Compute slider position/length */
2252 gint x, left, right, width;
2254 left = layout->trough.x + stepper_spacing;
2255 right = layout->trough.x + layout->trough.width - stepper_spacing;
2257 /* slider width is the fraction (page_size /
2258 * total_adjustment_range) times the trough width in pixels
2261 if (range->adjustment->upper - range->adjustment->lower != 0)
2262 width = ((right - left) * (range->adjustment->page_size /
2263 (range->adjustment->upper - range->adjustment->lower)));
2265 width = range->min_slider_size;
2267 if (width < range->min_slider_size ||
2268 range->slider_size_fixed)
2269 width = range->min_slider_size;
2271 width = MIN (width, (layout->trough.width - stepper_spacing * 2));
2275 if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0)
2276 x += (right - left - width) * ((adjustment_value - range->adjustment->lower) /
2277 (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
2279 x = CLAMP (x, left, right);
2281 if (should_invert (range))
2282 x = right - (x - left + width);
2284 layout->slider.x = x;
2285 layout->slider.width = width;
2287 /* These are publically exported */
2288 range->slider_start = layout->slider.x;
2289 range->slider_end = layout->slider.x + layout->slider.width;
2293 gtk_range_update_mouse_location (range);
2296 static GdkRectangle*
2297 get_area (GtkRange *range,
2298 MouseLocation location)
2302 case MOUSE_STEPPER_A:
2303 return &range->layout->stepper_a;
2304 case MOUSE_STEPPER_B:
2305 return &range->layout->stepper_b;
2306 case MOUSE_STEPPER_C:
2307 return &range->layout->stepper_c;
2308 case MOUSE_STEPPER_D:
2309 return &range->layout->stepper_d;
2311 return &range->layout->trough;
2313 return &range->layout->slider;
2319 g_warning (G_STRLOC": bug");
2324 gtk_range_internal_set_value (GtkRange *range,
2327 /* potentially adjust the bounds _before we clamp */
2328 g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value);
2330 value = CLAMP (value, range->adjustment->lower,
2331 (range->adjustment->upper - range->adjustment->page_size));
2333 if (range->round_digits >= 0)
2337 /* This is just so darn lame. */
2338 g_snprintf (buffer, 128, "%0.*f",
2339 range->round_digits, value);
2340 sscanf (buffer, "%lf", &value);
2343 if (range->adjustment->value != value)
2345 range->need_recalc = TRUE;
2347 gtk_widget_queue_draw (GTK_WIDGET (range));
2349 switch (range->update_policy)
2351 case GTK_UPDATE_CONTINUOUS:
2352 gtk_adjustment_set_value (range->adjustment, value);
2355 /* Delayed means we update after a period of inactivity */
2356 case GTK_UPDATE_DELAYED:
2357 gtk_range_reset_update_timer (range);
2360 /* Discontinuous means we update on button release */
2361 case GTK_UPDATE_DISCONTINUOUS:
2362 /* don't emit value_changed signal */
2363 range->adjustment->value = value;
2364 range->update_pending = TRUE;
2371 gtk_range_update_value (GtkRange *range)
2373 gtk_range_remove_update_timer (range);
2375 if (range->update_pending)
2377 gtk_adjustment_value_changed (range->adjustment);
2379 range->update_pending = FALSE;
2383 struct _GtkRangeStepTimer
2390 second_timeout (gpointer data)
2394 GDK_THREADS_ENTER ();
2395 range = GTK_RANGE (data);
2396 gtk_range_scroll (range, range->timer->step);
2397 GDK_THREADS_LEAVE ();
2403 initial_timeout (gpointer data)
2407 GDK_THREADS_ENTER ();
2408 range = GTK_RANGE (data);
2409 range->timer->timeout_id =
2410 g_timeout_add (SCROLL_LATER_DELAY,
2413 GDK_THREADS_LEAVE ();
2420 gtk_range_add_step_timer (GtkRange *range,
2423 g_return_if_fail (range->timer == NULL);
2424 g_return_if_fail (step != GTK_SCROLL_NONE);
2426 range->timer = g_new (GtkRangeStepTimer, 1);
2428 range->timer->timeout_id =
2429 g_timeout_add (SCROLL_INITIAL_DELAY,
2432 range->timer->step = step;
2434 gtk_range_scroll (range, range->timer->step);
2438 gtk_range_remove_step_timer (GtkRange *range)
2442 if (range->timer->timeout_id != 0)
2443 g_source_remove (range->timer->timeout_id);
2445 g_free (range->timer);
2447 range->timer = NULL;
2452 update_timeout (gpointer data)
2456 GDK_THREADS_ENTER ();
2457 range = GTK_RANGE (data);
2458 gtk_range_update_value (range);
2459 range->update_timeout_id = 0;
2460 GDK_THREADS_LEAVE ();
2467 gtk_range_reset_update_timer (GtkRange *range)
2469 gtk_range_remove_update_timer (range);
2471 range->update_timeout_id = g_timeout_add (UPDATE_DELAY,
2477 gtk_range_remove_update_timer (GtkRange *range)
2479 if (range->update_timeout_id != 0)
2481 g_source_remove (range->update_timeout_id);
2482 range->update_timeout_id = 0;