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