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