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