]> Pileus Git - ~andy/gtk/blob - gtk/gtkrange.c
Document GtkRange::adjust-bounds
[~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
30 #include <stdio.h>
31 #include <math.h>
32
33 #include <gdk/gdkkeysyms.h>
34 #include "gtkmain.h"
35 #include "gtkmarshalers.h"
36 #include "gtkorientable.h"
37 #include "gtkrange.h"
38 #include "gtkscale.h"
39 #include "gtkscrollbar.h"
40 #include "gtkprivate.h"
41 #include "gtkintl.h"
42 #include "gtkalias.h"
43
44
45 /**
46  * SECTION:gtkrange
47  * @Short_description: Base class for widgets which visualize an adjustment
48  * @Title: GtkRange
49  *
50  * #GtkRange is the common base class for widgets which visualize an
51  * adjustment, e.g #GtkScale or #GtkScroolbar.
52  *
53  * Apart from signals for monitoring the parameters of the adjustment,
54  * #GtkRange provides properties and methods for influencing the sensitivity
55  * of the "steppers". It also provides properties and methods for setting a
56  * "fill level" on range widgets. See gtk_range_set_fill_level().
57  */
58
59
60 #define SCROLL_DELAY_FACTOR 5    /* Scroll repeat multiplier */
61 #define UPDATE_DELAY        300  /* Delay for queued update */
62
63 enum {
64   PROP_0,
65   PROP_ORIENTATION,
66   PROP_UPDATE_POLICY,
67   PROP_ADJUSTMENT,
68   PROP_INVERTED,
69   PROP_LOWER_STEPPER_SENSITIVITY,
70   PROP_UPPER_STEPPER_SENSITIVITY,
71   PROP_SHOW_FILL_LEVEL,
72   PROP_RESTRICT_TO_FILL_LEVEL,
73   PROP_FILL_LEVEL
74 };
75
76 enum {
77   VALUE_CHANGED,
78   ADJUST_BOUNDS,
79   MOVE_SLIDER,
80   CHANGE_VALUE,
81   LAST_SIGNAL
82 };
83
84 typedef enum {
85   MOUSE_OUTSIDE,
86   MOUSE_STEPPER_A,
87   MOUSE_STEPPER_B,
88   MOUSE_STEPPER_C,
89   MOUSE_STEPPER_D,
90   MOUSE_TROUGH,
91   MOUSE_SLIDER,
92   MOUSE_WIDGET /* inside widget but not in any of the above GUI elements */
93 } MouseLocation;
94
95 #define GTK_RANGE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GTK_TYPE_RANGE, GtkRangeLayout))
96
97 struct _GtkRangeLayout
98 {
99   /* These are in widget->window coordinates */
100   GdkRectangle stepper_a;
101   GdkRectangle stepper_b;
102   GdkRectangle stepper_c;
103   GdkRectangle stepper_d;
104   /* The trough rectangle is the area the thumb can slide in, not the
105    * entire range_rect
106    */
107   GdkRectangle trough;
108   GdkRectangle slider;
109
110   /* Layout-related state */
111   
112   MouseLocation mouse_location;
113   /* last mouse coords we got, or -1 if mouse is outside the range */
114   gint mouse_x;
115   gint mouse_y;
116
117   /* "grabbed" mouse location, OUTSIDE for no grab */
118   MouseLocation grab_location;
119   guint grab_button : 8; /* 0 if none */
120
121   /* Stepper sensitivity */
122   guint lower_sensitive : 1;
123   guint upper_sensitive : 1;
124
125   /* Fill level */
126   guint show_fill_level : 1;
127   guint restrict_to_fill_level : 1;
128
129   GtkSensitivityType lower_sensitivity;
130   GtkSensitivityType upper_sensitivity;
131   guint repaint_id;
132
133   gdouble fill_level;
134
135   GQuark slider_detail_quark;
136   GQuark stepper_detail_quark;
137  
138   gdouble *marks;
139   gint *mark_pos;
140   gint n_marks;
141   gboolean recalc_marks;
142 };
143
144
145 static void gtk_range_set_property   (GObject          *object,
146                                       guint             prop_id,
147                                       const GValue     *value,
148                                       GParamSpec       *pspec);
149 static void gtk_range_get_property   (GObject          *object,
150                                       guint             prop_id,
151                                       GValue           *value,
152                                       GParamSpec       *pspec);
153 static void gtk_range_destroy        (GtkObject        *object);
154 static void gtk_range_size_request   (GtkWidget        *widget,
155                                       GtkRequisition   *requisition);
156 static void gtk_range_size_allocate  (GtkWidget        *widget,
157                                       GtkAllocation    *allocation);
158 static void gtk_range_realize        (GtkWidget        *widget);
159 static void gtk_range_unrealize      (GtkWidget        *widget);
160 static void gtk_range_map            (GtkWidget        *widget);
161 static void gtk_range_unmap          (GtkWidget        *widget);
162 static gboolean gtk_range_expose         (GtkWidget        *widget,
163                                       GdkEventExpose   *event);
164 static gboolean gtk_range_button_press   (GtkWidget        *widget,
165                                       GdkEventButton   *event);
166 static gboolean gtk_range_button_release (GtkWidget        *widget,
167                                       GdkEventButton   *event);
168 static gboolean gtk_range_motion_notify  (GtkWidget        *widget,
169                                       GdkEventMotion   *event);
170 static gboolean gtk_range_enter_notify   (GtkWidget        *widget,
171                                       GdkEventCrossing *event);
172 static gboolean gtk_range_leave_notify   (GtkWidget        *widget,
173                                       GdkEventCrossing *event);
174 static gboolean gtk_range_grab_broken (GtkWidget          *widget,
175                                        GdkEventGrabBroken *event);
176 static void gtk_range_grab_notify    (GtkWidget          *widget,
177                                       gboolean            was_grabbed);
178 static void gtk_range_state_changed  (GtkWidget          *widget,
179                                       GtkStateType        previous_state);
180 static gboolean gtk_range_scroll_event   (GtkWidget        *widget,
181                                       GdkEventScroll   *event);
182 static void gtk_range_style_set      (GtkWidget        *widget,
183                                       GtkStyle         *previous_style);
184 static void update_slider_position   (GtkRange         *range,
185                                       gint              mouse_x,
186                                       gint              mouse_y);
187 static void stop_scrolling           (GtkRange         *range);
188
189 /* Range methods */
190
191 static void gtk_range_move_slider              (GtkRange         *range,
192                                                 GtkScrollType     scroll);
193
194 /* Internals */
195 static gboolean      gtk_range_scroll                   (GtkRange      *range,
196                                                          GtkScrollType  scroll);
197 static gboolean      gtk_range_update_mouse_location    (GtkRange      *range);
198 static void          gtk_range_calc_layout              (GtkRange      *range,
199                                                          gdouble        adjustment_value);
200 static void          gtk_range_calc_marks               (GtkRange      *range);
201 static void          gtk_range_get_props                (GtkRange      *range,
202                                                          gint          *slider_width,
203                                                          gint          *stepper_size,
204                                                          gint          *focus_width,
205                                                          gint          *trough_border,
206                                                          gint          *stepper_spacing,
207                                                          gboolean      *trough_under_steppers,
208                                                          gint          *arrow_displacement_x,
209                                                          gint          *arrow_displacement_y);
210 static void          gtk_range_calc_request             (GtkRange      *range,
211                                                          gint           slider_width,
212                                                          gint           stepper_size,
213                                                          gint           focus_width,
214                                                          gint           trough_border,
215                                                          gint           stepper_spacing,
216                                                          GdkRectangle  *range_rect,
217                                                          GtkBorder     *border,
218                                                          gint          *n_steppers_p,
219                                                          gboolean      *has_steppers_ab,
220                                                          gboolean      *has_steppers_cd,
221                                                          gint          *slider_length_p);
222 static void          gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
223                                                          gpointer       data);
224 static void          gtk_range_adjustment_changed       (GtkAdjustment *adjustment,
225                                                          gpointer       data);
226 static void          gtk_range_add_step_timer           (GtkRange      *range,
227                                                          GtkScrollType  step);
228 static void          gtk_range_remove_step_timer        (GtkRange      *range);
229 static void          gtk_range_reset_update_timer       (GtkRange      *range);
230 static void          gtk_range_remove_update_timer      (GtkRange      *range);
231 static GdkRectangle* get_area                           (GtkRange      *range,
232                                                          MouseLocation  location);
233 static gboolean      gtk_range_real_change_value        (GtkRange      *range,
234                                                          GtkScrollType  scroll,
235                                                          gdouble        value);
236 static void          gtk_range_update_value             (GtkRange      *range);
237 static gboolean      gtk_range_key_press                (GtkWidget     *range,
238                                                          GdkEventKey   *event);
239
240
241 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GtkRange, gtk_range, GTK_TYPE_WIDGET,
242                                   G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
243                                                          NULL))
244
245 static guint signals[LAST_SIGNAL];
246
247
248 static void
249 gtk_range_class_init (GtkRangeClass *class)
250 {
251   GObjectClass   *gobject_class;
252   GtkObjectClass *object_class;
253   GtkWidgetClass *widget_class;
254
255   gobject_class = G_OBJECT_CLASS (class);
256   object_class = (GtkObjectClass*) class;
257   widget_class = (GtkWidgetClass*) class;
258
259   gobject_class->set_property = gtk_range_set_property;
260   gobject_class->get_property = gtk_range_get_property;
261
262   object_class->destroy = gtk_range_destroy;
263
264   widget_class->size_request = gtk_range_size_request;
265   widget_class->size_allocate = gtk_range_size_allocate;
266   widget_class->realize = gtk_range_realize;
267   widget_class->unrealize = gtk_range_unrealize;  
268   widget_class->map = gtk_range_map;
269   widget_class->unmap = gtk_range_unmap;
270   widget_class->expose_event = gtk_range_expose;
271   widget_class->button_press_event = gtk_range_button_press;
272   widget_class->button_release_event = gtk_range_button_release;
273   widget_class->motion_notify_event = gtk_range_motion_notify;
274   widget_class->scroll_event = gtk_range_scroll_event;
275   widget_class->enter_notify_event = gtk_range_enter_notify;
276   widget_class->leave_notify_event = gtk_range_leave_notify;
277   widget_class->grab_broken_event = gtk_range_grab_broken;
278   widget_class->grab_notify = gtk_range_grab_notify;
279   widget_class->state_changed = gtk_range_state_changed;
280   widget_class->style_set = gtk_range_style_set;
281   widget_class->key_press_event = gtk_range_key_press;
282
283   class->move_slider = gtk_range_move_slider;
284   class->change_value = gtk_range_real_change_value;
285
286   class->slider_detail = "slider";
287   class->stepper_detail = "stepper";
288
289   /**
290    * GtkRange::value-changed:
291    * @range: the #GtkRange that received the signal
292    *
293    * Emitted when the range value changes.
294    */
295   signals[VALUE_CHANGED] =
296     g_signal_new (I_("value-changed"),
297                   G_TYPE_FROM_CLASS (gobject_class),
298                   G_SIGNAL_RUN_LAST,
299                   G_STRUCT_OFFSET (GtkRangeClass, value_changed),
300                   NULL, NULL,
301                   _gtk_marshal_VOID__VOID,
302                   G_TYPE_NONE, 0);
303
304   /**
305    * GtkRange::adjust-bounds:
306    * @range: the #GtkRange that received the signal
307    * @value: the value before we clamp
308    *
309    * Emitted before clamping a value, to give the application a
310    * chance to adjust the bounds.
311    */
312   signals[ADJUST_BOUNDS] =
313     g_signal_new (I_("adjust-bounds"),
314                   G_TYPE_FROM_CLASS (gobject_class),
315                   G_SIGNAL_RUN_LAST,
316                   G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds),
317                   NULL, NULL,
318                   _gtk_marshal_VOID__DOUBLE,
319                   G_TYPE_NONE, 1,
320                   G_TYPE_DOUBLE);
321   
322   /**
323    * GtkRange::move-slider:
324    * @range: the #GtkRange that received the signal
325    * @step: how to move the slider
326    *
327    * Virtual function that moves the slider. Used for keybindings.
328    */
329   signals[MOVE_SLIDER] =
330     g_signal_new (I_("move-slider"),
331                   G_TYPE_FROM_CLASS (gobject_class),
332                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
333                   G_STRUCT_OFFSET (GtkRangeClass, move_slider),
334                   NULL, NULL,
335                   _gtk_marshal_VOID__ENUM,
336                   G_TYPE_NONE, 1,
337                   GTK_TYPE_SCROLL_TYPE);
338
339   /**
340    * GtkRange::change-value:
341    * @range: the #GtkRange that received the signal
342    * @scroll: the type of scroll action that was performed
343    * @value: the new value resulting from the scroll action
344    * @returns: %TRUE to prevent other handlers from being invoked for the
345    * signal, %FALSE to propagate the signal further
346    *
347    * The ::change-value signal is emitted when a scroll action is
348    * performed on a range.  It allows an application to determine the
349    * type of scroll event that occurred and the resultant new value.
350    * The application can handle the event itself and return %TRUE to
351    * prevent further processing.  Or, by returning %FALSE, it can pass
352    * the event to other handlers until the default GTK+ handler is
353    * reached.
354    *
355    * The value parameter is unrounded.  An application that overrides
356    * the ::change-value signal is responsible for clamping the value to
357    * the desired number of decimal digits; the default GTK+ handler 
358    * clamps the value based on @range->round_digits.
359    *
360    * It is not possible to use delayed update policies in an overridden
361    * ::change-value handler.
362    *
363    * Since: 2.6
364    */
365   signals[CHANGE_VALUE] =
366     g_signal_new (I_("change-value"),
367                   G_TYPE_FROM_CLASS (gobject_class),
368                   G_SIGNAL_RUN_LAST,
369                   G_STRUCT_OFFSET (GtkRangeClass, change_value),
370                   _gtk_boolean_handled_accumulator, NULL,
371                   _gtk_marshal_BOOLEAN__ENUM_DOUBLE,
372                   G_TYPE_BOOLEAN, 2,
373                   GTK_TYPE_SCROLL_TYPE,
374                   G_TYPE_DOUBLE);
375
376   g_object_class_override_property (gobject_class,
377                                     PROP_ORIENTATION,
378                                     "orientation");
379
380   g_object_class_install_property (gobject_class,
381                                    PROP_UPDATE_POLICY,
382                                    g_param_spec_enum ("update-policy",
383                                                       P_("Update policy"),
384                                                       P_("How the range should be updated on the screen"),
385                                                       GTK_TYPE_UPDATE_TYPE,
386                                                       GTK_UPDATE_CONTINUOUS,
387                                                       GTK_PARAM_READWRITE));
388   
389   g_object_class_install_property (gobject_class,
390                                    PROP_ADJUSTMENT,
391                                    g_param_spec_object ("adjustment",
392                                                         P_("Adjustment"),
393                                                         P_("The GtkAdjustment that contains the current value of this range object"),
394                                                         GTK_TYPE_ADJUSTMENT,
395                                                         GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT));
396
397   g_object_class_install_property (gobject_class,
398                                    PROP_INVERTED,
399                                    g_param_spec_boolean ("inverted",
400                                                         P_("Inverted"),
401                                                         P_("Invert direction slider moves to increase range value"),
402                                                          FALSE,
403                                                          GTK_PARAM_READWRITE));
404
405   g_object_class_install_property (gobject_class,
406                                    PROP_LOWER_STEPPER_SENSITIVITY,
407                                    g_param_spec_enum ("lower-stepper-sensitivity",
408                                                       P_("Lower stepper sensitivity"),
409                                                       P_("The sensitivity policy for the stepper that points to the adjustment's lower side"),
410                                                       GTK_TYPE_SENSITIVITY_TYPE,
411                                                       GTK_SENSITIVITY_AUTO,
412                                                       GTK_PARAM_READWRITE));
413
414   g_object_class_install_property (gobject_class,
415                                    PROP_UPPER_STEPPER_SENSITIVITY,
416                                    g_param_spec_enum ("upper-stepper-sensitivity",
417                                                       P_("Upper stepper sensitivity"),
418                                                       P_("The sensitivity policy for the stepper that points to the adjustment's upper side"),
419                                                       GTK_TYPE_SENSITIVITY_TYPE,
420                                                       GTK_SENSITIVITY_AUTO,
421                                                       GTK_PARAM_READWRITE));
422
423   /**
424    * GtkRange:show-fill-level:
425    *
426    * The show-fill-level property controls whether fill level indicator
427    * graphics are displayed on the trough. See
428    * gtk_range_set_show_fill_level().
429    *
430    * Since: 2.12
431    **/
432   g_object_class_install_property (gobject_class,
433                                    PROP_SHOW_FILL_LEVEL,
434                                    g_param_spec_boolean ("show-fill-level",
435                                                          P_("Show Fill Level"),
436                                                          P_("Whether to display a fill level indicator graphics on trough."),
437                                                          FALSE,
438                                                          GTK_PARAM_READWRITE));
439
440   /**
441    * GtkRange:restrict-to-fill-level:
442    *
443    * The restrict-to-fill-level property controls whether slider
444    * movement is restricted to an upper boundary set by the
445    * fill level. See gtk_range_set_restrict_to_fill_level().
446    *
447    * Since: 2.12
448    **/
449   g_object_class_install_property (gobject_class,
450                                    PROP_RESTRICT_TO_FILL_LEVEL,
451                                    g_param_spec_boolean ("restrict-to-fill-level",
452                                                          P_("Restrict to Fill Level"),
453                                                          P_("Whether to restrict the upper boundary to the fill level."),
454                                                          TRUE,
455                                                          GTK_PARAM_READWRITE));
456
457   /**
458    * GtkRange:fill-level:
459    *
460    * The fill level (e.g. prebuffering of a network stream).
461    * See gtk_range_set_fill_level().
462    *
463    * Since: 2.12
464    **/
465   g_object_class_install_property (gobject_class,
466                                    PROP_FILL_LEVEL,
467                                    g_param_spec_double ("fill-level",
468                                                         P_("Fill Level"),
469                                                         P_("The fill level."),
470                                                         -G_MAXDOUBLE,
471                                                         G_MAXDOUBLE,
472                                                         G_MAXDOUBLE,
473                                                         GTK_PARAM_READWRITE));
474
475   gtk_widget_class_install_style_property (widget_class,
476                                            g_param_spec_int ("slider-width",
477                                                              P_("Slider Width"),
478                                                              P_("Width of scrollbar or scale thumb"),
479                                                              0,
480                                                              G_MAXINT,
481                                                              14,
482                                                              GTK_PARAM_READABLE));
483   gtk_widget_class_install_style_property (widget_class,
484                                            g_param_spec_int ("trough-border",
485                                                              P_("Trough Border"),
486                                                              P_("Spacing between thumb/steppers and outer trough bevel"),
487                                                              0,
488                                                              G_MAXINT,
489                                                              1,
490                                                              GTK_PARAM_READABLE));
491   gtk_widget_class_install_style_property (widget_class,
492                                            g_param_spec_int ("stepper-size",
493                                                              P_("Stepper Size"),
494                                                              P_("Length of step buttons at ends"),
495                                                              0,
496                                                              G_MAXINT,
497                                                              14,
498                                                              GTK_PARAM_READABLE));
499   /**
500    * GtkRange:stepper-spacing:
501    *
502    * The spacing between the stepper buttons and thumb. Note that
503    * setting this value to anything > 0 will automatically set the
504    * trough-under-steppers style property to %TRUE as well. Also,
505    * stepper-spacing won't have any effect if there are no steppers.
506    */
507   gtk_widget_class_install_style_property (widget_class,
508                                            g_param_spec_int ("stepper-spacing",
509                                                              P_("Stepper Spacing"),
510                                                              P_("Spacing between step buttons and thumb"),
511                                                              0,
512                                                              G_MAXINT,
513                                                              0,
514                                                              GTK_PARAM_READABLE));
515   gtk_widget_class_install_style_property (widget_class,
516                                            g_param_spec_int ("arrow-displacement-x",
517                                                              P_("Arrow X Displacement"),
518                                                              P_("How far in the x direction to move the arrow when the button is depressed"),
519                                                              G_MININT,
520                                                              G_MAXINT,
521                                                              0,
522                                                              GTK_PARAM_READABLE));
523   gtk_widget_class_install_style_property (widget_class,
524                                            g_param_spec_int ("arrow-displacement-y",
525                                                              P_("Arrow Y Displacement"),
526                                                              P_("How far in the y direction to move the arrow when the button is depressed"),
527                                                              G_MININT,
528                                                              G_MAXINT,
529                                                              0,
530                                                              GTK_PARAM_READABLE));
531
532   gtk_widget_class_install_style_property (widget_class,
533                                            g_param_spec_boolean ("activate-slider",
534                                                                  P_("Draw slider ACTIVE during drag"),
535                                                                  P_("With this option set to TRUE, sliders will be drawn ACTIVE and with shadow IN while they are dragged"),
536                                                                  FALSE,
537                                                                  GTK_PARAM_READABLE));
538
539   /**
540    * GtkRange:trough-side-details:
541    *
542    * When %TRUE, the parts of the trough on the two sides of the 
543    * slider are drawn with different details.
544    *
545    * Since: 2.10
546    */
547   gtk_widget_class_install_style_property (widget_class,
548                                            g_param_spec_boolean ("trough-side-details",
549                                                                  P_("Trough Side Details"),
550                                                                  P_("When TRUE, the parts of the trough on the two sides of the slider are drawn with different details"),
551                                                                  FALSE,
552                                                                  GTK_PARAM_READABLE));
553
554   /**
555    * GtkRange:trough-under-steppers:
556    *
557    * Whether to draw the trough across the full length of the range or
558    * to exclude the steppers and their spacing. Note that setting the
559    * #GtkRange:stepper-spacing style property to any value > 0 will
560    * automatically enable trough-under-steppers too.
561    *
562    * Since: 2.10
563    */
564   gtk_widget_class_install_style_property (widget_class,
565                                            g_param_spec_boolean ("trough-under-steppers",
566                                                                  P_("Trough Under Steppers"),
567                                                                  P_("Whether to draw trough for full length of range or exclude the steppers and spacing"),
568                                                                  TRUE,
569                                                                  GTK_PARAM_READABLE));
570
571   /**
572    * GtkRange:arrow-scaling:
573    *
574    * The arrow size proportion relative to the scroll button size.
575    *
576    * Since: 2.14
577    */
578   gtk_widget_class_install_style_property (widget_class,
579                                            g_param_spec_float ("arrow-scaling",
580                                                                P_("Arrow scaling"),
581                                                                P_("Arrow scaling with regard to scroll button size"),
582                                                                0.0, 1.0, 0.5,
583                                                                GTK_PARAM_READABLE));
584
585   g_type_class_add_private (class, sizeof (GtkRangeLayout));
586 }
587
588 static void
589 gtk_range_set_property (GObject      *object,
590                         guint         prop_id,
591                         const GValue *value,
592                         GParamSpec   *pspec)
593 {
594   GtkRange *range = GTK_RANGE (object);
595
596   switch (prop_id)
597     {
598     case PROP_ORIENTATION:
599       range->orientation = g_value_get_enum (value);
600
601       range->layout->slider_detail_quark = 0;
602       range->layout->stepper_detail_quark = 0;
603
604       gtk_widget_queue_resize (GTK_WIDGET (range));
605       break;
606     case PROP_UPDATE_POLICY:
607       gtk_range_set_update_policy (range, g_value_get_enum (value));
608       break;
609     case PROP_ADJUSTMENT:
610       gtk_range_set_adjustment (range, g_value_get_object (value));
611       break;
612     case PROP_INVERTED:
613       gtk_range_set_inverted (range, g_value_get_boolean (value));
614       break;
615     case PROP_LOWER_STEPPER_SENSITIVITY:
616       gtk_range_set_lower_stepper_sensitivity (range, g_value_get_enum (value));
617       break;
618     case PROP_UPPER_STEPPER_SENSITIVITY:
619       gtk_range_set_upper_stepper_sensitivity (range, g_value_get_enum (value));
620       break;
621     case PROP_SHOW_FILL_LEVEL:
622       gtk_range_set_show_fill_level (range, g_value_get_boolean (value));
623       break;
624     case PROP_RESTRICT_TO_FILL_LEVEL:
625       gtk_range_set_restrict_to_fill_level (range, g_value_get_boolean (value));
626       break;
627     case PROP_FILL_LEVEL:
628       gtk_range_set_fill_level (range, g_value_get_double (value));
629       break;
630     default:
631       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
632       break;
633     }
634 }
635
636 static void
637 gtk_range_get_property (GObject      *object,
638                         guint         prop_id,
639                         GValue       *value,
640                         GParamSpec   *pspec)
641 {
642   GtkRange *range = GTK_RANGE (object);
643
644   switch (prop_id)
645     {
646     case PROP_ORIENTATION:
647       g_value_set_enum (value, range->orientation);
648       break;
649     case PROP_UPDATE_POLICY:
650       g_value_set_enum (value, range->update_policy);
651       break;
652     case PROP_ADJUSTMENT:
653       g_value_set_object (value, range->adjustment);
654       break;
655     case PROP_INVERTED:
656       g_value_set_boolean (value, range->inverted);
657       break;
658     case PROP_LOWER_STEPPER_SENSITIVITY:
659       g_value_set_enum (value, gtk_range_get_lower_stepper_sensitivity (range));
660       break;
661     case PROP_UPPER_STEPPER_SENSITIVITY:
662       g_value_set_enum (value, gtk_range_get_upper_stepper_sensitivity (range));
663       break;
664     case PROP_SHOW_FILL_LEVEL:
665       g_value_set_boolean (value, gtk_range_get_show_fill_level (range));
666       break;
667     case PROP_RESTRICT_TO_FILL_LEVEL:
668       g_value_set_boolean (value, gtk_range_get_restrict_to_fill_level (range));
669       break;
670     case PROP_FILL_LEVEL:
671       g_value_set_double (value, gtk_range_get_fill_level (range));
672       break;
673     default:
674       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
675       break;
676     }
677 }
678
679 static void
680 gtk_range_init (GtkRange *range)
681 {
682   gtk_widget_set_has_window (GTK_WIDGET (range), FALSE);
683
684   range->orientation = GTK_ORIENTATION_HORIZONTAL;
685   range->adjustment = NULL;
686   range->update_policy = GTK_UPDATE_CONTINUOUS;
687   range->inverted = FALSE;
688   range->flippable = FALSE;
689   range->min_slider_size = 1;
690   range->has_stepper_a = FALSE;
691   range->has_stepper_b = FALSE;
692   range->has_stepper_c = FALSE;
693   range->has_stepper_d = FALSE;
694   range->need_recalc = TRUE;
695   range->round_digits = -1;
696   range->layout = GTK_RANGE_GET_PRIVATE (range);
697   range->layout->mouse_location = MOUSE_OUTSIDE;
698   range->layout->mouse_x = -1;
699   range->layout->mouse_y = -1;
700   range->layout->grab_location = MOUSE_OUTSIDE;
701   range->layout->grab_button = 0;
702   range->layout->lower_sensitivity = GTK_SENSITIVITY_AUTO;
703   range->layout->upper_sensitivity = GTK_SENSITIVITY_AUTO;
704   range->layout->lower_sensitive = TRUE;
705   range->layout->upper_sensitive = TRUE;
706   range->layout->show_fill_level = FALSE;
707   range->layout->restrict_to_fill_level = TRUE;
708   range->layout->fill_level = G_MAXDOUBLE;
709   range->timer = NULL;  
710 }
711
712 /**
713  * gtk_range_get_adjustment:
714  * @range: a #GtkRange
715  * 
716  * Get the #GtkAdjustment which is the "model" object for #GtkRange.
717  * See gtk_range_set_adjustment() for details.
718  * The return value does not have a reference added, so should not
719  * be unreferenced.
720  * 
721  * Return value: a #GtkAdjustment
722  **/
723 GtkAdjustment*
724 gtk_range_get_adjustment (GtkRange *range)
725 {
726   g_return_val_if_fail (GTK_IS_RANGE (range), NULL);
727
728   if (!range->adjustment)
729     gtk_range_set_adjustment (range, NULL);
730
731   return range->adjustment;
732 }
733
734 /**
735  * gtk_range_set_update_policy:
736  * @range: a #GtkRange
737  * @policy: update policy
738  *
739  * Sets the update policy for the range. #GTK_UPDATE_CONTINUOUS means that
740  * anytime the range slider is moved, the range value will change and the
741  * value_changed signal will be emitted. #GTK_UPDATE_DELAYED means that
742  * the value will be updated after a brief timeout where no slider motion
743  * occurs, so updates are spaced by a short time rather than
744  * continuous. #GTK_UPDATE_DISCONTINUOUS means that the value will only
745  * be updated when the user releases the button and ends the slider
746  * drag operation.
747  **/
748 void
749 gtk_range_set_update_policy (GtkRange      *range,
750                              GtkUpdateType  policy)
751 {
752   g_return_if_fail (GTK_IS_RANGE (range));
753
754   if (range->update_policy != policy)
755     {
756       range->update_policy = policy;
757       g_object_notify (G_OBJECT (range), "update-policy");
758     }
759 }
760
761 /**
762  * gtk_range_get_update_policy:
763  * @range: a #GtkRange
764  *
765  * Gets the update policy of @range. See gtk_range_set_update_policy().
766  *
767  * Return value: the current update policy
768  **/
769 GtkUpdateType
770 gtk_range_get_update_policy (GtkRange *range)
771 {
772   g_return_val_if_fail (GTK_IS_RANGE (range), GTK_UPDATE_CONTINUOUS);
773
774   return range->update_policy;
775 }
776
777 /**
778  * gtk_range_set_adjustment:
779  * @range: a #GtkRange
780  * @adjustment: a #GtkAdjustment
781  *
782  * Sets the adjustment to be used as the "model" object for this range
783  * widget. The adjustment indicates the current range value, the
784  * minimum and maximum range values, the step/page increments used
785  * for keybindings and scrolling, and the page size. The page size
786  * is normally 0 for #GtkScale and nonzero for #GtkScrollbar, and
787  * indicates the size of the visible area of the widget being scrolled.
788  * The page size affects the size of the scrollbar slider.
789  **/
790 void
791 gtk_range_set_adjustment (GtkRange      *range,
792                           GtkAdjustment *adjustment)
793 {
794   g_return_if_fail (GTK_IS_RANGE (range));
795   
796   if (!adjustment)
797     adjustment = (GtkAdjustment*) gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
798   else
799     g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
800
801   if (range->adjustment != adjustment)
802     {
803       if (range->adjustment)
804         {
805           g_signal_handlers_disconnect_by_func (range->adjustment,
806                                                 gtk_range_adjustment_changed,
807                                                 range);
808           g_signal_handlers_disconnect_by_func (range->adjustment,
809                                                 gtk_range_adjustment_value_changed,
810                                                 range);
811           g_object_unref (range->adjustment);
812         }
813
814       range->adjustment = adjustment;
815       g_object_ref_sink (adjustment);
816       
817       g_signal_connect (adjustment, "changed",
818                         G_CALLBACK (gtk_range_adjustment_changed),
819                         range);
820       g_signal_connect (adjustment, "value-changed",
821                         G_CALLBACK (gtk_range_adjustment_value_changed),
822                         range);
823       
824       gtk_range_adjustment_changed (adjustment, range);
825       g_object_notify (G_OBJECT (range), "adjustment");
826     }
827 }
828
829 /**
830  * gtk_range_set_inverted:
831  * @range: a #GtkRange
832  * @setting: %TRUE to invert the range
833  *
834  * Ranges normally move from lower to higher values as the
835  * slider moves from top to bottom or left to right. Inverted
836  * ranges have higher values at the top or on the right rather than
837  * on the bottom or left.
838  **/
839 void
840 gtk_range_set_inverted (GtkRange *range,
841                         gboolean  setting)
842 {
843   g_return_if_fail (GTK_IS_RANGE (range));
844   
845   setting = setting != FALSE;
846
847   if (setting != range->inverted)
848     {
849       range->inverted = setting;
850       g_object_notify (G_OBJECT (range), "inverted");
851       gtk_widget_queue_resize (GTK_WIDGET (range));
852     }
853 }
854
855 /**
856  * gtk_range_get_inverted:
857  * @range: a #GtkRange
858  * 
859  * Gets the value set by gtk_range_set_inverted().
860  * 
861  * Return value: %TRUE if the range is inverted
862  **/
863 gboolean
864 gtk_range_get_inverted (GtkRange *range)
865 {
866   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
867
868   return range->inverted;
869 }
870
871 /**
872  * gtk_range_set_flippable:
873  * @range: a #GtkRange
874  * @flippable: %TRUE to make the range flippable
875  *
876  * If a range is flippable, it will switch its direction if it is
877  * horizontal and its direction is %GTK_TEXT_DIR_RTL.
878  *
879  * See gtk_widget_get_direction().
880  *
881  * Since: 2.18
882  **/
883 void
884 gtk_range_set_flippable (GtkRange *range,
885                          gboolean  flippable)
886 {
887   g_return_if_fail (GTK_IS_RANGE (range));
888
889   flippable = flippable ? TRUE : FALSE;
890
891   if (flippable != range->flippable)
892     {
893       range->flippable = flippable;
894
895       gtk_widget_queue_draw (GTK_WIDGET (range));
896     }
897 }
898
899 /**
900  * gtk_range_get_flippable:
901  * @range: a #GtkRange
902  *
903  * Gets the value set by gtk_range_set_flippable().
904  *
905  * Return value: %TRUE if the range is flippable
906  *
907  * Since: 2.18
908  **/
909 gboolean
910 gtk_range_get_flippable (GtkRange *range)
911 {
912   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
913
914   return range->flippable;
915 }
916
917 /**
918  * gtk_range_set_slider_size_fixed:
919  * @range: a #GtkRange
920  * @size_fixed: %TRUE to make the slider size constant
921  *
922  * Sets whether the range's slider has a fixed size, or a size that
923  * depends on it's adjustment's page size.
924  *
925  * This function is useful mainly for #GtkRange subclasses.
926  *
927  * Since: 2.20
928  **/
929 void
930 gtk_range_set_slider_size_fixed (GtkRange *range,
931                                  gboolean  size_fixed)
932 {
933   g_return_if_fail (GTK_IS_RANGE (range));
934
935   if (size_fixed != range->slider_size_fixed)
936     {
937       range->slider_size_fixed = size_fixed ? TRUE : FALSE;
938
939       range->need_recalc = TRUE;
940       gtk_range_calc_layout (range, range->adjustment->value);
941       gtk_widget_queue_draw (GTK_WIDGET (range));
942     }
943 }
944
945 /**
946  * gtk_range_get_slider_size_fixed:
947  * @range: a #GtkRange
948  *
949  * This function is useful mainly for #GtkRange subclasses.
950  *
951  * See gtk_range_set_slider_size_fixed().
952  *
953  * Return value: whether the range's slider has a fixed size.
954  *
955  * Since: 2.20
956  **/
957 gboolean
958 gtk_range_get_slider_size_fixed (GtkRange *range)
959 {
960   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
961
962   return range->slider_size_fixed;
963 }
964
965 /**
966  * gtk_range_set_min_slider_size:
967  * @range: a #GtkRange
968  * @min_size: The slider's minimum size
969  *
970  * Sets the minimum size of the range's slider.
971  *
972  * This function is useful mainly for #GtkRange subclasses.
973  *
974  * Since: 2.20
975  **/
976 void
977 gtk_range_set_min_slider_size (GtkRange *range,
978                                gboolean  min_size)
979 {
980   g_return_if_fail (GTK_IS_RANGE (range));
981   g_return_if_fail (min_size > 0);
982
983   if (min_size != range->min_slider_size)
984     {
985       range->min_slider_size = min_size;
986
987       range->need_recalc = TRUE;
988       gtk_range_calc_layout (range, range->adjustment->value);
989       gtk_widget_queue_draw (GTK_WIDGET (range));
990     }
991 }
992
993 /**
994  * gtk_range_get_min_slider_size:
995  * @range: a #GtkRange
996  *
997  * This function is useful mainly for #GtkRange subclasses.
998  *
999  * See gtk_range_set_min_slider_size().
1000  *
1001  * Return value: The minimum size of the range's slider.
1002  *
1003  * Since: 2.20
1004  **/
1005 gint
1006 gtk_range_get_min_slider_size (GtkRange *range)
1007 {
1008   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1009
1010   return range->min_slider_size;
1011 }
1012
1013 /**
1014  * gtk_range_get_range_rect:
1015  * @range: a #GtkRange
1016  * @range_rect: return location for the range rectangle
1017  *
1018  * This function returns the area that contains the range's trough
1019  * and its steppers, in widget->window coordinates.
1020  *
1021  * This function is useful mainly for #GtkRange subclasses.
1022  *
1023  * Since: 2.20
1024  **/
1025 void
1026 gtk_range_get_range_rect (GtkRange     *range,
1027                           GdkRectangle *range_rect)
1028 {
1029   g_return_if_fail (GTK_IS_RANGE (range));
1030   g_return_if_fail (range_rect != NULL);
1031
1032   gtk_range_calc_layout (range, range->adjustment->value);
1033
1034   *range_rect = range->range_rect;
1035 }
1036
1037 /**
1038  * gtk_range_get_slider_range:
1039  * @range: a #GtkRange
1040  * @slider_start: (allow-none): return location for the slider's start, or %NULL
1041  * @slider_end: (allow-none): return location for the slider's end, or %NULL
1042  *
1043  * This function returns sliders range along the long dimension,
1044  * in widget->window coordinates.
1045  *
1046  * This function is useful mainly for #GtkRange subclasses.
1047  *
1048  * Since: 2.20
1049  **/
1050 void
1051 gtk_range_get_slider_range (GtkRange *range,
1052                             gint     *slider_start,
1053                             gint     *slider_end)
1054 {
1055   g_return_if_fail (GTK_IS_RANGE (range));
1056
1057   gtk_range_calc_layout (range, range->adjustment->value);
1058
1059   if (slider_start)
1060     *slider_start = range->slider_start;
1061
1062   if (slider_end)
1063     *slider_end = range->slider_end;
1064 }
1065
1066 /**
1067  * gtk_range_set_lower_stepper_sensitivity:
1068  * @range:       a #GtkRange
1069  * @sensitivity: the lower stepper's sensitivity policy.
1070  *
1071  * Sets the sensitivity policy for the stepper that points to the
1072  * 'lower' end of the GtkRange's adjustment.
1073  *
1074  * Since: 2.10
1075  **/
1076 void
1077 gtk_range_set_lower_stepper_sensitivity (GtkRange           *range,
1078                                          GtkSensitivityType  sensitivity)
1079 {
1080   g_return_if_fail (GTK_IS_RANGE (range));
1081
1082   if (range->layout->lower_sensitivity != sensitivity)
1083     {
1084       range->layout->lower_sensitivity = sensitivity;
1085
1086       range->need_recalc = TRUE;
1087       gtk_range_calc_layout (range, range->adjustment->value);
1088       gtk_widget_queue_draw (GTK_WIDGET (range));
1089
1090       g_object_notify (G_OBJECT (range), "lower-stepper-sensitivity");
1091     }
1092 }
1093
1094 /**
1095  * gtk_range_get_lower_stepper_sensitivity:
1096  * @range: a #GtkRange
1097  *
1098  * Gets the sensitivity policy for the stepper that points to the
1099  * 'lower' end of the GtkRange's adjustment.
1100  *
1101  * Return value: The lower stepper's sensitivity policy.
1102  *
1103  * Since: 2.10
1104  **/
1105 GtkSensitivityType
1106 gtk_range_get_lower_stepper_sensitivity (GtkRange *range)
1107 {
1108   g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
1109
1110   return range->layout->lower_sensitivity;
1111 }
1112
1113 /**
1114  * gtk_range_set_upper_stepper_sensitivity:
1115  * @range:       a #GtkRange
1116  * @sensitivity: the upper stepper's sensitivity policy.
1117  *
1118  * Sets the sensitivity policy for the stepper that points to the
1119  * 'upper' end of the GtkRange's adjustment.
1120  *
1121  * Since: 2.10
1122  **/
1123 void
1124 gtk_range_set_upper_stepper_sensitivity (GtkRange           *range,
1125                                          GtkSensitivityType  sensitivity)
1126 {
1127   g_return_if_fail (GTK_IS_RANGE (range));
1128
1129   if (range->layout->upper_sensitivity != sensitivity)
1130     {
1131       range->layout->upper_sensitivity = sensitivity;
1132
1133       range->need_recalc = TRUE;
1134       gtk_range_calc_layout (range, range->adjustment->value);
1135       gtk_widget_queue_draw (GTK_WIDGET (range));
1136
1137       g_object_notify (G_OBJECT (range), "upper-stepper-sensitivity");
1138     }
1139 }
1140
1141 /**
1142  * gtk_range_get_upper_stepper_sensitivity:
1143  * @range: a #GtkRange
1144  *
1145  * Gets the sensitivity policy for the stepper that points to the
1146  * 'upper' end of the GtkRange's adjustment.
1147  *
1148  * Return value: The upper stepper's sensitivity policy.
1149  *
1150  * Since: 2.10
1151  **/
1152 GtkSensitivityType
1153 gtk_range_get_upper_stepper_sensitivity (GtkRange *range)
1154 {
1155   g_return_val_if_fail (GTK_IS_RANGE (range), GTK_SENSITIVITY_AUTO);
1156
1157   return range->layout->upper_sensitivity;
1158 }
1159
1160 /**
1161  * gtk_range_set_increments:
1162  * @range: a #GtkRange
1163  * @step: step size
1164  * @page: page size
1165  *
1166  * Sets the step and page sizes for the range.
1167  * The step size is used when the user clicks the #GtkScrollbar
1168  * arrows or moves #GtkScale via arrow keys. The page size
1169  * is used for example when moving via Page Up or Page Down keys.
1170  **/
1171 void
1172 gtk_range_set_increments (GtkRange *range,
1173                           gdouble   step,
1174                           gdouble   page)
1175 {
1176   g_return_if_fail (GTK_IS_RANGE (range));
1177
1178   range->adjustment->step_increment = step;
1179   range->adjustment->page_increment = page;
1180
1181   gtk_adjustment_changed (range->adjustment);
1182 }
1183
1184 /**
1185  * gtk_range_set_range:
1186  * @range: a #GtkRange
1187  * @min: minimum range value
1188  * @max: maximum range value
1189  * 
1190  * Sets the allowable values in the #GtkRange, and clamps the range
1191  * value to be between @min and @max. (If the range has a non-zero
1192  * page size, it is clamped between @min and @max - page-size.)
1193  **/
1194 void
1195 gtk_range_set_range (GtkRange *range,
1196                      gdouble   min,
1197                      gdouble   max)
1198 {
1199   gdouble value;
1200   
1201   g_return_if_fail (GTK_IS_RANGE (range));
1202   g_return_if_fail (min < max);
1203   
1204   range->adjustment->lower = min;
1205   range->adjustment->upper = max;
1206
1207   value = range->adjustment->value;
1208
1209   if (range->layout->restrict_to_fill_level)
1210     value = MIN (value, MAX (range->adjustment->lower,
1211                              range->layout->fill_level));
1212
1213   gtk_adjustment_set_value (range->adjustment, value);
1214   gtk_adjustment_changed (range->adjustment);
1215 }
1216
1217 /**
1218  * gtk_range_set_value:
1219  * @range: a #GtkRange
1220  * @value: new value of the range
1221  *
1222  * Sets the current value of the range; if the value is outside the
1223  * minimum or maximum range values, it will be clamped to fit inside
1224  * them. The range emits the #GtkRange::value-changed signal if the 
1225  * value changes.
1226  **/
1227 void
1228 gtk_range_set_value (GtkRange *range,
1229                      gdouble   value)
1230 {
1231   g_return_if_fail (GTK_IS_RANGE (range));
1232
1233   if (range->layout->restrict_to_fill_level)
1234     value = MIN (value, MAX (range->adjustment->lower,
1235                              range->layout->fill_level));
1236
1237   gtk_adjustment_set_value (range->adjustment, value);
1238 }
1239
1240 /**
1241  * gtk_range_get_value:
1242  * @range: a #GtkRange
1243  * 
1244  * Gets the current value of the range.
1245  * 
1246  * Return value: current value of the range.
1247  **/
1248 gdouble
1249 gtk_range_get_value (GtkRange *range)
1250 {
1251   g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1252
1253   return range->adjustment->value;
1254 }
1255
1256 /**
1257  * gtk_range_set_show_fill_level:
1258  * @range:           A #GtkRange
1259  * @show_fill_level: Whether a fill level indicator graphics is shown.
1260  *
1261  * Sets whether a graphical fill level is show on the trough. See
1262  * gtk_range_set_fill_level() for a general description of the fill
1263  * level concept.
1264  *
1265  * Since: 2.12
1266  **/
1267 void
1268 gtk_range_set_show_fill_level (GtkRange *range,
1269                                gboolean  show_fill_level)
1270 {
1271   g_return_if_fail (GTK_IS_RANGE (range));
1272
1273   show_fill_level = show_fill_level ? TRUE : FALSE;
1274
1275   if (show_fill_level != range->layout->show_fill_level)
1276     {
1277       range->layout->show_fill_level = show_fill_level;
1278       g_object_notify (G_OBJECT (range), "show-fill-level");
1279       gtk_widget_queue_draw (GTK_WIDGET (range));
1280     }
1281 }
1282
1283 /**
1284  * gtk_range_get_show_fill_level:
1285  * @range: A #GtkRange
1286  *
1287  * Gets whether the range displays the fill level graphically.
1288  *
1289  * Return value: %TRUE if @range shows the fill level.
1290  *
1291  * Since: 2.12
1292  **/
1293 gboolean
1294 gtk_range_get_show_fill_level (GtkRange *range)
1295 {
1296   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1297
1298   return range->layout->show_fill_level;
1299 }
1300
1301 /**
1302  * gtk_range_set_restrict_to_fill_level:
1303  * @range:                  A #GtkRange
1304  * @restrict_to_fill_level: Whether the fill level restricts slider movement.
1305  *
1306  * Sets whether the slider is restricted to the fill level. See
1307  * gtk_range_set_fill_level() for a general description of the fill
1308  * level concept.
1309  *
1310  * Since: 2.12
1311  **/
1312 void
1313 gtk_range_set_restrict_to_fill_level (GtkRange *range,
1314                                       gboolean  restrict_to_fill_level)
1315 {
1316   g_return_if_fail (GTK_IS_RANGE (range));
1317
1318   restrict_to_fill_level = restrict_to_fill_level ? TRUE : FALSE;
1319
1320   if (restrict_to_fill_level != range->layout->restrict_to_fill_level)
1321     {
1322       range->layout->restrict_to_fill_level = restrict_to_fill_level;
1323       g_object_notify (G_OBJECT (range), "restrict-to-fill-level");
1324
1325       gtk_range_set_value (range, gtk_range_get_value (range));
1326     }
1327 }
1328
1329 /**
1330  * gtk_range_get_restrict_to_fill_level:
1331  * @range: A #GtkRange
1332  *
1333  * Gets whether the range is restricted to the fill level.
1334  *
1335  * Return value: %TRUE if @range is restricted to the fill level.
1336  *
1337  * Since: 2.12
1338  **/
1339 gboolean
1340 gtk_range_get_restrict_to_fill_level (GtkRange *range)
1341 {
1342   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1343
1344   return range->layout->restrict_to_fill_level;
1345 }
1346
1347 /**
1348  * gtk_range_set_fill_level:
1349  * @range: a #GtkRange
1350  * @fill_level: the new position of the fill level indicator
1351  *
1352  * Set the new position of the fill level indicator.
1353  *
1354  * The "fill level" is probably best described by its most prominent
1355  * use case, which is an indicator for the amount of pre-buffering in
1356  * a streaming media player. In that use case, the value of the range
1357  * would indicate the current play position, and the fill level would
1358  * be the position up to which the file/stream has been downloaded.
1359  *
1360  * This amount of prebuffering can be displayed on the range's trough
1361  * and is themeable separately from the trough. To enable fill level
1362  * display, use gtk_range_set_show_fill_level(). The range defaults
1363  * to not showing the fill level.
1364  *
1365  * Additionally, it's possible to restrict the range's slider position
1366  * to values which are smaller than the fill level. This is controller
1367  * by gtk_range_set_restrict_to_fill_level() and is by default
1368  * enabled.
1369  *
1370  * Since: 2.12
1371  **/
1372 void
1373 gtk_range_set_fill_level (GtkRange *range,
1374                           gdouble   fill_level)
1375 {
1376   g_return_if_fail (GTK_IS_RANGE (range));
1377
1378   if (fill_level != range->layout->fill_level)
1379     {
1380       range->layout->fill_level = fill_level;
1381       g_object_notify (G_OBJECT (range), "fill-level");
1382
1383       if (range->layout->show_fill_level)
1384         gtk_widget_queue_draw (GTK_WIDGET (range));
1385
1386       if (range->layout->restrict_to_fill_level)
1387         gtk_range_set_value (range, gtk_range_get_value (range));
1388     }
1389 }
1390
1391 /**
1392  * gtk_range_get_fill_level:
1393  * @range : A #GtkRange
1394  *
1395  * Gets the current position of the fill level indicator.
1396  *
1397  * Return value: The current fill level
1398  *
1399  * Since: 2.12
1400  **/
1401 gdouble
1402 gtk_range_get_fill_level (GtkRange *range)
1403 {
1404   g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1405
1406   return range->layout->fill_level;
1407 }
1408
1409 static gboolean
1410 should_invert (GtkRange *range)
1411 {  
1412   if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
1413     return
1414       (range->inverted && !range->flippable) ||
1415       (range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
1416       (!range->inverted && range->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
1417   else
1418     return range->inverted;
1419 }
1420
1421 static void
1422 gtk_range_destroy (GtkObject *object)
1423 {
1424   GtkRange *range = GTK_RANGE (object);
1425
1426   gtk_range_remove_step_timer (range);
1427   gtk_range_remove_update_timer (range);
1428
1429   if (range->layout->repaint_id)
1430     g_source_remove (range->layout->repaint_id);
1431   range->layout->repaint_id = 0;
1432
1433   if (range->adjustment)
1434     {
1435       g_signal_handlers_disconnect_by_func (range->adjustment,
1436                                             gtk_range_adjustment_changed,
1437                                             range);
1438       g_signal_handlers_disconnect_by_func (range->adjustment,
1439                                             gtk_range_adjustment_value_changed,
1440                                             range);
1441       g_object_unref (range->adjustment);
1442       range->adjustment = NULL;
1443     }
1444
1445   if (range->layout->n_marks)
1446     {
1447       g_free (range->layout->marks);
1448       range->layout->marks = NULL;
1449       g_free (range->layout->mark_pos);
1450       range->layout->mark_pos = NULL;
1451       range->layout->n_marks = 0;
1452     }
1453
1454   GTK_OBJECT_CLASS (gtk_range_parent_class)->destroy (object);
1455 }
1456
1457 static void
1458 gtk_range_size_request (GtkWidget      *widget,
1459                         GtkRequisition *requisition)
1460 {
1461   GtkRange *range;
1462   gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing;
1463   GdkRectangle range_rect;
1464   GtkBorder border;
1465   
1466   range = GTK_RANGE (widget);
1467   
1468   gtk_range_get_props (range,
1469                        &slider_width, &stepper_size,
1470                        &focus_width, &trough_border,
1471                        &stepper_spacing, NULL,
1472                        NULL, NULL);
1473
1474   gtk_range_calc_request (range, 
1475                           slider_width, stepper_size,
1476                           focus_width, trough_border, stepper_spacing,
1477                           &range_rect, &border, NULL, NULL, NULL, NULL);
1478
1479   requisition->width = range_rect.width + border.left + border.right;
1480   requisition->height = range_rect.height + border.top + border.bottom;
1481 }
1482
1483 static void
1484 gtk_range_size_allocate (GtkWidget     *widget,
1485                          GtkAllocation *allocation)
1486 {
1487   GtkRange *range;
1488
1489   range = GTK_RANGE (widget);
1490
1491   widget->allocation = *allocation;
1492   
1493   range->layout->recalc_marks = TRUE;
1494
1495   range->need_recalc = TRUE;
1496   gtk_range_calc_layout (range, range->adjustment->value);
1497
1498   if (gtk_widget_get_realized (widget))
1499     gdk_window_move_resize (range->event_window,
1500                             widget->allocation.x,
1501                             widget->allocation.y,
1502                             widget->allocation.width,
1503                             widget->allocation.height);
1504 }
1505
1506 static void
1507 gtk_range_realize (GtkWidget *widget)
1508 {
1509   GtkRange *range;
1510   GdkWindowAttr attributes;
1511   gint attributes_mask;  
1512
1513   range = GTK_RANGE (widget);
1514
1515   gtk_range_calc_layout (range, range->adjustment->value);
1516   
1517   gtk_widget_set_realized (widget, TRUE);
1518
1519   widget->window = gtk_widget_get_parent_window (widget);
1520   g_object_ref (widget->window);
1521   
1522   attributes.window_type = GDK_WINDOW_CHILD;
1523   attributes.x = widget->allocation.x;
1524   attributes.y = widget->allocation.y;
1525   attributes.width = widget->allocation.width;
1526   attributes.height = widget->allocation.height;
1527   attributes.wclass = GDK_INPUT_ONLY;
1528   attributes.event_mask = gtk_widget_get_events (widget);
1529   attributes.event_mask |= (GDK_BUTTON_PRESS_MASK |
1530                             GDK_BUTTON_RELEASE_MASK |
1531                             GDK_ENTER_NOTIFY_MASK |
1532                             GDK_LEAVE_NOTIFY_MASK |
1533                             GDK_POINTER_MOTION_MASK |
1534                             GDK_POINTER_MOTION_HINT_MASK);
1535
1536   attributes_mask = GDK_WA_X | GDK_WA_Y;
1537
1538   range->event_window = gdk_window_new (gtk_widget_get_parent_window (widget),
1539                                         &attributes, attributes_mask);
1540   gdk_window_set_user_data (range->event_window, range);
1541
1542   widget->style = gtk_style_attach (widget->style, widget->window);
1543 }
1544
1545 static void
1546 gtk_range_unrealize (GtkWidget *widget)
1547 {
1548   GtkRange *range = GTK_RANGE (widget);
1549
1550   gtk_range_remove_step_timer (range);
1551   gtk_range_remove_update_timer (range);
1552   
1553   gdk_window_set_user_data (range->event_window, NULL);
1554   gdk_window_destroy (range->event_window);
1555   range->event_window = NULL;
1556
1557   GTK_WIDGET_CLASS (gtk_range_parent_class)->unrealize (widget);
1558 }
1559
1560 static void
1561 gtk_range_map (GtkWidget *widget)
1562 {
1563   GtkRange *range = GTK_RANGE (widget);
1564   
1565   gdk_window_show (range->event_window);
1566
1567   GTK_WIDGET_CLASS (gtk_range_parent_class)->map (widget);
1568 }
1569
1570 static void
1571 gtk_range_unmap (GtkWidget *widget)
1572 {
1573   GtkRange *range = GTK_RANGE (widget);
1574     
1575   stop_scrolling (range);
1576
1577   gdk_window_hide (range->event_window);
1578
1579   GTK_WIDGET_CLASS (gtk_range_parent_class)->unmap (widget);
1580 }
1581
1582 static const gchar *
1583 gtk_range_get_slider_detail (GtkRange *range)
1584 {
1585   const gchar *slider_detail;
1586
1587   if (range->layout->slider_detail_quark)
1588     return g_quark_to_string (range->layout->slider_detail_quark);
1589
1590   slider_detail = GTK_RANGE_GET_CLASS (range)->slider_detail;
1591
1592   if (slider_detail && slider_detail[0] == 'X')
1593     {
1594       gchar *detail = g_strdup (slider_detail);
1595
1596       detail[0] = range->orientation == GTK_ORIENTATION_HORIZONTAL ? 'h' : 'v';
1597
1598       range->layout->slider_detail_quark = g_quark_from_string (detail);
1599
1600       g_free (detail);
1601
1602       return g_quark_to_string (range->layout->slider_detail_quark);
1603     }
1604
1605   return slider_detail;
1606 }
1607
1608 static const gchar *
1609 gtk_range_get_stepper_detail (GtkRange *range)
1610 {
1611   const gchar *stepper_detail;
1612
1613   if (range->layout->stepper_detail_quark)
1614     return g_quark_to_string (range->layout->stepper_detail_quark);
1615
1616   stepper_detail = GTK_RANGE_GET_CLASS (range)->stepper_detail;
1617
1618   if (stepper_detail && stepper_detail[0] == 'X')
1619     {
1620       gchar *detail = g_strdup (stepper_detail);
1621
1622       detail[0] = range->orientation == GTK_ORIENTATION_HORIZONTAL ? 'h' : 'v';
1623
1624       range->layout->stepper_detail_quark = g_quark_from_string (detail);
1625
1626       g_free (detail);
1627
1628       return g_quark_to_string (range->layout->stepper_detail_quark);
1629     }
1630
1631   return stepper_detail;
1632 }
1633
1634 static void
1635 draw_stepper (GtkRange     *range,
1636               GdkRectangle *rect,
1637               GtkArrowType  arrow_type,
1638               gboolean      clicked,
1639               gboolean      prelighted,
1640               GdkRectangle *area)
1641 {
1642   GtkStateType state_type;
1643   GtkShadowType shadow_type;
1644   GdkRectangle intersection;
1645   GtkWidget *widget = GTK_WIDGET (range);
1646   gfloat arrow_scaling;
1647
1648   gint arrow_x;
1649   gint arrow_y;
1650   gint arrow_width;
1651   gint arrow_height;
1652
1653   gboolean arrow_sensitive = TRUE;
1654
1655   /* More to get the right clip region than for efficiency */
1656   if (!gdk_rectangle_intersect (area, rect, &intersection))
1657     return;
1658
1659   intersection.x += widget->allocation.x;
1660   intersection.y += widget->allocation.y;
1661
1662   if ((!range->inverted && (arrow_type == GTK_ARROW_DOWN ||
1663                             arrow_type == GTK_ARROW_RIGHT)) ||
1664       (range->inverted  && (arrow_type == GTK_ARROW_UP ||
1665                             arrow_type == GTK_ARROW_LEFT)))
1666     {
1667       arrow_sensitive = range->layout->upper_sensitive;
1668     }
1669   else
1670     {
1671       arrow_sensitive = range->layout->lower_sensitive;
1672     }
1673
1674   if (!gtk_widget_is_sensitive (GTK_WIDGET (range)) || !arrow_sensitive)
1675     state_type = GTK_STATE_INSENSITIVE;
1676   else if (clicked)
1677     state_type = GTK_STATE_ACTIVE;
1678   else if (prelighted)
1679     state_type = GTK_STATE_PRELIGHT;
1680   else 
1681     state_type = GTK_STATE_NORMAL;
1682
1683   if (clicked && arrow_sensitive)
1684     shadow_type = GTK_SHADOW_IN;
1685   else
1686     shadow_type = GTK_SHADOW_OUT;
1687
1688   gtk_paint_box (widget->style,
1689                  widget->window,
1690                  state_type, shadow_type,
1691                  &intersection, widget,
1692                  gtk_range_get_stepper_detail (range),
1693                  widget->allocation.x + rect->x,
1694                  widget->allocation.y + rect->y,
1695                  rect->width,
1696                  rect->height);
1697
1698   gtk_widget_style_get (widget, "arrow-scaling", &arrow_scaling, NULL);
1699
1700   arrow_width = rect->width * arrow_scaling;
1701   arrow_height = rect->height * arrow_scaling;
1702   arrow_x = widget->allocation.x + rect->x + (rect->width - arrow_width) / 2;
1703   arrow_y = widget->allocation.y + rect->y + (rect->height - arrow_height) / 2;
1704
1705   if (clicked && arrow_sensitive)
1706     {
1707       gint arrow_displacement_x;
1708       gint arrow_displacement_y;
1709
1710       gtk_range_get_props (GTK_RANGE (widget),
1711                            NULL, NULL, NULL, NULL, NULL, NULL,
1712                            &arrow_displacement_x, &arrow_displacement_y);
1713       
1714       arrow_x += arrow_displacement_x;
1715       arrow_y += arrow_displacement_y;
1716     }
1717   
1718   gtk_paint_arrow (widget->style,
1719                    widget->window,
1720                    state_type, shadow_type, 
1721                    &intersection, widget,
1722                    gtk_range_get_stepper_detail (range),
1723                    arrow_type,
1724                    TRUE,
1725                    arrow_x, arrow_y, arrow_width, arrow_height);
1726 }
1727
1728 static gboolean
1729 gtk_range_expose (GtkWidget      *widget,
1730                   GdkEventExpose *event)
1731 {
1732   GtkRange *range = GTK_RANGE (widget);
1733   gboolean sensitive;
1734   GtkStateType state;
1735   GtkShadowType shadow_type;
1736   GdkRectangle expose_area;     /* Relative to widget->allocation */
1737   GdkRectangle area;
1738   gint focus_line_width = 0;
1739   gint focus_padding = 0;
1740   gboolean touchscreen;
1741
1742   g_object_get (gtk_widget_get_settings (widget),
1743                 "gtk-touchscreen-mode", &touchscreen,
1744                 NULL);
1745   if (gtk_widget_get_can_focus (GTK_WIDGET (range)))
1746     gtk_widget_style_get (GTK_WIDGET (range),
1747                           "focus-line-width", &focus_line_width,
1748                           "focus-padding", &focus_padding,
1749                           NULL);
1750
1751   /* we're now exposing, so there's no need to force early repaints */
1752   if (range->layout->repaint_id)
1753     g_source_remove (range->layout->repaint_id);
1754   range->layout->repaint_id = 0;
1755
1756   expose_area = event->area;
1757   expose_area.x -= widget->allocation.x;
1758   expose_area.y -= widget->allocation.y;
1759   
1760   gtk_range_calc_marks (range);
1761   gtk_range_calc_layout (range, range->adjustment->value);
1762
1763   sensitive = gtk_widget_is_sensitive (widget);
1764
1765   /* Just to be confusing, we draw the trough for the whole
1766    * range rectangle, not the trough rectangle (the trough
1767    * rectangle is just for hit detection)
1768    */
1769   /* The gdk_rectangle_intersect is more to get the right
1770    * clip region (limited to range_rect) than for efficiency
1771    */
1772   if (gdk_rectangle_intersect (&expose_area, &range->range_rect,
1773                                &area))
1774     {
1775       gint     x      = (widget->allocation.x + range->range_rect.x +
1776                          focus_line_width + focus_padding);
1777       gint     y      = (widget->allocation.y + range->range_rect.y +
1778                          focus_line_width + focus_padding);
1779       gint     width  = (range->range_rect.width -
1780                          2 * (focus_line_width + focus_padding));
1781       gint     height = (range->range_rect.height -
1782                          2 * (focus_line_width + focus_padding));
1783       gboolean trough_side_details;
1784       gboolean trough_under_steppers;
1785       gint     stepper_size;
1786       gint     stepper_spacing;
1787
1788       area.x += widget->allocation.x;
1789       area.y += widget->allocation.y;
1790
1791       gtk_widget_style_get (GTK_WIDGET (range),
1792                             "trough-side-details",   &trough_side_details,
1793                             "trough-under-steppers", &trough_under_steppers,
1794                             "stepper-size",          &stepper_size,
1795                             "stepper-spacing",       &stepper_spacing,
1796                             NULL);
1797
1798       if (stepper_spacing > 0)
1799         trough_under_steppers = FALSE;
1800
1801       if (! trough_under_steppers)
1802         {
1803           gint offset  = 0;
1804           gint shorter = 0;
1805
1806           if (range->has_stepper_a)
1807             offset += stepper_size;
1808
1809           if (range->has_stepper_b)
1810             offset += stepper_size;
1811
1812           shorter += offset;
1813
1814           if (range->has_stepper_c)
1815             shorter += stepper_size;
1816
1817           if (range->has_stepper_d)
1818             shorter += stepper_size;
1819
1820           if (range->has_stepper_a || range->has_stepper_b)
1821             {
1822               offset  += stepper_spacing;
1823               shorter += stepper_spacing;
1824             }
1825
1826           if (range->has_stepper_c || range->has_stepper_d)
1827             {
1828               shorter += stepper_spacing;
1829             }
1830
1831           if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
1832             {
1833               x     += offset;
1834               width -= shorter;
1835             }
1836           else
1837             {
1838               y      += offset;
1839               height -= shorter;
1840             }
1841         }
1842
1843       if (! trough_side_details)
1844         {
1845           gtk_paint_box (widget->style,
1846                          widget->window,
1847                          sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
1848                          GTK_SHADOW_IN,
1849                          &area, GTK_WIDGET(range), "trough",
1850                          x, y,
1851                          width, height);
1852         }
1853       else
1854         {
1855           gint trough_change_pos_x = width;
1856           gint trough_change_pos_y = height;
1857
1858           if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
1859             trough_change_pos_x = (range->layout->slider.x +
1860                                    range->layout->slider.width / 2 -
1861                                    (x - widget->allocation.x));
1862           else
1863             trough_change_pos_y = (range->layout->slider.y +
1864                                    range->layout->slider.height / 2 -
1865                                    (y - widget->allocation.y));
1866
1867           gtk_paint_box (widget->style,
1868                          widget->window,
1869                          sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
1870                          GTK_SHADOW_IN,
1871                          &area, GTK_WIDGET (range),
1872                          should_invert (range) ? "trough-upper" : "trough-lower",
1873                          x, y,
1874                          trough_change_pos_x, trough_change_pos_y);
1875
1876           if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
1877             trough_change_pos_y = 0;
1878           else
1879             trough_change_pos_x = 0;
1880
1881           gtk_paint_box (widget->style,
1882                          widget->window,
1883                          sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
1884                          GTK_SHADOW_IN,
1885                          &area, GTK_WIDGET (range),
1886                          should_invert (range) ? "trough-lower" : "trough-upper",
1887                          x + trough_change_pos_x, y + trough_change_pos_y,
1888                          width - trough_change_pos_x,
1889                          height - trough_change_pos_y);
1890         }
1891
1892       if (range->layout->show_fill_level &&
1893           range->adjustment->upper - range->adjustment->page_size -
1894           range->adjustment->lower != 0)
1895         {
1896           gdouble  fill_level  = range->layout->fill_level;
1897           gint     fill_x      = x;
1898           gint     fill_y      = y;
1899           gint     fill_width  = width;
1900           gint     fill_height = height;
1901           gchar   *fill_detail;
1902
1903           fill_level = CLAMP (fill_level, range->adjustment->lower,
1904                               range->adjustment->upper -
1905                               range->adjustment->page_size);
1906
1907           if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
1908             {
1909               fill_x     = widget->allocation.x + range->layout->trough.x;
1910               fill_width = (range->layout->slider.width +
1911                             (fill_level - range->adjustment->lower) /
1912                             (range->adjustment->upper -
1913                              range->adjustment->lower -
1914                              range->adjustment->page_size) *
1915                             (range->layout->trough.width -
1916                              range->layout->slider.width));
1917
1918               if (should_invert (range))
1919                 fill_x += range->layout->trough.width - fill_width;
1920             }
1921           else
1922             {
1923               fill_y      = widget->allocation.y + range->layout->trough.y;
1924               fill_height = (range->layout->slider.height +
1925                              (fill_level - range->adjustment->lower) /
1926                              (range->adjustment->upper -
1927                               range->adjustment->lower -
1928                               range->adjustment->page_size) *
1929                              (range->layout->trough.height -
1930                               range->layout->slider.height));
1931
1932               if (should_invert (range))
1933                 fill_y += range->layout->trough.height - fill_height;
1934             }
1935
1936           if (fill_level < range->adjustment->upper - range->adjustment->page_size)
1937             fill_detail = "trough-fill-level-full";
1938           else
1939             fill_detail = "trough-fill-level";
1940
1941           gtk_paint_box (widget->style,
1942                          widget->window,
1943                          sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
1944                          GTK_SHADOW_OUT,
1945                          &area, GTK_WIDGET (range), fill_detail,
1946                          fill_x, fill_y,
1947                          fill_width, fill_height);
1948         }
1949
1950       if (sensitive && gtk_widget_has_focus (widget))
1951         gtk_paint_focus (widget->style, widget->window, gtk_widget_get_state (widget),
1952                          &area, widget, "trough",
1953                          widget->allocation.x + range->range_rect.x,
1954                          widget->allocation.y + range->range_rect.y,
1955                          range->range_rect.width,
1956                          range->range_rect.height);
1957     }
1958
1959   shadow_type = GTK_SHADOW_OUT;
1960
1961   if (!sensitive)
1962     state = GTK_STATE_INSENSITIVE;
1963   else if (!touchscreen && range->layout->mouse_location == MOUSE_SLIDER)
1964     state = GTK_STATE_PRELIGHT;
1965   else
1966     state = GTK_STATE_NORMAL;
1967
1968   if (range->layout->grab_location == MOUSE_SLIDER)
1969     {
1970       gboolean activate_slider;
1971
1972       gtk_widget_style_get (widget, "activate-slider", &activate_slider, NULL);
1973
1974       if (activate_slider)
1975         {
1976           state = GTK_STATE_ACTIVE;
1977           shadow_type = GTK_SHADOW_IN;
1978         }
1979     }
1980
1981   if (gdk_rectangle_intersect (&expose_area,
1982                                &range->layout->slider,
1983                                &area))
1984     {
1985       area.x += widget->allocation.x;
1986       area.y += widget->allocation.y;
1987       
1988       gtk_paint_slider (widget->style,
1989                         widget->window,
1990                         state,
1991                         shadow_type,
1992                         &area,
1993                         widget,
1994                         gtk_range_get_slider_detail (range),
1995                         widget->allocation.x + range->layout->slider.x,
1996                         widget->allocation.y + range->layout->slider.y,
1997                         range->layout->slider.width,
1998                         range->layout->slider.height,
1999                         range->orientation);
2000     }
2001   
2002   if (range->has_stepper_a)
2003     draw_stepper (range, &range->layout->stepper_a,
2004                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
2005                   range->layout->grab_location == MOUSE_STEPPER_A,
2006                   !touchscreen && range->layout->mouse_location == MOUSE_STEPPER_A,
2007                   &expose_area);
2008
2009   if (range->has_stepper_b)
2010     draw_stepper (range, &range->layout->stepper_b,
2011                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
2012                   range->layout->grab_location == MOUSE_STEPPER_B,
2013                   !touchscreen && range->layout->mouse_location == MOUSE_STEPPER_B,
2014                   &expose_area);
2015
2016   if (range->has_stepper_c)
2017     draw_stepper (range, &range->layout->stepper_c,
2018                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
2019                   range->layout->grab_location == MOUSE_STEPPER_C,
2020                   !touchscreen && range->layout->mouse_location == MOUSE_STEPPER_C,
2021                   &expose_area);
2022
2023   if (range->has_stepper_d)
2024     draw_stepper (range, &range->layout->stepper_d,
2025                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
2026                   range->layout->grab_location == MOUSE_STEPPER_D,
2027                   !touchscreen && range->layout->mouse_location == MOUSE_STEPPER_D,
2028                   &expose_area);
2029   
2030   return FALSE;
2031 }
2032
2033 static void
2034 range_grab_add (GtkRange      *range,
2035                 MouseLocation  location,
2036                 gint           button)
2037 {
2038   /* we don't actually gtk_grab, since a button is down */
2039
2040   gtk_grab_add (GTK_WIDGET (range));
2041   
2042   range->layout->grab_location = location;
2043   range->layout->grab_button = button;
2044   
2045   if (gtk_range_update_mouse_location (range))
2046     gtk_widget_queue_draw (GTK_WIDGET (range));
2047 }
2048
2049 static void
2050 range_grab_remove (GtkRange *range)
2051 {
2052   MouseLocation location;
2053
2054   gtk_grab_remove (GTK_WIDGET (range));
2055  
2056   location = range->layout->grab_location; 
2057   range->layout->grab_location = MOUSE_OUTSIDE;
2058   range->layout->grab_button = 0;
2059
2060   if (gtk_range_update_mouse_location (range) ||
2061       location != MOUSE_OUTSIDE)
2062     gtk_widget_queue_draw (GTK_WIDGET (range));
2063 }
2064
2065 static GtkScrollType
2066 range_get_scroll_for_grab (GtkRange      *range)
2067
2068   gboolean invert;
2069
2070   invert = should_invert (range);
2071   switch (range->layout->grab_location)
2072     {
2073       /* Backward stepper */
2074     case MOUSE_STEPPER_A:
2075     case MOUSE_STEPPER_C:
2076       switch (range->layout->grab_button)
2077         {
2078         case 1:
2079           return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
2080           break;
2081         case 2:
2082           return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD;
2083           break;
2084         case 3:
2085           return invert ? GTK_SCROLL_END : GTK_SCROLL_START;
2086           break;
2087         }
2088       break;
2089
2090       /* Forward stepper */
2091     case MOUSE_STEPPER_B:
2092     case MOUSE_STEPPER_D:
2093       switch (range->layout->grab_button)
2094         {
2095         case 1:
2096           return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
2097           break;
2098         case 2:
2099           return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD;
2100           break;
2101         case 3:
2102           return invert ? GTK_SCROLL_START : GTK_SCROLL_END;
2103           break;
2104        }
2105       break;
2106
2107       /* In the trough */
2108     case MOUSE_TROUGH:
2109       {
2110         if (range->trough_click_forward)
2111           return GTK_SCROLL_PAGE_FORWARD;
2112         else
2113           return GTK_SCROLL_PAGE_BACKWARD;
2114       }
2115       break;
2116
2117     case MOUSE_OUTSIDE:
2118     case MOUSE_SLIDER:
2119     case MOUSE_WIDGET:
2120       break;
2121     }
2122
2123   return GTK_SCROLL_NONE;
2124 }
2125
2126 static gdouble
2127 coord_to_value (GtkRange *range,
2128                 gint      coord)
2129 {
2130   gdouble frac;
2131   gdouble value;
2132   gint    trough_length;
2133   gint    trough_start;
2134   gint    slider_length;
2135   gint    trough_border;
2136   gint    trough_under_steppers;
2137
2138   if (range->orientation == GTK_ORIENTATION_VERTICAL)
2139     {
2140       trough_length = range->layout->trough.height;
2141       trough_start  = range->layout->trough.y;
2142       slider_length = range->layout->slider.height;
2143     }
2144   else
2145     {
2146       trough_length = range->layout->trough.width;
2147       trough_start  = range->layout->trough.x;
2148       slider_length = range->layout->slider.width;
2149     }
2150
2151   gtk_range_get_props (range, NULL, NULL, NULL, &trough_border, NULL,
2152                        &trough_under_steppers, NULL, NULL);
2153
2154   if (! trough_under_steppers)
2155     {
2156       trough_start += trough_border;
2157       trough_length -= 2 * trough_border;
2158     }
2159
2160   if (trough_length == slider_length)
2161     frac = 1.0;
2162   else
2163     frac = (MAX (0, coord - trough_start) /
2164             (gdouble) (trough_length - slider_length));
2165
2166   if (should_invert (range))
2167     frac = 1.0 - frac;
2168
2169   value = range->adjustment->lower + frac * (range->adjustment->upper -
2170                                              range->adjustment->lower -
2171                                              range->adjustment->page_size);
2172
2173   return value;
2174 }
2175
2176 static gboolean
2177 gtk_range_key_press (GtkWidget   *widget,
2178                      GdkEventKey *event)
2179 {
2180   GtkRange *range = GTK_RANGE (widget);
2181
2182   if (event->keyval == GDK_Escape &&
2183       range->layout->grab_location != MOUSE_OUTSIDE)
2184     {
2185       stop_scrolling (range);
2186
2187       update_slider_position (range,
2188                               range->slide_initial_coordinate,
2189                               range->slide_initial_coordinate);
2190
2191       return TRUE;
2192     }
2193
2194   return GTK_WIDGET_CLASS (gtk_range_parent_class)->key_press_event (widget, event);
2195 }
2196
2197 static gint
2198 gtk_range_button_press (GtkWidget      *widget,
2199                         GdkEventButton *event)
2200 {
2201   GtkRange *range = GTK_RANGE (widget);
2202   
2203   if (!gtk_widget_has_focus (widget))
2204     gtk_widget_grab_focus (widget);
2205
2206   /* ignore presses when we're already doing something else. */
2207   if (range->layout->grab_location != MOUSE_OUTSIDE)
2208     return FALSE;
2209
2210   range->layout->mouse_x = event->x;
2211   range->layout->mouse_y = event->y;
2212   if (gtk_range_update_mouse_location (range))
2213     gtk_widget_queue_draw (widget);
2214     
2215   if (range->layout->mouse_location == MOUSE_TROUGH  &&
2216       event->button == 1)
2217     {
2218       /* button 1 steps by page increment, as with button 2 on a stepper
2219        */
2220       GtkScrollType scroll;
2221       gdouble click_value;
2222       
2223       click_value = coord_to_value (range,
2224                                     range->orientation == GTK_ORIENTATION_VERTICAL ?
2225                                     event->y : event->x);
2226       
2227       range->trough_click_forward = click_value > range->adjustment->value;
2228       range_grab_add (range, MOUSE_TROUGH, event->button);
2229       
2230       scroll = range_get_scroll_for_grab (range);
2231       
2232       gtk_range_add_step_timer (range, scroll);
2233
2234       return TRUE;
2235     }
2236   else if ((range->layout->mouse_location == MOUSE_STEPPER_A ||
2237             range->layout->mouse_location == MOUSE_STEPPER_B ||
2238             range->layout->mouse_location == MOUSE_STEPPER_C ||
2239             range->layout->mouse_location == MOUSE_STEPPER_D) &&
2240            (event->button == 1 || event->button == 2 || event->button == 3))
2241     {
2242       GdkRectangle *stepper_area;
2243       GtkScrollType scroll;
2244       
2245       range_grab_add (range, range->layout->mouse_location, event->button);
2246
2247       stepper_area = get_area (range, range->layout->mouse_location);
2248       gtk_widget_queue_draw_area (widget,
2249                                   widget->allocation.x + stepper_area->x,
2250                                   widget->allocation.y + stepper_area->y,
2251                                   stepper_area->width,
2252                                   stepper_area->height);
2253
2254       scroll = range_get_scroll_for_grab (range);
2255       if (scroll != GTK_SCROLL_NONE)
2256         gtk_range_add_step_timer (range, scroll);
2257       
2258       return TRUE;
2259     }
2260   else if ((range->layout->mouse_location == MOUSE_TROUGH &&
2261             event->button == 2) ||
2262            range->layout->mouse_location == MOUSE_SLIDER)
2263     {
2264       gboolean need_value_update = FALSE;
2265       gboolean activate_slider;
2266
2267       /* Any button can be used to drag the slider, but you can start
2268        * dragging the slider with a trough click using button 2;
2269        * On button 2 press, we warp the slider to mouse position,
2270        * then begin the slider drag.
2271        */
2272       if (event->button == 2)
2273         {
2274           gdouble slider_low_value, slider_high_value, new_value;
2275           
2276           slider_high_value =
2277             coord_to_value (range,
2278                             range->orientation == GTK_ORIENTATION_VERTICAL ?
2279                             event->y : event->x);
2280           slider_low_value =
2281             coord_to_value (range,
2282                             range->orientation == GTK_ORIENTATION_VERTICAL ?
2283                             event->y - range->layout->slider.height :
2284                             event->x - range->layout->slider.width);
2285           
2286           /* compute new value for warped slider */
2287           new_value = slider_low_value + (slider_high_value - slider_low_value) / 2;
2288
2289           /* recalc slider, so we can set slide_initial_slider_position
2290            * properly
2291            */
2292           range->need_recalc = TRUE;
2293           gtk_range_calc_layout (range, new_value);
2294
2295           /* defer adjustment updates to update_slider_position() in order
2296            * to keep pixel quantisation
2297            */
2298           need_value_update = TRUE;
2299         }
2300       
2301       if (range->orientation == GTK_ORIENTATION_VERTICAL)
2302         {
2303           range->slide_initial_slider_position = range->layout->slider.y;
2304           range->slide_initial_coordinate = event->y;
2305         }
2306       else
2307         {
2308           range->slide_initial_slider_position = range->layout->slider.x;
2309           range->slide_initial_coordinate = event->x;
2310         }
2311
2312       range_grab_add (range, MOUSE_SLIDER, event->button);
2313
2314       gtk_widget_style_get (widget, "activate-slider", &activate_slider, NULL);
2315
2316       /* force a redraw, if the active slider is drawn differently to the
2317        * prelight one
2318        */
2319       if (activate_slider)
2320         gtk_widget_queue_draw (widget);
2321
2322       if (need_value_update)
2323         update_slider_position (range, event->x, event->y);
2324
2325       return TRUE;
2326     }
2327   
2328   return FALSE;
2329 }
2330
2331 /* During a slide, move the slider as required given new mouse position */
2332 static void
2333 update_slider_position (GtkRange *range,
2334                         gint      mouse_x,
2335                         gint      mouse_y)
2336 {
2337   gint delta;
2338   gint c;
2339   gdouble new_value;
2340   gboolean handled;
2341   gdouble next_value;
2342   gdouble mark_value;
2343   gdouble mark_delta;
2344   gint i;
2345
2346   if (range->orientation == GTK_ORIENTATION_VERTICAL)
2347     delta = mouse_y - range->slide_initial_coordinate;
2348   else
2349     delta = mouse_x - range->slide_initial_coordinate;
2350
2351   c = range->slide_initial_slider_position + delta;
2352
2353   new_value = coord_to_value (range, c);
2354   next_value = coord_to_value (range, c + 1);
2355   mark_delta = fabs (next_value - new_value); 
2356
2357   for (i = 0; i < range->layout->n_marks; i++)
2358     {
2359       mark_value = range->layout->marks[i];
2360
2361       if (fabs (range->adjustment->value - mark_value) < 3 * mark_delta)
2362         {
2363           if (fabs (new_value - mark_value) < (range->slider_end - range->slider_start) * 0.5 * mark_delta)
2364             {
2365               new_value = mark_value;
2366               break;
2367             }
2368         }
2369     }  
2370
2371   g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value,
2372                  &handled);
2373 }
2374
2375 static void 
2376 stop_scrolling (GtkRange *range)
2377 {
2378   range_grab_remove (range);
2379   gtk_range_remove_step_timer (range);
2380   /* Flush any pending discontinuous/delayed updates */
2381   gtk_range_update_value (range);
2382 }
2383
2384 static gboolean
2385 gtk_range_grab_broken (GtkWidget          *widget,
2386                        GdkEventGrabBroken *event)
2387 {
2388   GtkRange *range = GTK_RANGE (widget);
2389
2390   if (range->layout->grab_location != MOUSE_OUTSIDE)
2391     {
2392       if (range->layout->grab_location == MOUSE_SLIDER)
2393         update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y);
2394       
2395       stop_scrolling (range);
2396       
2397       return TRUE;
2398     }
2399   
2400   return FALSE;
2401 }
2402
2403 static gint
2404 gtk_range_button_release (GtkWidget      *widget,
2405                           GdkEventButton *event)
2406 {
2407   GtkRange *range = GTK_RANGE (widget);
2408
2409   if (event->window == range->event_window)
2410     {
2411       range->layout->mouse_x = event->x;
2412       range->layout->mouse_y = event->y;
2413     }
2414   else
2415     {
2416       gdk_window_get_pointer (range->event_window,
2417                               &range->layout->mouse_x,
2418                               &range->layout->mouse_y,
2419                               NULL);
2420     }
2421   
2422   if (range->layout->grab_button == event->button)
2423     {
2424       if (range->layout->grab_location == MOUSE_SLIDER)
2425         update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y);
2426
2427       stop_scrolling (range);
2428       
2429       return TRUE;
2430     }
2431
2432   return FALSE;
2433 }
2434
2435 /**
2436  * _gtk_range_get_wheel_delta:
2437  * @range: a #GtkRange
2438  * @direction: A #GdkScrollDirection
2439  * 
2440  * Returns a good step value for the mouse wheel.
2441  * 
2442  * Return value: A good step value for the mouse wheel. 
2443  * 
2444  * Since: 2.4
2445  **/
2446 gdouble
2447 _gtk_range_get_wheel_delta (GtkRange           *range,
2448                             GdkScrollDirection  direction)
2449 {
2450   GtkAdjustment *adj = range->adjustment;
2451   gdouble delta;
2452
2453   if (GTK_IS_SCROLLBAR (range))
2454     delta = pow (adj->page_size, 2.0 / 3.0);
2455   else
2456     delta = adj->step_increment * 2;
2457   
2458   if (direction == GDK_SCROLL_UP ||
2459       direction == GDK_SCROLL_LEFT)
2460     delta = - delta;
2461   
2462   if (range->inverted)
2463     delta = - delta;
2464
2465   return delta;
2466 }
2467       
2468 static gboolean
2469 gtk_range_scroll_event (GtkWidget      *widget,
2470                         GdkEventScroll *event)
2471 {
2472   GtkRange *range = GTK_RANGE (widget);
2473
2474   if (gtk_widget_get_realized (widget))
2475     {
2476       GtkAdjustment *adj = GTK_RANGE (range)->adjustment;
2477       gdouble delta;
2478       gboolean handled;
2479
2480       delta = _gtk_range_get_wheel_delta (range, event->direction);
2481
2482       g_signal_emit (range, signals[CHANGE_VALUE], 0,
2483                      GTK_SCROLL_JUMP, adj->value + delta,
2484                      &handled);
2485       
2486       /* Policy DELAYED makes sense with scroll events,
2487        * but DISCONTINUOUS doesn't, so we update immediately
2488        * for DISCONTINUOUS
2489        */
2490       if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
2491         gtk_range_update_value (range);
2492     }
2493
2494   return TRUE;
2495 }
2496
2497 static gboolean
2498 gtk_range_motion_notify (GtkWidget      *widget,
2499                          GdkEventMotion *event)
2500 {
2501   GtkRange *range;
2502
2503   range = GTK_RANGE (widget);
2504
2505   gdk_event_request_motions (event);
2506   
2507   range->layout->mouse_x = event->x;
2508   range->layout->mouse_y = event->y;
2509
2510   if (gtk_range_update_mouse_location (range))
2511     gtk_widget_queue_draw (widget);
2512
2513   if (range->layout->grab_location == MOUSE_SLIDER)
2514     update_slider_position (range, event->x, event->y);
2515
2516   /* We handled the event if the mouse was in the range_rect */
2517   return range->layout->mouse_location != MOUSE_OUTSIDE;
2518 }
2519
2520 static gboolean
2521 gtk_range_enter_notify (GtkWidget        *widget,
2522                         GdkEventCrossing *event)
2523 {
2524   GtkRange *range = GTK_RANGE (widget);
2525
2526   range->layout->mouse_x = event->x;
2527   range->layout->mouse_y = event->y;
2528
2529   if (gtk_range_update_mouse_location (range))
2530     gtk_widget_queue_draw (widget);
2531   
2532   return TRUE;
2533 }
2534
2535 static gboolean
2536 gtk_range_leave_notify (GtkWidget        *widget,
2537                         GdkEventCrossing *event)
2538 {
2539   GtkRange *range = GTK_RANGE (widget);
2540
2541   range->layout->mouse_x = -1;
2542   range->layout->mouse_y = -1;
2543
2544   if (gtk_range_update_mouse_location (range))
2545     gtk_widget_queue_draw (widget);
2546   
2547   return TRUE;
2548 }
2549
2550 static void
2551 gtk_range_grab_notify (GtkWidget *widget,
2552                        gboolean   was_grabbed)
2553 {
2554   if (!was_grabbed)
2555     stop_scrolling (GTK_RANGE (widget));
2556 }
2557
2558 static void
2559 gtk_range_state_changed (GtkWidget    *widget,
2560                          GtkStateType  previous_state)
2561 {
2562   if (!gtk_widget_is_sensitive (widget))
2563     stop_scrolling (GTK_RANGE (widget));
2564 }
2565
2566 #define check_rectangle(rectangle1, rectangle2)              \
2567   {                                                          \
2568     if (rectangle1.x != rectangle2.x) return TRUE;           \
2569     if (rectangle1.y != rectangle2.y) return TRUE;           \
2570     if (rectangle1.width  != rectangle2.width)  return TRUE; \
2571     if (rectangle1.height != rectangle2.height) return TRUE; \
2572   }
2573
2574 static gboolean
2575 layout_changed (GtkRangeLayout *layout1, 
2576                 GtkRangeLayout *layout2)
2577 {
2578   check_rectangle (layout1->slider, layout2->slider);
2579   check_rectangle (layout1->trough, layout2->trough);
2580   check_rectangle (layout1->stepper_a, layout2->stepper_a);
2581   check_rectangle (layout1->stepper_d, layout2->stepper_d);
2582   check_rectangle (layout1->stepper_b, layout2->stepper_b);
2583   check_rectangle (layout1->stepper_c, layout2->stepper_c);
2584
2585   if (layout1->upper_sensitive != layout2->upper_sensitive) return TRUE;
2586   if (layout1->lower_sensitive != layout2->lower_sensitive) return TRUE;
2587
2588   return FALSE;
2589 }
2590
2591 static void
2592 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
2593                               gpointer       data)
2594 {
2595   GtkRange *range = GTK_RANGE (data);
2596   /* create a copy of the layout */
2597   GtkRangeLayout layout = *range->layout;
2598
2599   range->layout->recalc_marks = TRUE;
2600   range->need_recalc = TRUE;
2601   gtk_range_calc_layout (range, range->adjustment->value);
2602   
2603   /* now check whether the layout changed  */
2604   if (layout_changed (range->layout, &layout))
2605     gtk_widget_queue_draw (GTK_WIDGET (range));
2606
2607   /* Note that we don't round off to range->round_digits here.
2608    * that's because it's really broken to change a value
2609    * in response to a change signal on that value; round_digits
2610    * is therefore defined to be a filter on what the GtkRange
2611    * can input into the adjustment, not a filter that the GtkRange
2612    * will enforce on the adjustment.
2613    */
2614 }
2615
2616 static gboolean
2617 force_repaint (gpointer data)
2618 {
2619   GtkRange *range = GTK_RANGE (data);
2620
2621   range->layout->repaint_id = 0;
2622   if (gtk_widget_is_drawable (GTK_WIDGET (range)))
2623     gdk_window_process_updates (GTK_WIDGET (range)->window, FALSE);
2624
2625   return FALSE;
2626 }
2627
2628 static void
2629 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
2630                                     gpointer       data)
2631 {
2632   GtkRange *range = GTK_RANGE (data);
2633   /* create a copy of the layout */
2634   GtkRangeLayout layout = *range->layout;
2635
2636   range->need_recalc = TRUE;
2637   gtk_range_calc_layout (range, range->adjustment->value);
2638   
2639   /* now check whether the layout changed  */
2640   if (layout_changed (range->layout, &layout) ||
2641       (GTK_IS_SCALE (range) && GTK_SCALE (range)->draw_value))
2642     {
2643       gtk_widget_queue_draw (GTK_WIDGET (range));
2644       /* setup a timer to ensure the range isn't lagging too much behind the scroll position */
2645       if (!range->layout->repaint_id)
2646         range->layout->repaint_id = gdk_threads_add_timeout_full (GDK_PRIORITY_EVENTS, 181, force_repaint, range, NULL);
2647     }
2648   
2649   /* Note that we don't round off to range->round_digits here.
2650    * that's because it's really broken to change a value
2651    * in response to a change signal on that value; round_digits
2652    * is therefore defined to be a filter on what the GtkRange
2653    * can input into the adjustment, not a filter that the GtkRange
2654    * will enforce on the adjustment.
2655    */
2656
2657   g_signal_emit (range, signals[VALUE_CHANGED], 0);
2658 }
2659
2660 static void
2661 gtk_range_style_set (GtkWidget *widget,
2662                      GtkStyle  *previous_style)
2663 {
2664   GtkRange *range = GTK_RANGE (widget);
2665
2666   range->need_recalc = TRUE;
2667
2668   GTK_WIDGET_CLASS (gtk_range_parent_class)->style_set (widget, previous_style);
2669 }
2670
2671 static void
2672 apply_marks (GtkRange *range, 
2673              gdouble   oldval,
2674              gdouble  *newval)
2675 {
2676   gint i;
2677   gdouble mark;
2678
2679   for (i = 0; i < range->layout->n_marks; i++)
2680     {
2681       mark = range->layout->marks[i];
2682       if ((oldval < mark && mark < *newval) ||
2683           (oldval > mark && mark > *newval))
2684         {
2685           *newval = mark;
2686           return;
2687         }
2688     }
2689 }
2690
2691 static void
2692 step_back (GtkRange *range)
2693 {
2694   gdouble newval;
2695   gboolean handled;
2696   
2697   newval = range->adjustment->value - range->adjustment->step_increment;
2698   apply_marks (range, range->adjustment->value, &newval);
2699   g_signal_emit (range, signals[CHANGE_VALUE], 0,
2700                  GTK_SCROLL_STEP_BACKWARD, newval, &handled);
2701 }
2702
2703 static void
2704 step_forward (GtkRange *range)
2705 {
2706   gdouble newval;
2707   gboolean handled;
2708
2709   newval = range->adjustment->value + range->adjustment->step_increment;
2710   apply_marks (range, range->adjustment->value, &newval);
2711   g_signal_emit (range, signals[CHANGE_VALUE], 0,
2712                  GTK_SCROLL_STEP_FORWARD, newval, &handled);
2713 }
2714
2715
2716 static void
2717 page_back (GtkRange *range)
2718 {
2719   gdouble newval;
2720   gboolean handled;
2721
2722   newval = range->adjustment->value - range->adjustment->page_increment;
2723   apply_marks (range, range->adjustment->value, &newval);
2724   g_signal_emit (range, signals[CHANGE_VALUE], 0,
2725                  GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
2726 }
2727
2728 static void
2729 page_forward (GtkRange *range)
2730 {
2731   gdouble newval;
2732   gboolean handled;
2733
2734   newval = range->adjustment->value + range->adjustment->page_increment;
2735   apply_marks (range, range->adjustment->value, &newval);
2736   g_signal_emit (range, signals[CHANGE_VALUE], 0,
2737                  GTK_SCROLL_PAGE_FORWARD, newval, &handled);
2738 }
2739
2740 static void
2741 scroll_begin (GtkRange *range)
2742 {
2743   gboolean handled;
2744   g_signal_emit (range, signals[CHANGE_VALUE], 0,
2745                  GTK_SCROLL_START, range->adjustment->lower,
2746                  &handled);
2747 }
2748
2749 static void
2750 scroll_end (GtkRange *range)
2751 {
2752   gdouble newval;
2753   gboolean handled;
2754
2755   newval = range->adjustment->upper - range->adjustment->page_size;
2756   g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_END, newval,
2757                  &handled);
2758 }
2759
2760 static gboolean
2761 gtk_range_scroll (GtkRange     *range,
2762                   GtkScrollType scroll)
2763 {
2764   gdouble old_value = range->adjustment->value;
2765
2766   switch (scroll)
2767     {
2768     case GTK_SCROLL_STEP_LEFT:
2769       if (should_invert (range))
2770         step_forward (range);
2771       else
2772         step_back (range);
2773       break;
2774                     
2775     case GTK_SCROLL_STEP_UP:
2776       if (should_invert (range))
2777         step_forward (range);
2778       else
2779         step_back (range);
2780       break;
2781
2782     case GTK_SCROLL_STEP_RIGHT:
2783       if (should_invert (range))
2784         step_back (range);
2785       else
2786         step_forward (range);
2787       break;
2788                     
2789     case GTK_SCROLL_STEP_DOWN:
2790       if (should_invert (range))
2791         step_back (range);
2792       else
2793         step_forward (range);
2794       break;
2795                   
2796     case GTK_SCROLL_STEP_BACKWARD:
2797       step_back (range);
2798       break;
2799                   
2800     case GTK_SCROLL_STEP_FORWARD:
2801       step_forward (range);
2802       break;
2803
2804     case GTK_SCROLL_PAGE_LEFT:
2805       if (should_invert (range))
2806         page_forward (range);
2807       else
2808         page_back (range);
2809       break;
2810                     
2811     case GTK_SCROLL_PAGE_UP:
2812       if (should_invert (range))
2813         page_forward (range);
2814       else
2815         page_back (range);
2816       break;
2817
2818     case GTK_SCROLL_PAGE_RIGHT:
2819       if (should_invert (range))
2820         page_back (range);
2821       else
2822         page_forward (range);
2823       break;
2824                     
2825     case GTK_SCROLL_PAGE_DOWN:
2826       if (should_invert (range))
2827         page_back (range);
2828       else
2829         page_forward (range);
2830       break;
2831                   
2832     case GTK_SCROLL_PAGE_BACKWARD:
2833       page_back (range);
2834       break;
2835                   
2836     case GTK_SCROLL_PAGE_FORWARD:
2837       page_forward (range);
2838       break;
2839
2840     case GTK_SCROLL_START:
2841       scroll_begin (range);
2842       break;
2843
2844     case GTK_SCROLL_END:
2845       scroll_end (range);
2846       break;
2847
2848     case GTK_SCROLL_JUMP:
2849       /* Used by CList, range doesn't use it. */
2850       break;
2851
2852     case GTK_SCROLL_NONE:
2853       break;
2854     }
2855
2856   return range->adjustment->value != old_value;
2857 }
2858
2859 static void
2860 gtk_range_move_slider (GtkRange     *range,
2861                        GtkScrollType scroll)
2862 {
2863   gboolean cursor_only;
2864
2865   g_object_get (gtk_widget_get_settings (GTK_WIDGET (range)),
2866                 "gtk-keynav-cursor-only", &cursor_only,
2867                 NULL);
2868
2869   if (cursor_only)
2870     {
2871       GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (range));
2872
2873       if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
2874         {
2875           if (scroll == GTK_SCROLL_STEP_UP ||
2876               scroll == GTK_SCROLL_STEP_DOWN)
2877             {
2878               if (toplevel)
2879                 gtk_widget_child_focus (toplevel,
2880                                         scroll == GTK_SCROLL_STEP_UP ?
2881                                         GTK_DIR_UP : GTK_DIR_DOWN);
2882               return;
2883             }
2884         }
2885       else
2886         {
2887           if (scroll == GTK_SCROLL_STEP_LEFT ||
2888               scroll == GTK_SCROLL_STEP_RIGHT)
2889             {
2890               if (toplevel)
2891                 gtk_widget_child_focus (toplevel,
2892                                         scroll == GTK_SCROLL_STEP_LEFT ?
2893                                         GTK_DIR_LEFT : GTK_DIR_RIGHT);
2894               return;
2895             }
2896         }
2897     }
2898
2899   if (! gtk_range_scroll (range, scroll))
2900     gtk_widget_error_bell (GTK_WIDGET (range));
2901
2902   /* Policy DELAYED makes sense with key events,
2903    * but DISCONTINUOUS doesn't, so we update immediately
2904    * for DISCONTINUOUS
2905    */
2906   if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
2907     gtk_range_update_value (range);
2908 }
2909
2910 static void
2911 gtk_range_get_props (GtkRange  *range,
2912                      gint      *slider_width,
2913                      gint      *stepper_size,
2914                      gint      *focus_width,
2915                      gint      *trough_border,
2916                      gint      *stepper_spacing,
2917                      gboolean  *trough_under_steppers,
2918                      gint      *arrow_displacement_x,
2919                      gint      *arrow_displacement_y)
2920 {
2921   GtkWidget *widget =  GTK_WIDGET (range);
2922   gint tmp_slider_width, tmp_stepper_size, tmp_focus_width, tmp_trough_border;
2923   gint tmp_stepper_spacing, tmp_trough_under_steppers;
2924   gint tmp_arrow_displacement_x, tmp_arrow_displacement_y;
2925   
2926   gtk_widget_style_get (widget,
2927                         "slider-width", &tmp_slider_width,
2928                         "trough-border", &tmp_trough_border,
2929                         "stepper-size", &tmp_stepper_size,
2930                         "stepper-spacing", &tmp_stepper_spacing,
2931                         "trough-under-steppers", &tmp_trough_under_steppers,
2932                         "arrow-displacement-x", &tmp_arrow_displacement_x,
2933                         "arrow-displacement-y", &tmp_arrow_displacement_y,
2934                         NULL);
2935
2936   if (tmp_stepper_spacing > 0)
2937     tmp_trough_under_steppers = FALSE;
2938
2939   if (gtk_widget_get_can_focus (GTK_WIDGET (range)))
2940     {
2941       gint focus_line_width;
2942       gint focus_padding;
2943       
2944       gtk_widget_style_get (GTK_WIDGET (range),
2945                             "focus-line-width", &focus_line_width,
2946                             "focus-padding", &focus_padding,
2947                             NULL);
2948
2949       tmp_focus_width = focus_line_width + focus_padding;
2950     }
2951   else
2952     {
2953       tmp_focus_width = 0;
2954     }
2955   
2956   if (slider_width)
2957     *slider_width = tmp_slider_width;
2958
2959   if (focus_width)
2960     *focus_width = tmp_focus_width;
2961
2962   if (trough_border)
2963     *trough_border = tmp_trough_border;
2964
2965   if (stepper_size)
2966     *stepper_size = tmp_stepper_size;
2967
2968   if (stepper_spacing)
2969     *stepper_spacing = tmp_stepper_spacing;
2970
2971   if (trough_under_steppers)
2972     *trough_under_steppers = tmp_trough_under_steppers;
2973
2974   if (arrow_displacement_x)
2975     *arrow_displacement_x = tmp_arrow_displacement_x;
2976
2977   if (arrow_displacement_y)
2978     *arrow_displacement_y = tmp_arrow_displacement_y;
2979 }
2980
2981 #define POINT_IN_RECT(xcoord, ycoord, rect) \
2982  ((xcoord) >= (rect).x &&                   \
2983   (xcoord) <  ((rect).x + (rect).width) &&  \
2984   (ycoord) >= (rect).y &&                   \
2985   (ycoord) <  ((rect).y + (rect).height))
2986
2987 /* Update mouse location, return TRUE if it changes */
2988 static gboolean
2989 gtk_range_update_mouse_location (GtkRange *range)
2990 {
2991   gint x, y;
2992   MouseLocation old;
2993   GtkWidget *widget;
2994
2995   widget = GTK_WIDGET (range);
2996   
2997   old = range->layout->mouse_location;
2998   
2999   x = range->layout->mouse_x;
3000   y = range->layout->mouse_y;
3001
3002   if (range->layout->grab_location != MOUSE_OUTSIDE)
3003     range->layout->mouse_location = range->layout->grab_location;
3004   else if (POINT_IN_RECT (x, y, range->layout->stepper_a))
3005     range->layout->mouse_location = MOUSE_STEPPER_A;
3006   else if (POINT_IN_RECT (x, y, range->layout->stepper_b))
3007     range->layout->mouse_location = MOUSE_STEPPER_B;
3008   else if (POINT_IN_RECT (x, y, range->layout->stepper_c))
3009     range->layout->mouse_location = MOUSE_STEPPER_C;
3010   else if (POINT_IN_RECT (x, y, range->layout->stepper_d))
3011     range->layout->mouse_location = MOUSE_STEPPER_D;
3012   else if (POINT_IN_RECT (x, y, range->layout->slider))
3013     range->layout->mouse_location = MOUSE_SLIDER;
3014   else if (POINT_IN_RECT (x, y, range->layout->trough))
3015     range->layout->mouse_location = MOUSE_TROUGH;
3016   else if (POINT_IN_RECT (x, y, widget->allocation))
3017     range->layout->mouse_location = MOUSE_WIDGET;
3018   else
3019     range->layout->mouse_location = MOUSE_OUTSIDE;
3020
3021   return old != range->layout->mouse_location;
3022 }
3023
3024 /* Clamp rect, border inside widget->allocation, such that we prefer
3025  * to take space from border not rect in all directions, and prefer to
3026  * give space to border over rect in one direction.
3027  */
3028 static void
3029 clamp_dimensions (GtkWidget    *widget,
3030                   GdkRectangle *rect,
3031                   GtkBorder    *border,
3032                   gboolean      border_expands_horizontally)
3033 {
3034   gint extra, shortage;
3035   
3036   g_return_if_fail (rect->x == 0);
3037   g_return_if_fail (rect->y == 0);  
3038   g_return_if_fail (rect->width >= 0);
3039   g_return_if_fail (rect->height >= 0);
3040
3041   /* Width */
3042   
3043   extra = widget->allocation.width - border->left - border->right - rect->width;
3044   if (extra > 0)
3045     {
3046       if (border_expands_horizontally)
3047         {
3048           border->left += extra / 2;
3049           border->right += extra / 2 + extra % 2;
3050         }
3051       else
3052         {
3053           rect->width += extra;
3054         }
3055     }
3056   
3057   /* See if we can fit rect, if not kill the border */
3058   shortage = rect->width - widget->allocation.width;
3059   if (shortage > 0)
3060     {
3061       rect->width = widget->allocation.width;
3062       /* lose the border */
3063       border->left = 0;
3064       border->right = 0;
3065     }
3066   else
3067     {
3068       /* See if we can fit rect with borders */
3069       shortage = rect->width + border->left + border->right -
3070         widget->allocation.width;
3071       if (shortage > 0)
3072         {
3073           /* Shrink borders */
3074           border->left -= shortage / 2;
3075           border->right -= shortage / 2 + shortage % 2;
3076         }
3077     }
3078
3079   /* Height */
3080   
3081   extra = widget->allocation.height - border->top - border->bottom - rect->height;
3082   if (extra > 0)
3083     {
3084       if (border_expands_horizontally)
3085         {
3086           /* don't expand border vertically */
3087           rect->height += extra;
3088         }
3089       else
3090         {
3091           border->top += extra / 2;
3092           border->bottom += extra / 2 + extra % 2;
3093         }
3094     }
3095   
3096   /* See if we can fit rect, if not kill the border */
3097   shortage = rect->height - widget->allocation.height;
3098   if (shortage > 0)
3099     {
3100       rect->height = widget->allocation.height;
3101       /* lose the border */
3102       border->top = 0;
3103       border->bottom = 0;
3104     }
3105   else
3106     {
3107       /* See if we can fit rect with borders */
3108       shortage = rect->height + border->top + border->bottom -
3109         widget->allocation.height;
3110       if (shortage > 0)
3111         {
3112           /* Shrink borders */
3113           border->top -= shortage / 2;
3114           border->bottom -= shortage / 2 + shortage % 2;
3115         }
3116     }
3117 }
3118
3119 static void
3120 gtk_range_calc_request (GtkRange      *range,
3121                         gint           slider_width,
3122                         gint           stepper_size,
3123                         gint           focus_width,
3124                         gint           trough_border,
3125                         gint           stepper_spacing,
3126                         GdkRectangle  *range_rect,
3127                         GtkBorder     *border,
3128                         gint          *n_steppers_p,
3129                         gboolean      *has_steppers_ab,
3130                         gboolean      *has_steppers_cd,
3131                         gint          *slider_length_p)
3132 {
3133   gint slider_length;
3134   gint n_steppers;
3135   gint n_steppers_ab;
3136   gint n_steppers_cd;
3137
3138   border->left = 0;
3139   border->right = 0;
3140   border->top = 0;
3141   border->bottom = 0;
3142
3143   if (GTK_RANGE_GET_CLASS (range)->get_range_border)
3144     GTK_RANGE_GET_CLASS (range)->get_range_border (range, border);
3145
3146   n_steppers_ab = 0;
3147   n_steppers_cd = 0;
3148
3149   if (range->has_stepper_a)
3150     n_steppers_ab += 1;
3151   if (range->has_stepper_b)
3152     n_steppers_ab += 1;
3153   if (range->has_stepper_c)
3154     n_steppers_cd += 1;
3155   if (range->has_stepper_d)
3156     n_steppers_cd += 1;
3157
3158   n_steppers = n_steppers_ab + n_steppers_cd;
3159
3160   slider_length = range->min_slider_size;
3161
3162   range_rect->x = 0;
3163   range_rect->y = 0;
3164   
3165   /* We never expand to fill available space in the small dimension
3166    * (i.e. vertical scrollbars are always a fixed width)
3167    */
3168   if (range->orientation == GTK_ORIENTATION_VERTICAL)
3169     {
3170       range_rect->width = (focus_width + trough_border) * 2 + slider_width;
3171       range_rect->height = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length;
3172
3173       if (n_steppers_ab > 0)
3174         range_rect->height += stepper_spacing;
3175
3176       if (n_steppers_cd > 0)
3177         range_rect->height += stepper_spacing;
3178     }
3179   else
3180     {
3181       range_rect->width = stepper_size * n_steppers + (focus_width + trough_border) * 2 + slider_length;
3182       range_rect->height = (focus_width + trough_border) * 2 + slider_width;
3183
3184       if (n_steppers_ab > 0)
3185         range_rect->width += stepper_spacing;
3186
3187       if (n_steppers_cd > 0)
3188         range_rect->width += stepper_spacing;
3189     }
3190
3191   if (n_steppers_p)
3192     *n_steppers_p = n_steppers;
3193
3194   if (has_steppers_ab)
3195     *has_steppers_ab = (n_steppers_ab > 0);
3196
3197   if (has_steppers_cd)
3198     *has_steppers_cd = (n_steppers_cd > 0);
3199
3200   if (slider_length_p)
3201     *slider_length_p = slider_length;
3202 }
3203
3204 static void
3205 gtk_range_calc_layout (GtkRange *range,
3206                        gdouble   adjustment_value)
3207 {
3208   gint slider_width, stepper_size, focus_width, trough_border, stepper_spacing;
3209   gint slider_length;
3210   GtkBorder border;
3211   gint n_steppers;
3212   gboolean has_steppers_ab;
3213   gboolean has_steppers_cd;
3214   gboolean trough_under_steppers;
3215   GdkRectangle range_rect;
3216   GtkRangeLayout *layout;
3217   GtkWidget *widget;
3218   
3219   if (!range->need_recalc)
3220     return;
3221
3222   /* If we have a too-small allocation, we prefer the steppers over
3223    * the trough/slider, probably the steppers are a more useful
3224    * feature in small spaces.
3225    *
3226    * Also, we prefer to draw the range itself rather than the border
3227    * areas if there's a conflict, since the borders will be decoration
3228    * not controls. Though this depends on subclasses cooperating by
3229    * not drawing on range->range_rect.
3230    */
3231
3232   widget = GTK_WIDGET (range);
3233   layout = range->layout;
3234   
3235   gtk_range_get_props (range,
3236                        &slider_width, &stepper_size,
3237                        &focus_width, &trough_border,
3238                        &stepper_spacing, &trough_under_steppers,
3239                        NULL, NULL);
3240
3241   gtk_range_calc_request (range, 
3242                           slider_width, stepper_size,
3243                           focus_width, trough_border, stepper_spacing,
3244                           &range_rect, &border, &n_steppers,
3245                           &has_steppers_ab, &has_steppers_cd, &slider_length);
3246   
3247   /* We never expand to fill available space in the small dimension
3248    * (i.e. vertical scrollbars are always a fixed width)
3249    */
3250   if (range->orientation == GTK_ORIENTATION_VERTICAL)
3251     {
3252       clamp_dimensions (widget, &range_rect, &border, TRUE);
3253     }
3254   else
3255     {
3256       clamp_dimensions (widget, &range_rect, &border, FALSE);
3257     }
3258   
3259   range_rect.x = border.left;
3260   range_rect.y = border.top;
3261   
3262   range->range_rect = range_rect;
3263   
3264   if (range->orientation == GTK_ORIENTATION_VERTICAL)
3265     {
3266       gint stepper_width, stepper_height;
3267
3268       /* Steppers are the width of the range, and stepper_size in
3269        * height, or if we don't have enough height, divided equally
3270        * among available space.
3271        */
3272       stepper_width = range_rect.width - focus_width * 2;
3273
3274       if (trough_under_steppers)
3275         stepper_width -= trough_border * 2;
3276
3277       if (stepper_width < 1)
3278         stepper_width = range_rect.width; /* screw the trough border */
3279
3280       if (n_steppers == 0)
3281         stepper_height = 0; /* avoid divide by n_steppers */
3282       else
3283         stepper_height = MIN (stepper_size, (range_rect.height / n_steppers));
3284
3285       /* Stepper A */
3286       
3287       layout->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers;
3288       layout->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers;
3289
3290       if (range->has_stepper_a)
3291         {
3292           layout->stepper_a.width = stepper_width;
3293           layout->stepper_a.height = stepper_height;
3294         }
3295       else
3296         {
3297           layout->stepper_a.width = 0;
3298           layout->stepper_a.height = 0;
3299         }
3300
3301       /* Stepper B */
3302       
3303       layout->stepper_b.x = layout->stepper_a.x;
3304       layout->stepper_b.y = layout->stepper_a.y + layout->stepper_a.height;
3305
3306       if (range->has_stepper_b)
3307         {
3308           layout->stepper_b.width = stepper_width;
3309           layout->stepper_b.height = stepper_height;
3310         }
3311       else
3312         {
3313           layout->stepper_b.width = 0;
3314           layout->stepper_b.height = 0;
3315         }
3316
3317       /* Stepper D */
3318
3319       if (range->has_stepper_d)
3320         {
3321           layout->stepper_d.width = stepper_width;
3322           layout->stepper_d.height = stepper_height;
3323         }
3324       else
3325         {
3326           layout->stepper_d.width = 0;
3327           layout->stepper_d.height = 0;
3328         }
3329       
3330       layout->stepper_d.x = layout->stepper_a.x;
3331       layout->stepper_d.y = range_rect.y + range_rect.height - layout->stepper_d.height - focus_width - trough_border * trough_under_steppers;
3332
3333       /* Stepper C */
3334
3335       if (range->has_stepper_c)
3336         {
3337           layout->stepper_c.width = stepper_width;
3338           layout->stepper_c.height = stepper_height;
3339         }
3340       else
3341         {
3342           layout->stepper_c.width = 0;
3343           layout->stepper_c.height = 0;
3344         }
3345       
3346       layout->stepper_c.x = layout->stepper_a.x;
3347       layout->stepper_c.y = layout->stepper_d.y - layout->stepper_c.height;
3348
3349       /* Now the trough is the remaining space between steppers B and C,
3350        * if any, minus spacing
3351        */
3352       layout->trough.x = range_rect.x;
3353       layout->trough.y = layout->stepper_b.y + layout->stepper_b.height + stepper_spacing * has_steppers_ab;
3354       layout->trough.width = range_rect.width;
3355       layout->trough.height = layout->stepper_c.y - layout->trough.y - stepper_spacing * has_steppers_cd;
3356
3357       /* Slider fits into the trough, with stepper_spacing on either side,
3358        * and the size/position based on the adjustment or fixed, depending.
3359        */
3360       layout->slider.x = layout->trough.x + focus_width + trough_border;
3361       layout->slider.width = layout->trough.width - (focus_width + trough_border) * 2;
3362
3363       /* Compute slider position/length */
3364       {
3365         gint y, bottom, top, height;
3366         
3367         top = layout->trough.y;
3368         bottom = layout->trough.y + layout->trough.height;
3369
3370         if (! trough_under_steppers)
3371           {
3372             top += trough_border;
3373             bottom -= trough_border;
3374           }
3375
3376         /* slider height is the fraction (page_size /
3377          * total_adjustment_range) times the trough height in pixels
3378          */
3379
3380         if (range->adjustment->upper - range->adjustment->lower != 0)
3381           height = ((bottom - top) * (range->adjustment->page_size /
3382                                        (range->adjustment->upper - range->adjustment->lower)));
3383         else
3384           height = range->min_slider_size;
3385         
3386         if (height < range->min_slider_size ||
3387             range->slider_size_fixed)
3388           height = range->min_slider_size;
3389
3390         height = MIN (height, layout->trough.height);
3391         
3392         y = top;
3393         
3394         if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0)
3395           y += (bottom - top - height) * ((adjustment_value - range->adjustment->lower) /
3396                                           (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
3397         
3398         y = CLAMP (y, top, bottom);
3399         
3400         if (should_invert (range))
3401           y = bottom - (y - top + height);
3402         
3403         layout->slider.y = y;
3404         layout->slider.height = height;
3405
3406         /* These are publically exported */
3407         range->slider_start = layout->slider.y;
3408         range->slider_end = layout->slider.y + layout->slider.height;
3409       }
3410     }
3411   else
3412     {
3413       gint stepper_width, stepper_height;
3414
3415       /* Steppers are the height of the range, and stepper_size in
3416        * width, or if we don't have enough width, divided equally
3417        * among available space.
3418        */
3419       stepper_height = range_rect.height + focus_width * 2;
3420
3421       if (trough_under_steppers)
3422         stepper_height -= trough_border * 2;
3423
3424       if (stepper_height < 1)
3425         stepper_height = range_rect.height; /* screw the trough border */
3426
3427       if (n_steppers == 0)
3428         stepper_width = 0; /* avoid divide by n_steppers */
3429       else
3430         stepper_width = MIN (stepper_size, (range_rect.width / n_steppers));
3431
3432       /* Stepper A */
3433       
3434       layout->stepper_a.x = range_rect.x + focus_width + trough_border * trough_under_steppers;
3435       layout->stepper_a.y = range_rect.y + focus_width + trough_border * trough_under_steppers;
3436
3437       if (range->has_stepper_a)
3438         {
3439           layout->stepper_a.width = stepper_width;
3440           layout->stepper_a.height = stepper_height;
3441         }
3442       else
3443         {
3444           layout->stepper_a.width = 0;
3445           layout->stepper_a.height = 0;
3446         }
3447
3448       /* Stepper B */
3449       
3450       layout->stepper_b.x = layout->stepper_a.x + layout->stepper_a.width;
3451       layout->stepper_b.y = layout->stepper_a.y;
3452
3453       if (range->has_stepper_b)
3454         {
3455           layout->stepper_b.width = stepper_width;
3456           layout->stepper_b.height = stepper_height;
3457         }
3458       else
3459         {
3460           layout->stepper_b.width = 0;
3461           layout->stepper_b.height = 0;
3462         }
3463
3464       /* Stepper D */
3465
3466       if (range->has_stepper_d)
3467         {
3468           layout->stepper_d.width = stepper_width;
3469           layout->stepper_d.height = stepper_height;
3470         }
3471       else
3472         {
3473           layout->stepper_d.width = 0;
3474           layout->stepper_d.height = 0;
3475         }
3476
3477       layout->stepper_d.x = range_rect.x + range_rect.width - layout->stepper_d.width - focus_width - trough_border * trough_under_steppers;
3478       layout->stepper_d.y = layout->stepper_a.y;
3479
3480
3481       /* Stepper C */
3482
3483       if (range->has_stepper_c)
3484         {
3485           layout->stepper_c.width = stepper_width;
3486           layout->stepper_c.height = stepper_height;
3487         }
3488       else
3489         {
3490           layout->stepper_c.width = 0;
3491           layout->stepper_c.height = 0;
3492         }
3493       
3494       layout->stepper_c.x = layout->stepper_d.x - layout->stepper_c.width;
3495       layout->stepper_c.y = layout->stepper_a.y;
3496
3497       /* Now the trough is the remaining space between steppers B and C,
3498        * if any
3499        */
3500       layout->trough.x = layout->stepper_b.x + layout->stepper_b.width + stepper_spacing * has_steppers_ab;
3501       layout->trough.y = range_rect.y;
3502
3503       layout->trough.width = layout->stepper_c.x - layout->trough.x - stepper_spacing * has_steppers_cd;
3504       layout->trough.height = range_rect.height;
3505
3506       /* Slider fits into the trough, with stepper_spacing on either side,
3507        * and the size/position based on the adjustment or fixed, depending.
3508        */
3509       layout->slider.y = layout->trough.y + focus_width + trough_border;
3510       layout->slider.height = layout->trough.height - (focus_width + trough_border) * 2;
3511
3512       /* Compute slider position/length */
3513       {
3514         gint x, left, right, width;
3515         
3516         left = layout->trough.x;
3517         right = layout->trough.x + layout->trough.width;
3518
3519         if (! trough_under_steppers)
3520           {
3521             left += trough_border;
3522             right -= trough_border;
3523           }
3524
3525         /* slider width is the fraction (page_size /
3526          * total_adjustment_range) times the trough width in pixels
3527          */
3528         
3529         if (range->adjustment->upper - range->adjustment->lower != 0)
3530           width = ((right - left) * (range->adjustment->page_size /
3531                                    (range->adjustment->upper - range->adjustment->lower)));
3532         else
3533           width = range->min_slider_size;
3534         
3535         if (width < range->min_slider_size ||
3536             range->slider_size_fixed)
3537           width = range->min_slider_size;
3538         
3539         width = MIN (width, layout->trough.width);
3540         
3541         x = left;
3542         
3543         if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0)
3544           x += (right - left - width) * ((adjustment_value - range->adjustment->lower) /
3545                                          (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
3546         
3547         x = CLAMP (x, left, right);
3548         
3549         if (should_invert (range))
3550           x = right - (x - left + width);
3551         
3552         layout->slider.x = x;
3553         layout->slider.width = width;
3554
3555         /* These are publically exported */
3556         range->slider_start = layout->slider.x;
3557         range->slider_end = layout->slider.x + layout->slider.width;
3558       }
3559     }
3560   
3561   gtk_range_update_mouse_location (range);
3562
3563   switch (range->layout->upper_sensitivity)
3564     {
3565     case GTK_SENSITIVITY_AUTO:
3566       range->layout->upper_sensitive =
3567         (range->adjustment->value <
3568          (range->adjustment->upper - range->adjustment->page_size));
3569       break;
3570
3571     case GTK_SENSITIVITY_ON:
3572       range->layout->upper_sensitive = TRUE;
3573       break;
3574
3575     case GTK_SENSITIVITY_OFF:
3576       range->layout->upper_sensitive = FALSE;
3577       break;
3578     }
3579
3580   switch (range->layout->lower_sensitivity)
3581     {
3582     case GTK_SENSITIVITY_AUTO:
3583       range->layout->lower_sensitive =
3584         (range->adjustment->value > range->adjustment->lower);
3585       break;
3586
3587     case GTK_SENSITIVITY_ON:
3588       range->layout->lower_sensitive = TRUE;
3589       break;
3590
3591     case GTK_SENSITIVITY_OFF:
3592       range->layout->lower_sensitive = FALSE;
3593       break;
3594     }
3595 }
3596
3597 static GdkRectangle*
3598 get_area (GtkRange     *range,
3599           MouseLocation location)
3600 {
3601   switch (location)
3602     {
3603     case MOUSE_STEPPER_A:
3604       return &range->layout->stepper_a;
3605     case MOUSE_STEPPER_B:
3606       return &range->layout->stepper_b;
3607     case MOUSE_STEPPER_C:
3608       return &range->layout->stepper_c;
3609     case MOUSE_STEPPER_D:
3610       return &range->layout->stepper_d;
3611     case MOUSE_TROUGH:
3612       return &range->layout->trough;
3613     case MOUSE_SLIDER:
3614       return &range->layout->slider;
3615     case MOUSE_WIDGET:
3616     case MOUSE_OUTSIDE:
3617       break;
3618     }
3619
3620   g_warning (G_STRLOC": bug");
3621   return NULL;
3622 }
3623
3624 static void
3625 gtk_range_calc_marks (GtkRange *range)
3626 {
3627   gint i;
3628   
3629   if (!range->layout->recalc_marks)
3630     return;
3631
3632   range->layout->recalc_marks = FALSE;
3633
3634   for (i = 0; i < range->layout->n_marks; i++)
3635     {
3636       range->need_recalc = TRUE;
3637       gtk_range_calc_layout (range, range->layout->marks[i]);
3638       if (range->orientation == GTK_ORIENTATION_HORIZONTAL)
3639         range->layout->mark_pos[i] = range->layout->slider.x + range->layout->slider.width / 2;
3640       else
3641         range->layout->mark_pos[i] = range->layout->slider.y + range->layout->slider.height / 2;
3642     }
3643
3644   range->need_recalc = TRUE;
3645 }
3646
3647 static gboolean
3648 gtk_range_real_change_value (GtkRange     *range,
3649                              GtkScrollType scroll,
3650                              gdouble       value)
3651 {
3652   /* potentially adjust the bounds _before_ we clamp */
3653   g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value);
3654
3655   if (range->layout->restrict_to_fill_level)
3656     value = MIN (value, MAX (range->adjustment->lower,
3657                              range->layout->fill_level));
3658
3659   value = CLAMP (value, range->adjustment->lower,
3660                  (range->adjustment->upper - range->adjustment->page_size));
3661
3662   if (range->round_digits >= 0)
3663     {
3664       gdouble power;
3665       gint i;
3666
3667       i = range->round_digits;
3668       power = 1;
3669       while (i--)
3670         power *= 10;
3671       
3672       value = floor ((value * power) + 0.5) / power;
3673     }
3674   
3675   if (range->adjustment->value != value)
3676     {
3677       range->need_recalc = TRUE;
3678
3679       gtk_widget_queue_draw (GTK_WIDGET (range));
3680       
3681       switch (range->update_policy)
3682         {
3683         case GTK_UPDATE_CONTINUOUS:
3684           gtk_adjustment_set_value (range->adjustment, value);
3685           break;
3686
3687           /* Delayed means we update after a period of inactivity */
3688         case GTK_UPDATE_DELAYED:
3689           gtk_range_reset_update_timer (range);
3690           /* FALL THRU */
3691
3692           /* Discontinuous means we update on button release */
3693         case GTK_UPDATE_DISCONTINUOUS:
3694           /* don't emit value_changed signal */
3695           range->adjustment->value = value;
3696           range->update_pending = TRUE;
3697           break;
3698         }
3699     }
3700   return FALSE;
3701 }
3702
3703 static void
3704 gtk_range_update_value (GtkRange *range)
3705 {
3706   gtk_range_remove_update_timer (range);
3707   
3708   if (range->update_pending)
3709     {
3710       gtk_adjustment_value_changed (range->adjustment);
3711
3712       range->update_pending = FALSE;
3713     }
3714 }
3715
3716 struct _GtkRangeStepTimer
3717 {
3718   guint timeout_id;
3719   GtkScrollType step;
3720 };
3721
3722 static gboolean
3723 second_timeout (gpointer data)
3724 {
3725   GtkRange *range;
3726
3727   range = GTK_RANGE (data);
3728   gtk_range_scroll (range, range->timer->step);
3729   
3730   return TRUE;
3731 }
3732
3733 static gboolean
3734 initial_timeout (gpointer data)
3735 {
3736   GtkRange    *range;
3737   GtkSettings *settings;
3738   guint        timeout;
3739
3740   settings = gtk_widget_get_settings (GTK_WIDGET (data));
3741   g_object_get (settings, "gtk-timeout-repeat", &timeout, NULL);
3742
3743   range = GTK_RANGE (data);
3744   range->timer->timeout_id = gdk_threads_add_timeout (timeout * SCROLL_DELAY_FACTOR,
3745                                             second_timeout,
3746                                             range);
3747   /* remove self */
3748   return FALSE;
3749 }
3750
3751 static void
3752 gtk_range_add_step_timer (GtkRange      *range,
3753                           GtkScrollType  step)
3754 {
3755   GtkSettings *settings;
3756   guint        timeout;
3757
3758   g_return_if_fail (range->timer == NULL);
3759   g_return_if_fail (step != GTK_SCROLL_NONE);
3760
3761   settings = gtk_widget_get_settings (GTK_WIDGET (range));
3762   g_object_get (settings, "gtk-timeout-initial", &timeout, NULL);
3763
3764   range->timer = g_new (GtkRangeStepTimer, 1);
3765
3766   range->timer->timeout_id = gdk_threads_add_timeout (timeout,
3767                                             initial_timeout,
3768                                             range);
3769   range->timer->step = step;
3770
3771   gtk_range_scroll (range, range->timer->step);
3772 }
3773
3774 static void
3775 gtk_range_remove_step_timer (GtkRange *range)
3776 {
3777   if (range->timer)
3778     {
3779       if (range->timer->timeout_id != 0)
3780         g_source_remove (range->timer->timeout_id);
3781
3782       g_free (range->timer);
3783
3784       range->timer = NULL;
3785     }
3786 }
3787
3788 static gboolean
3789 update_timeout (gpointer data)
3790 {
3791   GtkRange *range;
3792
3793   range = GTK_RANGE (data);
3794   gtk_range_update_value (range);
3795   range->update_timeout_id = 0;
3796
3797   /* self-remove */
3798   return FALSE;
3799 }
3800
3801 static void
3802 gtk_range_reset_update_timer (GtkRange *range)
3803 {
3804   gtk_range_remove_update_timer (range);
3805
3806   range->update_timeout_id = gdk_threads_add_timeout (UPDATE_DELAY,
3807                                             update_timeout,
3808                                             range);
3809 }
3810
3811 static void
3812 gtk_range_remove_update_timer (GtkRange *range)
3813 {
3814   if (range->update_timeout_id != 0)
3815     {
3816       g_source_remove (range->update_timeout_id);
3817       range->update_timeout_id = 0;
3818     }
3819 }
3820
3821 void
3822 _gtk_range_set_stop_values (GtkRange *range,
3823                             gdouble  *values,
3824                             gint      n_values)
3825 {
3826   gint i;
3827
3828   g_free (range->layout->marks);
3829   range->layout->marks = g_new (gdouble, n_values);
3830
3831   g_free (range->layout->mark_pos);
3832   range->layout->mark_pos = g_new (gint, n_values);
3833
3834   range->layout->n_marks = n_values;
3835
3836   for (i = 0; i < n_values; i++) 
3837     range->layout->marks[i] = values[i];
3838
3839   range->layout->recalc_marks = TRUE;
3840 }
3841
3842 gint
3843 _gtk_range_get_stop_positions (GtkRange  *range,
3844                                gint     **values)
3845 {
3846   gtk_range_calc_marks (range);
3847
3848   if (values)
3849     *values = g_memdup (range->layout->mark_pos, range->layout->n_marks * sizeof (gint));
3850
3851   return range->layout->n_marks;
3852 }
3853
3854 #define __GTK_RANGE_C__
3855 #include "gtkaliasdef.c"