]> Pileus Git - ~andy/gtk/blob - gtk/gtkrange.c
Define macros GTK_PARAM_READABLE, GTK_PARAM_WRITABLE, GTK_PARAM_READWRITE
[~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
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   gdk_window_hide (range->event_window);
895
896   GTK_WIDGET_CLASS (parent_class)->unmap (widget);
897 }
898
899 static void
900 draw_stepper (GtkRange     *range,
901               GdkRectangle *rect,
902               GtkArrowType  arrow_type,
903               gboolean      clicked,
904               gboolean      prelighted,
905               GdkRectangle *area)
906 {
907   GtkStateType state_type;
908   GtkShadowType shadow_type;
909   GdkRectangle intersection;
910   GtkWidget *widget = GTK_WIDGET (range);
911
912   gint arrow_x;
913   gint arrow_y;
914   gint arrow_width;
915   gint arrow_height;
916
917   /* More to get the right clip region than for efficiency */
918   if (!gdk_rectangle_intersect (area, rect, &intersection))
919     return;
920
921   intersection.x += widget->allocation.x;
922   intersection.y += widget->allocation.y;
923   
924   if (!GTK_WIDGET_IS_SENSITIVE (range))
925     state_type = GTK_STATE_INSENSITIVE;
926   else if (clicked)
927     state_type = GTK_STATE_ACTIVE;
928   else if (prelighted)
929     state_type = GTK_STATE_PRELIGHT;
930   else 
931     state_type = GTK_STATE_NORMAL;
932   
933   if (clicked)
934     shadow_type = GTK_SHADOW_IN;
935   else
936     shadow_type = GTK_SHADOW_OUT;
937
938   gtk_paint_box (widget->style,
939                  widget->window,
940                  state_type, shadow_type,
941                  &intersection, widget,
942                  GTK_RANGE_GET_CLASS (range)->stepper_detail,
943                  widget->allocation.x + rect->x,
944                  widget->allocation.y + rect->y,
945                  rect->width,
946                  rect->height);
947
948   arrow_width = rect->width / 2;
949   arrow_height = rect->height / 2;
950   arrow_x = widget->allocation.x + rect->x + (rect->width - arrow_width) / 2;
951   arrow_y = widget->allocation.y + rect->y + (rect->height - arrow_height) / 2;
952   
953   if (clicked)
954     {
955       gint arrow_displacement_x;
956       gint arrow_displacement_y;
957
958       gtk_range_get_props (GTK_RANGE (widget), NULL, NULL, NULL, NULL,
959                            &arrow_displacement_x, &arrow_displacement_y);
960       
961       arrow_x += arrow_displacement_x;
962       arrow_y += arrow_displacement_y;
963     }
964   
965   gtk_paint_arrow (widget->style,
966                    widget->window,
967                    state_type, shadow_type, 
968                    &intersection, widget,
969                    GTK_RANGE_GET_CLASS (range)->stepper_detail,
970                    arrow_type,
971                    TRUE,
972                    arrow_x, arrow_y, arrow_width, arrow_height);
973 }
974
975 static gint
976 gtk_range_expose (GtkWidget      *widget,
977                   GdkEventExpose *event)
978 {
979   GtkRange *range;
980   gboolean sensitive;
981   GtkStateType state;
982   GdkRectangle expose_area;     /* Relative to widget->allocation */
983   GdkRectangle area;
984   gint focus_line_width = 0;
985   gint focus_padding = 0;
986
987   range = GTK_RANGE (widget);
988
989   if (GTK_WIDGET_CAN_FOCUS (range))
990     {
991       gtk_widget_style_get (GTK_WIDGET (range),
992                             "focus-line-width", &focus_line_width,
993                             "focus-padding", &focus_padding,
994                             NULL);
995     }
996   
997   expose_area = event->area;
998   expose_area.x -= widget->allocation.x;
999   expose_area.y -= widget->allocation.y;
1000   
1001   gtk_range_calc_layout (range, range->adjustment->value);
1002
1003   sensitive = GTK_WIDGET_IS_SENSITIVE (widget);
1004
1005   /* Just to be confusing, we draw the trough for the whole
1006    * range rectangle, not the trough rectangle (the trough
1007    * rectangle is just for hit detection)
1008    */
1009   /* The gdk_rectangle_intersect is more to get the right
1010    * clip region (limited to range_rect) than for efficiency
1011    */
1012   if (gdk_rectangle_intersect (&expose_area, &range->range_rect,
1013                                &area))
1014     {
1015       area.x += widget->allocation.x;
1016       area.y += widget->allocation.y;
1017       
1018       gtk_paint_box (widget->style,
1019                      widget->window,
1020                      sensitive ? GTK_STATE_ACTIVE : GTK_STATE_INSENSITIVE,
1021                      GTK_SHADOW_IN,
1022                      &area, GTK_WIDGET(range), "trough",
1023                      widget->allocation.x + range->range_rect.x + focus_line_width + focus_padding,
1024                      widget->allocation.y + range->range_rect.y + focus_line_width + focus_padding,
1025                      range->range_rect.width - 2 * (focus_line_width + focus_padding),
1026                      range->range_rect.height - 2 * (focus_line_width + focus_padding));
1027       
1028                  
1029       if (sensitive &&
1030           GTK_WIDGET_HAS_FOCUS (range))
1031         gtk_paint_focus (widget->style, widget->window, GTK_WIDGET_STATE (widget),
1032                          &area, widget, "trough",
1033                          widget->allocation.x + range->range_rect.x,
1034                          widget->allocation.y + range->range_rect.y,
1035                          range->range_rect.width,
1036                          range->range_rect.height);
1037     }
1038
1039   if (!sensitive)
1040     state = GTK_STATE_INSENSITIVE;
1041   else if (range->layout->mouse_location == MOUSE_SLIDER)
1042     state = GTK_STATE_PRELIGHT;
1043   else
1044     state = GTK_STATE_NORMAL;
1045
1046   if (gdk_rectangle_intersect (&expose_area,
1047                                &range->layout->slider,
1048                                &area))
1049     {
1050       area.x += widget->allocation.x;
1051       area.y += widget->allocation.y;
1052       
1053       gtk_paint_slider (widget->style,
1054                         widget->window,
1055                         state,
1056                         GTK_SHADOW_OUT,
1057                         &area,
1058                         widget,
1059                         GTK_RANGE_GET_CLASS (range)->slider_detail,
1060                         widget->allocation.x + range->layout->slider.x,
1061                         widget->allocation.y + range->layout->slider.y,
1062                         range->layout->slider.width,
1063                         range->layout->slider.height,
1064                         range->orientation);
1065     }
1066   
1067   if (range->has_stepper_a)
1068     draw_stepper (range, &range->layout->stepper_a,
1069                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
1070                   range->layout->grab_location == MOUSE_STEPPER_A,
1071                   range->layout->mouse_location == MOUSE_STEPPER_A,
1072                   &expose_area);
1073
1074   if (range->has_stepper_b)
1075     draw_stepper (range, &range->layout->stepper_b,
1076                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
1077                   range->layout->grab_location == MOUSE_STEPPER_B,
1078                   range->layout->mouse_location == MOUSE_STEPPER_B,
1079                   &expose_area);
1080
1081   if (range->has_stepper_c)
1082     draw_stepper (range, &range->layout->stepper_c,
1083                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_UP : GTK_ARROW_LEFT,
1084                   range->layout->grab_location == MOUSE_STEPPER_C,
1085                   range->layout->mouse_location == MOUSE_STEPPER_C,
1086                   &expose_area);
1087
1088   if (range->has_stepper_d)
1089     draw_stepper (range, &range->layout->stepper_d,
1090                   range->orientation == GTK_ORIENTATION_VERTICAL ? GTK_ARROW_DOWN : GTK_ARROW_RIGHT,
1091                   range->layout->grab_location == MOUSE_STEPPER_D,
1092                   range->layout->mouse_location == MOUSE_STEPPER_D,
1093                   &expose_area);
1094   
1095   return FALSE;
1096 }
1097
1098 static void
1099 range_grab_add (GtkRange      *range,
1100                 MouseLocation  location,
1101                 gint           button)
1102 {
1103   /* we don't actually gtk_grab, since a button is down */
1104
1105   gtk_grab_add (GTK_WIDGET (range));
1106   
1107   range->layout->grab_location = location;
1108   range->layout->grab_button = button;
1109   
1110   if (gtk_range_update_mouse_location (range))
1111     gtk_widget_queue_draw (GTK_WIDGET (range));
1112 }
1113
1114 static void
1115 range_grab_remove (GtkRange *range)
1116 {
1117   gtk_grab_remove (GTK_WIDGET (range));
1118   
1119   range->layout->grab_location = MOUSE_OUTSIDE;
1120   range->layout->grab_button = 0;
1121
1122   if (gtk_range_update_mouse_location (range))
1123     gtk_widget_queue_draw (GTK_WIDGET (range));
1124 }
1125
1126 static GtkScrollType
1127 range_get_scroll_for_grab (GtkRange      *range)
1128
1129   gboolean invert;
1130
1131   invert = should_invert (range);
1132   switch (range->layout->grab_location)
1133     {
1134       /* Backward stepper */
1135     case MOUSE_STEPPER_A:
1136     case MOUSE_STEPPER_C:
1137       switch (range->layout->grab_button)
1138         {
1139         case 1:
1140           return invert ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
1141           break;
1142         case 2:
1143           return invert ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_PAGE_BACKWARD;
1144           break;
1145         case 3:
1146           return invert ? GTK_SCROLL_END : GTK_SCROLL_START;
1147           break;
1148         }
1149       break;
1150
1151       /* Forward stepper */
1152     case MOUSE_STEPPER_B:
1153     case MOUSE_STEPPER_D:
1154       switch (range->layout->grab_button)
1155         {
1156         case 1:
1157           return invert ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
1158           break;
1159         case 2:
1160           return invert ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_PAGE_FORWARD;
1161           break;
1162         case 3:
1163           return invert ? GTK_SCROLL_START : GTK_SCROLL_END;
1164           break;
1165        }
1166       break;
1167
1168       /* In the trough */
1169     case MOUSE_TROUGH:
1170       {
1171         if (range->trough_click_forward)
1172           return GTK_SCROLL_PAGE_FORWARD;
1173         else
1174           return GTK_SCROLL_PAGE_BACKWARD;
1175       }
1176       break;
1177
1178     case MOUSE_OUTSIDE:
1179     case MOUSE_SLIDER:
1180     case MOUSE_WIDGET:
1181       break;
1182     }
1183
1184   return GTK_SCROLL_NONE;
1185 }
1186
1187 static gdouble
1188 coord_to_value (GtkRange *range,
1189                 gint      coord)
1190 {
1191   gdouble frac;
1192   gdouble value;
1193   
1194   if (range->orientation == GTK_ORIENTATION_VERTICAL)
1195     if (range->layout->trough.height == range->layout->slider.height)
1196       frac = 1.0;
1197     else 
1198       frac = ((coord - range->layout->trough.y) /
1199               (gdouble) (range->layout->trough.height - range->layout->slider.height));
1200   else
1201     if (range->layout->trough.width == range->layout->slider.width)
1202       frac = 1.0;
1203     else
1204       frac = ((coord - range->layout->trough.x) /
1205               (gdouble) (range->layout->trough.width - range->layout->slider.width));
1206
1207   if (should_invert (range))
1208     frac = 1.0 - frac;
1209   
1210   value = range->adjustment->lower +
1211     frac * (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size);
1212
1213   return value;
1214 }
1215
1216 static gint
1217 gtk_range_button_press (GtkWidget      *widget,
1218                         GdkEventButton *event)
1219 {
1220   GtkRange *range = GTK_RANGE (widget);
1221   
1222   if (!GTK_WIDGET_HAS_FOCUS (widget))
1223     gtk_widget_grab_focus (widget);
1224
1225   /* ignore presses when we're already doing something else. */
1226   if (range->layout->grab_location != MOUSE_OUTSIDE)
1227     return FALSE;
1228
1229   range->layout->mouse_x = event->x;
1230   range->layout->mouse_y = event->y;
1231   if (gtk_range_update_mouse_location (range))
1232     gtk_widget_queue_draw (widget);
1233     
1234   if (range->layout->mouse_location == MOUSE_TROUGH  &&
1235       event->button == 1)
1236     {
1237       /* button 1 steps by page increment, as with button 2 on a stepper
1238        */
1239       GtkScrollType scroll;
1240       gdouble click_value;
1241       
1242       click_value = coord_to_value (range,
1243                                     range->orientation == GTK_ORIENTATION_VERTICAL ?
1244                                     event->y : event->x);
1245       
1246       range->trough_click_forward = click_value > range->adjustment->value;
1247       range_grab_add (range, MOUSE_TROUGH, event->button);
1248       
1249       scroll = range_get_scroll_for_grab (range);
1250       
1251       gtk_range_add_step_timer (range, scroll);
1252
1253       return TRUE;
1254     }
1255   else if ((range->layout->mouse_location == MOUSE_STEPPER_A ||
1256             range->layout->mouse_location == MOUSE_STEPPER_B ||
1257             range->layout->mouse_location == MOUSE_STEPPER_C ||
1258             range->layout->mouse_location == MOUSE_STEPPER_D) &&
1259            (event->button == 1 || event->button == 2 || event->button == 3))
1260     {
1261       GdkRectangle *stepper_area;
1262       GtkScrollType scroll;
1263       
1264       range_grab_add (range, range->layout->mouse_location, event->button);
1265
1266       stepper_area = get_area (range, range->layout->mouse_location);
1267       gtk_widget_queue_draw_area (widget,
1268                                   widget->allocation.x + stepper_area->x,
1269                                   widget->allocation.y + stepper_area->y,
1270                                   stepper_area->width,
1271                                   stepper_area->height);
1272
1273       scroll = range_get_scroll_for_grab (range);
1274       if (scroll != GTK_SCROLL_NONE)
1275         gtk_range_add_step_timer (range, scroll);
1276       
1277       return TRUE;
1278     }
1279   else if ((range->layout->mouse_location == MOUSE_TROUGH &&
1280             event->button == 2) ||
1281            range->layout->mouse_location == MOUSE_SLIDER)
1282     {
1283       gboolean need_value_update = FALSE;
1284
1285       /* Any button can be used to drag the slider, but you can start
1286        * dragging the slider with a trough click using button 2;
1287        * On button 2 press, we warp the slider to mouse position,
1288        * then begin the slider drag.
1289        */
1290       if (event->button == 2)
1291         {
1292           gdouble slider_low_value, slider_high_value, new_value;
1293           
1294           slider_high_value =
1295             coord_to_value (range,
1296                             range->orientation == GTK_ORIENTATION_VERTICAL ?
1297                             event->y : event->x);
1298           slider_low_value =
1299             coord_to_value (range,
1300                             range->orientation == GTK_ORIENTATION_VERTICAL ?
1301                             event->y - range->layout->slider.height :
1302                             event->x - range->layout->slider.width);
1303           
1304           /* compute new value for warped slider */
1305           new_value = slider_low_value + (slider_high_value - slider_low_value) / 2;
1306
1307           /* recalc slider, so we can set slide_initial_slider_position
1308            * properly
1309            */
1310           range->need_recalc = TRUE;
1311           gtk_range_calc_layout (range, new_value);
1312
1313           /* defer adjustment updates to update_slider_position() in order
1314            * to keep pixel quantisation
1315            */
1316           need_value_update = TRUE;
1317         }
1318       
1319       if (range->orientation == GTK_ORIENTATION_VERTICAL)
1320         {
1321           range->slide_initial_slider_position = range->layout->slider.y;
1322           range->slide_initial_coordinate = event->y;
1323         }
1324       else
1325         {
1326           range->slide_initial_slider_position = range->layout->slider.x;
1327           range->slide_initial_coordinate = event->x;
1328         }
1329
1330       if (need_value_update)
1331         update_slider_position (range, event->x, event->y);
1332
1333       range_grab_add (range, MOUSE_SLIDER, event->button);
1334       
1335       return TRUE;
1336     }
1337   
1338   return FALSE;
1339 }
1340
1341 /* During a slide, move the slider as required given new mouse position */
1342 static void
1343 update_slider_position (GtkRange *range,
1344                         gint      mouse_x,
1345                         gint      mouse_y)
1346 {
1347   gint delta;
1348   gint c;
1349   gdouble new_value;
1350   gboolean handled;
1351
1352   if (range->orientation == GTK_ORIENTATION_VERTICAL)
1353     delta = mouse_y - range->slide_initial_coordinate;
1354   else
1355     delta = mouse_x - range->slide_initial_coordinate;
1356
1357   c = range->slide_initial_slider_position + delta;
1358
1359   new_value = coord_to_value (range, c);
1360   
1361   g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value,
1362                  &handled);
1363 }
1364
1365 static void stop_scrolling (GtkRange *range)
1366 {
1367   range_grab_remove (range);
1368   gtk_range_remove_step_timer (range);
1369   /* Flush any pending discontinuous/delayed updates */
1370   gtk_range_update_value (range);
1371   
1372   /* Just be lazy about this, if we scrolled it will all redraw anyway,
1373    * so no point optimizing the button deactivate case
1374    */
1375   gtk_widget_queue_draw (GTK_WIDGET (range));
1376 }
1377
1378 static gint
1379 gtk_range_button_release (GtkWidget      *widget,
1380                           GdkEventButton *event)
1381 {
1382   GtkRange *range = GTK_RANGE (widget);
1383
1384   if (event->window == range->event_window)
1385     {
1386       range->layout->mouse_x = event->x;
1387       range->layout->mouse_y = event->y;
1388     }
1389   else
1390     {
1391       gdk_window_get_pointer (range->event_window,
1392                               &range->layout->mouse_x,
1393                               &range->layout->mouse_y,
1394                               NULL);
1395     }
1396   
1397   if (range->layout->grab_button == event->button)
1398     {
1399       if (range->layout->grab_location == MOUSE_SLIDER)
1400         update_slider_position (range, range->layout->mouse_x, range->layout->mouse_y);
1401
1402       stop_scrolling (range);
1403       
1404       return TRUE;
1405     }
1406
1407   return FALSE;
1408 }
1409
1410 /**
1411  * _gtk_range_get_wheel_delta:
1412  * @range: a #GtkRange
1413  * @direction: A #GdkScrollDirection
1414  * 
1415  * Returns a good step value for the mouse wheel.
1416  * 
1417  * Return value: A good step value for the mouse wheel. 
1418  * 
1419  * Since: 2.4
1420  **/
1421 gdouble
1422 _gtk_range_get_wheel_delta (GtkRange           *range,
1423                             GdkScrollDirection  direction)
1424 {
1425   GtkAdjustment *adj = range->adjustment;
1426   gdouble delta;
1427
1428   if (GTK_IS_SCROLLBAR (range))
1429     delta = pow (adj->page_size, 2.0 / 3.0);
1430   else
1431     delta = adj->step_increment * 2;
1432   
1433   if (direction == GDK_SCROLL_UP ||
1434       direction == GDK_SCROLL_LEFT)
1435     delta = - delta;
1436   
1437   if (range->inverted)
1438     delta = - delta;
1439
1440   return delta;
1441 }
1442       
1443 static gint
1444 gtk_range_scroll_event (GtkWidget      *widget,
1445                         GdkEventScroll *event)
1446 {
1447   GtkRange *range = GTK_RANGE (widget);
1448
1449   if (GTK_WIDGET_REALIZED (range))
1450     {
1451       GtkAdjustment *adj = GTK_RANGE (range)->adjustment;
1452       gdouble delta;
1453       gboolean handled;
1454
1455       delta = _gtk_range_get_wheel_delta (range, event->direction);
1456
1457       g_signal_emit (range, signals[CHANGE_VALUE], 0,
1458                      GTK_SCROLL_JUMP, adj->value + delta,
1459                      &handled);
1460       
1461       /* Policy DELAYED makes sense with scroll events,
1462        * but DISCONTINUOUS doesn't, so we update immediately
1463        * for DISCONTINUOUS
1464        */
1465       if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
1466         gtk_range_update_value (range);
1467     }
1468
1469   return TRUE;
1470 }
1471
1472 static gint
1473 gtk_range_motion_notify (GtkWidget      *widget,
1474                          GdkEventMotion *event)
1475 {
1476   GtkRange *range;
1477   gint x, y;
1478
1479   range = GTK_RANGE (widget);
1480
1481   gdk_window_get_pointer (range->event_window, &x, &y, NULL);
1482   
1483   range->layout->mouse_x = x;
1484   range->layout->mouse_y = y;
1485
1486   if (gtk_range_update_mouse_location (range))
1487     gtk_widget_queue_draw (widget);
1488
1489   if (range->layout->grab_location == MOUSE_SLIDER)
1490     update_slider_position (range, x, y);
1491
1492   /* We handled the event if the mouse was in the range_rect */
1493   return range->layout->mouse_location != MOUSE_OUTSIDE;
1494 }
1495
1496 static gint
1497 gtk_range_enter_notify (GtkWidget        *widget,
1498                         GdkEventCrossing *event)
1499 {
1500   GtkRange *range = GTK_RANGE (widget);
1501
1502   range->layout->mouse_x = event->x;
1503   range->layout->mouse_y = event->y;
1504
1505   if (gtk_range_update_mouse_location (range))
1506     gtk_widget_queue_draw (widget);
1507   
1508   return TRUE;
1509 }
1510
1511 static gint
1512 gtk_range_leave_notify (GtkWidget        *widget,
1513                         GdkEventCrossing *event)
1514 {
1515   GtkRange *range = GTK_RANGE (widget);
1516
1517   range->layout->mouse_x = -1;
1518   range->layout->mouse_y = -1;
1519
1520   if (gtk_range_update_mouse_location (range))
1521     gtk_widget_queue_draw (widget);
1522   
1523   return TRUE;
1524 }
1525
1526 static void
1527 gtk_range_grab_notify (GtkWidget *widget,
1528                        gboolean   was_grabbed)
1529 {
1530   if (!was_grabbed)
1531     stop_scrolling (GTK_RANGE (widget));
1532 }
1533
1534 static void
1535 gtk_range_state_changed (GtkWidget    *widget,
1536                          GtkStateType  previous_state)
1537 {
1538   if (!GTK_WIDGET_IS_SENSITIVE (widget)) 
1539     stop_scrolling (GTK_RANGE (widget));
1540 }
1541
1542 static void
1543 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
1544                               gpointer       data)
1545 {
1546   GtkRange *range = GTK_RANGE (data);
1547
1548   range->need_recalc = TRUE;
1549   gtk_widget_queue_draw (GTK_WIDGET (range));
1550
1551   /* Note that we don't round off to range->round_digits here.
1552    * that's because it's really broken to change a value
1553    * in response to a change signal on that value; round_digits
1554    * is therefore defined to be a filter on what the GtkRange
1555    * can input into the adjustment, not a filter that the GtkRange
1556    * will enforce on the adjustment.
1557    */
1558 }
1559
1560 static void
1561 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
1562                                     gpointer       data)
1563 {
1564   GtkRange *range = GTK_RANGE (data);
1565
1566   range->need_recalc = TRUE;
1567
1568   gtk_widget_queue_draw (GTK_WIDGET (range));
1569   /* This is so we don't lag the widget being scrolled. */
1570   if (GTK_WIDGET_REALIZED (range))
1571     gdk_window_process_updates (GTK_WIDGET (range)->window, FALSE);
1572   
1573   /* Note that we don't round off to range->round_digits here.
1574    * that's because it's really broken to change a value
1575    * in response to a change signal on that value; round_digits
1576    * is therefore defined to be a filter on what the GtkRange
1577    * can input into the adjustment, not a filter that the GtkRange
1578    * will enforce on the adjustment.
1579    */
1580
1581   g_signal_emit (range, signals[VALUE_CHANGED], 0);
1582 }
1583
1584 static void
1585 gtk_range_style_set (GtkWidget *widget,
1586                      GtkStyle  *previous_style)
1587 {
1588   GtkRange *range = GTK_RANGE (widget);
1589
1590   range->need_recalc = TRUE;
1591
1592   (* GTK_WIDGET_CLASS (parent_class)->style_set) (widget, previous_style);
1593 }
1594
1595 static void
1596 step_back (GtkRange *range)
1597 {
1598   gdouble newval;
1599   gboolean handled;
1600   
1601   newval = range->adjustment->value - range->adjustment->step_increment;
1602   g_signal_emit (range, signals[CHANGE_VALUE], 0,
1603                  GTK_SCROLL_STEP_BACKWARD, newval, &handled);
1604 }
1605
1606 static void
1607 step_forward (GtkRange *range)
1608 {
1609   gdouble newval;
1610   gboolean handled;
1611
1612   newval = range->adjustment->value + range->adjustment->step_increment;
1613   g_signal_emit (range, signals[CHANGE_VALUE], 0,
1614                  GTK_SCROLL_STEP_FORWARD, newval, &handled);
1615 }
1616
1617
1618 static void
1619 page_back (GtkRange *range)
1620 {
1621   gdouble newval;
1622   gboolean handled;
1623
1624   newval = range->adjustment->value - range->adjustment->page_increment;
1625   g_signal_emit (range, signals[CHANGE_VALUE], 0,
1626                  GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
1627 }
1628
1629 static void
1630 page_forward (GtkRange *range)
1631 {
1632   gdouble newval;
1633   gboolean handled;
1634
1635   newval = range->adjustment->value + range->adjustment->page_increment;
1636   g_signal_emit (range, signals[CHANGE_VALUE], 0,
1637                  GTK_SCROLL_PAGE_FORWARD, newval, &handled);
1638 }
1639
1640 static void
1641 scroll_begin (GtkRange *range)
1642 {
1643   gboolean handled;
1644   g_signal_emit (range, signals[CHANGE_VALUE], 0,
1645                  GTK_SCROLL_START, range->adjustment->lower,
1646                  &handled);
1647 }
1648
1649 static void
1650 scroll_end (GtkRange *range)
1651 {
1652   gdouble newval;
1653   gboolean handled;
1654
1655   newval = range->adjustment->upper - range->adjustment->page_size;
1656   g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_END, newval,
1657                  &handled);
1658 }
1659
1660 static void
1661 gtk_range_scroll (GtkRange     *range,
1662                   GtkScrollType scroll)
1663 {
1664   switch (scroll)
1665     {
1666     case GTK_SCROLL_STEP_LEFT:
1667       if (should_invert (range))
1668         step_forward (range);
1669       else
1670         step_back (range);
1671       break;
1672                     
1673     case GTK_SCROLL_STEP_UP:
1674       if (should_invert (range))
1675         step_forward (range);
1676       else
1677         step_back (range);
1678       break;
1679
1680     case GTK_SCROLL_STEP_RIGHT:
1681       if (should_invert (range))
1682         step_back (range);
1683       else
1684         step_forward (range);
1685       break;
1686                     
1687     case GTK_SCROLL_STEP_DOWN:
1688       if (should_invert (range))
1689         step_back (range);
1690       else
1691         step_forward (range);
1692       break;
1693                   
1694     case GTK_SCROLL_STEP_BACKWARD:
1695       step_back (range);
1696       break;
1697                   
1698     case GTK_SCROLL_STEP_FORWARD:
1699       step_forward (range);
1700       break;
1701
1702     case GTK_SCROLL_PAGE_LEFT:
1703       if (should_invert (range))
1704         page_forward (range);
1705       else
1706         page_back (range);
1707       break;
1708                     
1709     case GTK_SCROLL_PAGE_UP:
1710       if (should_invert (range))
1711         page_forward (range);
1712       else
1713         page_back (range);
1714       break;
1715
1716     case GTK_SCROLL_PAGE_RIGHT:
1717       if (should_invert (range))
1718         page_back (range);
1719       else
1720         page_forward (range);
1721       break;
1722                     
1723     case GTK_SCROLL_PAGE_DOWN:
1724       if (should_invert (range))
1725         page_back (range);
1726       else
1727         page_forward (range);
1728       break;
1729                   
1730     case GTK_SCROLL_PAGE_BACKWARD:
1731       page_back (range);
1732       break;
1733                   
1734     case GTK_SCROLL_PAGE_FORWARD:
1735       page_forward (range);
1736       break;
1737
1738     case GTK_SCROLL_START:
1739       scroll_begin (range);
1740       break;
1741
1742     case GTK_SCROLL_END:
1743       scroll_end (range);
1744       break;
1745
1746     case GTK_SCROLL_JUMP:
1747       /* Used by CList, range doesn't use it. */
1748       break;
1749
1750     case GTK_SCROLL_NONE:
1751       break;
1752     }
1753 }
1754
1755 static void
1756 gtk_range_move_slider (GtkRange     *range,
1757                        GtkScrollType scroll)
1758 {
1759   gtk_range_scroll (range, scroll);
1760
1761   /* Policy DELAYED makes sense with key events,
1762    * but DISCONTINUOUS doesn't, so we update immediately
1763    * for DISCONTINUOUS
1764    */
1765   if (range->update_policy == GTK_UPDATE_DISCONTINUOUS)
1766     gtk_range_update_value (range);
1767 }
1768
1769 static void
1770 gtk_range_get_props (GtkRange  *range,
1771                      gint      *slider_width,
1772                      gint      *stepper_size,
1773                      gint      *trough_border,
1774                      gint      *stepper_spacing,
1775                      gint      *arrow_displacement_x,
1776                      gint      *arrow_displacement_y)
1777 {
1778   GtkWidget *widget =  GTK_WIDGET (range);
1779   gint tmp_slider_width, tmp_stepper_size, tmp_trough_border, tmp_stepper_spacing;
1780   gint tmp_arrow_displacement_x, tmp_arrow_displacement_y;
1781   
1782   gtk_widget_style_get (widget,
1783                         "slider_width", &tmp_slider_width,
1784                         "trough_border", &tmp_trough_border,
1785                         "stepper_size", &tmp_stepper_size,
1786                         "stepper_spacing", &tmp_stepper_spacing,
1787                         "arrow_displacement_x", &tmp_arrow_displacement_x,
1788                         "arrow_displacement_y", &tmp_arrow_displacement_y,
1789                         NULL);
1790   
1791   if (GTK_WIDGET_CAN_FOCUS (range))
1792     {
1793       gint focus_line_width;
1794       gint focus_padding;
1795       
1796       gtk_widget_style_get (GTK_WIDGET (range),
1797                             "focus-line-width", &focus_line_width,
1798                             "focus-padding", &focus_padding,
1799                             NULL);
1800       
1801       tmp_trough_border += focus_line_width + focus_padding;
1802     }
1803   
1804   if (slider_width)
1805     *slider_width = tmp_slider_width;
1806
1807   if (trough_border)
1808     *trough_border = tmp_trough_border;
1809
1810   if (stepper_size)
1811     *stepper_size = tmp_stepper_size;
1812
1813   if (stepper_spacing)
1814     *stepper_spacing = tmp_stepper_spacing;
1815
1816   if (arrow_displacement_x)
1817     *arrow_displacement_x = tmp_arrow_displacement_x;
1818
1819   if (arrow_displacement_y)
1820     *arrow_displacement_y = tmp_arrow_displacement_y;
1821 }
1822
1823 #define POINT_IN_RECT(xcoord, ycoord, rect) \
1824  ((xcoord) >= (rect).x &&                   \
1825   (xcoord) <  ((rect).x + (rect).width) &&  \
1826   (ycoord) >= (rect).y &&                   \
1827   (ycoord) <  ((rect).y + (rect).height))
1828
1829 /* Update mouse location, return TRUE if it changes */
1830 static gboolean
1831 gtk_range_update_mouse_location (GtkRange *range)
1832 {
1833   gint x, y;
1834   MouseLocation old;
1835   GtkWidget *widget;
1836
1837   widget = GTK_WIDGET (range);
1838   
1839   old = range->layout->mouse_location;
1840   
1841   x = range->layout->mouse_x;
1842   y = range->layout->mouse_y;
1843
1844   if (range->layout->grab_location != MOUSE_OUTSIDE)
1845     range->layout->mouse_location = range->layout->grab_location;
1846   else if (POINT_IN_RECT (x, y, range->layout->stepper_a))
1847     range->layout->mouse_location = MOUSE_STEPPER_A;
1848   else if (POINT_IN_RECT (x, y, range->layout->stepper_b))
1849     range->layout->mouse_location = MOUSE_STEPPER_B;
1850   else if (POINT_IN_RECT (x, y, range->layout->stepper_c))
1851     range->layout->mouse_location = MOUSE_STEPPER_C;
1852   else if (POINT_IN_RECT (x, y, range->layout->stepper_d))
1853     range->layout->mouse_location = MOUSE_STEPPER_D;
1854   else if (POINT_IN_RECT (x, y, range->layout->slider))
1855     range->layout->mouse_location = MOUSE_SLIDER;
1856   else if (POINT_IN_RECT (x, y, range->layout->trough))
1857     range->layout->mouse_location = MOUSE_TROUGH;
1858   else if (POINT_IN_RECT (x, y, widget->allocation))
1859     range->layout->mouse_location = MOUSE_WIDGET;
1860   else
1861     range->layout->mouse_location = MOUSE_OUTSIDE;
1862
1863   return old != range->layout->mouse_location;
1864 }
1865
1866 /* Clamp rect, border inside widget->allocation, such that we prefer
1867  * to take space from border not rect in all directions, and prefer to
1868  * give space to border over rect in one direction.
1869  */
1870 static void
1871 clamp_dimensions (GtkWidget    *widget,
1872                   GdkRectangle *rect,
1873                   GtkBorder    *border,
1874                   gboolean      border_expands_horizontally)
1875 {
1876   gint extra, shortage;
1877   
1878   g_return_if_fail (rect->x == 0);
1879   g_return_if_fail (rect->y == 0);  
1880   g_return_if_fail (rect->width >= 0);
1881   g_return_if_fail (rect->height >= 0);
1882
1883   /* Width */
1884   
1885   extra = widget->allocation.width - border->left - border->right - rect->width;
1886   if (extra > 0)
1887     {
1888       if (border_expands_horizontally)
1889         {
1890           border->left += extra / 2;
1891           border->right += extra / 2 + extra % 2;
1892         }
1893       else
1894         {
1895           rect->width += extra;
1896         }
1897     }
1898   
1899   /* See if we can fit rect, if not kill the border */
1900   shortage = rect->width - widget->allocation.width;
1901   if (shortage > 0)
1902     {
1903       rect->width = widget->allocation.width;
1904       /* lose the border */
1905       border->left = 0;
1906       border->right = 0;
1907     }
1908   else
1909     {
1910       /* See if we can fit rect with borders */
1911       shortage = rect->width + border->left + border->right -
1912         widget->allocation.width;
1913       if (shortage > 0)
1914         {
1915           /* Shrink borders */
1916           border->left -= shortage / 2;
1917           border->right -= shortage / 2 + shortage % 2;
1918         }
1919     }
1920
1921   /* Height */
1922   
1923   extra = widget->allocation.height - border->top - border->bottom - rect->height;
1924   if (extra > 0)
1925     {
1926       if (border_expands_horizontally)
1927         {
1928           /* don't expand border vertically */
1929           rect->height += extra;
1930         }
1931       else
1932         {
1933           border->top += extra / 2;
1934           border->bottom += extra / 2 + extra % 2;
1935         }
1936     }
1937   
1938   /* See if we can fit rect, if not kill the border */
1939   shortage = rect->height - widget->allocation.height;
1940   if (shortage > 0)
1941     {
1942       rect->height = widget->allocation.height;
1943       /* lose the border */
1944       border->top = 0;
1945       border->bottom = 0;
1946     }
1947   else
1948     {
1949       /* See if we can fit rect with borders */
1950       shortage = rect->height + border->top + border->bottom -
1951         widget->allocation.height;
1952       if (shortage > 0)
1953         {
1954           /* Shrink borders */
1955           border->top -= shortage / 2;
1956           border->bottom -= shortage / 2 + shortage % 2;
1957         }
1958     }
1959 }
1960
1961 static void
1962 gtk_range_calc_request (GtkRange      *range,
1963                         gint           slider_width,
1964                         gint           stepper_size,
1965                         gint           trough_border,
1966                         gint           stepper_spacing,
1967                         GdkRectangle  *range_rect,
1968                         GtkBorder     *border,
1969                         gint          *n_steppers_p,
1970                         gint          *slider_length_p)
1971 {
1972   gint slider_length;
1973   gint n_steppers;
1974
1975   border->left = 0;
1976   border->right = 0;
1977   border->top = 0;
1978   border->bottom = 0;
1979
1980   if (GTK_RANGE_GET_CLASS (range)->get_range_border)
1981     (* GTK_RANGE_GET_CLASS (range)->get_range_border) (range, border);
1982   
1983   n_steppers = 0;
1984   if (range->has_stepper_a)
1985     n_steppers += 1;
1986   if (range->has_stepper_b)
1987     n_steppers += 1;
1988   if (range->has_stepper_c)
1989     n_steppers += 1;
1990   if (range->has_stepper_d)
1991     n_steppers += 1;
1992
1993   slider_length = range->min_slider_size;
1994
1995   range_rect->x = 0;
1996   range_rect->y = 0;
1997   
1998   /* We never expand to fill available space in the small dimension
1999    * (i.e. vertical scrollbars are always a fixed width)
2000    */
2001   if (range->orientation == GTK_ORIENTATION_VERTICAL)
2002     {
2003       range_rect->width = trough_border * 2 + slider_width;
2004       range_rect->height = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length;
2005     }
2006   else
2007     {
2008       range_rect->width = stepper_size * n_steppers + stepper_spacing * 2 + trough_border * 2 + slider_length;
2009       range_rect->height = trough_border * 2 + slider_width;
2010     }
2011
2012   if (n_steppers_p)
2013     *n_steppers_p = n_steppers;
2014
2015   if (slider_length_p)
2016     *slider_length_p = slider_length;
2017 }
2018
2019 static void
2020 gtk_range_calc_layout (GtkRange *range,
2021                        gdouble   adjustment_value)
2022 {
2023   gint slider_width, stepper_size, trough_border, stepper_spacing;
2024   gint slider_length;
2025   GtkBorder border;
2026   gint n_steppers;
2027   GdkRectangle range_rect;
2028   GtkRangeLayout *layout;
2029   GtkWidget *widget;
2030   
2031   if (!range->need_recalc)
2032     return;
2033
2034   /* If we have a too-small allocation, we prefer the steppers over
2035    * the trough/slider, probably the steppers are a more useful
2036    * feature in small spaces.
2037    *
2038    * Also, we prefer to draw the range itself rather than the border
2039    * areas if there's a conflict, since the borders will be decoration
2040    * not controls. Though this depends on subclasses cooperating by
2041    * not drawing on range->range_rect.
2042    */
2043
2044   widget = GTK_WIDGET (range);
2045   layout = range->layout;
2046   
2047   gtk_range_get_props (range,
2048                        &slider_width, &stepper_size, &trough_border, &stepper_spacing,
2049                        NULL, NULL);
2050
2051   gtk_range_calc_request (range, 
2052                           slider_width, stepper_size, trough_border, stepper_spacing,
2053                           &range_rect, &border, &n_steppers, &slider_length);
2054   
2055   /* We never expand to fill available space in the small dimension
2056    * (i.e. vertical scrollbars are always a fixed width)
2057    */
2058   if (range->orientation == GTK_ORIENTATION_VERTICAL)
2059     {
2060       clamp_dimensions (widget, &range_rect, &border, TRUE);
2061     }
2062   else
2063     {
2064       clamp_dimensions (widget, &range_rect, &border, FALSE);
2065     }
2066   
2067   range_rect.x = border.left;
2068   range_rect.y = border.top;
2069   
2070   range->range_rect = range_rect;
2071   
2072   if (range->orientation == GTK_ORIENTATION_VERTICAL)
2073     {
2074       gint stepper_width, stepper_height;
2075
2076       /* Steppers are the width of the range, and stepper_size in
2077        * height, or if we don't have enough height, divided equally
2078        * among available space.
2079        */
2080       stepper_width = range_rect.width - trough_border * 2;
2081
2082       if (stepper_width < 1)
2083         stepper_width = range_rect.width; /* screw the trough border */
2084
2085       if (n_steppers == 0)
2086         stepper_height = 0; /* avoid divide by n_steppers */
2087       else
2088         stepper_height = MIN (stepper_size, (range_rect.height / n_steppers));
2089
2090       /* Stepper A */
2091       
2092       layout->stepper_a.x = range_rect.x + trough_border;
2093       layout->stepper_a.y = range_rect.y + trough_border;
2094
2095       if (range->has_stepper_a)
2096         {
2097           layout->stepper_a.width = stepper_width;
2098           layout->stepper_a.height = stepper_height;
2099         }
2100       else
2101         {
2102           layout->stepper_a.width = 0;
2103           layout->stepper_a.height = 0;
2104         }
2105
2106       /* Stepper B */
2107       
2108       layout->stepper_b.x = layout->stepper_a.x;
2109       layout->stepper_b.y = layout->stepper_a.y + layout->stepper_a.height;
2110
2111       if (range->has_stepper_b)
2112         {
2113           layout->stepper_b.width = stepper_width;
2114           layout->stepper_b.height = stepper_height;
2115         }
2116       else
2117         {
2118           layout->stepper_b.width = 0;
2119           layout->stepper_b.height = 0;
2120         }
2121
2122       /* Stepper D */
2123
2124       if (range->has_stepper_d)
2125         {
2126           layout->stepper_d.width = stepper_width;
2127           layout->stepper_d.height = stepper_height;
2128         }
2129       else
2130         {
2131           layout->stepper_d.width = 0;
2132           layout->stepper_d.height = 0;
2133         }
2134       
2135       layout->stepper_d.x = layout->stepper_a.x;
2136       layout->stepper_d.y = range_rect.y + range_rect.height - layout->stepper_d.height - trough_border;
2137
2138       /* Stepper C */
2139
2140       if (range->has_stepper_c)
2141         {
2142           layout->stepper_c.width = stepper_width;
2143           layout->stepper_c.height = stepper_height;
2144         }
2145       else
2146         {
2147           layout->stepper_c.width = 0;
2148           layout->stepper_c.height = 0;
2149         }
2150       
2151       layout->stepper_c.x = layout->stepper_a.x;
2152       layout->stepper_c.y = layout->stepper_d.y - layout->stepper_c.height;
2153
2154       /* Now the trough is the remaining space between steppers B and C,
2155        * if any
2156        */
2157       layout->trough.x = range_rect.x;
2158       layout->trough.y = layout->stepper_b.y + layout->stepper_b.height;
2159       layout->trough.width = range_rect.width;
2160       layout->trough.height = layout->stepper_c.y - (layout->stepper_b.y + layout->stepper_b.height);
2161       
2162       /* Slider fits into the trough, with stepper_spacing on either side,
2163        * and the size/position based on the adjustment or fixed, depending.
2164        */
2165       layout->slider.x = layout->trough.x + trough_border;
2166       layout->slider.width = layout->trough.width - trough_border * 2;
2167
2168       /* Compute slider position/length */
2169       {
2170         gint y, bottom, top, height;
2171         
2172         top = layout->trough.y + stepper_spacing;
2173         bottom = layout->trough.y + layout->trough.height - stepper_spacing;
2174         
2175         /* slider height is the fraction (page_size /
2176          * total_adjustment_range) times the trough height in pixels
2177          */
2178
2179         if (range->adjustment->upper - range->adjustment->lower != 0)
2180           height = ((bottom - top) * (range->adjustment->page_size /
2181                                        (range->adjustment->upper - range->adjustment->lower)));
2182         else
2183           height = range->min_slider_size;
2184         
2185         if (height < range->min_slider_size ||
2186             range->slider_size_fixed)
2187           height = range->min_slider_size;
2188         
2189         height = MIN (height, (layout->trough.height - stepper_spacing * 2));
2190         
2191         y = top;
2192         
2193         if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0)
2194           y += (bottom - top - height) * ((adjustment_value - range->adjustment->lower) /
2195                                           (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
2196         
2197         y = CLAMP (y, top, bottom);
2198         
2199         if (should_invert (range))
2200           y = bottom - (y - top + height);
2201         
2202         layout->slider.y = y;
2203         layout->slider.height = height;
2204
2205         /* These are publically exported */
2206         range->slider_start = layout->slider.y;
2207         range->slider_end = layout->slider.y + layout->slider.height;
2208       }
2209     }
2210   else
2211     {
2212       gint stepper_width, stepper_height;
2213
2214       /* Steppers are the height of the range, and stepper_size in
2215        * width, or if we don't have enough width, divided equally
2216        * among available space.
2217        */
2218       stepper_height = range_rect.height - trough_border * 2;
2219
2220       if (stepper_height < 1)
2221         stepper_height = range_rect.height; /* screw the trough border */
2222
2223       if (n_steppers == 0)
2224         stepper_width = 0; /* avoid divide by n_steppers */
2225       else
2226         stepper_width = MIN (stepper_size, (range_rect.width / n_steppers));
2227
2228       /* Stepper A */
2229       
2230       layout->stepper_a.x = range_rect.x + trough_border;
2231       layout->stepper_a.y = range_rect.y + trough_border;
2232
2233       if (range->has_stepper_a)
2234         {
2235           layout->stepper_a.width = stepper_width;
2236           layout->stepper_a.height = stepper_height;
2237         }
2238       else
2239         {
2240           layout->stepper_a.width = 0;
2241           layout->stepper_a.height = 0;
2242         }
2243
2244       /* Stepper B */
2245       
2246       layout->stepper_b.x = layout->stepper_a.x + layout->stepper_a.width;
2247       layout->stepper_b.y = layout->stepper_a.y;
2248
2249       if (range->has_stepper_b)
2250         {
2251           layout->stepper_b.width = stepper_width;
2252           layout->stepper_b.height = stepper_height;
2253         }
2254       else
2255         {
2256           layout->stepper_b.width = 0;
2257           layout->stepper_b.height = 0;
2258         }
2259
2260       /* Stepper D */
2261
2262       if (range->has_stepper_d)
2263         {
2264           layout->stepper_d.width = stepper_width;
2265           layout->stepper_d.height = stepper_height;
2266         }
2267       else
2268         {
2269           layout->stepper_d.width = 0;
2270           layout->stepper_d.height = 0;
2271         }
2272
2273       layout->stepper_d.x = range_rect.x + range_rect.width - layout->stepper_d.width - trough_border;
2274       layout->stepper_d.y = layout->stepper_a.y;
2275
2276
2277       /* Stepper C */
2278
2279       if (range->has_stepper_c)
2280         {
2281           layout->stepper_c.width = stepper_width;
2282           layout->stepper_c.height = stepper_height;
2283         }
2284       else
2285         {
2286           layout->stepper_c.width = 0;
2287           layout->stepper_c.height = 0;
2288         }
2289       
2290       layout->stepper_c.x = layout->stepper_d.x - layout->stepper_c.width;
2291       layout->stepper_c.y = layout->stepper_a.y;
2292
2293       /* Now the trough is the remaining space between steppers B and C,
2294        * if any
2295        */
2296       layout->trough.x = layout->stepper_b.x + layout->stepper_b.width;
2297       layout->trough.y = range_rect.y;
2298
2299       layout->trough.width = layout->stepper_c.x - (layout->stepper_b.x + layout->stepper_b.width);
2300       layout->trough.height = range_rect.height;
2301       
2302       /* Slider fits into the trough, with stepper_spacing on either side,
2303        * and the size/position based on the adjustment or fixed, depending.
2304        */
2305       layout->slider.y = layout->trough.y + trough_border;
2306       layout->slider.height = layout->trough.height - trough_border * 2;
2307
2308       /* Compute slider position/length */
2309       {
2310         gint x, left, right, width;
2311         
2312         left = layout->trough.x + stepper_spacing;
2313         right = layout->trough.x + layout->trough.width - stepper_spacing;
2314         
2315         /* slider width is the fraction (page_size /
2316          * total_adjustment_range) times the trough width in pixels
2317          */
2318         
2319         if (range->adjustment->upper - range->adjustment->lower != 0)
2320           width = ((right - left) * (range->adjustment->page_size /
2321                                    (range->adjustment->upper - range->adjustment->lower)));
2322         else
2323           width = range->min_slider_size;
2324         
2325         if (width < range->min_slider_size ||
2326             range->slider_size_fixed)
2327           width = range->min_slider_size;
2328         
2329         width = MIN (width, (layout->trough.width - stepper_spacing * 2));
2330         
2331         x = left;
2332         
2333         if (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size != 0)
2334           x += (right - left - width) * ((adjustment_value - range->adjustment->lower) /
2335                                          (range->adjustment->upper - range->adjustment->lower - range->adjustment->page_size));
2336         
2337         x = CLAMP (x, left, right);
2338         
2339         if (should_invert (range))
2340           x = right - (x - left + width);
2341         
2342         layout->slider.x = x;
2343         layout->slider.width = width;
2344
2345         /* These are publically exported */
2346         range->slider_start = layout->slider.x;
2347         range->slider_end = layout->slider.x + layout->slider.width;
2348       }
2349     }
2350   
2351   gtk_range_update_mouse_location (range);
2352 }
2353
2354 static GdkRectangle*
2355 get_area (GtkRange     *range,
2356           MouseLocation location)
2357 {
2358   switch (location)
2359     {
2360     case MOUSE_STEPPER_A:
2361       return &range->layout->stepper_a;
2362     case MOUSE_STEPPER_B:
2363       return &range->layout->stepper_b;
2364     case MOUSE_STEPPER_C:
2365       return &range->layout->stepper_c;
2366     case MOUSE_STEPPER_D:
2367       return &range->layout->stepper_d;
2368     case MOUSE_TROUGH:
2369       return &range->layout->trough;
2370     case MOUSE_SLIDER:
2371       return &range->layout->slider;
2372     case MOUSE_WIDGET:
2373     case MOUSE_OUTSIDE:
2374       break;
2375     }
2376
2377   g_warning (G_STRLOC": bug");
2378   return NULL;
2379 }
2380
2381 static gboolean
2382 gtk_range_real_change_value (GtkRange     *range,
2383                              GtkScrollType scroll,
2384                              gdouble       value)
2385 {
2386   /* potentially adjust the bounds _before we clamp */
2387   g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value);
2388
2389   value = CLAMP (value, range->adjustment->lower,
2390                  (range->adjustment->upper - range->adjustment->page_size));
2391
2392   if (range->round_digits >= 0)
2393     {
2394       gdouble power;
2395       gint i;
2396
2397       i = range->round_digits;
2398       power = 1;
2399       while (i--)
2400         power *= 10;
2401       
2402       value = floor ((value * power) + 0.5) / power;
2403     }
2404   
2405   if (range->adjustment->value != value)
2406     {
2407       range->need_recalc = TRUE;
2408
2409       gtk_widget_queue_draw (GTK_WIDGET (range));
2410       
2411       switch (range->update_policy)
2412         {
2413         case GTK_UPDATE_CONTINUOUS:
2414           gtk_adjustment_set_value (range->adjustment, value);
2415           break;
2416
2417           /* Delayed means we update after a period of inactivity */
2418         case GTK_UPDATE_DELAYED:
2419           gtk_range_reset_update_timer (range);
2420           /* FALL THRU */
2421
2422           /* Discontinuous means we update on button release */
2423         case GTK_UPDATE_DISCONTINUOUS:
2424           /* don't emit value_changed signal */
2425           range->adjustment->value = value;
2426           range->update_pending = TRUE;
2427           break;
2428         }
2429     }
2430   return FALSE;
2431 }
2432
2433 static void
2434 gtk_range_update_value (GtkRange *range)
2435 {
2436   gtk_range_remove_update_timer (range);
2437   
2438   if (range->update_pending)
2439     {
2440       gtk_adjustment_value_changed (range->adjustment);
2441
2442       range->update_pending = FALSE;
2443     }
2444 }
2445
2446 struct _GtkRangeStepTimer
2447 {
2448   guint timeout_id;
2449   GtkScrollType step;
2450 };
2451
2452 static gboolean
2453 second_timeout (gpointer data)
2454 {
2455   GtkRange *range;
2456
2457   GDK_THREADS_ENTER ();
2458   range = GTK_RANGE (data);
2459   gtk_range_scroll (range, range->timer->step);
2460   GDK_THREADS_LEAVE ();
2461   
2462   return TRUE;
2463 }
2464
2465 static gboolean
2466 initial_timeout (gpointer data)
2467 {
2468   GtkRange *range;
2469
2470   GDK_THREADS_ENTER ();
2471   range = GTK_RANGE (data);
2472   range->timer->timeout_id = 
2473     g_timeout_add (SCROLL_LATER_DELAY,
2474                    second_timeout,
2475                    range);
2476   GDK_THREADS_LEAVE ();
2477
2478   /* remove self */
2479   return FALSE;
2480 }
2481
2482 static void
2483 gtk_range_add_step_timer (GtkRange      *range,
2484                           GtkScrollType  step)
2485 {
2486   g_return_if_fail (range->timer == NULL);
2487   g_return_if_fail (step != GTK_SCROLL_NONE);
2488   
2489   range->timer = g_new (GtkRangeStepTimer, 1);
2490
2491   range->timer->timeout_id =
2492     g_timeout_add (SCROLL_INITIAL_DELAY,
2493                    initial_timeout,
2494                    range);
2495   range->timer->step = step;
2496
2497   gtk_range_scroll (range, range->timer->step);
2498 }
2499
2500 static void
2501 gtk_range_remove_step_timer (GtkRange *range)
2502 {
2503   if (range->timer)
2504     {
2505       if (range->timer->timeout_id != 0)
2506         g_source_remove (range->timer->timeout_id);
2507
2508       g_free (range->timer);
2509
2510       range->timer = NULL;
2511     }
2512 }
2513
2514 static gboolean
2515 update_timeout (gpointer data)
2516 {
2517   GtkRange *range;
2518
2519   GDK_THREADS_ENTER ();
2520   range = GTK_RANGE (data);
2521   gtk_range_update_value (range);
2522   range->update_timeout_id = 0;
2523   GDK_THREADS_LEAVE ();
2524
2525   /* self-remove */
2526   return FALSE;
2527 }
2528
2529 static void
2530 gtk_range_reset_update_timer (GtkRange *range)
2531 {
2532   gtk_range_remove_update_timer (range);
2533
2534   range->update_timeout_id = g_timeout_add (UPDATE_DELAY,
2535                                             update_timeout,
2536                                             range);
2537 }
2538
2539 static void
2540 gtk_range_remove_update_timer (GtkRange *range)
2541 {
2542   if (range->update_timeout_id != 0)
2543     {
2544       g_source_remove (range->update_timeout_id);
2545       range->update_timeout_id = 0;
2546     }
2547 }
2548
2549 #define __GTK_RANGE_C__
2550 #include "gtkaliasdef.c"