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