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