]> Pileus Git - ~andy/gtk/blob - gtk/gtkrange.c
Fix stupid error introduced last night that was making things decidedly
[~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_newc ("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_newc ("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     frac = ((coord - range->layout->trough.y) /
1025             (gdouble) (range->layout->trough.height - range->layout->slider.height));
1026   else
1027     frac = ((coord - range->layout->trough.x) /
1028             (gdouble) (range->layout->trough.width - range->layout->slider.width));
1029
1030   if (should_invert (range))
1031     frac = 1.0 - frac;
1032   
1033   value = range->adjustment->lower +
1034     frac * (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size);
1035
1036   return value;
1037 }
1038
1039 static gint
1040 gtk_range_button_press (GtkWidget      *widget,
1041                         GdkEventButton *event)
1042 {
1043   GtkRange *range;
1044
1045   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1046   g_return_val_if_fail (event != NULL, FALSE);
1047
1048   range = GTK_RANGE (widget);
1049   
1050   if (!GTK_WIDGET_HAS_FOCUS (widget))
1051     gtk_widget_grab_focus (widget);
1052
1053   /* ignore presses when we're already doing something else. */
1054   if (range->layout->grab_location != MOUSE_OUTSIDE)
1055     return FALSE;
1056
1057   range->layout->mouse_x = event->x;
1058   range->layout->mouse_y = event->y;
1059   if (gtk_range_update_mouse_location (range))
1060     gtk_widget_queue_draw (widget);
1061     
1062   if (range->layout->mouse_location == MOUSE_TROUGH  &&
1063       (event->button == 1 || event->button == 3))
1064     {
1065       /* button 1 steps by step increment, as with button 1 on a stepper,
1066        * button 3 steps by page increment, as with button 2 on a stepper
1067        */
1068       GtkScrollType scroll;
1069       gdouble click_value;
1070       
1071       click_value = coord_to_value (range,
1072                                     range->orientation == GTK_ORIENTATION_VERTICAL ?
1073                                     event->y : event->x);
1074       
1075       range->trough_click_forward = click_value > range->adjustment->value;
1076       range_grab_add (range, MOUSE_TROUGH, event->button);
1077       
1078       scroll = range_get_scroll_for_grab (range);
1079       
1080       gtk_range_add_step_timer (range, scroll);
1081
1082       return TRUE;
1083     }
1084   else if ((range->layout->mouse_location == MOUSE_STEPPER_A ||
1085             range->layout->mouse_location == MOUSE_STEPPER_B ||
1086             range->layout->mouse_location == MOUSE_STEPPER_C ||
1087             range->layout->mouse_location == MOUSE_STEPPER_D) &&
1088            (event->button == 1 || event->button == 2 || event->button == 3))
1089     {
1090       GdkRectangle *stepper_area;
1091       GtkScrollType scroll;
1092       
1093       range_grab_add (range, range->layout->mouse_location, event->button);
1094
1095       stepper_area = get_area (range, range->layout->mouse_location);
1096       gtk_widget_queue_draw_area (widget,
1097                                   stepper_area->x,
1098                                   stepper_area->y,
1099                                   stepper_area->width,
1100                                   stepper_area->height);
1101
1102       scroll = range_get_scroll_for_grab (range);
1103       if (scroll != GTK_SCROLL_NONE)
1104         gtk_range_add_step_timer (range, scroll);
1105       
1106       return TRUE;
1107     }
1108   else if ((range->layout->mouse_location == MOUSE_TROUGH &&
1109             event->button == 2) ||
1110            range->layout->mouse_location == MOUSE_SLIDER)
1111     {
1112       /* Any button can be used to drag the slider, but you can start
1113        * dragging the slider with a trough click using button 2;
1114        * On button 2 press, we warp the slider to mouse position,
1115        * then begin the slider drag.
1116        */
1117       if (event->button == 2)
1118         {
1119           gdouble slider_low_value, slider_high_value;
1120           
1121           slider_high_value =
1122             coord_to_value (range,
1123                             range->orientation == GTK_ORIENTATION_VERTICAL ?
1124                             event->y : event->x);
1125           slider_low_value =
1126             coord_to_value (range,
1127                             range->orientation == GTK_ORIENTATION_VERTICAL ?
1128                             event->y - range->layout->slider.height :
1129                             event->x - range->layout->slider.width);
1130           
1131           /* middle button jumps to point */
1132           gtk_range_internal_set_value (range,
1133                                         slider_low_value + (slider_high_value - slider_low_value) / 2);
1134
1135           /* Calc layout so we can set slide_initial_slider_position
1136            * properly
1137            */
1138           gtk_range_calc_layout (range);
1139         }
1140       
1141       if (range->orientation == GTK_ORIENTATION_VERTICAL)
1142         {
1143           range->slide_initial_slider_position = range->layout->slider.y;
1144           range->slide_initial_coordinate = event->y;
1145         }
1146       else
1147         {
1148           range->slide_initial_slider_position = range->layout->slider.x;
1149           range->slide_initial_coordinate = event->x;
1150         }
1151
1152       range_grab_add (range, MOUSE_SLIDER, event->button);
1153       
1154       return TRUE;
1155     }
1156   
1157   return FALSE;
1158 }
1159
1160 /* During a slide, move the slider as required given new mouse position */
1161 static void
1162 update_slider_position (GtkRange *range,
1163                         gint      mouse_x,
1164                         gint      mouse_y)
1165 {
1166   gint delta;
1167   gint c;
1168   gdouble new_value;
1169   
1170   if (range->orientation == GTK_ORIENTATION_VERTICAL)
1171     delta = mouse_y - range->slide_initial_coordinate;
1172   else
1173     delta = mouse_x - range->slide_initial_coordinate;
1174
1175   c = range->slide_initial_slider_position + delta;
1176
1177   new_value = coord_to_value (range, c);
1178   
1179   gtk_range_internal_set_value (range, new_value);
1180 }
1181
1182 static gint
1183 gtk_range_button_release (GtkWidget      *widget,
1184                           GdkEventButton *event)
1185 {
1186   GtkRange *range;
1187
1188   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1189   g_return_val_if_fail (event != NULL, FALSE);
1190
1191   range = GTK_RANGE (widget);
1192
1193   range->layout->mouse_x = event->x;
1194   range->layout->mouse_y = event->y;
1195   
1196   if (range->layout->grab_button == event->button)
1197     {
1198       GtkScrollType scroll;
1199       MouseLocation grab_location;
1200
1201       grab_location = range->layout->grab_location;
1202
1203       scroll = range_get_scroll_for_grab (range);
1204       
1205       range_grab_remove (range);
1206       gtk_range_remove_step_timer (range);
1207       
1208       /* We only do the move if we're still on top of the button at
1209        * release
1210        */
1211       if (grab_location == range->layout->mouse_location &&
1212           scroll != GTK_SCROLL_NONE)
1213         {
1214           gtk_range_scroll (range, scroll);
1215         }
1216
1217       if (grab_location == MOUSE_SLIDER)
1218         update_slider_position (range, event->x, event->y);
1219
1220       /* Flush any pending discontinuous/delayed updates */
1221       gtk_range_update_value (range);
1222       
1223       /* Just be lazy about this, if we scrolled it will all redraw anyway,
1224        * so no point optimizing the button deactivate case
1225        */
1226       gtk_widget_queue_draw (widget);
1227       
1228       return TRUE;
1229     }
1230
1231   return FALSE;
1232 }
1233
1234 static gint
1235 gtk_range_scroll_event (GtkWidget      *widget,
1236                         GdkEventScroll *event)
1237 {
1238   GtkRange *range;
1239
1240   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1241   g_return_val_if_fail (event != NULL, FALSE);
1242
1243   range = GTK_RANGE (widget);
1244
1245   if (GTK_WIDGET_REALIZED (range))
1246     {
1247       GtkAdjustment *adj = GTK_RANGE (range)->adjustment;
1248       gdouble new_value = adj->value + ((event->direction == GDK_SCROLL_UP ||
1249                                          event->direction == GDK_SCROLL_LEFT) ? 
1250                                         -adj->page_increment / 2: 
1251                                         adj->page_increment / 2);
1252
1253       gtk_range_internal_set_value (range, new_value);
1254
1255       /* Policy DELAYED makes sense with scroll events,
1256        * but DISCONTINUOUS doesn't, so we update immediately
1257        * for DISCONTINOUS
1258        */
1259       if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
1260         gtk_range_update_value (range);
1261     }
1262
1263   return TRUE;
1264 }
1265
1266 static gint
1267 gtk_range_motion_notify (GtkWidget      *widget,
1268                          GdkEventMotion *event)
1269 {
1270   GtkRange *range;
1271   gint x, y;
1272
1273   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1274   g_return_val_if_fail (event != NULL, FALSE);
1275
1276   range = GTK_RANGE (widget);
1277
1278   gdk_window_get_pointer (widget->window, &x, &y, NULL);
1279   
1280   range->layout->mouse_x = x;
1281   range->layout->mouse_y = y;
1282
1283   if (gtk_range_update_mouse_location (range))
1284     gtk_widget_queue_draw (widget);
1285
1286   if (range->layout->grab_location == MOUSE_SLIDER)
1287     update_slider_position (range, event->x, event->y);
1288
1289   /* We handled the event if the mouse was in the range_rect */
1290   return range->layout->mouse_location != MOUSE_OUTSIDE;
1291 }
1292
1293 static gint
1294 gtk_range_enter_notify (GtkWidget        *widget,
1295                         GdkEventCrossing *event)
1296 {
1297   GtkRange *range;
1298
1299   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1300   g_return_val_if_fail (event != NULL, FALSE);
1301
1302   range = GTK_RANGE (widget);
1303
1304   range->layout->mouse_x = event->x;
1305   range->layout->mouse_y = event->y;
1306
1307   if (gtk_range_update_mouse_location (range))
1308     gtk_widget_queue_draw (widget);
1309   
1310   return TRUE;
1311 }
1312
1313 static gint
1314 gtk_range_leave_notify (GtkWidget        *widget,
1315                         GdkEventCrossing *event)
1316 {
1317   GtkRange *range;
1318
1319   g_return_val_if_fail (GTK_IS_RANGE (widget), FALSE);
1320   g_return_val_if_fail (event != NULL, FALSE);
1321
1322   range = GTK_RANGE (widget);
1323
1324   range->layout->mouse_x = -1;
1325   range->layout->mouse_y = -1;
1326
1327   if (gtk_range_update_mouse_location (range))
1328     gtk_widget_queue_draw (widget);
1329   
1330   return TRUE;
1331 }
1332
1333 static void
1334 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
1335                               gpointer       data)
1336 {
1337   GtkRange *range;
1338
1339   g_return_if_fail (adjustment != NULL);
1340   g_return_if_fail (data != NULL);
1341
1342   range = GTK_RANGE (data);
1343
1344   range->need_recalc = TRUE;
1345   gtk_widget_queue_draw (GTK_WIDGET (range));
1346
1347   /* Note that we don't round off to range->round_digits here.
1348    * that's because it's really broken to change a value
1349    * in response to a change signal on that value; round_digits
1350    * is therefore defined to be a filter on what the GtkRange
1351    * can input into the adjustment, not a filter that the GtkRange
1352    * will enforce on the adjustment.
1353    */
1354 }
1355
1356 static void
1357 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
1358                                     gpointer       data)
1359 {
1360   GtkRange *range;
1361
1362   g_return_if_fail (adjustment != NULL);
1363   g_return_if_fail (data != NULL);
1364
1365   range = GTK_RANGE (data);
1366
1367   range->need_recalc = TRUE;
1368
1369   gtk_widget_queue_draw (GTK_WIDGET (range));
1370   /* This is so we don't lag the widget being scrolled. */
1371   if (GTK_WIDGET_REALIZED (range))
1372     gdk_window_process_updates (GTK_WIDGET (range)->window, TRUE);
1373   
1374   /* Note that we don't round off to range->round_digits here.
1375    * that's because it's really broken to change a value
1376    * in response to a change signal on that value; round_digits
1377    * is therefore defined to be a filter on what the GtkRange
1378    * can input into the adjustment, not a filter that the GtkRange
1379    * will enforce on the adjustment.
1380    */
1381
1382   g_signal_emit (G_OBJECT (range), signals[VALUE_CHANGED], 0);
1383 }
1384
1385 static void
1386 gtk_range_style_set (GtkWidget *widget,
1387                      GtkStyle  *previous_style)
1388 {
1389   GtkRange *range;
1390
1391   g_return_if_fail (widget != NULL);
1392   g_return_if_fail (GTK_IS_RANGE (widget));
1393
1394   range = GTK_RANGE (widget);
1395
1396   range->need_recalc = TRUE;
1397
1398   (* GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style);
1399 }
1400
1401 static void
1402 step_back (GtkRange *range)
1403 {
1404   gdouble newval;
1405   
1406   newval = range->adjustment->value - range->adjustment->step_increment;
1407   gtk_range_internal_set_value (range, newval);
1408 }
1409
1410 static void
1411 step_forward (GtkRange *range)
1412 {
1413   gdouble newval;
1414
1415   newval = range->adjustment->value + range->adjustment->step_increment;
1416
1417   gtk_range_internal_set_value (range, newval);
1418 }
1419
1420
1421 static void
1422 page_back (GtkRange *range)
1423 {
1424   gdouble newval;
1425
1426   newval = range->adjustment->value - range->adjustment->page_increment;
1427   gtk_range_internal_set_value (range, newval);
1428 }
1429
1430 static void
1431 page_forward (GtkRange *range)
1432 {
1433   gdouble newval;
1434
1435   newval = range->adjustment->value + range->adjustment->page_increment;
1436
1437   gtk_range_internal_set_value (range, newval);
1438 }
1439
1440 static void
1441 gtk_range_scroll (GtkRange     *range,
1442                   GtkScrollType scroll)
1443 {
1444   switch (scroll)
1445     {
1446     case GTK_SCROLL_STEP_LEFT:
1447       if (should_invert (range))
1448         step_forward (range);
1449       else
1450         step_back (range);
1451       break;
1452                     
1453     case GTK_SCROLL_STEP_UP:
1454       if (should_invert (range))
1455         step_forward (range);
1456       else
1457         step_back (range);
1458       break;
1459
1460     case GTK_SCROLL_STEP_RIGHT:
1461       if (should_invert (range))
1462         step_back (range);
1463       else
1464         step_forward (range);
1465       break;
1466                     
1467     case GTK_SCROLL_STEP_DOWN:
1468       if (should_invert (range))
1469         step_back (range);
1470       else
1471         step_forward (range);
1472       break;
1473                   
1474     case GTK_SCROLL_STEP_BACKWARD:
1475       step_back (range);
1476       break;
1477                   
1478     case GTK_SCROLL_STEP_FORWARD:
1479       step_forward (range);
1480       break;
1481
1482     case GTK_SCROLL_PAGE_LEFT:
1483       if (should_invert (range))
1484         page_forward (range);
1485       else
1486         page_back (range);
1487       break;
1488                     
1489     case GTK_SCROLL_PAGE_UP:
1490       if (should_invert (range))
1491         page_forward (range);
1492       else
1493         page_back (range);
1494       break;
1495
1496     case GTK_SCROLL_PAGE_RIGHT:
1497       if (should_invert (range))
1498         page_back (range);
1499       else
1500         page_forward (range);
1501       break;
1502                     
1503     case GTK_SCROLL_PAGE_DOWN:
1504       if (should_invert (range))
1505         page_back (range);
1506       else
1507         page_forward (range);
1508       break;
1509                   
1510     case GTK_SCROLL_PAGE_BACKWARD:
1511       page_back (range);
1512       break;
1513                   
1514     case GTK_SCROLL_PAGE_FORWARD:
1515       page_forward (range);
1516       break;
1517
1518     case GTK_SCROLL_START:
1519       gtk_range_internal_set_value (range,
1520                                     range->adjustment->lower);
1521       break;
1522
1523     case GTK_SCROLL_END:
1524       gtk_range_internal_set_value (range,
1525                                     range->adjustment->upper - range->adjustment->page_size);
1526       break;
1527
1528     case GTK_SCROLL_JUMP:
1529       /* Used by CList, range doesn't use it. */
1530       break;
1531
1532     case GTK_SCROLL_NONE:
1533       break;
1534     }
1535 }
1536
1537 static void
1538 gtk_range_move_slider (GtkRange     *range,
1539                        GtkScrollType scroll)
1540 {
1541   gtk_range_scroll (range, scroll);
1542
1543   /* Policy DELAYED makes sense with key events,
1544    * but DISCONTINUOUS doesn't, so we update immediately
1545    * for DISCONTINOUS
1546    */
1547   if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
1548     gtk_range_update_value (range);
1549 }
1550
1551 static void
1552 gtk_range_get_props (GtkRange  *range,
1553                      gint      *slider_width,
1554                      gint      *stepper_size,
1555                      gint      *trough_border,
1556                      gint      *stepper_spacing)
1557 {
1558   GtkWidget *widget =  GTK_WIDGET (range);
1559   gint tmp_slider_width, tmp_stepper_size, tmp_trough_border, tmp_stepper_spacing;
1560   
1561   gtk_widget_style_get (widget,
1562                         "slider_width", &tmp_slider_width,
1563                         "trough_border", &tmp_trough_border,
1564                         "stepper_size", &tmp_stepper_size,
1565                         "stepper_spacing", &tmp_stepper_spacing,
1566                         NULL);
1567   
1568   if (slider_width)
1569     *slider_width = tmp_slider_width;
1570
1571   if (trough_border)
1572     *trough_border = tmp_trough_border;
1573
1574   if (stepper_size)
1575     *stepper_size = tmp_stepper_size;
1576
1577   if (stepper_spacing)
1578     *stepper_spacing = tmp_stepper_spacing;
1579 }
1580
1581 #define POINT_IN_RECT(xcoord, ycoord, rect) \
1582  ((xcoord) >= (rect).x &&                   \
1583   (xcoord) <  ((rect).x + (rect).width) &&  \
1584   (ycoord) >= (rect).y &&                   \
1585   (ycoord) <  ((rect).y + (rect).height))
1586
1587 /* Update mouse location, return TRUE if it changes */
1588 static gboolean
1589 gtk_range_update_mouse_location (GtkRange *range)
1590 {
1591   gint x, y;
1592   MouseLocation old;
1593   GtkWidget *widget;
1594
1595   widget = GTK_WIDGET (range);
1596   
1597   old = range->layout->mouse_location;
1598   
1599   x = range->layout->mouse_x;
1600   y = range->layout->mouse_y;
1601
1602   if (range->layout->grab_location != MOUSE_OUTSIDE)
1603     range->layout->mouse_location = range->layout->grab_location;
1604   else if (POINT_IN_RECT (x, y, range->layout->stepper_a))
1605     range->layout->mouse_location = MOUSE_STEPPER_A;
1606   else if (POINT_IN_RECT (x, y, range->layout->stepper_b))
1607     range->layout->mouse_location = MOUSE_STEPPER_B;
1608   else if (POINT_IN_RECT (x, y, range->layout->stepper_c))
1609     range->layout->mouse_location = MOUSE_STEPPER_C;
1610   else if (POINT_IN_RECT (x, y, range->layout->stepper_d))
1611     range->layout->mouse_location = MOUSE_STEPPER_D;
1612   else if (POINT_IN_RECT (x, y, range->layout->slider))
1613     range->layout->mouse_location = MOUSE_SLIDER;
1614   else if (POINT_IN_RECT (x, y, range->layout->trough))
1615     range->layout->mouse_location = MOUSE_TROUGH;
1616   else if (POINT_IN_RECT (x, y, widget->allocation))
1617     range->layout->mouse_location = MOUSE_WIDGET;
1618   else
1619     range->layout->mouse_location = MOUSE_OUTSIDE;
1620
1621   return old != range->layout->mouse_location;
1622 }
1623
1624 /* Clamp rect, border inside widget->allocation, such that we prefer
1625  * to take space from border not rect in all directions, and prefer to
1626  * give space to border over rect in one direction.
1627  */
1628 static void
1629 clamp_dimensions (GtkWidget    *widget,
1630                   GdkRectangle *rect,
1631                   GtkBorder    *border,
1632                   gboolean      border_expands_horizontally)
1633 {
1634   gint extra, shortage;
1635   
1636   g_return_if_fail (rect->x == 0);
1637   g_return_if_fail (rect->y == 0);  
1638   g_return_if_fail (rect->width >= 0);
1639   g_return_if_fail (rect->height >= 0);
1640
1641   /* Width */
1642   
1643   extra = widget->allocation.width - border->left - border->right - rect->width;
1644   if (extra > 0)
1645     {
1646       if (border_expands_horizontally)
1647         {
1648           border->left += extra / 2;
1649           border->right += extra / 2 + extra % 2;
1650         }
1651       else
1652         {
1653           rect->width += extra;
1654         }
1655     }
1656   
1657   /* See if we can fit rect, if not kill the border */
1658   shortage = rect->width - widget->allocation.width;
1659   if (shortage > 0)
1660     {
1661       rect->width = widget->allocation.width;
1662       /* lose the border */
1663       border->left = 0;
1664       border->right = 0;
1665     }
1666   else
1667     {
1668       /* See if we can fit rect with borders */
1669       shortage = rect->width + border->left + border->right -
1670         widget->allocation.width;
1671       if (shortage > 0)
1672         {
1673           /* Shrink borders */
1674           border->left -= shortage / 2;
1675           border->right -= shortage / 2 + shortage % 2;
1676         }
1677     }
1678
1679   /* Height */
1680   
1681   extra = widget->allocation.height - border->top - border->bottom - rect->height;
1682   if (extra > 0)
1683     {
1684       if (border_expands_horizontally)
1685         {
1686           /* don't expand border vertically */
1687           rect->height += extra;
1688         }
1689       else
1690         {
1691           border->top += extra / 2;
1692           border->bottom += extra / 2 + extra % 2;
1693         }
1694     }
1695   
1696   /* See if we can fit rect, if not kill the border */
1697   shortage = rect->height - widget->allocation.height;
1698   if (shortage > 0)
1699     {
1700       rect->height = widget->allocation.height;
1701       /* lose the border */
1702       border->top = 0;
1703       border->bottom = 0;
1704     }
1705   else
1706     {
1707       /* See if we can fit rect with borders */
1708       shortage = rect->height + border->top + border->bottom -
1709         widget->allocation.height;
1710       if (shortage > 0)
1711         {
1712           /* Shrink borders */
1713           border->top -= shortage / 2;
1714           border->bottom -= shortage / 2 + shortage % 2;
1715         }
1716     }
1717 }
1718
1719 static void
1720 gtk_range_calc_request (GtkRange      *range,
1721                         gint           slider_width,
1722                         gint           stepper_size,
1723                         gint           trough_border,
1724                         gint           stepper_spacing,
1725                         GdkRectangle  *range_rect,
1726                         GtkBorder     *border,
1727                         gint          *n_steppers_p,
1728                         gint          *slider_length_p)
1729 {
1730   gint slider_length;
1731   gint n_steppers;
1732   
1733   border->left = 0;
1734   border->right = 0;
1735   border->top = 0;
1736   border->bottom = 0;
1737
1738   if (GTK_RANGE_GET_CLASS (range)->get_range_border)
1739     (* GTK_RANGE_GET_CLASS (range)->get_range_border) (range, border);
1740   
1741   n_steppers = 0;
1742   if (range->has_stepper_a)
1743     n_steppers += 1;
1744   if (range->has_stepper_b)
1745     n_steppers += 1;
1746   if (range->has_stepper_c)
1747     n_steppers += 1;
1748   if (range->has_stepper_d)
1749     n_steppers += 1;
1750
1751   slider_length = range->min_slider_size;
1752
1753   range_rect->x = 0;
1754   range_rect->y = 0;
1755   
1756   /* We never expand to fill available space in the small dimension
1757    * (i.e. vertical scrollbars are always a fixed width)
1758    */
1759   if (range->orientation == GTK_ORIENTATION_VERTICAL)
1760     {
1761       range_rect->width = trough_border * 2 + slider_width;
1762       range_rect->height = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length;
1763     }
1764   else
1765     {
1766       range_rect->width = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length;
1767       range_rect->height = trough_border * 2 + slider_width;
1768     }
1769
1770   if (n_steppers_p)
1771     *n_steppers_p = n_steppers;
1772
1773   if (slider_length_p)
1774     *slider_length_p = slider_length;
1775 }
1776
1777 static void
1778 gtk_range_calc_layout (GtkRange *range)
1779 {
1780   gint slider_width, stepper_size, trough_border, stepper_spacing;
1781   gint slider_length;
1782   GtkBorder border;
1783   gint n_steppers;
1784   GdkRectangle range_rect;
1785   GtkRangeLayout *layout;
1786   GtkWidget *widget;
1787   
1788   if (!range->need_recalc)
1789     return;
1790
1791   /* If we have a too-small allocation, we prefer the steppers over
1792    * the trough/slider, probably the steppers are a more useful
1793    * feature in small spaces.
1794    *
1795    * Also, we prefer to draw the range itself rather than the border
1796    * areas if there's a conflict, since the borders will be decoration
1797    * not controls. Though this depends on subclasses cooperating by
1798    * not drawing on range->range_rect.
1799    */
1800
1801   widget = GTK_WIDGET (range);
1802   layout = range->layout;
1803   
1804   gtk_range_get_props (range,
1805                        &slider_width, &stepper_size, &trough_border, &stepper_spacing);
1806
1807   gtk_range_calc_request (range, 
1808                           slider_width, stepper_size, trough_border, stepper_spacing,
1809                           &range_rect, &border, &n_steppers, &slider_length);
1810   
1811   /* We never expand to fill available space in the small dimension
1812    * (i.e. vertical scrollbars are always a fixed width)
1813    */
1814   if (range->orientation == GTK_ORIENTATION_VERTICAL)
1815     {
1816       clamp_dimensions (widget, &range_rect, &border, TRUE);
1817     }
1818   else
1819     {
1820       clamp_dimensions (widget, &range_rect, &border, FALSE);
1821     }
1822   
1823   range_rect.x = border.left;
1824   range_rect.y = border.top;
1825   
1826   range->range_rect = range_rect;
1827   
1828   if (range->orientation == GTK_ORIENTATION_VERTICAL)
1829     {
1830       gint stepper_width, stepper_height;
1831
1832       /* Steppers are the width of the range, and stepper_size in
1833        * height, or if we don't have enough height, divided equally
1834        * among available space.
1835        */
1836       stepper_width = range_rect.width - trough_border * 2;
1837
1838       if (stepper_width < 1)
1839         stepper_width = range_rect.width; /* screw the trough border */
1840
1841       if (n_steppers == 0)
1842         stepper_height = 0; /* avoid divide by n_steppers */
1843       else
1844         stepper_height = MIN (stepper_size, (range_rect.height / n_steppers));
1845
1846       /* Stepper A */
1847       
1848       layout->stepper_a.x = range_rect.x + trough_border;
1849       layout->stepper_a.y = range_rect.y + trough_border;
1850
1851       if (range->has_stepper_a)
1852         {
1853           layout->stepper_a.width = stepper_width;
1854           layout->stepper_a.height = stepper_height;
1855         }
1856       else
1857         {
1858           layout->stepper_a.width = 0;
1859           layout->stepper_a.height = 0;
1860         }
1861
1862       /* Stepper B */
1863       
1864       layout->stepper_b.x = layout->stepper_a.x;
1865       layout->stepper_b.y = layout->stepper_a.y + layout->stepper_a.height;
1866
1867       if (range->has_stepper_b)
1868         {
1869           layout->stepper_b.width = stepper_width;
1870           layout->stepper_b.height = stepper_height;
1871         }
1872       else
1873         {
1874           layout->stepper_b.width = 0;
1875           layout->stepper_b.height = 0;
1876         }
1877
1878       /* Stepper D */
1879
1880       if (range->has_stepper_d)
1881         {
1882           layout->stepper_d.width = stepper_width;
1883           layout->stepper_d.height = stepper_height;
1884         }
1885       else
1886         {
1887           layout->stepper_d.width = 0;
1888           layout->stepper_d.height = 0;
1889         }
1890       
1891       layout->stepper_d.x = layout->stepper_a.x;
1892       layout->stepper_d.y = range_rect.y + range_rect.height - layout->stepper_d.height - trough_border;
1893
1894       /* Stepper C */
1895
1896       if (range->has_stepper_c)
1897         {
1898           layout->stepper_c.width = stepper_width;
1899           layout->stepper_c.height = stepper_height;
1900         }
1901       else
1902         {
1903           layout->stepper_c.width = 0;
1904           layout->stepper_c.height = 0;
1905         }
1906       
1907       layout->stepper_c.x = layout->stepper_a.x;
1908       layout->stepper_c.y = layout->stepper_d.y - layout->stepper_c.height;
1909
1910       /* Now the trough is the remaining space between steppers B and C,
1911        * if any
1912        */
1913       layout->trough.x = range_rect.x;
1914       layout->trough.y = layout->stepper_b.y + layout->stepper_b.height;
1915       layout->trough.width = range_rect.width;
1916       layout->trough.height = layout->stepper_c.y - (layout->stepper_b.y + layout->stepper_b.height);
1917       
1918       /* Slider fits into the trough, with stepper_spacing on either side,
1919        * and the size/position based on the adjustment or fixed, depending.
1920        */
1921       layout->slider.x = layout->trough.x + trough_border;
1922       layout->slider.width = layout->trough.width - trough_border * 2;
1923
1924       /* Compute slider position/length */
1925       {
1926         gint y, bottom, top, height;
1927         
1928         top = layout->trough.y + stepper_spacing;
1929         bottom = layout->trough.y + layout->trough.height - stepper_spacing;
1930         
1931         /* slider height is the fraction (page_size /
1932          * total_adjustment_range) times the trough height in pixels
1933          */
1934         height = ((bottom - top) * (range->adjustment->page_size /
1935                                     (range->adjustment->upper - range->adjustment->lower)));
1936         
1937         if (height < range->min_slider_size ||
1938             range->slider_size_fixed)
1939           height = range->min_slider_size;
1940         
1941         height = MIN (height, (layout->trough.height - stepper_spacing * 2));
1942         
1943         y = top;
1944         
1945         y += (bottom - top - height) * ((range->adjustment->value - range->adjustment->lower) /
1946                                         (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
1947         
1948         y = CLAMP (y, top, bottom);
1949         
1950         if (should_invert (range))
1951           y = bottom - (y - top + height);
1952         
1953         layout->slider.y = y;
1954         layout->slider.height = height;
1955
1956         /* These are publically exported */
1957         range->slider_start = layout->slider.y;
1958         range->slider_end = layout->slider.y + layout->slider.height;
1959       }
1960     }
1961   else
1962     {
1963       gint stepper_width, stepper_height;
1964
1965       /* Steppers are the height of the range, and stepper_size in
1966        * width, or if we don't have enough width, divided equally
1967        * among available space.
1968        */
1969       stepper_height = range_rect.height - trough_border * 2;
1970
1971       if (stepper_height < 1)
1972         stepper_height = range_rect.height; /* screw the trough border */
1973
1974       if (n_steppers == 0)
1975         stepper_width = 0; /* avoid divide by n_steppers */
1976       else
1977         stepper_width = MIN (stepper_size, (range_rect.width / n_steppers));
1978
1979       /* Stepper A */
1980       
1981       layout->stepper_a.x = range_rect.x + trough_border;
1982       layout->stepper_a.y = range_rect.y + trough_border;
1983
1984       if (range->has_stepper_a)
1985         {
1986           layout->stepper_a.width = stepper_width;
1987           layout->stepper_a.height = stepper_height;
1988         }
1989       else
1990         {
1991           layout->stepper_a.width = 0;
1992           layout->stepper_a.height = 0;
1993         }
1994
1995       /* Stepper B */
1996       
1997       layout->stepper_b.x = layout->stepper_a.x + layout->stepper_a.width;
1998       layout->stepper_b.y = layout->stepper_a.y;
1999
2000       if (range->has_stepper_b)
2001         {
2002           layout->stepper_b.width = stepper_width;
2003           layout->stepper_b.height = stepper_height;
2004         }
2005       else
2006         {
2007           layout->stepper_b.width = 0;
2008           layout->stepper_b.height = 0;
2009         }
2010
2011       /* Stepper D */
2012
2013       if (range->has_stepper_d)
2014         {
2015           layout->stepper_d.width = stepper_width;
2016           layout->stepper_d.height = stepper_height;
2017         }
2018       else
2019         {
2020           layout->stepper_d.width = 0;
2021           layout->stepper_d.height = 0;
2022         }
2023
2024       layout->stepper_d.x = range_rect.x + range_rect.width - layout->stepper_d.width - trough_border;
2025       layout->stepper_d.y = layout->stepper_a.y;
2026
2027
2028       /* Stepper C */
2029
2030       if (range->has_stepper_c)
2031         {
2032           layout->stepper_c.width = stepper_width;
2033           layout->stepper_c.height = stepper_height;
2034         }
2035       else
2036         {
2037           layout->stepper_c.width = 0;
2038           layout->stepper_c.height = 0;
2039         }
2040       
2041       layout->stepper_c.x = layout->stepper_d.x - layout->stepper_c.width;
2042       layout->stepper_c.y = layout->stepper_a.y;
2043
2044       /* Now the trough is the remaining space between steppers B and C,
2045        * if any
2046        */
2047       layout->trough.x = layout->stepper_b.x + layout->stepper_b.width;
2048       layout->trough.y = range_rect.y;
2049
2050       layout->trough.width = layout->stepper_c.x - (layout->stepper_b.x + layout->stepper_b.width);
2051       layout->trough.height = range_rect.height;
2052       
2053       /* Slider fits into the trough, with stepper_spacing on either side,
2054        * and the size/position based on the adjustment or fixed, depending.
2055        */
2056       layout->slider.y = layout->trough.y + trough_border;
2057       layout->slider.height = layout->trough.height - trough_border * 2;
2058
2059       /* Compute slider position/length */
2060       {
2061         gint x, left, right, width;
2062         
2063         left = layout->trough.x + stepper_spacing;
2064         right = layout->trough.x + layout->trough.width - stepper_spacing;
2065         
2066         /* slider width is the fraction (page_size /
2067          * total_adjustment_range) times the trough width in pixels
2068          */
2069         width = ((right - left) * (range->adjustment->page_size /
2070                                    (range->adjustment->upper - range->adjustment->lower)));
2071         
2072         if (width < range->min_slider_size ||
2073             range->slider_size_fixed)
2074           width = range->min_slider_size;
2075         
2076         width = MIN (width, (layout->trough.width - stepper_spacing * 2));
2077         
2078         x = left;
2079         
2080         x += (right - left - width) * ((range->adjustment->value - range->adjustment->lower) /
2081                                        (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
2082         
2083         x = CLAMP (x, left, right);
2084         
2085         if (should_invert (range))
2086           x = right - (x - left + width);
2087         
2088         layout->slider.x = x;
2089         layout->slider.width = width;
2090
2091         /* These are publically exported */
2092         range->slider_start = layout->slider.x;
2093         range->slider_end = layout->slider.x + layout->slider.width;
2094       }
2095     }
2096   
2097   gtk_range_update_mouse_location (range);
2098 }
2099
2100 static GdkRectangle*
2101 get_area (GtkRange     *range,
2102           MouseLocation location)
2103 {
2104   switch (location)
2105     {
2106     case MOUSE_STEPPER_A:
2107       return &range->layout->stepper_a;
2108     case MOUSE_STEPPER_B:
2109       return &range->layout->stepper_b;
2110     case MOUSE_STEPPER_C:
2111       return &range->layout->stepper_c;
2112     case MOUSE_STEPPER_D:
2113       return &range->layout->stepper_d;
2114     case MOUSE_TROUGH:
2115       return &range->layout->trough;
2116     case MOUSE_SLIDER:
2117       return &range->layout->slider;
2118     case MOUSE_WIDGET:
2119     case MOUSE_OUTSIDE:
2120       break;
2121     }
2122
2123   g_warning (G_STRLOC": bug");
2124   return NULL;
2125 }
2126
2127 static void
2128 gtk_range_internal_set_value (GtkRange *range,
2129                               gdouble   value)
2130 {
2131   value = CLAMP (value, range->adjustment->lower,
2132                  (range->adjustment->upper - range->adjustment->page_size));
2133
2134   if (range->round_digits >= 0)
2135     {
2136       char buffer[128];
2137
2138       /* This is just so darn lame. */
2139       g_snprintf (buffer, 128, "%0.*f",
2140                   range->round_digits, value);
2141       sscanf (buffer, "%lf", &value);
2142     }
2143   
2144   if (range->adjustment->value != value)
2145     {
2146       range->need_recalc = TRUE;
2147
2148       gtk_widget_queue_draw (GTK_WIDGET (range));
2149       
2150       switch (range->update_policy)
2151         {
2152         case GTK_UPDATE_CONTINUOUS:
2153           gtk_adjustment_set_value (range->adjustment, value);
2154           break;
2155
2156           /* Delayed means we update after a period of inactivity */
2157         case GTK_UPDATE_DELAYED:
2158           gtk_range_reset_update_timer (range);
2159           /* FALL THRU */
2160
2161           /* Discontinuous means we update on button release */
2162         case GTK_UPDATE_DISCONTINUOUS:
2163           /* don't emit value_changed signal */
2164           range->adjustment->value = value;
2165           range->update_pending = TRUE;
2166           break;
2167         }
2168     }
2169 }
2170
2171 static void
2172 gtk_range_update_value (GtkRange *range)
2173 {
2174   gtk_range_remove_update_timer (range);
2175   
2176   if (range->update_pending)
2177     {
2178       gtk_adjustment_value_changed (range->adjustment);
2179
2180       range->update_pending = FALSE;
2181     }
2182 }
2183
2184 struct _GtkRangeStepTimer
2185 {
2186   guint timeout_id;
2187   GtkScrollType step;
2188 };
2189
2190 static gboolean
2191 second_timeout (gpointer data)
2192 {
2193   GtkRange *range;
2194
2195   range = GTK_RANGE (data);
2196
2197   gtk_range_scroll (range, range->timer->step);
2198   
2199   return TRUE;
2200 }
2201
2202 static gboolean
2203 initial_timeout (gpointer data)
2204 {
2205   GtkRange *range;
2206
2207   range = GTK_RANGE (data);
2208
2209   range->timer->timeout_id = 
2210     g_timeout_add (SCROLL_LATER_DELAY,
2211                    second_timeout,
2212                    range);
2213
2214   /* remove self */
2215   return FALSE;
2216 }
2217
2218 static void
2219 gtk_range_add_step_timer (GtkRange      *range,
2220                           GtkScrollType  step)
2221 {
2222   g_return_if_fail (range->timer == NULL);
2223   g_return_if_fail (step != GTK_SCROLL_NONE);
2224   
2225   range->timer = g_new (GtkRangeStepTimer, 1);
2226
2227   range->timer->timeout_id =
2228     g_timeout_add (SCROLL_INITIAL_DELAY,
2229                    initial_timeout,
2230                    range);
2231   range->timer->step = step;
2232 }
2233
2234 static void
2235 gtk_range_remove_step_timer (GtkRange *range)
2236 {
2237   if (range->timer)
2238     {
2239       if (range->timer->timeout_id != 0)
2240         g_source_remove (range->timer->timeout_id);
2241
2242       g_free (range->timer);
2243
2244       range->timer = NULL;
2245     }
2246 }
2247
2248 static gboolean
2249 update_timeout (gpointer data)
2250 {
2251   GtkRange *range;
2252
2253   range = GTK_RANGE (data);
2254
2255   gtk_range_update_value (range);
2256
2257   range->update_timeout_id = 0;
2258
2259   /* self-remove */
2260   return FALSE;
2261 }
2262
2263 static void
2264 gtk_range_reset_update_timer (GtkRange *range)
2265 {
2266   gtk_range_remove_update_timer (range);
2267
2268   range->update_timeout_id = g_timeout_add (UPDATE_DELAY,
2269                                             update_timeout,
2270                                             range);
2271 }
2272
2273 static void
2274 gtk_range_remove_update_timer (GtkRange *range)
2275 {
2276   if (range->update_timeout_id != 0)
2277     {
2278       g_source_remove (range->update_timeout_id);
2279       range->update_timeout_id = 0;
2280     }
2281 }