]> Pileus Git - ~andy/gtk/blob - gtk/gtkrange.c
Patch from George Lebl to fix division by zero for full scroll bars.
[~andy/gtk] / gtk / gtkrange.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
3  * Copyright (C) 2001 Red Hat, Inc.
4  *
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.
9  *
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.
14  *
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.
19  */
20
21 /*
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/. 
26  */
27
28 #include <stdio.h>
29 #include "gtkintl.h"
30 #include "gtkmain.h"
31 #include "gtkrange.h"
32 #include "gtksignal.h"
33 #include "gtkintl.h"
34
35 #define SCROLL_INITIAL_DELAY 250  /* must hold button this long before ... */
36 #define SCROLL_LATER_DELAY   100  /* ... it starts repeating at this rate  */
37 #define UPDATE_DELAY         300  /* Delay for queued update */
38
39 enum {
40   PROP_0,
41   PROP_UPDATE_POLICY,
42   PROP_ADJUSTMENT,
43   PROP_INVERTED
44 };
45
46 enum {
47   VALUE_CHANGED,
48   MOVE_SLIDER,
49   LAST_SIGNAL
50 };
51
52 typedef enum {
53   MOUSE_OUTSIDE,
54   MOUSE_STEPPER_A,
55   MOUSE_STEPPER_B,
56   MOUSE_STEPPER_C,
57   MOUSE_STEPPER_D,
58   MOUSE_TROUGH,
59   MOUSE_SLIDER,
60   MOUSE_WIDGET /* inside widget but not in any of the above GUI elements */
61 } MouseLocation;
62
63 struct _GtkRangeLayout
64 {
65   /* These are in widget->window coordinates */
66   GdkRectangle stepper_a;
67   GdkRectangle stepper_b;
68   GdkRectangle stepper_c;
69   GdkRectangle stepper_d;
70   /* The trough rectangle is the area the thumb can slide in, not the
71    * entire range_rect
72    */
73   GdkRectangle trough;
74   GdkRectangle slider;
75
76   /* Layout-related state */
77   
78   MouseLocation mouse_location;
79   /* last mouse coords we got, or -1 if mouse is outside the range */
80   gint mouse_x;
81   gint mouse_y;
82   /* "grabbed" mouse location, OUTSIDE for no grab */
83   MouseLocation grab_location;
84   gint grab_button; /* 0 if none */
85 };
86
87
88 static void gtk_range_class_init     (GtkRangeClass    *klass);
89 static void gtk_range_init           (GtkRange         *range);
90 static void gtk_range_set_property   (GObject          *object,
91                                       guint             prop_id,
92                                       const GValue     *value,
93                                       GParamSpec       *pspec);
94 static void gtk_range_get_property   (GObject          *object,
95                                       guint             prop_id,
96                                       GValue           *value,
97                                       GParamSpec       *pspec);
98 static void gtk_range_destroy        (GtkObject        *object);
99 static void gtk_range_finalize       (GObject          *object);
100 static void gtk_range_size_request   (GtkWidget        *widget,
101                                       GtkRequisition   *requisition);
102 static void gtk_range_size_allocate  (GtkWidget        *widget,
103                                       GtkAllocation    *allocation);
104 static void gtk_range_realize        (GtkWidget        *widget);
105 static void gtk_range_unrealize      (GtkWidget        *widget);
106 static gint gtk_range_expose         (GtkWidget        *widget,
107                                       GdkEventExpose   *event);
108 static gint gtk_range_button_press   (GtkWidget        *widget,
109                                       GdkEventButton   *event);
110 static gint gtk_range_button_release (GtkWidget        *widget,
111                                       GdkEventButton   *event);
112 static gint gtk_range_motion_notify  (GtkWidget        *widget,
113                                       GdkEventMotion   *event);
114 static gint gtk_range_enter_notify   (GtkWidget        *widget,
115                                       GdkEventCrossing *event);
116 static gint gtk_range_leave_notify   (GtkWidget        *widget,
117                                       GdkEventCrossing *event);
118 static gint gtk_range_scroll_event   (GtkWidget        *widget,
119                                       GdkEventScroll   *event);
120 static void gtk_range_style_set      (GtkWidget        *widget,
121                                       GtkStyle         *previous_style);
122 static void update_slider_position   (GtkRange         *range,
123                                       gint              mouse_x,
124                                       gint              mouse_y);
125
126
127 /* Range methods */
128
129 static void gtk_range_move_slider              (GtkRange         *range,
130                                                 GtkScrollType     scroll);
131
132 /* Internals */
133 static void          gtk_range_scroll                   (GtkRange      *range,
134                                                          GtkScrollType  scroll);
135 static gboolean      gtk_range_update_mouse_location    (GtkRange      *range);
136 static void          gtk_range_calc_layout              (GtkRange      *range,
137                                                          gdouble        adjustment_value);
138 static void          gtk_range_get_props                (GtkRange      *range,
139                                                          gint          *slider_width,
140                                                          gint          *stepper_size,
141                                                          gint          *trough_border,
142                                                          gint          *stepper_spacing);
143 static void          gtk_range_calc_request             (GtkRange      *range,
144                                                          gint           slider_width,
145                                                          gint           stepper_size,
146                                                          gint           trough_border,
147                                                          gint           stepper_spacing,
148                                                          GdkRectangle  *range_rect,
149                                                          GtkBorder     *border,
150                                                          gint          *n_steppers_p,
151                                                          gint          *slider_length_p);
152 static void          gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
153                                                          gpointer       data);
154 static void          gtk_range_adjustment_changed       (GtkAdjustment *adjustment,
155                                                          gpointer       data);
156 static void          gtk_range_add_step_timer           (GtkRange      *range,
157                                                          GtkScrollType  step);
158 static void          gtk_range_remove_step_timer        (GtkRange      *range);
159 static void          gtk_range_reset_update_timer       (GtkRange      *range);
160 static void          gtk_range_remove_update_timer      (GtkRange      *range);
161 static GdkRectangle* get_area                           (GtkRange      *range,
162                                                          MouseLocation  location);
163 static void          gtk_range_internal_set_value       (GtkRange      *range,
164                                                          gdouble        value);
165 static void          gtk_range_update_value             (GtkRange      *range);
166
167
168 static GtkWidgetClass *parent_class = NULL;
169 static guint signals[LAST_SIGNAL];
170
171
172 GtkType
173 gtk_range_get_type (void)
174 {
175   static GtkType range_type = 0;
176
177   if (!range_type)
178     {
179       static const GtkTypeInfo range_info =
180       {
181         "GtkRange",
182         sizeof (GtkRange),
183         sizeof (GtkRangeClass),
184         (GtkClassInitFunc) gtk_range_class_init,
185         (GtkObjectInitFunc) gtk_range_init,
186         /* reserved_1 */ NULL,
187         /* reserved_2 */ NULL,
188         (GtkClassInitFunc) NULL,
189       };
190
191       range_type = gtk_type_unique (GTK_TYPE_WIDGET, &range_info);
192     }
193
194   return range_type;
195 }
196
197 static void
198 gtk_range_class_init (GtkRangeClass *class)
199 {
200   GObjectClass   *gobject_class;
201   GtkObjectClass *object_class;
202   GtkWidgetClass *widget_class;
203
204   gobject_class = G_OBJECT_CLASS (class);
205   object_class = (GtkObjectClass*) class;
206   widget_class = (GtkWidgetClass*) class;
207
208   parent_class = g_type_class_peek_parent (class);
209
210   gobject_class->set_property = gtk_range_set_property;
211   gobject_class->get_property = gtk_range_get_property;
212   gobject_class->finalize = gtk_range_finalize;
213   object_class->destroy = gtk_range_destroy;
214
215   widget_class->size_request = gtk_range_size_request;
216   widget_class->size_allocate = gtk_range_size_allocate;
217   widget_class->realize = gtk_range_realize;
218   widget_class->unrealize = gtk_range_unrealize;  
219   widget_class->expose_event = gtk_range_expose;
220   widget_class->button_press_event = gtk_range_button_press;
221   widget_class->button_release_event = gtk_range_button_release;
222   widget_class->motion_notify_event = gtk_range_motion_notify;
223   widget_class->scroll_event = gtk_range_scroll_event;
224   widget_class->enter_notify_event = gtk_range_enter_notify;
225   widget_class->leave_notify_event = gtk_range_leave_notify;
226   widget_class->style_set = gtk_range_style_set;
227
228   class->move_slider = gtk_range_move_slider;
229
230   class->slider_detail = "slider";
231   class->stepper_detail = "stepper";
232
233   signals[VALUE_CHANGED] =
234     g_signal_new ("value_changed",
235                   G_TYPE_FROM_CLASS (object_class),
236                   G_SIGNAL_RUN_LAST,
237                   G_STRUCT_OFFSET (GtkRangeClass, value_changed),
238                   NULL, NULL,
239                   gtk_marshal_NONE__NONE,
240                   G_TYPE_NONE, 0);
241   
242   signals[MOVE_SLIDER] =
243     g_signal_new ("move_slider",
244                   G_TYPE_FROM_CLASS (object_class),
245                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
246                   G_STRUCT_OFFSET (GtkRangeClass, move_slider),
247                   NULL, NULL,
248                   gtk_marshal_VOID__ENUM,
249                   G_TYPE_NONE, 1,
250                   GTK_TYPE_SCROLL_TYPE);
251   
252   
253   g_object_class_install_property (gobject_class,
254                                    PROP_UPDATE_POLICY,
255                                    g_param_spec_enum ("update_policy",
256                                                       _("Update policy"),
257                                                       _("How the range should be updated on the screen"),
258                                                       GTK_TYPE_UPDATE_TYPE,
259                                                       GTK_UPDATE_CONTINUOUS,
260                                                       G_PARAM_READWRITE));
261   
262   g_object_class_install_property (gobject_class,
263                                    PROP_ADJUSTMENT,
264                                    g_param_spec_object ("adjustment",
265                                                         _("Adjustment"),
266                                                         _("The GtkAdjustment that contains the current value of this range object"),
267                                                         GTK_TYPE_ADJUSTMENT,
268                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
269
270   g_object_class_install_property (gobject_class,
271                                    PROP_INVERTED,
272                                    g_param_spec_boolean ("inverted",
273                                                         _("Inverted"),
274                                                         _("Invert direction slider moves to increase range value"),
275                                                          FALSE,
276                                                          G_PARAM_READWRITE));
277   
278   gtk_widget_class_install_style_property (widget_class,
279                                            g_param_spec_int ("slider_width",
280                                                              _("Slider Width"),
281                                                              _("Width of scrollbar or scale thumb"),
282                                                              0,
283                                                              G_MAXINT,
284                                                              14,
285                                                              G_PARAM_READABLE));
286   gtk_widget_class_install_style_property (widget_class,
287                                            g_param_spec_int ("trough_border",
288                                                              _("Trough Border"),
289                                                              _("Spacing between thumb/steppers and outer trough bevel"),
290                                                              0,
291                                                              G_MAXINT,
292                                                              1,
293                                                              G_PARAM_READABLE));
294   gtk_widget_class_install_style_property (widget_class,
295                                            g_param_spec_int ("stepper_size",
296                                                              _("Stepper Size"),
297                                                              _("Length of step buttons at ends"),
298                                                              0,
299                                                              G_MAXINT,
300                                                              14,
301                                                              G_PARAM_READABLE));
302   gtk_widget_class_install_style_property (widget_class,
303                                            g_param_spec_int ("stepper_spacing",
304                                                              _("Stepper Spacing"),
305                                                              _("Spacing between step buttons and thumb"),
306                                                              0,
307                                                              G_MAXINT,
308                                                              0,
309                                                              G_PARAM_READABLE));
310 }
311
312 static void
313 gtk_range_set_property (GObject      *object,
314                         guint         prop_id,
315                         const GValue *value,
316                         GParamSpec   *pspec)
317 {
318   GtkRange *range;
319
320   range = GTK_RANGE (object);
321
322   switch (prop_id)
323     {
324     case PROP_UPDATE_POLICY:
325       gtk_range_set_update_policy (range, g_value_get_enum (value));
326       break;
327     case PROP_ADJUSTMENT:
328       gtk_range_set_adjustment (range, g_value_get_object (value));
329       break;
330     case PROP_INVERTED:
331       gtk_range_set_inverted (range, g_value_get_boolean (value));
332       break;
333     default:
334       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
335       break;
336     }
337 }
338
339 static void
340 gtk_range_get_property (GObject      *object,
341                         guint         prop_id,
342                         GValue       *value,
343                         GParamSpec   *pspec)
344 {
345   GtkRange *range;
346
347   range = GTK_RANGE (object);
348
349   switch (prop_id)
350     {
351     case PROP_UPDATE_POLICY:
352       g_value_set_enum (value, range->update_policy);
353       break;
354     case PROP_ADJUSTMENT:
355       g_value_set_object (value, G_OBJECT (range->adjustment));
356       break;
357     case PROP_INVERTED:
358       g_value_set_boolean (value, range->inverted);
359       break;
360     default:
361       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
362       break;
363     }
364 }
365
366 static void
367 gtk_range_init (GtkRange *range)
368 {
369   range->adjustment = NULL;
370   range->update_policy = GTK_UPDATE_CONTINUOUS;
371   range->inverted = FALSE;
372   range->flippable = FALSE;
373   range->min_slider_size = 1;
374   range->has_stepper_a = FALSE;
375   range->has_stepper_b = FALSE;
376   range->has_stepper_c = FALSE;
377   range->has_stepper_d = FALSE;
378   range->need_recalc = TRUE;
379   range->round_digits = -1;
380   range->layout = g_new0 (GtkRangeLayout, 1);
381   range->layout->grab_location = MOUSE_OUTSIDE;
382   range->layout->grab_button = 0;
383   range->timer = NULL;  
384 }
385
386 /**
387  * gtk_range_get_adjustment:
388  * @range: a #GtkRange
389  * 
390  * Get the #GtkAdjustment which is the "model" object for #GtkRange.
391  * See gtk_range_set_adjustment() for details.
392  * The return value does not have a reference added, so should not
393  * be unreferenced.
394  * 
395  * Return value: a #GtkAdjustment
396  **/
397 GtkAdjustment*
398 gtk_range_get_adjustment (GtkRange *range)
399 {
400   g_return_val_if_fail (GTK_IS_RANGE (range), NULL);
401
402   if (!range->adjustment)
403     gtk_range_set_adjustment (range, NULL);
404
405   return range->adjustment;
406 }
407
408 /**
409  * gtk_range_set_update_policy:
410  * @range: a #GtkRange
411  * @policy: update policy
412  *
413  * Sets the update policy for the range. #GTK_UPDATE_CONTINUOUS means that
414  * anytime the range slider is moved, the range value will change and the
415  * value_changed signal will be emitted. #GTK_UPDATE_DELAYED means that
416  * the value will be updated after a brief timeout where no slider motion
417  * occurs, so updates are spaced by a short time rather than
418  * continuous. #GTK_UPDATE_DISCONTINUOUS means that the value will only
419  * be updated when the user releases the button and ends the slider
420  * drag operation.
421  * 
422  **/
423 void
424 gtk_range_set_update_policy (GtkRange      *range,
425                              GtkUpdateType  policy)
426 {
427   g_return_if_fail (GTK_IS_RANGE (range));
428
429   if (range->update_policy != policy)
430     {
431       range->update_policy = policy;
432       g_object_notify (G_OBJECT (range), "update_policy");
433     }
434 }
435
436 /**
437  * gtk_range_get_update_policy:
438  * @range: a #GtkRange
439  *
440  * Gets the update policy of @range. See gtk_range_set_update_policy().
441  *
442  * Return value: the current update policy
443  **/
444 GtkUpdateType
445 gtk_range_get_update_policy (GtkRange *range)
446 {
447   g_return_val_if_fail (GTK_IS_RANGE (range), GTK_UPDATE_CONTINUOUS);
448
449   return range->update_policy;
450 }
451
452 /**
453  * gtk_range_set_adjustment:
454  * @range: a #GtkRange
455  * @adjustment: a #GtkAdjustment
456  *
457  * Sets the adjustment to be used as the "model" object for this range
458  * widget. The adjustment indicates the current range value, the
459  * minimum and maximum range values, the step/page increments used
460  * for keybindings and scrolling, and the page size. The page size
461  * is normally 0 for #GtkScale and nonzero for #GtkScrollbar, and
462  * indicates the size of the visible area of the widget being scrolled.
463  * The page size affects the size of the scrollbar slider.
464  * 
465  **/
466 void
467 gtk_range_set_adjustment (GtkRange      *range,
468                           GtkAdjustment *adjustment)
469 {
470   g_return_if_fail (GTK_IS_RANGE (range));
471   
472   if (!adjustment)
473     adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
474   else
475     g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
476
477   if (range->adjustment != adjustment)
478     {
479       if (range->adjustment)
480         {
481           gtk_signal_disconnect_by_func (GTK_OBJECT (range->adjustment),
482                                          G_CALLBACK (gtk_range_adjustment_changed),
483                                          range);
484           gtk_signal_disconnect_by_func (GTK_OBJECT (range->adjustment),
485                                          G_CALLBACK (gtk_range_adjustment_value_changed),
486                                          range);
487           gtk_object_unref (GTK_OBJECT (range->adjustment));
488         }
489
490       range->adjustment = adjustment;
491       gtk_object_ref (GTK_OBJECT (adjustment));
492       gtk_object_sink (GTK_OBJECT (adjustment));
493       
494       gtk_signal_connect (GTK_OBJECT (adjustment), "changed",
495                           (GtkSignalFunc) gtk_range_adjustment_changed,
496                           range);
497       gtk_signal_connect (GTK_OBJECT (adjustment), "value_changed",
498                           (GtkSignalFunc) gtk_range_adjustment_value_changed,
499                           range);
500       
501       gtk_range_adjustment_changed (adjustment, range);
502       g_object_notify (G_OBJECT (range), "adjustment");
503     }
504 }
505
506 /**
507  * gtk_range_set_inverted:
508  * @range: a #GtkRange
509  * @setting: %TRUE to invert the range
510  *
511  * Ranges normally move from lower to higher values as the
512  * slider moves from top to bottom or left to right. Inverted
513  * ranges have higher values at the top or on the right rather than
514  * on the bottom or left.
515  * 
516  **/
517 void
518 gtk_range_set_inverted (GtkRange *range,
519                         gboolean  setting)
520 {
521   g_return_if_fail (GTK_IS_RANGE (range));
522   
523   setting = setting != FALSE;
524
525   if (setting != range->inverted)
526     {
527       range->inverted = setting;
528       g_object_notify (G_OBJECT (range), "inverted");
529       gtk_widget_queue_resize (GTK_WIDGET (range));
530     }
531 }
532
533 /**
534  * gtk_range_get_inverted:
535  * @range: a #GtkRange
536  * 
537  * Gets the value set by gtk_range_set_inverted().
538  * 
539  * Return value: %TRUE if the range is inverted
540  **/
541 gboolean
542 gtk_range_get_inverted (GtkRange *range)
543 {
544   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
545
546   return range->inverted;
547 }
548
549 /**
550  * gtk_range_set_increments:
551  * @range: a #GtkRange
552  * @step: step size
553  * @page: page size
554  *
555  * Sets the step and page sizes for the range.
556  * The step size is used when the user clicks the #GtkScrollbar
557  * arrows or moves #GtkScale via arrow keys. The page size
558  * is used for example when moving via Page Up or Page Down keys.
559  * 
560  **/
561 void
562 gtk_range_set_increments (GtkRange *range,
563                           gdouble   step,
564                           gdouble   page)
565 {
566   g_return_if_fail (GTK_IS_RANGE (range));
567
568   range->adjustment->step_increment = step;
569   range->adjustment->page_increment = page;
570
571   gtk_adjustment_changed (range->adjustment);
572 }
573
574 /**
575  * gtk_range_set_range:
576  * @range: a #GtkRange
577  * @min: minimum range value
578  * @max: maximum range value
579  * 
580  * Sets the allowable values in the #GtkRange, and clamps the range
581  * value to be between min and max.
582  **/
583 void
584 gtk_range_set_range (GtkRange *range,
585                      gdouble   min,
586                      gdouble   max)
587 {
588   gdouble value;
589   
590   g_return_if_fail (GTK_IS_RANGE (range));
591   g_return_if_fail (min < max);
592   
593   range->adjustment->lower = min;
594   range->adjustment->upper = max;
595
596   value = CLAMP (range->adjustment->value,
597                  range->adjustment->lower,
598                  (range->adjustment->upper - range->adjustment->page_size));
599   
600   gtk_adjustment_changed (range->adjustment);  
601 }
602
603 /**
604  * gtk_range_set_value:
605  * @range: a #GtkRange
606  * @value: new value of the range
607  *
608  * Sets the current value of the range; if the value is outside the
609  * minimum or maximum range values, it will be clamped to fit inside
610  * them. The range emits the "value_changed" signal if the value
611  * changes.
612  * 
613  **/
614 void
615 gtk_range_set_value (GtkRange *range,
616                      gdouble   value)
617 {
618   g_return_if_fail (GTK_IS_RANGE (range));
619   
620   value = CLAMP (value, range->adjustment->lower,
621                  (range->adjustment->upper - range->adjustment->page_size));
622
623   gtk_adjustment_set_value (range->adjustment, value);
624 }
625
626 /**
627  * gtk_range_get_value:
628  * @range: a #GtkRange
629  * 
630  * Gets the current value of the range.
631  * 
632  * Return value: current value of the range.
633  **/
634 gdouble
635 gtk_range_get_value (GtkRange *range)
636 {
637   g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
638
639   return range->adjustment->value;
640 }
641
642 static gboolean
643 should_invert (GtkRange *range)
644 {  
645   if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
646     return
647       (range->inverted && !range->flippable) ||
648       (range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
649       (!range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
650   else
651     return range->inverted;
652 }
653
654 static void
655 gtk_range_finalize (GObject *object)
656 {
657   GtkRange *range;
658
659   g_return_if_fail (GTK_IS_RANGE (object));
660
661   range = GTK_RANGE (object);
662
663   g_free (range->layout);
664
665   (* G_OBJECT_CLASS (parent_class)->finalize) (object);
666 }
667
668 static void
669 gtk_range_destroy (GtkObject *object)
670 {
671   GtkRange *range;
672
673   g_return_if_fail (GTK_IS_RANGE (object));
674
675   range = GTK_RANGE (object);
676
677   gtk_range_remove_step_timer (range);
678   gtk_range_remove_update_timer (range);
679   
680   if (range->adjustment)
681     {
682       if (range->adjustment)
683         gtk_signal_disconnect_by_data (GTK_OBJECT (range->adjustment),
684                                        (gpointer) range);
685       gtk_object_unref (GTK_OBJECT (range->adjustment));
686       range->adjustment = NULL;
687     }
688
689   (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
690 }
691
692 static void
693 gtk_range_size_request (GtkWidget      *widget,
694                         GtkRequisition *requisition)
695 {
696   GtkRange *range;
697   gint slider_width, stepper_size, trough_border, stepper_spacing;
698   GdkRectangle range_rect;
699   GtkBorder border;
700   
701   range = GTK_RANGE (widget);
702   
703   gtk_range_get_props (range,
704                        &slider_width, &stepper_size, &trough_border, &stepper_spacing);
705
706   gtk_range_calc_request (range, 
707                           slider_width, stepper_size, trough_border, stepper_spacing,
708                           &range_rect, &border, NULL, NULL);
709
710   requisition->width = range_rect.width + border.left + border.right;
711   requisition->height = range_rect.height + border.top + border.bottom;
712 }
713
714 static void
715 gtk_range_size_allocate (GtkWidget     *widget,
716                          GtkAllocation *allocation)
717 {
718   GtkRange *range;
719
720   range = GTK_RANGE (widget);
721
722   range->need_recalc = TRUE;
723   gtk_range_calc_layout (range, range->adjustment->value);
724
725   (* GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation);
726 }
727
728 static void
729 gtk_range_realize (GtkWidget *widget)
730 {
731   GtkRange *range;
732   GdkWindowAttr attributes;
733   gint attributes_mask;  
734
735   range = GTK_RANGE (widget);
736
737   gtk_range_calc_layout (range, range->adjustment->value);
738   
739   GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
740
741   attributes.window_type = GDK_WINDOW_CHILD;
742   attributes.x = widget->allocation.x;
743   attributes.y = widget->allocation.y;
744   attributes.width = widget->allocation.width;
745   attributes.height = widget->allocation.height;
746   attributes.wclass = GDK_INPUT_OUTPUT;
747   attributes.visual = gtk_widget_get_visual (widget);
748   attributes.colormap = gtk_widget_get_colormap (widget);
749   attributes.event_mask = gtk_widget_get_events (widget);
750   attributes.event_mask |= (GDK_EXPOSURE_MASK |
751                             GDK_BUTTON_PRESS_MASK |
752                             GDK_BUTTON_RELEASE_MASK |
753                             GDK_ENTER_NOTIFY_MASK |
754                             GDK_LEAVE_NOTIFY_MASK |
755                             GDK_POINTER_MOTION_MASK |
756                             GDK_POINTER_MOTION_HINT_MASK);
757
758   attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
759
760   widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
761   gdk_window_set_user_data (widget->window, range);
762
763   widget->style = gtk_style_attach (widget->style, widget->window);
764   gtk_style_set_background (widget->style, widget->window, widget->state);
765 }
766
767 static void
768 gtk_range_unrealize (GtkWidget *widget)
769 {
770   GtkRange *range;
771
772   g_return_if_fail (GTK_IS_RANGE (widget));
773
774   range = GTK_RANGE (widget);
775
776   gtk_range_remove_step_timer (range);
777   gtk_range_remove_update_timer (range);
778   
779   if (GTK_WIDGET_CLASS (parent_class)->unrealize)
780     (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
781 }
782
783 static void
784 draw_stepper (GtkRange     *range,
785               GdkRectangle *rect,
786               GtkArrowType  arrow_type,
787               gboolean      clicked,
788               gboolean      prelighted,
789               GdkRectangle *area)
790 {
791   GtkStateType state_type;
792   GtkShadowType shadow_type;
793   GdkRectangle intersection;
794
795   /* More to get the right clip region than for efficiency */
796   if (!gdk_rectangle_intersect (area, rect, &intersection))
797     return;
798   
799   if (!GTK_WIDGET_IS_SENSITIVE (range))
800     state_type = GTK_STATE_INSENSITIVE;
801   else if (clicked)
802     state_type = GTK_STATE_ACTIVE;
803   else if (prelighted)
804     state_type = GTK_STATE_PRELIGHT;
805   else 
806     state_type = GTK_STATE_NORMAL;
807   
808   if (clicked)
809     shadow_type = GTK_SHADOW_IN;
810   else
811     shadow_type = GTK_SHADOW_OUT;
812   
813   gtk_paint_arrow (GTK_WIDGET (range)->style,
814                    GTK_WIDGET (range)->window,
815                    state_type, shadow_type, 
816                    &intersection, GTK_WIDGET (range),
817                    GTK_RANGE_GET_CLASS (range)->stepper_detail,
818                    arrow_type,
819                    TRUE, rect->x, rect->y, rect->width, rect->height);
820 }
821
822 static gint
823 gtk_range_expose (GtkWidget      *widget,
824                   GdkEventExpose *event)
825 {
826   GtkRange *range;
827   gboolean sensitive;
828   GtkStateType state;
829   GdkRectangle area;
830   
831   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
832   g_return_val_if_fail (event != NULL, FALSE);
833
834   range = GTK_RANGE (widget);
835
836   gtk_range_calc_layout (range, range->adjustment->value);
837
838   sensitive = GTK_WIDGET_IS_SENSITIVE (widget);
839   
840   /* Just to be confusing, we draw the trough for the whole
841    * range rectangle, not the trough rectangle (the trough
842    * rectangle is just for hit detection)
843    */
844   /* The gdk_rectangle_intersect is more to get the right
845    * clip region (limited to range_rect) than for efficiency
846    */
847   if (gdk_rectangle_intersect (&event->area, &range->range_rect,
848                                &area))
849     {
850       gtk_paint_box (widget->style,
851                      widget->window,
852                      sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
853                      GTK_SHADOW_IN,
854                      &area, GTK_WIDGET(range), "trough",
855                      range->range_rect.x,
856                      range->range_rect.y,
857                      range->range_rect.width,
858                      range->range_rect.height);
859       
860                  
861       if (sensitive &&
862           GTK_WIDGET_HAS_FOCUS (range))
863         gtk_paint_focus (widget->style,
864                          widget->window,
865                          &area, widget, "trough",
866                          range->range_rect.x,
867                          range->range_rect.y,
868                          range->range_rect.width,
869                          range->range_rect.height);
870     }
871
872   if (!sensitive)
873     state = GTK_STATE_INSENSITIVE;
874   else if (range->layout->mouse_location == MOUSE_SLIDER)
875     state = GTK_STATE_PRELIGHT;
876   else
877     state = GTK_STATE_NORMAL;
878
879   if (gdk_rectangle_intersect (&event->area,
880                                &range->layout->slider,
881                                &area))
882     {
883       gtk_paint_slider (widget->style,
884                         widget->window,
885                         state,
886                         GTK_SHADOW_OUT,
887                         &event->area,
888                         widget,
889                         GTK_RANGE_GET_CLASS (range)->slider_detail,
890                         range->layout->slider.x,
891                         range->layout->slider.y,
892                         range->layout->slider.width,
893                         range->layout->slider.height,
894                         range->orientation);
895     }
896   
897   if (range->has_stepper_a)
898     draw_stepper (range, &range->layout->stepper_a,
899                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
900                   range->layout->grab_location == MOUSE_STEPPER_A,
901                   range->layout->mouse_location == MOUSE_STEPPER_A,
902                   &event->area);
903
904   if (range->has_stepper_b)
905     draw_stepper (range, &range->layout->stepper_b,
906                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
907                   range->layout->grab_location == MOUSE_STEPPER_B,
908                   range->layout->mouse_location == MOUSE_STEPPER_B,
909                   &event->area);
910
911   if (range->has_stepper_c)
912     draw_stepper (range, &range->layout->stepper_c,
913                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
914                   range->layout->grab_location == MOUSE_STEPPER_C,
915                   range->layout->mouse_location == MOUSE_STEPPER_C,
916                   &event->area);
917
918   if (range->has_stepper_d)
919     draw_stepper (range, &range->layout->stepper_d,
920                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
921                   range->layout->grab_location == MOUSE_STEPPER_D,
922                   range->layout->mouse_location == MOUSE_STEPPER_D,
923                   &event->area);
924   
925   return FALSE;
926 }
927
928 static void
929 range_grab_add (GtkRange      *range,
930                 MouseLocation  location,
931                 gint           button)
932 {
933   /* we don't actually gtk_grab, since a button is down */
934
935   range->layout->grab_location = location;
936   range->layout->grab_button = button;
937   
938   if (gtk_range_update_mouse_location (range))
939     gtk_widget_queue_draw (GTK_WIDGET (range));
940 }
941
942 static void
943 range_grab_remove (GtkRange *range)
944 {
945   range->layout->grab_location = MOUSE_OUTSIDE;
946   range->layout->grab_button = 0;
947
948   if (gtk_range_update_mouse_location (range))
949     gtk_widget_queue_draw (GTK_WIDGET (range));
950 }
951
952 static GtkScrollType
953 range_get_scroll_for_grab (GtkRange      *range)
954 {  
955   switch (range->layout->grab_location)
956     {
957       /* Backward stepper */
958     case MOUSE_STEPPER_A:
959     case MOUSE_STEPPER_C:
960       switch (range->layout->grab_button)
961         {
962         case 1:
963           return GTK_SCROLL_STEP_BACKWARD;
964           break;
965         case 2:
966           return GTK_SCROLL_PAGE_BACKWARD;
967           break;
968         case 3:
969           return GTK_SCROLL_START;
970           break;
971         }
972       break;
973
974       /* Forward stepper */
975     case MOUSE_STEPPER_B:
976     case MOUSE_STEPPER_D:
977       switch (range->layout->grab_button)
978         {
979         case 1:
980           return GTK_SCROLL_STEP_FORWARD;
981           break;
982         case 2:
983           return GTK_SCROLL_PAGE_FORWARD;
984           break;
985         case 3:
986           return GTK_SCROLL_END;
987           break;
988         }
989       break;
990
991       /* In the trough */
992     case MOUSE_TROUGH:
993       {
994         if (range->trough_click_forward)
995           {
996             return range->layout->grab_button == 3
997               ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_STEP_FORWARD;
998           }
999         else
1000           {
1001             return range->layout->grab_button == 3
1002               ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_STEP_BACKWARD;
1003           }
1004       }
1005       break;
1006
1007     case MOUSE_OUTSIDE:
1008     case MOUSE_SLIDER:
1009     case MOUSE_WIDGET:
1010       break;
1011     }
1012
1013   return GTK_SCROLL_NONE;
1014 }
1015
1016 static gdouble
1017 coord_to_value (GtkRange *range,
1018                 gint      coord)
1019 {
1020   gdouble frac;
1021   gdouble value;
1022   
1023   if (range->orientation == GTK_ORIENTATION_VERTICAL)
1024     if (range->layout->trough.height == range->layout->slider.height)
1025       frac = 1.0;
1026     else 
1027       frac = ((coord - range->layout->trough.y) /
1028               (gdouble) (range->layout->trough.height - range->layout->slider.height));
1029   else
1030     if (range->layout->trough.width == range->layout->slider.width)
1031       frac = 1.0;
1032     else
1033       frac = ((coord - range->layout->trough.x) /
1034               (gdouble) (range->layout->trough.width - range->layout->slider.width));
1035
1036   if (should_invert (range))
1037     frac = 1.0 - frac;
1038   
1039   value = range->adjustment->lower +
1040     frac * (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size);
1041
1042   return value;
1043 }
1044
1045 static gint
1046 gtk_range_button_press (GtkWidget      *widget,
1047                         GdkEventButton *event)
1048 {
1049   GtkRange *range;
1050
1051   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1052   g_return_val_if_fail (event != NULL, FALSE);
1053
1054   range = GTK_RANGE (widget);
1055   
1056   if (!GTK_WIDGET_HAS_FOCUS (widget))
1057     gtk_widget_grab_focus (widget);
1058
1059   /* ignore presses when we're already doing something else. */
1060   if (range->layout->grab_location != MOUSE_OUTSIDE)
1061     return FALSE;
1062
1063   range->layout->mouse_x = event->x;
1064   range->layout->mouse_y = event->y;
1065   if (gtk_range_update_mouse_location (range))
1066     gtk_widget_queue_draw (widget);
1067     
1068   if (range->layout->mouse_location == MOUSE_TROUGH  &&
1069       (event->button == 1 || event->button == 3))
1070     {
1071       /* button 1 steps by step increment, as with button 1 on a stepper,
1072        * button 3 steps by page increment, as with button 2 on a stepper
1073        */
1074       GtkScrollType scroll;
1075       gdouble click_value;
1076       
1077       click_value = coord_to_value (range,
1078                                     range->orientation == GTK_ORIENTATION_VERTICAL ?
1079                                     event->y : event->x);
1080       
1081       range->trough_click_forward = click_value > range->adjustment->value;
1082       range_grab_add (range, MOUSE_TROUGH, event->button);
1083       
1084       scroll = range_get_scroll_for_grab (range);
1085       
1086       gtk_range_add_step_timer (range, scroll);
1087
1088       return TRUE;
1089     }
1090   else if ((range->layout->mouse_location == MOUSE_STEPPER_A ||
1091             range->layout->mouse_location == MOUSE_STEPPER_B ||
1092             range->layout->mouse_location == MOUSE_STEPPER_C ||
1093             range->layout->mouse_location == MOUSE_STEPPER_D) &&
1094            (event->button == 1 || event->button == 2 || event->button == 3))
1095     {
1096       GdkRectangle *stepper_area;
1097       GtkScrollType scroll;
1098       
1099       range_grab_add (range, range->layout->mouse_location, event->button);
1100
1101       stepper_area = get_area (range, range->layout->mouse_location);
1102       gtk_widget_queue_draw_area (widget,
1103                                   stepper_area->x,
1104                                   stepper_area->y,
1105                                   stepper_area->width,
1106                                   stepper_area->height);
1107
1108       scroll = range_get_scroll_for_grab (range);
1109       if (scroll != GTK_SCROLL_NONE)
1110         gtk_range_add_step_timer (range, scroll);
1111       
1112       return TRUE;
1113     }
1114   else if ((range->layout->mouse_location == MOUSE_TROUGH &&
1115             event->button == 2) ||
1116            range->layout->mouse_location == MOUSE_SLIDER)
1117     {
1118       gboolean need_value_update = FALSE;
1119
1120       /* Any button can be used to drag the slider, but you can start
1121        * dragging the slider with a trough click using button 2;
1122        * On button 2 press, we warp the slider to mouse position,
1123        * then begin the slider drag.
1124        */
1125       if (event->button == 2)
1126         {
1127           gdouble slider_low_value, slider_high_value, new_value;
1128           
1129           slider_high_value =
1130             coord_to_value (range,
1131                             range->orientation == GTK_ORIENTATION_VERTICAL ?
1132                             event->y : event->x);
1133           slider_low_value =
1134             coord_to_value (range,
1135                             range->orientation == GTK_ORIENTATION_VERTICAL ?
1136                             event->y - range->layout->slider.height :
1137                             event->x - range->layout->slider.width);
1138           
1139           /* compute new value for warped slider */
1140           new_value = slider_low_value + (slider_high_value - slider_low_value) / 2;
1141
1142           /* recalc slider, so we can set slide_initial_slider_position
1143            * properly
1144            */
1145           range->need_recalc = TRUE;
1146           gtk_range_calc_layout (range, new_value);
1147
1148           /* defer adjustment updates to update_slider_position() in order
1149            * to keep pixel quantisation
1150            */
1151           need_value_update = TRUE;
1152         }
1153       
1154       if (range->orientation == GTK_ORIENTATION_VERTICAL)
1155         {
1156           range->slide_initial_slider_position = range->layout->slider.y;
1157           range->slide_initial_coordinate = event->y;
1158         }
1159       else
1160         {
1161           range->slide_initial_slider_position = range->layout->slider.x;
1162           range->slide_initial_coordinate = event->x;
1163         }
1164
1165       if (need_value_update)
1166         update_slider_position (range, event->x, event->y);
1167
1168       range_grab_add (range, MOUSE_SLIDER, event->button);
1169       
1170       return TRUE;
1171     }
1172   
1173   return FALSE;
1174 }
1175
1176 /* During a slide, move the slider as required given new mouse position */
1177 static void
1178 update_slider_position (GtkRange *range,
1179                         gint      mouse_x,
1180                         gint      mouse_y)
1181 {
1182   gint delta;
1183   gint c;
1184   gdouble new_value;
1185   
1186   if (range->orientation == GTK_ORIENTATION_VERTICAL)
1187     delta = mouse_y - range->slide_initial_coordinate;
1188   else
1189     delta = mouse_x - range->slide_initial_coordinate;
1190
1191   c = range->slide_initial_slider_position + delta;
1192
1193   new_value = coord_to_value (range, c);
1194   
1195   gtk_range_internal_set_value (range, new_value);
1196 }
1197
1198 static gint
1199 gtk_range_button_release (GtkWidget      *widget,
1200                           GdkEventButton *event)
1201 {
1202   GtkRange *range;
1203
1204   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1205   g_return_val_if_fail (event != NULL, FALSE);
1206
1207   range = GTK_RANGE (widget);
1208
1209   range->layout->mouse_x = event->x;
1210   range->layout->mouse_y = event->y;
1211   
1212   if (range->layout->grab_button == event->button)
1213     {
1214       GtkScrollType scroll;
1215       MouseLocation grab_location;
1216
1217       grab_location = range->layout->grab_location;
1218
1219       scroll = range_get_scroll_for_grab (range);
1220       
1221       range_grab_remove (range);
1222       gtk_range_remove_step_timer (range);
1223       
1224       /* We only do the move if we're still on top of the button at
1225        * release
1226        */
1227       if (grab_location == range->layout->mouse_location &&
1228           scroll != GTK_SCROLL_NONE)
1229         {
1230           gtk_range_scroll (range, scroll);
1231         }
1232
1233       if (grab_location == MOUSE_SLIDER)
1234         update_slider_position (range, event->x, event->y);
1235
1236       /* Flush any pending discontinuous/delayed updates */
1237       gtk_range_update_value (range);
1238       
1239       /* Just be lazy about this, if we scrolled it will all redraw anyway,
1240        * so no point optimizing the button deactivate case
1241        */
1242       gtk_widget_queue_draw (widget);
1243       
1244       return TRUE;
1245     }
1246
1247   return FALSE;
1248 }
1249
1250 static gint
1251 gtk_range_scroll_event (GtkWidget      *widget,
1252                         GdkEventScroll *event)
1253 {
1254   GtkRange *range;
1255
1256   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1257   g_return_val_if_fail (event != NULL, FALSE);
1258
1259   range = GTK_RANGE (widget);
1260
1261   if (GTK_WIDGET_REALIZED (range))
1262     {
1263       GtkAdjustment *adj = GTK_RANGE (range)->adjustment;
1264       gdouble new_value = adj->value + ((event->direction == GDK_SCROLL_UP ||
1265                                          event->direction == GDK_SCROLL_LEFT) ? 
1266                                         -adj->page_increment / 2: 
1267                                         adj->page_increment / 2);
1268
1269       gtk_range_internal_set_value (range, new_value);
1270
1271       /* Policy DELAYED makes sense with scroll events,
1272        * but DISCONTINUOUS doesn't, so we update immediately
1273        * for DISCONTINOUS
1274        */
1275       if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
1276         gtk_range_update_value (range);
1277     }
1278
1279   return TRUE;
1280 }
1281
1282 static gint
1283 gtk_range_motion_notify (GtkWidget      *widget,
1284                          GdkEventMotion *event)
1285 {
1286   GtkRange *range;
1287   gint x, y;
1288
1289   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1290   g_return_val_if_fail (event != NULL, FALSE);
1291
1292   range = GTK_RANGE (widget);
1293
1294   gdk_window_get_pointer (widget->window, &x, &y, NULL);
1295   
1296   range->layout->mouse_x = x;
1297   range->layout->mouse_y = y;
1298
1299   if (gtk_range_update_mouse_location (range))
1300     gtk_widget_queue_draw (widget);
1301
1302   if (range->layout->grab_location == MOUSE_SLIDER)
1303     update_slider_position (range, x, y);
1304
1305   /* We handled the event if the mouse was in the range_rect */
1306   return range->layout->mouse_location != MOUSE_OUTSIDE;
1307 }
1308
1309 static gint
1310 gtk_range_enter_notify (GtkWidget        *widget,
1311                         GdkEventCrossing *event)
1312 {
1313   GtkRange *range;
1314
1315   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1316   g_return_val_if_fail (event != NULL, FALSE);
1317
1318   range = GTK_RANGE (widget);
1319
1320   range->layout->mouse_x = event->x;
1321   range->layout->mouse_y = event->y;
1322
1323   if (gtk_range_update_mouse_location (range))
1324     gtk_widget_queue_draw (widget);
1325   
1326   return TRUE;
1327 }
1328
1329 static gint
1330 gtk_range_leave_notify (GtkWidget        *widget,
1331                         GdkEventCrossing *event)
1332 {
1333   GtkRange *range;
1334
1335   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1336   g_return_val_if_fail (event != NULL, FALSE);
1337
1338   range = GTK_RANGE (widget);
1339
1340   range->layout->mouse_x = -1;
1341   range->layout->mouse_y = -1;
1342
1343   if (gtk_range_update_mouse_location (range))
1344     gtk_widget_queue_draw (widget);
1345   
1346   return TRUE;
1347 }
1348
1349 static void
1350 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
1351                               gpointer       data)
1352 {
1353   GtkRange *range;
1354
1355   g_return_if_fail (adjustment != NULL);
1356   g_return_if_fail (data != NULL);
1357
1358   range = GTK_RANGE (data);
1359
1360   range->need_recalc = TRUE;
1361   gtk_widget_queue_draw (GTK_WIDGET (range));
1362
1363   /* Note that we don't round off to range->round_digits here.
1364    * that's because it's really broken to change a value
1365    * in response to a change signal on that value; round_digits
1366    * is therefore defined to be a filter on what the GtkRange
1367    * can input into the adjustment, not a filter that the GtkRange
1368    * will enforce on the adjustment.
1369    */
1370 }
1371
1372 static void
1373 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
1374                                     gpointer       data)
1375 {
1376   GtkRange *range;
1377
1378   g_return_if_fail (adjustment != NULL);
1379   g_return_if_fail (data != NULL);
1380
1381   range = GTK_RANGE (data);
1382
1383   range->need_recalc = TRUE;
1384
1385   gtk_widget_queue_draw (GTK_WIDGET (range));
1386   /* This is so we don't lag the widget being scrolled. */
1387   if (GTK_WIDGET_REALIZED (range))
1388     gdk_window_process_updates (GTK_WIDGET (range)->window, TRUE);
1389   
1390   /* Note that we don't round off to range->round_digits here.
1391    * that's because it's really broken to change a value
1392    * in response to a change signal on that value; round_digits
1393    * is therefore defined to be a filter on what the GtkRange
1394    * can input into the adjustment, not a filter that the GtkRange
1395    * will enforce on the adjustment.
1396    */
1397
1398   g_signal_emit (G_OBJECT (range), signals[VALUE_CHANGED], 0);
1399 }
1400
1401 static void
1402 gtk_range_style_set (GtkWidget *widget,
1403                      GtkStyle  *previous_style)
1404 {
1405   GtkRange *range;
1406
1407   g_return_if_fail (GTK_IS_RANGE (widget));
1408
1409   range = GTK_RANGE (widget);
1410
1411   range->need_recalc = TRUE;
1412
1413   (* GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style);
1414 }
1415
1416 static void
1417 step_back (GtkRange *range)
1418 {
1419   gdouble newval;
1420   
1421   newval = range->adjustment->value - range->adjustment->step_increment;
1422   gtk_range_internal_set_value (range, newval);
1423 }
1424
1425 static void
1426 step_forward (GtkRange *range)
1427 {
1428   gdouble newval;
1429
1430   newval = range->adjustment->value + range->adjustment->step_increment;
1431
1432   gtk_range_internal_set_value (range, newval);
1433 }
1434
1435
1436 static void
1437 page_back (GtkRange *range)
1438 {
1439   gdouble newval;
1440
1441   newval = range->adjustment->value - range->adjustment->page_increment;
1442   gtk_range_internal_set_value (range, newval);
1443 }
1444
1445 static void
1446 page_forward (GtkRange *range)
1447 {
1448   gdouble newval;
1449
1450   newval = range->adjustment->value + range->adjustment->page_increment;
1451
1452   gtk_range_internal_set_value (range, newval);
1453 }
1454
1455 static void
1456 gtk_range_scroll (GtkRange     *range,
1457                   GtkScrollType scroll)
1458 {
1459   switch (scroll)
1460     {
1461     case GTK_SCROLL_STEP_LEFT:
1462       if (should_invert (range))
1463         step_forward (range);
1464       else
1465         step_back (range);
1466       break;
1467                     
1468     case GTK_SCROLL_STEP_UP:
1469       if (should_invert (range))
1470         step_forward (range);
1471       else
1472         step_back (range);
1473       break;
1474
1475     case GTK_SCROLL_STEP_RIGHT:
1476       if (should_invert (range))
1477         step_back (range);
1478       else
1479         step_forward (range);
1480       break;
1481                     
1482     case GTK_SCROLL_STEP_DOWN:
1483       if (should_invert (range))
1484         step_back (range);
1485       else
1486         step_forward (range);
1487       break;
1488                   
1489     case GTK_SCROLL_STEP_BACKWARD:
1490       step_back (range);
1491       break;
1492                   
1493     case GTK_SCROLL_STEP_FORWARD:
1494       step_forward (range);
1495       break;
1496
1497     case GTK_SCROLL_PAGE_LEFT:
1498       if (should_invert (range))
1499         page_forward (range);
1500       else
1501         page_back (range);
1502       break;
1503                     
1504     case GTK_SCROLL_PAGE_UP:
1505       if (should_invert (range))
1506         page_forward (range);
1507       else
1508         page_back (range);
1509       break;
1510
1511     case GTK_SCROLL_PAGE_RIGHT:
1512       if (should_invert (range))
1513         page_back (range);
1514       else
1515         page_forward (range);
1516       break;
1517                     
1518     case GTK_SCROLL_PAGE_DOWN:
1519       if (should_invert (range))
1520         page_back (range);
1521       else
1522         page_forward (range);
1523       break;
1524                   
1525     case GTK_SCROLL_PAGE_BACKWARD:
1526       page_back (range);
1527       break;
1528                   
1529     case GTK_SCROLL_PAGE_FORWARD:
1530       page_forward (range);
1531       break;
1532
1533     case GTK_SCROLL_START:
1534       gtk_range_internal_set_value (range,
1535                                     range->adjustment->lower);
1536       break;
1537
1538     case GTK_SCROLL_END:
1539       gtk_range_internal_set_value (range,
1540                                     range->adjustment->upper - range->adjustment->page_size);
1541       break;
1542
1543     case GTK_SCROLL_JUMP:
1544       /* Used by CList, range doesn't use it. */
1545       break;
1546
1547     case GTK_SCROLL_NONE:
1548       break;
1549     }
1550 }
1551
1552 static void
1553 gtk_range_move_slider (GtkRange     *range,
1554                        GtkScrollType scroll)
1555 {
1556   gtk_range_scroll (range, scroll);
1557
1558   /* Policy DELAYED makes sense with key events,
1559    * but DISCONTINUOUS doesn't, so we update immediately
1560    * for DISCONTINOUS
1561    */
1562   if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
1563     gtk_range_update_value (range);
1564 }
1565
1566 static void
1567 gtk_range_get_props (GtkRange  *range,
1568                      gint      *slider_width,
1569                      gint      *stepper_size,
1570                      gint      *trough_border,
1571                      gint      *stepper_spacing)
1572 {
1573   GtkWidget *widget =  GTK_WIDGET (range);
1574   gint tmp_slider_width, tmp_stepper_size, tmp_trough_border, tmp_stepper_spacing;
1575   
1576   gtk_widget_style_get (widget,
1577                         "slider_width", &tmp_slider_width,
1578                         "trough_border", &tmp_trough_border,
1579                         "stepper_size", &tmp_stepper_size,
1580                         "stepper_spacing", &tmp_stepper_spacing,
1581                         NULL);
1582   
1583   if (slider_width)
1584     *slider_width = tmp_slider_width;
1585
1586   if (trough_border)
1587     *trough_border = tmp_trough_border;
1588
1589   if (stepper_size)
1590     *stepper_size = tmp_stepper_size;
1591
1592   if (stepper_spacing)
1593     *stepper_spacing = tmp_stepper_spacing;
1594 }
1595
1596 #define POINT_IN_RECT(xcoord, ycoord, rect) \
1597  ((xcoord) >= (rect).x &&                   \
1598   (xcoord) <  ((rect).x + (rect).width) &&  \
1599   (ycoord) >= (rect).y &&                   \
1600   (ycoord) <  ((rect).y + (rect).height))
1601
1602 /* Update mouse location, return TRUE if it changes */
1603 static gboolean
1604 gtk_range_update_mouse_location (GtkRange *range)
1605 {
1606   gint x, y;
1607   MouseLocation old;
1608   GtkWidget *widget;
1609
1610   widget = GTK_WIDGET (range);
1611   
1612   old = range->layout->mouse_location;
1613   
1614   x = range->layout->mouse_x;
1615   y = range->layout->mouse_y;
1616
1617   if (range->layout->grab_location != MOUSE_OUTSIDE)
1618     range->layout->mouse_location = range->layout->grab_location;
1619   else if (POINT_IN_RECT (x, y, range->layout->stepper_a))
1620     range->layout->mouse_location = MOUSE_STEPPER_A;
1621   else if (POINT_IN_RECT (x, y, range->layout->stepper_b))
1622     range->layout->mouse_location = MOUSE_STEPPER_B;
1623   else if (POINT_IN_RECT (x, y, range->layout->stepper_c))
1624     range->layout->mouse_location = MOUSE_STEPPER_C;
1625   else if (POINT_IN_RECT (x, y, range->layout->stepper_d))
1626     range->layout->mouse_location = MOUSE_STEPPER_D;
1627   else if (POINT_IN_RECT (x, y, range->layout->slider))
1628     range->layout->mouse_location = MOUSE_SLIDER;
1629   else if (POINT_IN_RECT (x, y, range->layout->trough))
1630     range->layout->mouse_location = MOUSE_TROUGH;
1631   else if (POINT_IN_RECT (x, y, widget->allocation))
1632     range->layout->mouse_location = MOUSE_WIDGET;
1633   else
1634     range->layout->mouse_location = MOUSE_OUTSIDE;
1635
1636   return old != range->layout->mouse_location;
1637 }
1638
1639 /* Clamp rect, border inside widget->allocation, such that we prefer
1640  * to take space from border not rect in all directions, and prefer to
1641  * give space to border over rect in one direction.
1642  */
1643 static void
1644 clamp_dimensions (GtkWidget    *widget,
1645                   GdkRectangle *rect,
1646                   GtkBorder    *border,
1647                   gboolean      border_expands_horizontally)
1648 {
1649   gint extra, shortage;
1650   
1651   g_return_if_fail (rect->x == 0);
1652   g_return_if_fail (rect->y == 0);  
1653   g_return_if_fail (rect->width >= 0);
1654   g_return_if_fail (rect->height >= 0);
1655
1656   /* Width */
1657   
1658   extra = widget->allocation.width - border->left - border->right - rect->width;
1659   if (extra > 0)
1660     {
1661       if (border_expands_horizontally)
1662         {
1663           border->left += extra / 2;
1664           border->right += extra / 2 + extra % 2;
1665         }
1666       else
1667         {
1668           rect->width += extra;
1669         }
1670     }
1671   
1672   /* See if we can fit rect, if not kill the border */
1673   shortage = rect->width - widget->allocation.width;
1674   if (shortage > 0)
1675     {
1676       rect->width = widget->allocation.width;
1677       /* lose the border */
1678       border->left = 0;
1679       border->right = 0;
1680     }
1681   else
1682     {
1683       /* See if we can fit rect with borders */
1684       shortage = rect->width + border->left + border->right -
1685         widget->allocation.width;
1686       if (shortage > 0)
1687         {
1688           /* Shrink borders */
1689           border->left -= shortage / 2;
1690           border->right -= shortage / 2 + shortage % 2;
1691         }
1692     }
1693
1694   /* Height */
1695   
1696   extra = widget->allocation.height - border->top - border->bottom - rect->height;
1697   if (extra > 0)
1698     {
1699       if (border_expands_horizontally)
1700         {
1701           /* don't expand border vertically */
1702           rect->height += extra;
1703         }
1704       else
1705         {
1706           border->top += extra / 2;
1707           border->bottom += extra / 2 + extra % 2;
1708         }
1709     }
1710   
1711   /* See if we can fit rect, if not kill the border */
1712   shortage = rect->height - widget->allocation.height;
1713   if (shortage > 0)
1714     {
1715       rect->height = widget->allocation.height;
1716       /* lose the border */
1717       border->top = 0;
1718       border->bottom = 0;
1719     }
1720   else
1721     {
1722       /* See if we can fit rect with borders */
1723       shortage = rect->height + border->top + border->bottom -
1724         widget->allocation.height;
1725       if (shortage > 0)
1726         {
1727           /* Shrink borders */
1728           border->top -= shortage / 2;
1729           border->bottom -= shortage / 2 + shortage % 2;
1730         }
1731     }
1732 }
1733
1734 static void
1735 gtk_range_calc_request (GtkRange      *range,
1736                         gint           slider_width,
1737                         gint           stepper_size,
1738                         gint           trough_border,
1739                         gint           stepper_spacing,
1740                         GdkRectangle  *range_rect,
1741                         GtkBorder     *border,
1742                         gint          *n_steppers_p,
1743                         gint          *slider_length_p)
1744 {
1745   gint slider_length;
1746   gint n_steppers;
1747   
1748   border->left = 0;
1749   border->right = 0;
1750   border->top = 0;
1751   border->bottom = 0;
1752
1753   if (GTK_RANGE_GET_CLASS (range)->get_range_border)
1754     (* GTK_RANGE_GET_CLASS (range)->get_range_border) (range, border);
1755   
1756   n_steppers = 0;
1757   if (range->has_stepper_a)
1758     n_steppers += 1;
1759   if (range->has_stepper_b)
1760     n_steppers += 1;
1761   if (range->has_stepper_c)
1762     n_steppers += 1;
1763   if (range->has_stepper_d)
1764     n_steppers += 1;
1765
1766   slider_length = range->min_slider_size;
1767
1768   range_rect->x = 0;
1769   range_rect->y = 0;
1770   
1771   /* We never expand to fill available space in the small dimension
1772    * (i.e. vertical scrollbars are always a fixed width)
1773    */
1774   if (range->orientation == GTK_ORIENTATION_VERTICAL)
1775     {
1776       range_rect->width = trough_border * 2 + slider_width;
1777       range_rect->height = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length;
1778     }
1779   else
1780     {
1781       range_rect->width = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length;
1782       range_rect->height = trough_border * 2 + slider_width;
1783     }
1784
1785   if (n_steppers_p)
1786     *n_steppers_p = n_steppers;
1787
1788   if (slider_length_p)
1789     *slider_length_p = slider_length;
1790 }
1791
1792 static void
1793 gtk_range_calc_layout (GtkRange *range,
1794                        gdouble   adjustment_value)
1795 {
1796   gint slider_width, stepper_size, trough_border, stepper_spacing;
1797   gint slider_length;
1798   GtkBorder border;
1799   gint n_steppers;
1800   GdkRectangle range_rect;
1801   GtkRangeLayout *layout;
1802   GtkWidget *widget;
1803   
1804   if (!range->need_recalc)
1805     return;
1806
1807   /* If we have a too-small allocation, we prefer the steppers over
1808    * the trough/slider, probably the steppers are a more useful
1809    * feature in small spaces.
1810    *
1811    * Also, we prefer to draw the range itself rather than the border
1812    * areas if there's a conflict, since the borders will be decoration
1813    * not controls. Though this depends on subclasses cooperating by
1814    * not drawing on range->range_rect.
1815    */
1816
1817   widget = GTK_WIDGET (range);
1818   layout = range->layout;
1819   
1820   gtk_range_get_props (range,
1821                        &slider_width, &stepper_size, &trough_border, &stepper_spacing);
1822
1823   gtk_range_calc_request (range, 
1824                           slider_width, stepper_size, trough_border, stepper_spacing,
1825                           &range_rect, &border, &n_steppers, &slider_length);
1826   
1827   /* We never expand to fill available space in the small dimension
1828    * (i.e. vertical scrollbars are always a fixed width)
1829    */
1830   if (range->orientation == GTK_ORIENTATION_VERTICAL)
1831     {
1832       clamp_dimensions (widget, &range_rect, &border, TRUE);
1833     }
1834   else
1835     {
1836       clamp_dimensions (widget, &range_rect, &border, FALSE);
1837     }
1838   
1839   range_rect.x = border.left;
1840   range_rect.y = border.top;
1841   
1842   range->range_rect = range_rect;
1843   
1844   if (range->orientation == GTK_ORIENTATION_VERTICAL)
1845     {
1846       gint stepper_width, stepper_height;
1847
1848       /* Steppers are the width of the range, and stepper_size in
1849        * height, or if we don't have enough height, divided equally
1850        * among available space.
1851        */
1852       stepper_width = range_rect.width - trough_border * 2;
1853
1854       if (stepper_width < 1)
1855         stepper_width = range_rect.width; /* screw the trough border */
1856
1857       if (n_steppers == 0)
1858         stepper_height = 0; /* avoid divide by n_steppers */
1859       else
1860         stepper_height = MIN (stepper_size, (range_rect.height / n_steppers));
1861
1862       /* Stepper A */
1863       
1864       layout->stepper_a.x = range_rect.x + trough_border;
1865       layout->stepper_a.y = range_rect.y + trough_border;
1866
1867       if (range->has_stepper_a)
1868         {
1869           layout->stepper_a.width = stepper_width;
1870           layout->stepper_a.height = stepper_height;
1871         }
1872       else
1873         {
1874           layout->stepper_a.width = 0;
1875           layout->stepper_a.height = 0;
1876         }
1877
1878       /* Stepper B */
1879       
1880       layout->stepper_b.x = layout->stepper_a.x;
1881       layout->stepper_b.y = layout->stepper_a.y + layout->stepper_a.height;
1882
1883       if (range->has_stepper_b)
1884         {
1885           layout->stepper_b.width = stepper_width;
1886           layout->stepper_b.height = stepper_height;
1887         }
1888       else
1889         {
1890           layout->stepper_b.width = 0;
1891           layout->stepper_b.height = 0;
1892         }
1893
1894       /* Stepper D */
1895
1896       if (range->has_stepper_d)
1897         {
1898           layout->stepper_d.width = stepper_width;
1899           layout->stepper_d.height = stepper_height;
1900         }
1901       else
1902         {
1903           layout->stepper_d.width = 0;
1904           layout->stepper_d.height = 0;
1905         }
1906       
1907       layout->stepper_d.x = layout->stepper_a.x;
1908       layout->stepper_d.y = range_rect.y + range_rect.height - layout->stepper_d.height - trough_border;
1909
1910       /* Stepper C */
1911
1912       if (range->has_stepper_c)
1913         {
1914           layout->stepper_c.width = stepper_width;
1915           layout->stepper_c.height = stepper_height;
1916         }
1917       else
1918         {
1919           layout->stepper_c.width = 0;
1920           layout->stepper_c.height = 0;
1921         }
1922       
1923       layout->stepper_c.x = layout->stepper_a.x;
1924       layout->stepper_c.y = layout->stepper_d.y - layout->stepper_c.height;
1925
1926       /* Now the trough is the remaining space between steppers B and C,
1927        * if any
1928        */
1929       layout->trough.x = range_rect.x;
1930       layout->trough.y = layout->stepper_b.y + layout->stepper_b.height;
1931       layout->trough.width = range_rect.width;
1932       layout->trough.height = layout->stepper_c.y - (layout->stepper_b.y + layout->stepper_b.height);
1933       
1934       /* Slider fits into the trough, with stepper_spacing on either side,
1935        * and the size/position based on the adjustment or fixed, depending.
1936        */
1937       layout->slider.x = layout->trough.x + trough_border;
1938       layout->slider.width = layout->trough.width - trough_border * 2;
1939
1940       /* Compute slider position/length */
1941       {
1942         gint y, bottom, top, height;
1943         
1944         top = layout->trough.y + stepper_spacing;
1945         bottom = layout->trough.y + layout->trough.height - stepper_spacing;
1946         
1947         /* slider height is the fraction (page_size /
1948          * total_adjustment_range) times the trough height in pixels
1949          */
1950         height = ((bottom - top) * (range->adjustment->page_size /
1951                                     (range->adjustment->upper - range->adjustment->lower)));
1952         
1953         if (height < range->min_slider_size ||
1954             range->slider_size_fixed)
1955           height = range->min_slider_size;
1956         
1957         height = MIN (height, (layout->trough.height - stepper_spacing * 2));
1958         
1959         y = top;
1960         
1961         if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0)
1962                 y += (bottom - top - height) * ((adjustment_value - range->adjustment->lower) /
1963                                                 (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
1964         
1965         y = CLAMP (y, top, bottom);
1966         
1967         if (should_invert (range))
1968           y = bottom - (y - top + height);
1969         
1970         layout->slider.y = y;
1971         layout->slider.height = height;
1972
1973         /* These are publically exported */
1974         range->slider_start = layout->slider.y;
1975         range->slider_end = layout->slider.y + layout->slider.height;
1976       }
1977     }
1978   else
1979     {
1980       gint stepper_width, stepper_height;
1981
1982       /* Steppers are the height of the range, and stepper_size in
1983        * width, or if we don't have enough width, divided equally
1984        * among available space.
1985        */
1986       stepper_height = range_rect.height - trough_border * 2;
1987
1988       if (stepper_height < 1)
1989         stepper_height = range_rect.height; /* screw the trough border */
1990
1991       if (n_steppers == 0)
1992         stepper_width = 0; /* avoid divide by n_steppers */
1993       else
1994         stepper_width = MIN (stepper_size, (range_rect.width / n_steppers));
1995
1996       /* Stepper A */
1997       
1998       layout->stepper_a.x = range_rect.x + trough_border;
1999       layout->stepper_a.y = range_rect.y + trough_border;
2000
2001       if (range->has_stepper_a)
2002         {
2003           layout->stepper_a.width = stepper_width;
2004           layout->stepper_a.height = stepper_height;
2005         }
2006       else
2007         {
2008           layout->stepper_a.width = 0;
2009           layout->stepper_a.height = 0;
2010         }
2011
2012       /* Stepper B */
2013       
2014       layout->stepper_b.x = layout->stepper_a.x + layout->stepper_a.width;
2015       layout->stepper_b.y = layout->stepper_a.y;
2016
2017       if (range->has_stepper_b)
2018         {
2019           layout->stepper_b.width = stepper_width;
2020           layout->stepper_b.height = stepper_height;
2021         }
2022       else
2023         {
2024           layout->stepper_b.width = 0;
2025           layout->stepper_b.height = 0;
2026         }
2027
2028       /* Stepper D */
2029
2030       if (range->has_stepper_d)
2031         {
2032           layout->stepper_d.width = stepper_width;
2033           layout->stepper_d.height = stepper_height;
2034         }
2035       else
2036         {
2037           layout->stepper_d.width = 0;
2038           layout->stepper_d.height = 0;
2039         }
2040
2041       layout->stepper_d.x = range_rect.x + range_rect.width - layout->stepper_d.width - trough_border;
2042       layout->stepper_d.y = layout->stepper_a.y;
2043
2044
2045       /* Stepper C */
2046
2047       if (range->has_stepper_c)
2048         {
2049           layout->stepper_c.width = stepper_width;
2050           layout->stepper_c.height = stepper_height;
2051         }
2052       else
2053         {
2054           layout->stepper_c.width = 0;
2055           layout->stepper_c.height = 0;
2056         }
2057       
2058       layout->stepper_c.x = layout->stepper_d.x - layout->stepper_c.width;
2059       layout->stepper_c.y = layout->stepper_a.y;
2060
2061       /* Now the trough is the remaining space between steppers B and C,
2062        * if any
2063        */
2064       layout->trough.x = layout->stepper_b.x + layout->stepper_b.width;
2065       layout->trough.y = range_rect.y;
2066
2067       layout->trough.width = layout->stepper_c.x - (layout->stepper_b.x + layout->stepper_b.width);
2068       layout->trough.height = range_rect.height;
2069       
2070       /* Slider fits into the trough, with stepper_spacing on either side,
2071        * and the size/position based on the adjustment or fixed, depending.
2072        */
2073       layout->slider.y = layout->trough.y + trough_border;
2074       layout->slider.height = layout->trough.height - trough_border * 2;
2075
2076       /* Compute slider position/length */
2077       {
2078         gint x, left, right, width;
2079         
2080         left = layout->trough.x + stepper_spacing;
2081         right = layout->trough.x + layout->trough.width - stepper_spacing;
2082         
2083         /* slider width is the fraction (page_size /
2084          * total_adjustment_range) times the trough width in pixels
2085          */
2086         width = ((right - left) * (range->adjustment->page_size /
2087                                    (range->adjustment->upper - range->adjustment->lower)));
2088         
2089         if (width < range->min_slider_size ||
2090             range->slider_size_fixed)
2091           width = range->min_slider_size;
2092         
2093         width = MIN (width, (layout->trough.width - stepper_spacing * 2));
2094         
2095         x = left;
2096         
2097         x += (right - left - width) * ((adjustment_value - range->adjustment->lower) /
2098                                        (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
2099         
2100         x = CLAMP (x, left, right);
2101         
2102         if (should_invert (range))
2103           x = right - (x - left + width);
2104         
2105         layout->slider.x = x;
2106         layout->slider.width = width;
2107
2108         /* These are publically exported */
2109         range->slider_start = layout->slider.x;
2110         range->slider_end = layout->slider.x + layout->slider.width;
2111       }
2112     }
2113   
2114   gtk_range_update_mouse_location (range);
2115 }
2116
2117 static GdkRectangle*
2118 get_area (GtkRange     *range,
2119           MouseLocation location)
2120 {
2121   switch (location)
2122     {
2123     case MOUSE_STEPPER_A:
2124       return &range->layout->stepper_a;
2125     case MOUSE_STEPPER_B:
2126       return &range->layout->stepper_b;
2127     case MOUSE_STEPPER_C:
2128       return &range->layout->stepper_c;
2129     case MOUSE_STEPPER_D:
2130       return &range->layout->stepper_d;
2131     case MOUSE_TROUGH:
2132       return &range->layout->trough;
2133     case MOUSE_SLIDER:
2134       return &range->layout->slider;
2135     case MOUSE_WIDGET:
2136     case MOUSE_OUTSIDE:
2137       break;
2138     }
2139
2140   g_warning (G_STRLOC": bug");
2141   return NULL;
2142 }
2143
2144 static void
2145 gtk_range_internal_set_value (GtkRange *range,
2146                               gdouble   value)
2147 {
2148   value = CLAMP (value, range->adjustment->lower,
2149                  (range->adjustment->upper - range->adjustment->page_size));
2150
2151   if (range->round_digits >= 0)
2152     {
2153       char buffer[128];
2154
2155       /* This is just so darn lame. */
2156       g_snprintf (buffer, 128, "%0.*f",
2157                   range->round_digits, value);
2158       sscanf (buffer, "%lf", &value);
2159     }
2160   
2161   if (range->adjustment->value != value)
2162     {
2163       range->need_recalc = TRUE;
2164
2165       gtk_widget_queue_draw (GTK_WIDGET (range));
2166       
2167       switch (range->update_policy)
2168         {
2169         case GTK_UPDATE_CONTINUOUS:
2170           gtk_adjustment_set_value (range->adjustment, value);
2171           break;
2172
2173           /* Delayed means we update after a period of inactivity */
2174         case GTK_UPDATE_DELAYED:
2175           gtk_range_reset_update_timer (range);
2176           /* FALL THRU */
2177
2178           /* Discontinuous means we update on button release */
2179         case GTK_UPDATE_DISCONTINUOUS:
2180           /* don't emit value_changed signal */
2181           range->adjustment->value = value;
2182           range->update_pending = TRUE;
2183           break;
2184         }
2185     }
2186 }
2187
2188 static void
2189 gtk_range_update_value (GtkRange *range)
2190 {
2191   gtk_range_remove_update_timer (range);
2192   
2193   if (range->update_pending)
2194     {
2195       gtk_adjustment_value_changed (range->adjustment);
2196
2197       range->update_pending = FALSE;
2198     }
2199 }
2200
2201 struct _GtkRangeStepTimer
2202 {
2203   guint timeout_id;
2204   GtkScrollType step;
2205 };
2206
2207 static gboolean
2208 second_timeout (gpointer data)
2209 {
2210   GtkRange *range;
2211
2212   GDK_THREADS_ENTER ();
2213   range = GTK_RANGE (data);
2214   gtk_range_scroll (range, range->timer->step);
2215   GDK_THREADS_LEAVE ();
2216   
2217   return TRUE;
2218 }
2219
2220 static gboolean
2221 initial_timeout (gpointer data)
2222 {
2223   GtkRange *range;
2224
2225   GDK_THREADS_ENTER ();
2226   range = GTK_RANGE (data);
2227   range->timer->timeout_id = 
2228     g_timeout_add (SCROLL_LATER_DELAY,
2229                    second_timeout,
2230                    range);
2231   GDK_THREADS_LEAVE ();
2232
2233   /* remove self */
2234   return FALSE;
2235 }
2236
2237 static void
2238 gtk_range_add_step_timer (GtkRange      *range,
2239                           GtkScrollType  step)
2240 {
2241   g_return_if_fail (range->timer == NULL);
2242   g_return_if_fail (step != GTK_SCROLL_NONE);
2243   
2244   range->timer = g_new (GtkRangeStepTimer, 1);
2245
2246   range->timer->timeout_id =
2247     g_timeout_add (SCROLL_INITIAL_DELAY,
2248                    initial_timeout,
2249                    range);
2250   range->timer->step = step;
2251 }
2252
2253 static void
2254 gtk_range_remove_step_timer (GtkRange *range)
2255 {
2256   if (range->timer)
2257     {
2258       if (range->timer->timeout_id != 0)
2259         g_source_remove (range->timer->timeout_id);
2260
2261       g_free (range->timer);
2262
2263       range->timer = NULL;
2264     }
2265 }
2266
2267 static gboolean
2268 update_timeout (gpointer data)
2269 {
2270   GtkRange *range;
2271
2272   GDK_THREADS_ENTER ();
2273   range = GTK_RANGE (data);
2274   gtk_range_update_value (range);
2275   range->update_timeout_id = 0;
2276   GDK_THREADS_LEAVE ();
2277
2278   /* self-remove */
2279   return FALSE;
2280 }
2281
2282 static void
2283 gtk_range_reset_update_timer (GtkRange *range)
2284 {
2285   gtk_range_remove_update_timer (range);
2286
2287   range->update_timeout_id = g_timeout_add (UPDATE_DELAY,
2288                                             update_timeout,
2289                                             range);
2290 }
2291
2292 static void
2293 gtk_range_remove_update_timer (GtkRange *range)
2294 {
2295   if (range->update_timeout_id != 0)
2296     {
2297       g_source_remove (range->update_timeout_id);
2298       range->update_timeout_id = 0;
2299     }
2300 }