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