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