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