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