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