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