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