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 "gtkrangeprivate.h"
29 
30 #include "gtkaccessible.h"
31 #include "gtkadjustmentprivate.h"
32 #include "gtkcolorscaleprivate.h"
33 #include "gtkenums.h"
34 #include "gtkeventcontrollerkey.h"
35 #include "gtkeventcontrollerscroll.h"
36 #include "gtkgesturedrag.h"
37 #include "gtkgesturelongpressprivate.h"
38 #include "gtkgestureclick.h"
39 #include "gtkgizmoprivate.h"
40 #include "gtkintl.h"
41 #include "gtkmarshalers.h"
42 #include "gtkorientable.h"
43 #include "gtkprivate.h"
44 #include "gtkscale.h"
45 #include "gtktypebuiltins.h"
46 #include "gtkwidgetprivate.h"
47 
48 #include <stdio.h>
49 #include <math.h>
50 
51 /**
52  * GtkRange:
53  *
54  * `GtkRange` is the common base class for widgets which visualize an
55  * adjustment.
56  *
57  * Widgets that are derived from `GtkRange` include
58  * [class@Gtk.Scale] and [class@Gtk.Scrollbar].
59  *
60  * Apart from signals for monitoring the parameters of the adjustment,
61  * `GtkRange` provides properties and methods for setting a
62  * “fill level” on range widgets. See [method@Gtk.Range.set_fill_level].
63  */
64 
65 
66 #define TIMEOUT_INITIAL     500
67 #define TIMEOUT_REPEAT      250
68 #define AUTOSCROLL_FACTOR   20
69 #define SCROLL_EDGE_SIZE    15
70 #define MARK_SNAP_LENGTH    12
71 
72 typedef struct _GtkRangeStepTimer GtkRangeStepTimer;
73 
74 typedef struct _GtkRangePrivate       GtkRangePrivate;
75 struct _GtkRangePrivate
76 {
77   GtkWidget *grab_location;   /* "grabbed" mouse location, NULL for no grab */
78 
79   GtkRangeStepTimer *timer;
80 
81   GtkAdjustment     *adjustment;
82 
83   int slider_x;
84   int slider_y;
85 
86   GtkWidget    *trough_widget;
87   GtkWidget    *fill_widget;
88   GtkWidget    *highlight_widget;
89   GtkWidget    *slider_widget;
90 
91   GtkGesture *drag_gesture;
92 
93   double   fill_level;
94   double *marks;
95 
96   int *mark_pos;
97   int   n_marks;
98   int   round_digits;                /* Round off value to this many digits, -1 for no rounding */
99   int   slide_initial_slider_position;
100   int   slide_initial_coordinate_delta;
101 
102   guint flippable              : 1;
103   guint inverted               : 1;
104   guint slider_size_fixed      : 1;
105   guint trough_click_forward   : 1;  /* trough click was on the forward side of slider */
106 
107   /* Whether we're doing fine adjustment */
108   guint zoom                   : 1;
109 
110   /* Fill level */
111   guint show_fill_level        : 1;
112   guint restrict_to_fill_level : 1;
113 
114   /* Whether dragging is ongoing */
115   guint in_drag                : 1;
116 
117   GtkOrientation     orientation;
118 
119   GtkScrollType autoscroll_mode;
120   guint autoscroll_id;
121 };
122 
123 
124 enum {
125   PROP_0,
126   PROP_ADJUSTMENT,
127   PROP_INVERTED,
128   PROP_SHOW_FILL_LEVEL,
129   PROP_RESTRICT_TO_FILL_LEVEL,
130   PROP_FILL_LEVEL,
131   PROP_ROUND_DIGITS,
132   PROP_ORIENTATION,
133   LAST_PROP = PROP_ORIENTATION
134 };
135 
136 enum {
137   VALUE_CHANGED,
138   ADJUST_BOUNDS,
139   MOVE_SLIDER,
140   CHANGE_VALUE,
141   LAST_SIGNAL
142 };
143 
144 static void gtk_range_set_property   (GObject          *object,
145                                       guint             prop_id,
146                                       const GValue     *value,
147                                       GParamSpec       *pspec);
148 static void gtk_range_get_property   (GObject          *object,
149                                       guint             prop_id,
150                                       GValue           *value,
151                                       GParamSpec       *pspec);
152 static void gtk_range_finalize       (GObject          *object);
153 static void gtk_range_dispose        (GObject          *object);
154 static void gtk_range_measure        (GtkWidget      *widget,
155                                       GtkOrientation  orientation,
156                                       int             for_size,
157                                       int            *minimum,
158                                       int            *natural,
159                                       int            *minimum_baseline,
160                                       int            *natural_baseline);
161 static void gtk_range_size_allocate  (GtkWidget      *widget,
162                                       int             width,
163                                       int             height,
164                                       int             baseline);
165 static void gtk_range_unmap          (GtkWidget        *widget);
166 
167 static void gtk_range_click_gesture_pressed  (GtkGestureClick *gesture,
168                                                    guint                 n_press,
169                                                    double                x,
170                                                    double                y,
171                                                    GtkRange             *range);
172 static void gtk_range_drag_gesture_begin          (GtkGestureDrag       *gesture,
173                                                    double                offset_x,
174                                                    double                offset_y,
175                                                    GtkRange             *range);
176 static void gtk_range_drag_gesture_update         (GtkGestureDrag       *gesture,
177                                                    double                offset_x,
178                                                    double                offset_y,
179                                                    GtkRange             *range);
180 static void gtk_range_drag_gesture_end            (GtkGestureDrag       *gesture,
181                                                    double                offset_x,
182                                                    double                offset_y,
183                                                    GtkRange             *range);
184 static void gtk_range_long_press_gesture_pressed  (GtkGestureLongPress  *gesture,
185                                                    double                x,
186                                                    double                y,
187                                                    GtkRange             *range);
188 
189 
190 static void update_slider_position   (GtkRange	       *range,
191                                       int               mouse_x,
192                                       int               mouse_y);
193 static void stop_scrolling           (GtkRange         *range);
194 static void add_autoscroll           (GtkRange         *range);
195 static void remove_autoscroll        (GtkRange         *range);
196 
197 /* Range methods */
198 
199 static void gtk_range_move_slider              (GtkRange         *range,
200                                                 GtkScrollType     scroll);
201 
202 /* Internals */
203 static void          gtk_range_compute_slider_position  (GtkRange      *range,
204                                                          double         adjustment_value,
205                                                          GdkRectangle  *slider_rect);
206 static gboolean      gtk_range_scroll                   (GtkRange      *range,
207                                                          GtkScrollType  scroll);
208 static void          gtk_range_calc_marks               (GtkRange      *range);
209 static void          gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
210                                                          gpointer       data);
211 static void          gtk_range_adjustment_changed       (GtkAdjustment *adjustment,
212                                                          gpointer       data);
213 static void          gtk_range_add_step_timer           (GtkRange      *range,
214                                                          GtkScrollType  step);
215 static void          gtk_range_remove_step_timer        (GtkRange      *range);
216 static gboolean      gtk_range_real_change_value        (GtkRange      *range,
217                                                          GtkScrollType  scroll,
218                                                          double         value);
219 static gboolean      gtk_range_key_controller_key_pressed (GtkEventControllerKey *controller,
220                                                            guint                  keyval,
221                                                            guint                  keycode,
222                                                            GdkModifierType        state,
223                                                            GtkWidget             *widget);
224 static void          gtk_range_direction_changed        (GtkWidget     *widget,
225                                                          GtkTextDirection  previous_direction);
226 static void          gtk_range_measure_trough           (GtkGizmo       *gizmo,
227                                                          GtkOrientation  orientation,
228                                                          int             for_size,
229                                                          int            *minimum,
230                                                          int            *natural,
231                                                          int            *minimum_baseline,
232                                                          int            *natural_baseline);
233 static void          gtk_range_allocate_trough          (GtkGizmo            *gizmo,
234                                                          int                  width,
235                                                          int                  height,
236                                                          int                  baseline);
237 static void          gtk_range_render_trough            (GtkGizmo     *gizmo,
238                                                          GtkSnapshot  *snapshot);
239 
240 static gboolean      gtk_range_scroll_controller_scroll (GtkEventControllerScroll *scroll,
241                                                          double                    dx,
242                                                          double                    dy,
243                                                          GtkRange                 *range);
244 
245 static void          gtk_range_set_orientation          (GtkRange       *range,
246                                                          GtkOrientation  orientation);
247 
248 G_DEFINE_TYPE_WITH_CODE (GtkRange, gtk_range, GTK_TYPE_WIDGET,
249                          G_ADD_PRIVATE (GtkRange)
250                          G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE,
251                                                 NULL))
252 
253 static guint signals[LAST_SIGNAL];
254 static GParamSpec *properties[LAST_PROP];
255 
256 static void
gtk_range_class_init(GtkRangeClass * class)257 gtk_range_class_init (GtkRangeClass *class)
258 {
259   GObjectClass   *gobject_class;
260   GtkWidgetClass *widget_class;
261 
262   gobject_class = G_OBJECT_CLASS (class);
263   widget_class = (GtkWidgetClass*) class;
264 
265   gobject_class->set_property = gtk_range_set_property;
266   gobject_class->get_property = gtk_range_get_property;
267   gobject_class->finalize = gtk_range_finalize;
268   gobject_class->dispose = gtk_range_dispose;
269 
270   widget_class->measure = gtk_range_measure;
271   widget_class->size_allocate = gtk_range_size_allocate;
272   widget_class->unmap = gtk_range_unmap;
273   widget_class->direction_changed = gtk_range_direction_changed;
274 
275   class->move_slider = gtk_range_move_slider;
276   class->change_value = gtk_range_real_change_value;
277 
278   /**
279    * GtkRange::value-changed:
280    * @range: the `GtkRange` that received the signal
281    *
282    * Emitted when the range value changes.
283    */
284   signals[VALUE_CHANGED] =
285     g_signal_new (I_("value-changed"),
286                   G_TYPE_FROM_CLASS (gobject_class),
287                   G_SIGNAL_RUN_LAST,
288                   G_STRUCT_OFFSET (GtkRangeClass, value_changed),
289                   NULL, NULL,
290                   NULL,
291                   G_TYPE_NONE, 0);
292 
293   /**
294    * GtkRange::adjust-bounds:
295    * @range: the `GtkRange` that received the signal
296    * @value: the value before we clamp
297    *
298    * Emitted before clamping a value, to give the application a
299    * chance to adjust the bounds.
300    */
301   signals[ADJUST_BOUNDS] =
302     g_signal_new (I_("adjust-bounds"),
303                   G_TYPE_FROM_CLASS (gobject_class),
304                   G_SIGNAL_RUN_LAST,
305                   G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds),
306                   NULL, NULL,
307                   NULL,
308                   G_TYPE_NONE, 1,
309                   G_TYPE_DOUBLE);
310 
311   /**
312    * GtkRange::move-slider:
313    * @range: the `GtkRange` that received the signal
314    * @step: how to move the slider
315    *
316    * Virtual function that moves the slider.
317    *
318    * Used for keybindings.
319    */
320   signals[MOVE_SLIDER] =
321     g_signal_new (I_("move-slider"),
322                   G_TYPE_FROM_CLASS (gobject_class),
323                   G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
324                   G_STRUCT_OFFSET (GtkRangeClass, move_slider),
325                   NULL, NULL,
326                   NULL,
327                   G_TYPE_NONE, 1,
328                   GTK_TYPE_SCROLL_TYPE);
329 
330   /**
331    * GtkRange::change-value:
332    * @range: the `GtkRange` that received the signal
333    * @scroll: the type of scroll action that was performed
334    * @value: the new value resulting from the scroll action
335    *
336    * Emitted when a scroll action is performed on a range.
337    *
338    * It allows an application to determine the type of scroll event
339    * that occurred and the resultant new value. The application can
340    * handle the event itself and return %TRUE to prevent further
341    * processing. Or, by returning %FALSE, it can pass the event to
342    * other handlers until the default GTK handler is reached.
343    *
344    * The value parameter is unrounded. An application that overrides
345    * the ::change-value signal is responsible for clamping the value
346    * to the desired number of decimal digits; the default GTK
347    * handler clamps the value based on [property@Gtk.Range:round-digits].
348    *
349    * Returns: %TRUE to prevent other handlers from being invoked for
350    *     the signal, %FALSE to propagate the signal further
351    */
352   signals[CHANGE_VALUE] =
353     g_signal_new (I_("change-value"),
354                   G_TYPE_FROM_CLASS (gobject_class),
355                   G_SIGNAL_RUN_LAST,
356                   G_STRUCT_OFFSET (GtkRangeClass, change_value),
357                   _gtk_boolean_handled_accumulator, NULL,
358                   _gtk_marshal_BOOLEAN__ENUM_DOUBLE,
359                   G_TYPE_BOOLEAN, 2,
360                   GTK_TYPE_SCROLL_TYPE,
361                   G_TYPE_DOUBLE);
362 
363   g_object_class_override_property (gobject_class, PROP_ORIENTATION, "orientation");
364 
365   /**
366    * GtkRange:adjustment: (attributes org.gtk.Property.get=gtk_range_get_adjustment org.gtk.Property.set=gtk_range_set_adjustment)
367    *
368    * The adjustment that is controlled by the range.
369    */
370   properties[PROP_ADJUSTMENT] =
371       g_param_spec_object ("adjustment",
372                            P_("Adjustment"),
373                            P_("The GtkAdjustment that contains the current value of this range object"),
374                            GTK_TYPE_ADJUSTMENT,
375                            GTK_PARAM_READWRITE|G_PARAM_CONSTRUCT);
376 
377   /**
378    * GtkRange:inverted: (attributes org.gtk.Property.get=gtk_range_get_inverted org.gtk.Property.set=gtk_range_set_inverted)
379    *
380    * If %TRUE, the direction in which the slider moves is inverted.
381    */
382   properties[PROP_INVERTED] =
383       g_param_spec_boolean ("inverted",
384                             P_("Inverted"),
385                             P_("Invert direction slider moves to increase range value"),
386                             FALSE,
387                             GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
388 
389   /**
390    * GtkRange:show-fill-level: (attributes org.gtk.Property.get=gtk_range_get_show_fill_level org.gtk.Property.set=gtk_range_set_show_fill_level)
391    *
392    * Controls whether fill level indicator graphics are displayed
393    * on the trough.
394    */
395   properties[PROP_SHOW_FILL_LEVEL] =
396       g_param_spec_boolean ("show-fill-level",
397                             P_("Show Fill Level"),
398                             P_("Whether to display a fill level indicator graphics on trough."),
399                             FALSE,
400                             GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
401 
402   /**
403    * GtkRange:restrict-to-fill-level: (attributes org.gtk.Property.get=gtk_range_get_restrict_to_fill_level org.gtk.Property.set=gtk_range_set_restrict_to_fill_level)
404    *
405    * Controls whether slider movement is restricted to an
406    * upper boundary set by the fill level.
407    */
408   properties[PROP_RESTRICT_TO_FILL_LEVEL] =
409       g_param_spec_boolean ("restrict-to-fill-level",
410                             P_("Restrict to Fill Level"),
411                             P_("Whether to restrict the upper boundary to the fill level."),
412                             TRUE,
413                             GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
414 
415   /**
416    * GtkRange:fill-level: (attributes org.gtk.Property.get=gtk_range_get_fill_level org.gtk.Property.set=gtk_range_set_fill_level)
417    *
418    * The fill level (e.g. prebuffering of a network stream).
419    */
420   properties[PROP_FILL_LEVEL] =
421       g_param_spec_double ("fill-level",
422                            P_("Fill Level"),
423                            P_("The fill level."),
424                            -G_MAXDOUBLE, G_MAXDOUBLE,
425                            G_MAXDOUBLE,
426                            GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
427 
428   /**
429    * GtkRange:round-digits: (attributes org.gtk.Property.get=gtk_range_get_round_digits org.gtk.Property.set=gtk_range_set_round_digits)
430    *
431    * The number of digits to round the value to when
432    * it changes.
433    *
434    * See [signal@Gtk.Range::change-value].
435    */
436   properties[PROP_ROUND_DIGITS] =
437       g_param_spec_int ("round-digits",
438                         P_("Round Digits"),
439                         P_("The number of digits to round the value to."),
440                         -1, G_MAXINT,
441                         -1,
442                         GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
443 
444   g_object_class_install_properties (gobject_class, LAST_PROP, properties);
445 
446   gtk_widget_class_set_css_name (widget_class, I_("range"));
447 }
448 
449 static void
gtk_range_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)450 gtk_range_set_property (GObject      *object,
451 			guint         prop_id,
452 			const GValue *value,
453 			GParamSpec   *pspec)
454 {
455   GtkRange *range = GTK_RANGE (object);
456 
457   switch (prop_id)
458     {
459     case PROP_ORIENTATION:
460       gtk_range_set_orientation (range, g_value_get_enum (value));
461       break;
462     case PROP_ADJUSTMENT:
463       gtk_range_set_adjustment (range, g_value_get_object (value));
464       break;
465     case PROP_INVERTED:
466       gtk_range_set_inverted (range, g_value_get_boolean (value));
467       break;
468     case PROP_SHOW_FILL_LEVEL:
469       gtk_range_set_show_fill_level (range, g_value_get_boolean (value));
470       break;
471     case PROP_RESTRICT_TO_FILL_LEVEL:
472       gtk_range_set_restrict_to_fill_level (range, g_value_get_boolean (value));
473       break;
474     case PROP_FILL_LEVEL:
475       gtk_range_set_fill_level (range, g_value_get_double (value));
476       break;
477     case PROP_ROUND_DIGITS:
478       gtk_range_set_round_digits (range, g_value_get_int (value));
479       break;
480     default:
481       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
482       break;
483     }
484 }
485 
486 static void
gtk_range_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)487 gtk_range_get_property (GObject      *object,
488 			guint         prop_id,
489 			GValue       *value,
490 			GParamSpec   *pspec)
491 {
492   GtkRange *range = GTK_RANGE (object);
493   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
494 
495   switch (prop_id)
496     {
497     case PROP_ORIENTATION:
498       g_value_set_enum (value, priv->orientation);
499       break;
500     case PROP_ADJUSTMENT:
501       g_value_set_object (value, priv->adjustment);
502       break;
503     case PROP_INVERTED:
504       g_value_set_boolean (value, priv->inverted);
505       break;
506     case PROP_SHOW_FILL_LEVEL:
507       g_value_set_boolean (value, gtk_range_get_show_fill_level (range));
508       break;
509     case PROP_RESTRICT_TO_FILL_LEVEL:
510       g_value_set_boolean (value, gtk_range_get_restrict_to_fill_level (range));
511       break;
512     case PROP_FILL_LEVEL:
513       g_value_set_double (value, gtk_range_get_fill_level (range));
514       break;
515     case PROP_ROUND_DIGITS:
516       g_value_set_int (value, gtk_range_get_round_digits (range));
517       break;
518     default:
519       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
520       break;
521     }
522 }
523 
524 static void
gtk_range_init(GtkRange * range)525 gtk_range_init (GtkRange *range)
526 {
527   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
528   GtkGesture *gesture;
529   GtkEventController *controller;
530 
531   priv->orientation = GTK_ORIENTATION_HORIZONTAL;
532   priv->adjustment = NULL;
533   priv->inverted = FALSE;
534   priv->flippable = FALSE;
535   priv->round_digits = -1;
536   priv->show_fill_level = FALSE;
537   priv->restrict_to_fill_level = TRUE;
538   priv->fill_level = G_MAXDOUBLE;
539   priv->timer = NULL;
540 
541   gtk_widget_update_orientation (GTK_WIDGET (range), priv->orientation);
542 
543   priv->trough_widget = gtk_gizmo_new_with_role ("trough",
544                                                  GTK_ACCESSIBLE_ROLE_NONE,
545                                                  gtk_range_measure_trough,
546                                                  gtk_range_allocate_trough,
547                                                  gtk_range_render_trough,
548                                                  NULL,
549                                                  NULL, NULL);
550 
551   gtk_widget_set_parent (priv->trough_widget, GTK_WIDGET (range));
552 
553   priv->slider_widget = gtk_gizmo_new ("slider", NULL, NULL, NULL, NULL, NULL, NULL);
554   gtk_widget_set_parent (priv->slider_widget, priv->trough_widget);
555 
556   /* Note: Order is important here.
557    * The ::drag-begin handler relies on the state set up by the
558    * click ::pressed handler. Gestures are handling events
559    * in the opposite order in which they are added to their
560    * widget.
561    */
562   priv->drag_gesture = gtk_gesture_drag_new ();
563   gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (priv->drag_gesture), 0);
564   g_signal_connect (priv->drag_gesture, "drag-begin",
565                     G_CALLBACK (gtk_range_drag_gesture_begin), range);
566   g_signal_connect (priv->drag_gesture, "drag-update",
567                     G_CALLBACK (gtk_range_drag_gesture_update), range);
568   g_signal_connect (priv->drag_gesture, "drag-end",
569                     G_CALLBACK (gtk_range_drag_gesture_end), range);
570   gtk_widget_add_controller (GTK_WIDGET (range), GTK_EVENT_CONTROLLER (priv->drag_gesture));
571 
572   gesture = gtk_gesture_click_new ();
573   gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (gesture), 0);
574   g_signal_connect (gesture, "pressed",
575                     G_CALLBACK (gtk_range_click_gesture_pressed), range);
576   gtk_widget_add_controller (GTK_WIDGET (range), GTK_EVENT_CONTROLLER (gesture));
577   gtk_gesture_group (gesture, priv->drag_gesture);
578 
579   gesture = gtk_gesture_long_press_new ();
580   gtk_gesture_long_press_set_delay_factor (GTK_GESTURE_LONG_PRESS (gesture), 2.0);
581   g_signal_connect (gesture, "pressed",
582                     G_CALLBACK (gtk_range_long_press_gesture_pressed), range);
583   gtk_widget_add_controller (GTK_WIDGET (range), GTK_EVENT_CONTROLLER (gesture));
584   gtk_gesture_group (gesture, priv->drag_gesture);
585 
586   controller = gtk_event_controller_scroll_new (GTK_EVENT_CONTROLLER_SCROLL_BOTH_AXES);
587   g_signal_connect (controller, "scroll",
588                     G_CALLBACK (gtk_range_scroll_controller_scroll), range);
589   gtk_widget_add_controller (GTK_WIDGET (range), controller);
590 
591   controller = gtk_event_controller_key_new ();
592   g_signal_connect (controller, "key-pressed",
593                     G_CALLBACK (gtk_range_key_controller_key_pressed), range);
594   gtk_widget_add_controller (GTK_WIDGET (range), controller);
595 }
596 
597 static void
gtk_range_set_orientation(GtkRange * range,GtkOrientation orientation)598 gtk_range_set_orientation (GtkRange       *range,
599                            GtkOrientation  orientation)
600 {
601   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
602 
603   if (priv->orientation != orientation)
604     {
605       priv->orientation = orientation;
606 
607       gtk_accessible_update_property (GTK_ACCESSIBLE (range),
608                                       GTK_ACCESSIBLE_PROPERTY_ORIENTATION, priv->orientation,
609                                       -1);
610 
611       gtk_widget_update_orientation (GTK_WIDGET (range), priv->orientation);
612       gtk_widget_queue_resize (GTK_WIDGET (range));
613 
614       g_object_notify (G_OBJECT (range), "orientation");
615     }
616 }
617 
618 /**
619  * gtk_range_get_adjustment: (attributes org.gtk.Method.get_property=adjustment)
620  * @range: a `GtkRange`
621  *
622  * Get the adjustment which is the “model” object for `GtkRange`.
623  *
624  * Returns: (transfer none): a `GtkAdjustment`
625  **/
626 GtkAdjustment*
gtk_range_get_adjustment(GtkRange * range)627 gtk_range_get_adjustment (GtkRange *range)
628 {
629   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
630 
631   g_return_val_if_fail (GTK_IS_RANGE (range), NULL);
632 
633   if (!priv->adjustment)
634     gtk_range_set_adjustment (range, NULL);
635 
636   return priv->adjustment;
637 }
638 
639 /**
640  * gtk_range_set_adjustment: (attributes org.gtk.Method.set_property=adjustment)
641  * @range: a `GtkRange`
642  * @adjustment: a `GtkAdjustment`
643  *
644  * Sets the adjustment to be used as the “model” object for the `GtkRange`
645  *
646  * The adjustment indicates the current range value, the minimum and
647  * maximum range values, the step/page increments used for keybindings
648  * and scrolling, and the page size.
649  *
650  * The page size is normally 0 for `GtkScale` and nonzero for `GtkScrollbar`,
651  * and indicates the size of the visible area of the widget being scrolled.
652  * The page size affects the size of the scrollbar slider.
653  */
654 void
gtk_range_set_adjustment(GtkRange * range,GtkAdjustment * adjustment)655 gtk_range_set_adjustment (GtkRange      *range,
656 			  GtkAdjustment *adjustment)
657 {
658   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
659 
660   g_return_if_fail (GTK_IS_RANGE (range));
661 
662   if (!adjustment)
663     adjustment = gtk_adjustment_new (0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
664   else
665     g_return_if_fail (GTK_IS_ADJUSTMENT (adjustment));
666 
667   if (priv->adjustment != adjustment)
668     {
669       if (priv->adjustment)
670 	{
671 	  g_signal_handlers_disconnect_by_func (priv->adjustment,
672 						gtk_range_adjustment_changed,
673 						range);
674 	  g_signal_handlers_disconnect_by_func (priv->adjustment,
675 						gtk_range_adjustment_value_changed,
676 						range);
677 	  g_object_unref (priv->adjustment);
678 	}
679 
680       priv->adjustment = adjustment;
681       g_object_ref_sink (adjustment);
682 
683       g_signal_connect (adjustment, "changed",
684 			G_CALLBACK (gtk_range_adjustment_changed),
685 			range);
686       g_signal_connect (adjustment, "value-changed",
687 			G_CALLBACK (gtk_range_adjustment_value_changed),
688 			range);
689 
690       gtk_accessible_update_property (GTK_ACCESSIBLE (range),
691                                       GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, gtk_adjustment_get_upper (adjustment),
692                                       GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, gtk_adjustment_get_lower (adjustment),
693                                       GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment),
694                                       -1);
695 
696       gtk_range_adjustment_changed (adjustment, range);
697       gtk_range_adjustment_value_changed (adjustment, range);
698 
699       g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_ADJUSTMENT]);
700     }
701 }
702 
703 static gboolean
should_invert(GtkRange * range)704 should_invert (GtkRange *range)
705 {
706   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
707 
708   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
709     return
710       (priv->inverted && !priv->flippable) ||
711       (priv->inverted && priv->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_LTR) ||
712       (!priv->inverted && priv->flippable && gtk_widget_get_direction (GTK_WIDGET (range)) == GTK_TEXT_DIR_RTL);
713   else
714     return priv->inverted;
715 }
716 
717 static gboolean
should_invert_move(GtkRange * range,GtkOrientation move_orientation)718 should_invert_move (GtkRange       *range,
719                     GtkOrientation  move_orientation)
720 {
721   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
722 
723   /* If the move is parallel to the range, use general check for inversion */
724   if (move_orientation == priv->orientation)
725     return should_invert (range);
726 
727   /* H scale/V move: Always invert, so down/up always dec/increase the value */
728   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL && GTK_IS_SCALE (range))
729     return TRUE;
730 
731   /* V range/H move: Left/right always dec/increase the value */
732   return FALSE;
733 }
734 
735 static void
update_highlight_position(GtkRange * range)736 update_highlight_position (GtkRange *range)
737 {
738   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
739 
740   if (!priv->highlight_widget)
741     return;
742 
743   if (should_invert (range))
744     {
745       gtk_widget_add_css_class (priv->highlight_widget, "bottom");
746       gtk_widget_remove_css_class (priv->highlight_widget, "top");
747     }
748   else
749     {
750       gtk_widget_add_css_class (priv->highlight_widget, "top");
751       gtk_widget_remove_css_class (priv->highlight_widget, "bottom");
752     }
753 }
754 
755 static void
update_fill_position(GtkRange * range)756 update_fill_position (GtkRange *range)
757 {
758   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
759 
760   if (!priv->fill_widget)
761     return;
762 
763   if (should_invert (range))
764     {
765       gtk_widget_add_css_class (priv->fill_widget, "bottom");
766       gtk_widget_remove_css_class (priv->fill_widget, "top");
767     }
768   else
769     {
770       gtk_widget_add_css_class (priv->fill_widget, "top");
771       gtk_widget_remove_css_class (priv->fill_widget, "bottom");
772     }
773 }
774 
775 /**
776  * gtk_range_set_inverted: (attributes org.gtk.Method.set_property=inverted)
777  * @range: a `GtkRange`
778  * @setting: %TRUE to invert the range
779  *
780  * Sets whether to invert the range.
781  *
782  * Ranges normally move from lower to higher values as the
783  * slider moves from top to bottom or left to right. Inverted
784  * ranges have higher values at the top or on the right rather
785  * than on the bottom or left.
786  */
787 void
gtk_range_set_inverted(GtkRange * range,gboolean setting)788 gtk_range_set_inverted (GtkRange *range,
789                         gboolean  setting)
790 {
791   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
792 
793   g_return_if_fail (GTK_IS_RANGE (range));
794 
795   setting = setting != FALSE;
796 
797   if (setting != priv->inverted)
798     {
799       priv->inverted = setting;
800 
801       update_fill_position (range);
802       update_highlight_position (range);
803 
804       gtk_widget_queue_resize (priv->trough_widget);
805 
806       g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_INVERTED]);
807     }
808 }
809 
810 /**
811  * gtk_range_get_inverted: (attributes org.gtk.Method.get_property=inverted)
812  * @range: a `GtkRange`
813  *
814  * Gets whether the range is inverted.
815  *
816  * See [method@Gtk.Range.set_inverted].
817  *
818  * Returns: %TRUE if the range is inverted
819  */
820 gboolean
gtk_range_get_inverted(GtkRange * range)821 gtk_range_get_inverted (GtkRange *range)
822 {
823   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
824 
825   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
826 
827   return priv->inverted;
828 }
829 
830 /**
831  * gtk_range_set_flippable:
832  * @range: a `GtkRange`
833  * @flippable: %TRUE to make the range flippable
834  *
835  * Sets whether the `GtkRange` respects text direction.
836  *
837  * If a range is flippable, it will switch its direction
838  * if it is horizontal and its direction is %GTK_TEXT_DIR_RTL.
839  *
840  * See [method@Gtk.Widget.get_direction].
841  */
842 void
gtk_range_set_flippable(GtkRange * range,gboolean flippable)843 gtk_range_set_flippable (GtkRange *range,
844                          gboolean  flippable)
845 {
846   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
847 
848   g_return_if_fail (GTK_IS_RANGE (range));
849 
850   flippable = flippable ? TRUE : FALSE;
851 
852   if (flippable != priv->flippable)
853     {
854       priv->flippable = flippable;
855       update_fill_position (range);
856       update_highlight_position (range);
857 
858       gtk_widget_queue_allocate (GTK_WIDGET (range));
859     }
860 }
861 
862 /**
863  * gtk_range_get_flippable:
864  * @range: a `GtkRange`
865  *
866  * Gets whether the `GtkRange` respects text direction.
867  *
868  * See [method@Gtk.Range.set_flippable].
869  *
870  * Returns: %TRUE if the range is flippable
871  */
872 gboolean
gtk_range_get_flippable(GtkRange * range)873 gtk_range_get_flippable (GtkRange *range)
874 {
875   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
876 
877   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
878 
879   return priv->flippable;
880 }
881 
882 /**
883  * gtk_range_set_slider_size_fixed:
884  * @range: a `GtkRange`
885  * @size_fixed: %TRUE to make the slider size constant
886  *
887  * Sets whether the range’s slider has a fixed size, or a size that
888  * depends on its adjustment’s page size.
889  *
890  * This function is useful mainly for `GtkRange` subclasses.
891  */
892 void
gtk_range_set_slider_size_fixed(GtkRange * range,gboolean size_fixed)893 gtk_range_set_slider_size_fixed (GtkRange *range,
894                                  gboolean  size_fixed)
895 {
896   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
897 
898   g_return_if_fail (GTK_IS_RANGE (range));
899 
900   if (size_fixed != priv->slider_size_fixed)
901     {
902       priv->slider_size_fixed = size_fixed ? TRUE : FALSE;
903 
904       if (priv->adjustment && gtk_widget_get_mapped (GTK_WIDGET (range)))
905         gtk_widget_queue_allocate (priv->trough_widget);
906     }
907 }
908 
909 /**
910  * gtk_range_get_slider_size_fixed:
911  * @range: a `GtkRange`
912  *
913  * This function is useful mainly for `GtkRange` subclasses.
914  *
915  * See [method@Gtk.Range.set_slider_size_fixed].
916  *
917  * Returns: whether the range’s slider has a fixed size.
918  */
919 gboolean
gtk_range_get_slider_size_fixed(GtkRange * range)920 gtk_range_get_slider_size_fixed (GtkRange *range)
921 {
922   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
923 
924   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
925 
926   return priv->slider_size_fixed;
927 }
928 
929 /**
930  * gtk_range_get_range_rect:
931  * @range: a `GtkRange`
932  * @range_rect: (out): return location for the range rectangle
933  *
934  * This function returns the area that contains the range’s trough,
935  * in coordinates relative to @range's origin.
936  *
937  * This function is useful mainly for `GtkRange` subclasses.
938  */
939 void
gtk_range_get_range_rect(GtkRange * range,GdkRectangle * range_rect)940 gtk_range_get_range_rect (GtkRange     *range,
941                           GdkRectangle *range_rect)
942 {
943   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
944   graphene_rect_t r;
945 
946   g_return_if_fail (GTK_IS_RANGE (range));
947   g_return_if_fail (range_rect != NULL);
948 
949   if (!gtk_widget_compute_bounds (priv->trough_widget, GTK_WIDGET (range), &r))
950     {
951       *range_rect = (GdkRectangle) { 0, 0, 0, 0 };
952     }
953   else
954     {
955       *range_rect = (GdkRectangle) {
956         floorf (r.origin.x),
957         floorf (r.origin.y),
958         ceilf (r.size.width),
959         ceilf (r.size.height),
960       };
961     }
962 }
963 
964 /**
965  * gtk_range_get_slider_range:
966  * @range: a `GtkRange`
967  * @slider_start: (out) (optional): return location for the slider's start
968  * @slider_end: (out) (optional): return location for the slider's end
969  *
970  * This function returns sliders range along the long dimension,
971  * in widget->window coordinates.
972  *
973  * This function is useful mainly for `GtkRange` subclasses.
974  */
975 void
gtk_range_get_slider_range(GtkRange * range,int * slider_start,int * slider_end)976 gtk_range_get_slider_range (GtkRange *range,
977                             int      *slider_start,
978                             int      *slider_end)
979 {
980   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
981   graphene_rect_t slider_bounds;
982 
983   g_return_if_fail (GTK_IS_RANGE (range));
984 
985   if (!gtk_widget_compute_bounds (priv->slider_widget, GTK_WIDGET (range), &slider_bounds))
986     {
987       if (slider_start)
988         *slider_start = 0;
989       if (slider_end)
990         *slider_end = 0;
991       return;
992     }
993 
994   if (priv->orientation == GTK_ORIENTATION_VERTICAL)
995     {
996       if (slider_start)
997         *slider_start = slider_bounds.origin.y;
998       if (slider_end)
999         *slider_end = slider_bounds.origin.y + slider_bounds.size.height;
1000     }
1001   else
1002     {
1003       if (slider_start)
1004         *slider_start = slider_bounds.origin.y;
1005       if (slider_end)
1006         *slider_end = slider_bounds.origin.x + slider_bounds.size.width;
1007     }
1008 }
1009 
1010 /**
1011  * gtk_range_set_increments:
1012  * @range: a `GtkRange`
1013  * @step: step size
1014  * @page: page size
1015  *
1016  * Sets the step and page sizes for the range.
1017  *
1018  * The step size is used when the user clicks the `GtkScrollbar`
1019  * arrows or moves a `GtkScale` via arrow keys. The page size
1020  * is used for example when moving via Page Up or Page Down keys.
1021  */
1022 void
gtk_range_set_increments(GtkRange * range,double step,double page)1023 gtk_range_set_increments (GtkRange *range,
1024                           double    step,
1025                           double    page)
1026 {
1027   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1028   GtkAdjustment *adjustment;
1029 
1030   g_return_if_fail (GTK_IS_RANGE (range));
1031 
1032   adjustment = priv->adjustment;
1033 
1034   gtk_adjustment_configure (adjustment,
1035                             gtk_adjustment_get_value (adjustment),
1036                             gtk_adjustment_get_lower (adjustment),
1037                             gtk_adjustment_get_upper (adjustment),
1038                             step,
1039                             page,
1040                             gtk_adjustment_get_page_size (adjustment));
1041 }
1042 
1043 /**
1044  * gtk_range_set_range:
1045  * @range: a `GtkRange`
1046  * @min: minimum range value
1047  * @max: maximum range value
1048  *
1049  * Sets the allowable values in the `GtkRange`.
1050  *
1051  * The range value is clamped to be between @min and @max.
1052  * (If the range has a non-zero page size, it is clamped
1053  * between @min and @max - page-size.)
1054  */
1055 void
gtk_range_set_range(GtkRange * range,double min,double max)1056 gtk_range_set_range (GtkRange *range,
1057                      double    min,
1058                      double    max)
1059 {
1060   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1061   GtkAdjustment *adjustment;
1062   double value;
1063 
1064   g_return_if_fail (GTK_IS_RANGE (range));
1065   g_return_if_fail (min <= max);
1066 
1067   adjustment = priv->adjustment;
1068 
1069   value = gtk_adjustment_get_value (adjustment);
1070   if (priv->restrict_to_fill_level)
1071     value = MIN (value, MAX (gtk_adjustment_get_lower (adjustment),
1072                              priv->fill_level));
1073 
1074   gtk_adjustment_configure (adjustment,
1075                             value,
1076                             min,
1077                             max,
1078                             gtk_adjustment_get_step_increment (adjustment),
1079                             gtk_adjustment_get_page_increment (adjustment),
1080                             gtk_adjustment_get_page_size (adjustment));
1081 }
1082 
1083 /**
1084  * gtk_range_set_value:
1085  * @range: a `GtkRange`
1086  * @value: new value of the range
1087  *
1088  * Sets the current value of the range.
1089  *
1090  * If the value is outside the minimum or maximum range values,
1091  * it will be clamped to fit inside them. The range emits the
1092  * [signal@Gtk.Range::value-changed] signal if the value changes.
1093  */
1094 void
gtk_range_set_value(GtkRange * range,double value)1095 gtk_range_set_value (GtkRange *range,
1096                      double    value)
1097 {
1098   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1099 
1100   g_return_if_fail (GTK_IS_RANGE (range));
1101 
1102   if (priv->restrict_to_fill_level)
1103     value = MIN (value, MAX (gtk_adjustment_get_lower (priv->adjustment),
1104                              priv->fill_level));
1105 
1106   gtk_adjustment_set_value (priv->adjustment, value);
1107 }
1108 
1109 /**
1110  * gtk_range_get_value:
1111  * @range: a `GtkRange`
1112  *
1113  * Gets the current value of the range.
1114  *
1115  * Returns: current value of the range.
1116  */
1117 double
gtk_range_get_value(GtkRange * range)1118 gtk_range_get_value (GtkRange *range)
1119 {
1120   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1121 
1122   g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1123 
1124   return gtk_adjustment_get_value (priv->adjustment);
1125 }
1126 
1127 /**
1128  * gtk_range_set_show_fill_level: (attributes org.gtk.Method.set_property=show-fill-level)
1129  * @range: A `GtkRange`
1130  * @show_fill_level: Whether a fill level indicator graphics is shown.
1131  *
1132  * Sets whether a graphical fill level is show on the trough.
1133  *
1134  * See [method@Gtk.Range.set_fill_level] for a general description
1135  * of the fill level concept.
1136  */
1137 void
gtk_range_set_show_fill_level(GtkRange * range,gboolean show_fill_level)1138 gtk_range_set_show_fill_level (GtkRange *range,
1139                                gboolean  show_fill_level)
1140 {
1141   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1142 
1143   g_return_if_fail (GTK_IS_RANGE (range));
1144 
1145   show_fill_level = show_fill_level ? TRUE : FALSE;
1146 
1147   if (show_fill_level == priv->show_fill_level)
1148     return;
1149 
1150   priv->show_fill_level = show_fill_level;
1151 
1152   if (show_fill_level)
1153     {
1154       priv->fill_widget = gtk_gizmo_new ("fill", NULL, NULL, NULL, NULL, NULL, NULL);
1155       gtk_widget_insert_after (priv->fill_widget, priv->trough_widget, NULL);
1156       update_fill_position (range);
1157     }
1158   else
1159     {
1160       g_clear_pointer (&priv->fill_widget, gtk_widget_unparent);
1161     }
1162 
1163   g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_SHOW_FILL_LEVEL]);
1164   gtk_widget_queue_allocate (GTK_WIDGET (range));
1165 }
1166 
1167 /**
1168  * gtk_range_get_show_fill_level: (attributes org.gtk.Method.get_property=show-fill-level)
1169  * @range: A `GtkRange`
1170  *
1171  * Gets whether the range displays the fill level graphically.
1172  *
1173  * Returns: %TRUE if @range shows the fill level.
1174  */
1175 gboolean
gtk_range_get_show_fill_level(GtkRange * range)1176 gtk_range_get_show_fill_level (GtkRange *range)
1177 {
1178   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1179 
1180   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1181 
1182   return priv->show_fill_level;
1183 }
1184 
1185 /**
1186  * gtk_range_set_restrict_to_fill_level: (attributes org.gtk.Method.set_property=restrict-to-fill-level)
1187  * @range: A `GtkRange`
1188  * @restrict_to_fill_level: Whether the fill level restricts slider movement.
1189  *
1190  * Sets whether the slider is restricted to the fill level.
1191  *
1192  * See [method@Gtk.Range.set_fill_level] for a general description
1193  * of the fill level concept.
1194  */
1195 void
gtk_range_set_restrict_to_fill_level(GtkRange * range,gboolean restrict_to_fill_level)1196 gtk_range_set_restrict_to_fill_level (GtkRange *range,
1197                                       gboolean  restrict_to_fill_level)
1198 {
1199   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1200 
1201   g_return_if_fail (GTK_IS_RANGE (range));
1202 
1203   restrict_to_fill_level = restrict_to_fill_level ? TRUE : FALSE;
1204 
1205   if (restrict_to_fill_level != priv->restrict_to_fill_level)
1206     {
1207       priv->restrict_to_fill_level = restrict_to_fill_level;
1208       g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_RESTRICT_TO_FILL_LEVEL]);
1209 
1210       gtk_range_set_value (range, gtk_range_get_value (range));
1211     }
1212 }
1213 
1214 /**
1215  * gtk_range_get_restrict_to_fill_level: (attributes org.gtk.Method.get_property=restrict-to-fill-level)
1216  * @range: A `GtkRange`
1217  *
1218  * Gets whether the range is restricted to the fill level.
1219  *
1220  * Returns: %TRUE if @range is restricted to the fill level.
1221  **/
1222 gboolean
gtk_range_get_restrict_to_fill_level(GtkRange * range)1223 gtk_range_get_restrict_to_fill_level (GtkRange *range)
1224 {
1225   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1226 
1227   g_return_val_if_fail (GTK_IS_RANGE (range), FALSE);
1228 
1229   return priv->restrict_to_fill_level;
1230 }
1231 
1232 /**
1233  * gtk_range_set_fill_level: (attributes org.gtk.Method.set_property=fill-level)
1234  * @range: a `GtkRange`
1235  * @fill_level: the new position of the fill level indicator
1236  *
1237  * Set the new position of the fill level indicator.
1238  *
1239  * The “fill level” is probably best described by its most prominent
1240  * use case, which is an indicator for the amount of pre-buffering in
1241  * a streaming media player. In that use case, the value of the range
1242  * would indicate the current play position, and the fill level would
1243  * be the position up to which the file/stream has been downloaded.
1244  *
1245  * This amount of prebuffering can be displayed on the range’s trough
1246  * and is themeable separately from the trough. To enable fill level
1247  * display, use [method@Gtk.Range.set_show_fill_level]. The range defaults
1248  * to not showing the fill level.
1249  *
1250  * Additionally, it’s possible to restrict the range’s slider position
1251  * to values which are smaller than the fill level. This is controlled
1252  * by [method@Gtk.Range.set_restrict_to_fill_level] and is by default
1253  * enabled.
1254  */
1255 void
gtk_range_set_fill_level(GtkRange * range,double fill_level)1256 gtk_range_set_fill_level (GtkRange *range,
1257                           double    fill_level)
1258 {
1259   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1260 
1261   g_return_if_fail (GTK_IS_RANGE (range));
1262 
1263   if (fill_level != priv->fill_level)
1264     {
1265       priv->fill_level = fill_level;
1266       g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_FILL_LEVEL]);
1267 
1268       if (priv->show_fill_level)
1269         gtk_widget_queue_allocate (GTK_WIDGET (range));
1270 
1271       if (priv->restrict_to_fill_level)
1272         gtk_range_set_value (range, gtk_range_get_value (range));
1273     }
1274 }
1275 
1276 /**
1277  * gtk_range_get_fill_level: (attributes org.gtk.Method.get_property=fill-level)
1278  * @range: A `GtkRange`
1279  *
1280  * Gets the current position of the fill level indicator.
1281  *
1282  * Returns: The current fill level
1283  */
1284 double
gtk_range_get_fill_level(GtkRange * range)1285 gtk_range_get_fill_level (GtkRange *range)
1286 {
1287   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1288 
1289   g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1290 
1291   return priv->fill_level;
1292 }
1293 
1294 static void
gtk_range_dispose(GObject * object)1295 gtk_range_dispose (GObject *object)
1296 {
1297   GtkRange *range = GTK_RANGE (object);
1298   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1299 
1300   gtk_range_remove_step_timer (range);
1301 
1302   if (priv->adjustment)
1303     {
1304       g_signal_handlers_disconnect_by_func (priv->adjustment,
1305 					    gtk_range_adjustment_changed,
1306 					    range);
1307       g_signal_handlers_disconnect_by_func (priv->adjustment,
1308 					    gtk_range_adjustment_value_changed,
1309 					    range);
1310       g_object_unref (priv->adjustment);
1311       priv->adjustment = NULL;
1312     }
1313 
1314   if (priv->n_marks)
1315     {
1316       g_free (priv->marks);
1317       priv->marks = NULL;
1318       g_free (priv->mark_pos);
1319       priv->mark_pos = NULL;
1320       priv->n_marks = 0;
1321     }
1322 
1323   G_OBJECT_CLASS (gtk_range_parent_class)->dispose (object);
1324 }
1325 
1326 static void
gtk_range_finalize(GObject * object)1327 gtk_range_finalize (GObject *object)
1328 {
1329   GtkRange *range = GTK_RANGE (object);
1330   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1331 
1332   g_clear_pointer (&priv->slider_widget, gtk_widget_unparent);
1333   g_clear_pointer (&priv->fill_widget, gtk_widget_unparent);
1334   g_clear_pointer (&priv->highlight_widget, gtk_widget_unparent);
1335   g_clear_pointer (&priv->trough_widget, gtk_widget_unparent);
1336 
1337   G_OBJECT_CLASS (gtk_range_parent_class)->finalize (object);
1338 }
1339 
1340 static void
gtk_range_measure_trough(GtkGizmo * gizmo,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline)1341 gtk_range_measure_trough (GtkGizmo       *gizmo,
1342                           GtkOrientation  orientation,
1343                           int             for_size,
1344                           int            *minimum,
1345                           int            *natural,
1346                           int            *minimum_baseline,
1347                           int            *natural_baseline)
1348 {
1349   GtkWidget *widget = gtk_widget_get_parent (GTK_WIDGET (gizmo));
1350   GtkRange *range = GTK_RANGE (widget);
1351   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1352   int min, nat;
1353 
1354   gtk_widget_measure (priv->slider_widget,
1355                       orientation, -1,
1356                       minimum, natural,
1357                       NULL, NULL);
1358 
1359   if (priv->fill_widget)
1360     {
1361       gtk_widget_measure (priv->fill_widget,
1362                           orientation, for_size,
1363                           &min, &nat,
1364                           NULL, NULL);
1365       *minimum = MAX (*minimum, min);
1366       *natural = MAX (*natural, nat);
1367     }
1368 
1369   if (priv->highlight_widget)
1370     {
1371       gtk_widget_measure (priv->highlight_widget,
1372                           orientation, for_size,
1373                           &min, &nat,
1374                           NULL, NULL);
1375       *minimum = MAX (*minimum, min);
1376       *natural = MAX (*natural, nat);
1377     }
1378 }
1379 
1380 static void
gtk_range_measure(GtkWidget * widget,GtkOrientation orientation,int for_size,int * minimum,int * natural,int * minimum_baseline,int * natural_baseline)1381 gtk_range_measure (GtkWidget      *widget,
1382                    GtkOrientation  orientation,
1383                    int             for_size,
1384                    int            *minimum,
1385                    int            *natural,
1386                    int            *minimum_baseline,
1387                    int            *natural_baseline)
1388 {
1389   GtkRange *range = GTK_RANGE (widget);
1390   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1391   GtkBorder border = { 0 };
1392 
1393   /* Measure the main box */
1394   gtk_widget_measure (priv->trough_widget,
1395                       orientation,
1396                       -1,
1397                       minimum, natural,
1398                       NULL, NULL);
1399 
1400   if (GTK_RANGE_GET_CLASS (range)->get_range_border)
1401     GTK_RANGE_GET_CLASS (range)->get_range_border (range, &border);
1402 
1403   /* Add the border */
1404   if (orientation == GTK_ORIENTATION_HORIZONTAL)
1405     {
1406       *minimum += border.left + border.right;
1407       *natural += border.left + border.right;
1408     }
1409   else
1410     {
1411       *minimum += border.top + border.bottom;
1412       *natural += border.top + border.bottom;
1413     }
1414 }
1415 
1416 static void
gtk_range_allocate_trough(GtkGizmo * gizmo,int width,int height,int baseline)1417 gtk_range_allocate_trough (GtkGizmo *gizmo,
1418                            int       width,
1419                            int       height,
1420                            int       baseline)
1421 {
1422   GtkWidget *widget = gtk_widget_get_parent (GTK_WIDGET (gizmo));
1423   GtkRange *range = GTK_RANGE (widget);
1424   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1425   GtkAllocation slider_alloc;
1426   const double lower = gtk_adjustment_get_lower (priv->adjustment);
1427   const double upper = gtk_adjustment_get_upper (priv->adjustment);
1428   const double page_size = gtk_adjustment_get_page_size (priv->adjustment);
1429   double value;
1430 
1431   /* Slider */
1432   gtk_range_calc_marks (range);
1433 
1434   gtk_range_compute_slider_position (range,
1435                                      gtk_adjustment_get_value (priv->adjustment),
1436                                      &slider_alloc);
1437 
1438   gtk_widget_size_allocate (priv->slider_widget, &slider_alloc, -1);
1439   priv->slider_x = slider_alloc.x;
1440   priv->slider_y = slider_alloc.y;
1441 
1442   if (lower == upper)
1443     value = 0;
1444   else
1445     value = (gtk_adjustment_get_value (priv->adjustment) - lower) / (upper - lower);
1446 
1447   if (priv->show_fill_level &&
1448       upper - page_size - lower != 0)
1449     {
1450       double level, fill;
1451       GtkAllocation fill_alloc;
1452 
1453       fill_alloc = (GtkAllocation) {0, 0, width, height};
1454 
1455       level = CLAMP (priv->fill_level, lower, upper - page_size);
1456 
1457       fill = (level - lower) / (upper - lower - page_size);
1458 
1459       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1460         {
1461           fill_alloc.width *= fill;
1462 
1463           if (should_invert (range))
1464             fill_alloc.x += width - fill_alloc.width;
1465         }
1466       else
1467         {
1468           fill_alloc.height *= fill;
1469 
1470           if (should_invert (range))
1471             fill_alloc.y += height - fill_alloc.height;
1472         }
1473 
1474       gtk_widget_size_allocate (priv->fill_widget, &fill_alloc, -1);
1475     }
1476 
1477   if (priv->highlight_widget)
1478     {
1479       GtkAllocation highlight_alloc;
1480       int min, nat;
1481 
1482       gtk_widget_measure (priv->highlight_widget,
1483                           priv->orientation, -1,
1484                           &min, &nat,
1485                           NULL, NULL);
1486 
1487       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1488         {
1489           highlight_alloc.y = 0;
1490           highlight_alloc.width = MAX (min, value * width);
1491           highlight_alloc.height = height;
1492 
1493           if (!should_invert (range))
1494             highlight_alloc.x = 0;
1495           else
1496             highlight_alloc.x = width - highlight_alloc.width;
1497         }
1498       else
1499         {
1500           highlight_alloc.x = 0;
1501           highlight_alloc.width = width;
1502           highlight_alloc.height = MAX (min, height * value);
1503 
1504           if (!should_invert (range))
1505             highlight_alloc.y = 0;
1506           else
1507             highlight_alloc.y = height - highlight_alloc.height;
1508         }
1509 
1510       gtk_widget_size_allocate (priv->highlight_widget, &highlight_alloc, -1);
1511     }
1512 }
1513 
1514 /* Clamp dimensions and border inside allocation, such that we prefer
1515  * to take space from border not dimensions in all directions, and prefer to
1516  * give space to border over dimensions in one direction.
1517  */
1518 static void
clamp_dimensions(int range_width,int range_height,int * width,int * height,GtkBorder * border,gboolean border_expands_horizontally)1519 clamp_dimensions (int        range_width,
1520                   int        range_height,
1521                   int       *width,
1522                   int       *height,
1523                   GtkBorder *border,
1524                   gboolean   border_expands_horizontally)
1525 {
1526   int extra, shortage;
1527 
1528   /* Width */
1529   extra = range_width - border->left - border->right - *width;
1530   if (extra > 0)
1531     {
1532       if (border_expands_horizontally)
1533         {
1534           border->left += extra / 2;
1535           border->right += extra / 2 + extra % 2;
1536         }
1537       else
1538         {
1539           *width += extra;
1540         }
1541     }
1542 
1543   /* See if we can fit rect, if not kill the border */
1544   shortage = *width - range_width;
1545   if (shortage > 0)
1546     {
1547       *width = range_width;
1548       /* lose the border */
1549       border->left = 0;
1550       border->right = 0;
1551     }
1552   else
1553     {
1554       /* See if we can fit rect with borders */
1555       shortage = *width + border->left + border->right - range_width;
1556       if (shortage > 0)
1557         {
1558           /* Shrink borders */
1559           border->left -= shortage / 2;
1560           border->right -= shortage / 2 + shortage % 2;
1561         }
1562     }
1563 
1564   /* Height */
1565   extra = range_height - border->top - border->bottom - *height;
1566   if (extra > 0)
1567     {
1568       if (border_expands_horizontally)
1569         {
1570           /* don't expand border vertically */
1571           *height += extra;
1572         }
1573       else
1574         {
1575           border->top += extra / 2;
1576           border->bottom += extra / 2 + extra % 2;
1577         }
1578     }
1579 
1580   /* See if we can fit rect, if not kill the border */
1581   shortage = *height - range_height;
1582   if (shortage > 0)
1583     {
1584       *height = range_height;
1585       /* lose the border */
1586       border->top = 0;
1587       border->bottom = 0;
1588     }
1589   else
1590     {
1591       /* See if we can fit rect with borders */
1592       shortage = *height + border->top + border->bottom - range_height;
1593       if (shortage > 0)
1594         {
1595           /* Shrink borders */
1596           border->top -= shortage / 2;
1597           border->bottom -= shortage / 2 + shortage % 2;
1598         }
1599     }
1600 }
1601 
1602 static void
gtk_range_size_allocate(GtkWidget * widget,int width,int height,int baseline)1603 gtk_range_size_allocate (GtkWidget *widget,
1604                          int        width,
1605                          int        height,
1606                          int        baseline)
1607 {
1608   GtkRange *range = GTK_RANGE (widget);
1609   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1610   GtkBorder border = { 0 };
1611   GtkAllocation box_alloc;
1612   int box_min_width, box_min_height;
1613 
1614   if (GTK_RANGE_GET_CLASS (range)->get_range_border)
1615     GTK_RANGE_GET_CLASS (range)->get_range_border (range, &border);
1616 
1617   gtk_widget_measure (priv->trough_widget,
1618                       GTK_ORIENTATION_HORIZONTAL, -1,
1619                       &box_min_width, NULL,
1620                       NULL, NULL);
1621   gtk_widget_measure (priv->trough_widget,
1622                       GTK_ORIENTATION_VERTICAL, -1,
1623                       &box_min_height, NULL,
1624                       NULL, NULL);
1625 
1626   if (priv->orientation == GTK_ORIENTATION_VERTICAL)
1627     clamp_dimensions (width, height, &box_min_width, &box_min_height, &border, TRUE);
1628   else
1629     clamp_dimensions (width, height, &box_min_width, &box_min_height, &border, FALSE);
1630 
1631   box_alloc.x = border.left;
1632   box_alloc.y = border.top;
1633   box_alloc.width = box_min_width;
1634   box_alloc.height = box_min_height;
1635 
1636   gtk_widget_size_allocate (priv->trough_widget, &box_alloc, -1);
1637 }
1638 
1639 static void
gtk_range_unmap(GtkWidget * widget)1640 gtk_range_unmap (GtkWidget *widget)
1641 {
1642   GtkRange *range = GTK_RANGE (widget);
1643 
1644   stop_scrolling (range);
1645 
1646   GTK_WIDGET_CLASS (gtk_range_parent_class)->unmap (widget);
1647 }
1648 
1649 static void
gtk_range_direction_changed(GtkWidget * widget,GtkTextDirection previous_direction)1650 gtk_range_direction_changed (GtkWidget        *widget,
1651                              GtkTextDirection  previous_direction)
1652 {
1653   GtkRange *range = GTK_RANGE (widget);
1654 
1655   update_fill_position (range);
1656   update_highlight_position (range);
1657 
1658   GTK_WIDGET_CLASS (gtk_range_parent_class)->direction_changed (widget, previous_direction);
1659 }
1660 
1661 static void
gtk_range_render_trough(GtkGizmo * gizmo,GtkSnapshot * snapshot)1662 gtk_range_render_trough (GtkGizmo    *gizmo,
1663                          GtkSnapshot *snapshot)
1664 {
1665   GtkWidget *widget = gtk_widget_get_parent (GTK_WIDGET (gizmo));
1666   GtkRange *range = GTK_RANGE (widget);
1667   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1668 
1669   /* HACK: GtkColorScale wants to draw its own trough
1670    * so we let it...
1671    */
1672   if (GTK_IS_COLOR_SCALE (widget))
1673     gtk_color_scale_snapshot_trough (GTK_COLOR_SCALE (widget), snapshot,
1674                                      gtk_widget_get_width (GTK_WIDGET (gizmo)),
1675                                      gtk_widget_get_height (GTK_WIDGET (gizmo)));
1676 
1677   if (priv->show_fill_level &&
1678       gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment) -
1679       gtk_adjustment_get_lower (priv->adjustment) != 0)
1680     gtk_widget_snapshot_child (GTK_WIDGET (gizmo), priv->fill_widget, snapshot);
1681 
1682   if (priv->highlight_widget)
1683     gtk_widget_snapshot_child (GTK_WIDGET (gizmo), priv->highlight_widget, snapshot);
1684 
1685   gtk_widget_snapshot_child (GTK_WIDGET (gizmo), priv->slider_widget, snapshot);
1686 }
1687 
1688 static void
range_grab_add(GtkRange * range,GtkWidget * location)1689 range_grab_add (GtkRange  *range,
1690                 GtkWidget *location)
1691 {
1692   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1693 
1694   /* Don't perform any GDK/GTK grab here. Since a button
1695    * is down, there's an ongoing implicit grab on
1696    * the widget, which pretty much guarantees this
1697    * is the only widget receiving the pointer events.
1698    */
1699   priv->grab_location = location;
1700 
1701   gtk_widget_add_css_class (GTK_WIDGET (range), "dragging");
1702 }
1703 
1704 static void
update_zoom_state(GtkRange * range,gboolean enabled)1705 update_zoom_state (GtkRange *range,
1706                    gboolean  enabled)
1707 {
1708   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1709 
1710   if (enabled)
1711     gtk_widget_add_css_class (GTK_WIDGET (range), "fine-tune");
1712   else
1713     gtk_widget_remove_css_class (GTK_WIDGET (range), "fine-tune");
1714 
1715   priv->zoom = enabled;
1716 }
1717 
1718 static void
range_grab_remove(GtkRange * range)1719 range_grab_remove (GtkRange *range)
1720 {
1721   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1722 
1723   if (!priv->grab_location)
1724     return;
1725 
1726   priv->grab_location = NULL;
1727 
1728   update_zoom_state (range, FALSE);
1729 
1730   gtk_widget_remove_css_class (GTK_WIDGET (range), "dragging");
1731 }
1732 
1733 static GtkScrollType
range_get_scroll_for_grab(GtkRange * range)1734 range_get_scroll_for_grab (GtkRange *range)
1735 {
1736   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1737 
1738   if (!priv->grab_location)
1739     return GTK_SCROLL_NONE;
1740 
1741   /* In the trough */
1742   if (priv->grab_location == priv->trough_widget)
1743     {
1744       if (priv->trough_click_forward)
1745         return GTK_SCROLL_PAGE_FORWARD;
1746       else
1747         return GTK_SCROLL_PAGE_BACKWARD;
1748     }
1749 
1750   return GTK_SCROLL_NONE;
1751 }
1752 
1753 static double
coord_to_value(GtkRange * range,double coord)1754 coord_to_value (GtkRange *range,
1755                 double    coord)
1756 {
1757   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1758   double frac;
1759   double value;
1760   int     trough_length;
1761   int     slider_length;
1762   graphene_rect_t slider_bounds;
1763 
1764   if (!gtk_widget_compute_bounds (priv->slider_widget, priv->slider_widget, &slider_bounds))
1765     graphene_rect_init (&slider_bounds, 0, 0, gtk_widget_get_width (priv->trough_widget), gtk_widget_get_height (priv->trough_widget));
1766 
1767   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1768     {
1769       trough_length = gtk_widget_get_width (priv->trough_widget);
1770       slider_length = slider_bounds.size.width;
1771     }
1772   else
1773     {
1774       trough_length = gtk_widget_get_height (priv->trough_widget);
1775       slider_length = slider_bounds.size.height;
1776     }
1777 
1778   if (trough_length == slider_length)
1779     {
1780       frac = 1.0;
1781     }
1782   else
1783     {
1784       if (priv->slider_size_fixed)
1785         frac = CLAMP (coord / (double) trough_length, 0, 1);
1786       else
1787         frac = CLAMP (coord / (double) (trough_length - slider_length), 0, 1);
1788     }
1789 
1790   if (should_invert (range))
1791     frac = 1.0 - frac;
1792 
1793   value = gtk_adjustment_get_lower (priv->adjustment) +
1794           frac * (gtk_adjustment_get_upper (priv->adjustment) -
1795                   gtk_adjustment_get_lower (priv->adjustment) -
1796                   gtk_adjustment_get_page_size (priv->adjustment));
1797   return value;
1798 }
1799 
1800 static gboolean
gtk_range_key_controller_key_pressed(GtkEventControllerKey * controller,guint keyval,guint keycode,GdkModifierType state,GtkWidget * widget)1801 gtk_range_key_controller_key_pressed (GtkEventControllerKey *controller,
1802                                       guint                  keyval,
1803                                       guint                  keycode,
1804                                       GdkModifierType        state,
1805                                       GtkWidget             *widget)
1806 {
1807   GtkRange *range = GTK_RANGE (widget);
1808   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1809 
1810   if (gtk_gesture_is_active (priv->drag_gesture) &&
1811       keyval == GDK_KEY_Escape &&
1812       priv->grab_location != NULL)
1813     {
1814       stop_scrolling (range);
1815 
1816       return GDK_EVENT_STOP;
1817     }
1818   else if (priv->in_drag &&
1819            (keyval == GDK_KEY_Shift_L ||
1820             keyval == GDK_KEY_Shift_R))
1821     {
1822       graphene_rect_t slider_bounds;
1823 
1824       if (!gtk_widget_compute_bounds (priv->slider_widget, priv->trough_widget, &slider_bounds))
1825         return GDK_EVENT_STOP;
1826 
1827       if (priv->orientation == GTK_ORIENTATION_VERTICAL)
1828         priv->slide_initial_slider_position = slider_bounds.origin.y;
1829       else
1830         priv->slide_initial_slider_position = slider_bounds.origin.x;
1831       update_zoom_state (range, !priv->zoom);
1832 
1833       return GDK_EVENT_STOP;
1834     }
1835 
1836   return GDK_EVENT_PROPAGATE;
1837 }
1838 
1839 static void
update_initial_slider_position(GtkRange * range,double x,double y)1840 update_initial_slider_position (GtkRange *range,
1841                                 double    x,
1842                                 double    y)
1843 {
1844   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1845 
1846   gtk_widget_translate_coordinates (GTK_WIDGET (range), priv->trough_widget,
1847                                     x, y, &x, &y);
1848 
1849   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1850     {
1851       priv->slide_initial_slider_position = MAX (0, priv->slider_x);
1852       priv->slide_initial_coordinate_delta = x - priv->slide_initial_slider_position;
1853     }
1854   else
1855     {
1856       priv->slide_initial_slider_position = MAX (0, priv->slider_y);
1857       priv->slide_initial_coordinate_delta = y - priv->slide_initial_slider_position;
1858     }
1859 }
1860 
1861 static void
gtk_range_long_press_gesture_pressed(GtkGestureLongPress * gesture,double x,double y,GtkRange * range)1862 gtk_range_long_press_gesture_pressed (GtkGestureLongPress *gesture,
1863                                       double               x,
1864                                       double               y,
1865                                       GtkRange            *range)
1866 {
1867   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1868   GtkWidget *mouse_location;
1869 
1870   mouse_location = gtk_widget_pick (GTK_WIDGET (range), x, y, GTK_PICK_DEFAULT);
1871 
1872   if (mouse_location == priv->slider_widget && !priv->zoom)
1873     {
1874       update_initial_slider_position (range, x, y);
1875       update_zoom_state (range, TRUE);
1876     }
1877 }
1878 
1879 static void
gtk_range_click_gesture_pressed(GtkGestureClick * gesture,guint n_press,double x,double y,GtkRange * range)1880 gtk_range_click_gesture_pressed (GtkGestureClick *gesture,
1881                                  guint            n_press,
1882                                  double           x,
1883                                  double           y,
1884                                  GtkRange        *range)
1885 {
1886   GtkWidget *widget = GTK_WIDGET (range);
1887   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
1888   GdkDevice *source_device;
1889   GdkEventSequence *sequence;
1890   GdkEvent *event;
1891   GdkInputSource source;
1892   gboolean primary_warps;
1893   gboolean shift_pressed;
1894   guint button;
1895   GdkModifierType state_mask;
1896   GtkWidget *mouse_location;
1897 
1898   if (!gtk_widget_has_focus (widget))
1899     gtk_widget_grab_focus (widget);
1900 
1901   sequence = gtk_gesture_single_get_current_sequence (GTK_GESTURE_SINGLE (gesture));
1902   button = gtk_gesture_single_get_current_button (GTK_GESTURE_SINGLE (gesture));
1903   event = gtk_gesture_get_last_event (GTK_GESTURE (gesture), sequence);
1904   state_mask = gdk_event_get_modifier_state (event);
1905   shift_pressed = (state_mask & GDK_SHIFT_MASK) != 0;
1906 
1907   source_device = gdk_event_get_device ((GdkEvent *) event);
1908   source = gdk_device_get_source (source_device);
1909 
1910   g_object_get (gtk_widget_get_settings (widget),
1911                 "gtk-primary-button-warps-slider", &primary_warps,
1912                 NULL);
1913 
1914   mouse_location = gtk_widget_pick (widget, x, y, 0);
1915 
1916   /* For the purposes of this function, we treat anything outside
1917    * the slider like a click on the trough
1918    */
1919   if (mouse_location != priv->slider_widget)
1920     mouse_location = priv->trough_widget;
1921 
1922   if (mouse_location == priv->slider_widget)
1923     {
1924       /* Shift-click in the slider = fine adjustment */
1925       if (shift_pressed)
1926         update_zoom_state (range, TRUE);
1927 
1928       update_initial_slider_position (range, x, y);
1929       range_grab_add (range, priv->slider_widget);
1930     }
1931   else if (mouse_location == priv->trough_widget &&
1932            (source == GDK_SOURCE_TOUCHSCREEN ||
1933             (primary_warps && !shift_pressed && button == GDK_BUTTON_PRIMARY) ||
1934             (!primary_warps && shift_pressed && button == GDK_BUTTON_PRIMARY) ||
1935             (!primary_warps && button == GDK_BUTTON_MIDDLE)))
1936     {
1937       double slider_range_x, slider_range_y;
1938       graphene_rect_t slider_bounds;
1939 
1940       gtk_widget_translate_coordinates (priv->trough_widget, widget,
1941                                         priv->slider_x, priv->slider_y,
1942                                         &slider_range_x, &slider_range_y);
1943 
1944       /* If we aren't fixed, center on the slider. I.e. if this is not a scale... */
1945       if (!priv->slider_size_fixed &&
1946           gtk_widget_compute_bounds (priv->slider_widget, priv->slider_widget, &slider_bounds))
1947         {
1948           slider_range_x += (slider_bounds.size.width  / 2);
1949           slider_range_y += (slider_bounds.size.height / 2);
1950         }
1951 
1952       update_initial_slider_position (range, slider_range_x, slider_range_y);
1953 
1954       range_grab_add (range, priv->slider_widget);
1955 
1956       update_slider_position (range, x, y);
1957     }
1958   else if (mouse_location == priv->trough_widget &&
1959            ((primary_warps && shift_pressed && button == GDK_BUTTON_PRIMARY) ||
1960             (!primary_warps && !shift_pressed && button == GDK_BUTTON_PRIMARY) ||
1961             (primary_warps && button == GDK_BUTTON_MIDDLE)))
1962     {
1963       /* jump by pages */
1964       GtkScrollType scroll;
1965       double click_value;
1966 
1967       click_value = coord_to_value (range,
1968                                     priv->orientation == GTK_ORIENTATION_VERTICAL ?
1969                                     y : x);
1970 
1971       priv->trough_click_forward = click_value > gtk_adjustment_get_value (priv->adjustment);
1972       range_grab_add (range, priv->trough_widget);
1973 
1974       scroll = range_get_scroll_for_grab (range);
1975       gtk_range_add_step_timer (range, scroll);
1976     }
1977   else if (mouse_location == priv->trough_widget &&
1978            button == GDK_BUTTON_SECONDARY)
1979     {
1980       /* autoscroll */
1981       double click_value;
1982 
1983       click_value = coord_to_value (range,
1984                                     priv->orientation == GTK_ORIENTATION_VERTICAL ?
1985                                     y : x);
1986 
1987       priv->trough_click_forward = click_value > gtk_adjustment_get_value (priv->adjustment);
1988       range_grab_add (range, priv->trough_widget);
1989 
1990       remove_autoscroll (range);
1991       priv->autoscroll_mode = priv->trough_click_forward ? GTK_SCROLL_END : GTK_SCROLL_START;
1992       add_autoscroll (range);
1993     }
1994 
1995   if (priv->grab_location == priv->slider_widget);
1996     /* leave it to ::drag-begin to claim the sequence */
1997   else if (priv->grab_location != NULL)
1998     gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
1999 }
2000 
2001 /* During a slide, move the slider as required given new mouse position */
2002 static void
update_slider_position(GtkRange * range,int mouse_x,int mouse_y)2003 update_slider_position (GtkRange *range,
2004                         int       mouse_x,
2005                         int       mouse_y)
2006 {
2007   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2008   graphene_rect_t trough_bounds;
2009   double delta;
2010   double c;
2011   double new_value;
2012   gboolean handled;
2013   double next_value;
2014   double mark_value;
2015   double mark_delta;
2016   double zoom;
2017   int i;
2018   double x, y;
2019 
2020   gtk_widget_translate_coordinates (GTK_WIDGET (range), priv->trough_widget,
2021                                     mouse_x, mouse_y, &x, &y);
2022 
2023   if (priv->zoom &&
2024       gtk_widget_compute_bounds (priv->trough_widget, priv->trough_widget, &trough_bounds))
2025     {
2026       zoom = MIN(1.0, (priv->orientation == GTK_ORIENTATION_VERTICAL ?
2027                        trough_bounds.size.height : trough_bounds.size.width) /
2028                        (gtk_adjustment_get_upper (priv->adjustment) -
2029                         gtk_adjustment_get_lower (priv->adjustment) -
2030                         gtk_adjustment_get_page_size (priv->adjustment)));
2031 
2032       /* the above is ineffective for scales, so just set a zoom factor */
2033       if (zoom == 1.0)
2034         zoom = 0.25;
2035     }
2036   else
2037     zoom = 1.0;
2038 
2039   /* recalculate the initial position from the current position */
2040   if (priv->slide_initial_slider_position == -1)
2041     {
2042       graphene_rect_t slider_bounds;
2043       double zoom_divisor;
2044 
2045       if (!gtk_widget_compute_bounds (priv->slider_widget, GTK_WIDGET (range), &slider_bounds))
2046         graphene_rect_init (&slider_bounds, 0, 0, 0, 0);
2047 
2048       if (zoom == 1.0)
2049         zoom_divisor = 1.0;
2050       else
2051         zoom_divisor = zoom - 1.0;
2052 
2053       if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2054         priv->slide_initial_slider_position = (zoom * (y - priv->slide_initial_coordinate_delta) - slider_bounds.origin.y) / zoom_divisor;
2055       else
2056         priv->slide_initial_slider_position = (zoom * (x - priv->slide_initial_coordinate_delta) - slider_bounds.origin.x) / zoom_divisor;
2057     }
2058 
2059   if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2060     delta = y - (priv->slide_initial_coordinate_delta + priv->slide_initial_slider_position);
2061   else
2062     delta = x - (priv->slide_initial_coordinate_delta + priv->slide_initial_slider_position);
2063 
2064   c = priv->slide_initial_slider_position + zoom * delta;
2065 
2066   new_value = coord_to_value (range, c);
2067   next_value = coord_to_value (range, c + 1);
2068   mark_delta = fabs (next_value - new_value);
2069 
2070   for (i = 0; i < priv->n_marks; i++)
2071     {
2072       mark_value = priv->marks[i];
2073 
2074       if (fabs (gtk_adjustment_get_value (priv->adjustment) - mark_value) < 3 * mark_delta)
2075         {
2076           if (fabs (new_value - mark_value) < MARK_SNAP_LENGTH * mark_delta)
2077             {
2078               new_value = mark_value;
2079               break;
2080             }
2081         }
2082     }
2083 
2084   g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, new_value, &handled);
2085 }
2086 
2087 static void
remove_autoscroll(GtkRange * range)2088 remove_autoscroll (GtkRange *range)
2089 {
2090   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2091 
2092   if (priv->autoscroll_id)
2093     {
2094       gtk_widget_remove_tick_callback (GTK_WIDGET (range),
2095                                        priv->autoscroll_id);
2096       priv->autoscroll_id = 0;
2097     }
2098 
2099   /* unset initial position so it can be calculated */
2100   priv->slide_initial_slider_position = -1;
2101 
2102   priv->autoscroll_mode = GTK_SCROLL_NONE;
2103 }
2104 
2105 static gboolean
autoscroll_cb(GtkWidget * widget,GdkFrameClock * frame_clock,gpointer data)2106 autoscroll_cb (GtkWidget     *widget,
2107                GdkFrameClock *frame_clock,
2108                gpointer       data)
2109 {
2110   GtkRange *range = GTK_RANGE (data);
2111   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2112   GtkAdjustment *adj = priv->adjustment;
2113   double increment;
2114   double value;
2115   gboolean handled;
2116   double step, page;
2117 
2118   step = gtk_adjustment_get_step_increment (adj);
2119   page = gtk_adjustment_get_page_increment (adj);
2120 
2121   switch ((guint) priv->autoscroll_mode)
2122     {
2123     case GTK_SCROLL_STEP_FORWARD:
2124       increment = step / AUTOSCROLL_FACTOR;
2125       break;
2126     case GTK_SCROLL_PAGE_FORWARD:
2127       increment = page / AUTOSCROLL_FACTOR;
2128       break;
2129     case GTK_SCROLL_STEP_BACKWARD:
2130       increment = - step / AUTOSCROLL_FACTOR;
2131       break;
2132     case GTK_SCROLL_PAGE_BACKWARD:
2133       increment = - page / AUTOSCROLL_FACTOR;
2134       break;
2135     case GTK_SCROLL_START:
2136     case GTK_SCROLL_END:
2137       {
2138         double x, y;
2139         double distance, t;
2140 
2141         /* Vary scrolling speed from slow (ie step) to fast (2 * page),
2142          * based on the distance of the pointer from the widget. We start
2143          * speeding up if the pointer moves at least 20 pixels away, and
2144          * we reach maximum speed when it is 220 pixels away.
2145          */
2146         if (!gtk_gesture_drag_get_offset (GTK_GESTURE_DRAG (priv->drag_gesture), &x, &y))
2147           {
2148             x = 0.0;
2149             y = 0.0;
2150           }
2151         if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
2152           distance = fabs (y);
2153         else
2154           distance = fabs (x);
2155         distance = CLAMP (distance - 20, 0.0, 200);
2156         t = distance / 100.0;
2157         step = (1 - t) * step + t * page;
2158         if (priv->autoscroll_mode == GTK_SCROLL_END)
2159           increment = step / AUTOSCROLL_FACTOR;
2160         else
2161           increment = - step / AUTOSCROLL_FACTOR;
2162       }
2163       break;
2164     default:
2165       g_assert_not_reached ();
2166       break;
2167     }
2168 
2169   value = gtk_adjustment_get_value (adj);
2170   value += increment;
2171 
2172   g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_JUMP, value, &handled);
2173 
2174   return G_SOURCE_CONTINUE;
2175 }
2176 
2177 static void
add_autoscroll(GtkRange * range)2178 add_autoscroll (GtkRange *range)
2179 {
2180   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2181 
2182   if (priv->autoscroll_id != 0 ||
2183       priv->autoscroll_mode == GTK_SCROLL_NONE)
2184     return;
2185 
2186   priv->autoscroll_id = gtk_widget_add_tick_callback (GTK_WIDGET (range),
2187                                                       autoscroll_cb, range, NULL);
2188 }
2189 
2190 static void
stop_scrolling(GtkRange * range)2191 stop_scrolling (GtkRange *range)
2192 {
2193   range_grab_remove (range);
2194   gtk_range_remove_step_timer (range);
2195   remove_autoscroll (range);
2196 }
2197 
2198 static gboolean
gtk_range_scroll_controller_scroll(GtkEventControllerScroll * scroll,double dx,double dy,GtkRange * range)2199 gtk_range_scroll_controller_scroll (GtkEventControllerScroll *scroll,
2200                                     double                    dx,
2201                                     double                    dy,
2202                                     GtkRange                 *range)
2203 {
2204   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2205   double scroll_unit, delta;
2206   gboolean handled;
2207   GtkOrientation move_orientation;
2208 
2209 #ifdef GDK_WINDOWING_MACOS
2210   scroll_unit = 1;
2211 #else
2212   scroll_unit = gtk_adjustment_get_page_increment (priv->adjustment);
2213 #endif
2214 
2215   if (priv->orientation == GTK_ORIENTATION_HORIZONTAL && dx != 0)
2216     {
2217       move_orientation = GTK_ORIENTATION_HORIZONTAL;
2218       delta = dx * scroll_unit;
2219     }
2220   else
2221     {
2222       move_orientation = GTK_ORIENTATION_VERTICAL;
2223       delta = dy * scroll_unit;
2224     }
2225 
2226   if (delta != 0 && should_invert_move (range, move_orientation))
2227     delta = - delta;
2228 
2229   g_signal_emit (range, signals[CHANGE_VALUE], 0,
2230                  GTK_SCROLL_JUMP, gtk_adjustment_get_value (priv->adjustment) + delta,
2231                  &handled);
2232 
2233   return GDK_EVENT_STOP;
2234 }
2235 
2236 static void
update_autoscroll_mode(GtkRange * range,int mouse_x,int mouse_y)2237 update_autoscroll_mode (GtkRange *range,
2238                         int       mouse_x,
2239                         int       mouse_y)
2240 {
2241   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2242   GtkScrollType mode = GTK_SCROLL_NONE;
2243 
2244   if (priv->zoom)
2245     {
2246       int width, height;
2247       int size, pos;
2248 
2249       width = gtk_widget_get_width (GTK_WIDGET (range));
2250       height = gtk_widget_get_height (GTK_WIDGET (range));
2251 
2252       if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2253         {
2254           size = height;
2255           pos = mouse_y;
2256         }
2257       else
2258         {
2259           size = width;
2260           pos = mouse_x;
2261         }
2262 
2263       if (pos < SCROLL_EDGE_SIZE)
2264         mode = priv->inverted ? GTK_SCROLL_STEP_FORWARD : GTK_SCROLL_STEP_BACKWARD;
2265       else if (pos > (size - SCROLL_EDGE_SIZE))
2266         mode = priv->inverted ? GTK_SCROLL_STEP_BACKWARD : GTK_SCROLL_STEP_FORWARD;
2267     }
2268 
2269   if (mode != priv->autoscroll_mode)
2270     {
2271       remove_autoscroll (range);
2272       priv->autoscroll_mode = mode;
2273       add_autoscroll (range);
2274     }
2275 }
2276 
2277 static void
gtk_range_drag_gesture_update(GtkGestureDrag * gesture,double offset_x,double offset_y,GtkRange * range)2278 gtk_range_drag_gesture_update (GtkGestureDrag *gesture,
2279                                double          offset_x,
2280                                double          offset_y,
2281                                GtkRange       *range)
2282 {
2283   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2284   double start_x, start_y;
2285 
2286   if (priv->grab_location == priv->slider_widget)
2287     {
2288       int mouse_x, mouse_y;
2289 
2290       gtk_gesture_drag_get_start_point (gesture, &start_x, &start_y);
2291       mouse_x = start_x + offset_x;
2292       mouse_y = start_y + offset_y;
2293       priv->in_drag = TRUE;
2294       update_autoscroll_mode (range, mouse_x, mouse_y);
2295 
2296       if (priv->autoscroll_mode == GTK_SCROLL_NONE)
2297         update_slider_position (range, mouse_x, mouse_y);
2298     }
2299 }
2300 
2301 static void
gtk_range_drag_gesture_begin(GtkGestureDrag * gesture,double offset_x,double offset_y,GtkRange * range)2302 gtk_range_drag_gesture_begin (GtkGestureDrag *gesture,
2303                               double          offset_x,
2304                               double          offset_y,
2305                               GtkRange       *range)
2306 {
2307   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2308 
2309   if (priv->grab_location == priv->slider_widget)
2310     gtk_gesture_set_state (priv->drag_gesture, GTK_EVENT_SEQUENCE_CLAIMED);
2311 }
2312 
2313 static void
gtk_range_drag_gesture_end(GtkGestureDrag * gesture,double offset_x,double offset_y,GtkRange * range)2314 gtk_range_drag_gesture_end (GtkGestureDrag *gesture,
2315                             double          offset_x,
2316                             double          offset_y,
2317                             GtkRange       *range)
2318 {
2319   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2320 
2321   priv->in_drag = FALSE;
2322   stop_scrolling (range);
2323 }
2324 
2325 static void
gtk_range_adjustment_changed(GtkAdjustment * adjustment,gpointer data)2326 gtk_range_adjustment_changed (GtkAdjustment *adjustment,
2327                               gpointer       data)
2328 {
2329   GtkRange *range = GTK_RANGE (data);
2330   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2331   double upper = gtk_adjustment_get_upper (priv->adjustment);
2332   double lower = gtk_adjustment_get_lower (priv->adjustment);
2333 
2334   if (upper == lower && GTK_IS_SCALE (range))
2335     gtk_widget_hide (priv->slider_widget);
2336   else
2337     gtk_widget_show (priv->slider_widget);
2338 
2339   gtk_widget_queue_allocate (priv->trough_widget);
2340 
2341   gtk_accessible_update_property (GTK_ACCESSIBLE (range),
2342                                   GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, upper,
2343                                   GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, lower,
2344                                   -1);
2345 
2346   /* Note that we don't round off to priv->round_digits here.
2347    * that's because it's really broken to change a value
2348    * in response to a change signal on that value; round_digits
2349    * is therefore defined to be a filter on what the GtkRange
2350    * can input into the adjustment, not a filter that the GtkRange
2351    * will enforce on the adjustment.
2352    */
2353 }
2354 
2355 static void
gtk_range_adjustment_value_changed(GtkAdjustment * adjustment,gpointer data)2356 gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
2357 				    gpointer       data)
2358 {
2359   GtkRange *range = GTK_RANGE (data);
2360   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2361 
2362   /* Note that we don't round off to priv->round_digits here.
2363    * that's because it's really broken to change a value
2364    * in response to a change signal on that value; round_digits
2365    * is therefore defined to be a filter on what the GtkRange
2366    * can input into the adjustment, not a filter that the GtkRange
2367    * will enforce on the adjustment.
2368    */
2369 
2370   g_signal_emit (range, signals[VALUE_CHANGED], 0);
2371 
2372   gtk_accessible_update_property (GTK_ACCESSIBLE (range),
2373                                   GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment),
2374                                   -1);
2375 
2376   gtk_widget_queue_allocate (priv->trough_widget);
2377 }
2378 
2379 static void
apply_marks(GtkRange * range,double oldval,double * newval)2380 apply_marks (GtkRange *range,
2381              double    oldval,
2382              double   *newval)
2383 {
2384   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2385   int i;
2386   double mark;
2387 
2388   for (i = 0; i < priv->n_marks; i++)
2389     {
2390       mark = priv->marks[i];
2391       if ((oldval < mark && mark < *newval) ||
2392           (oldval > mark && mark > *newval))
2393         {
2394           *newval = mark;
2395           return;
2396         }
2397     }
2398 }
2399 
2400 static void
step_back(GtkRange * range)2401 step_back (GtkRange *range)
2402 {
2403   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2404   double newval;
2405   gboolean handled;
2406 
2407   newval = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_step_increment (priv->adjustment);
2408   apply_marks (range, gtk_adjustment_get_value (priv->adjustment), &newval);
2409   g_signal_emit (range, signals[CHANGE_VALUE], 0,
2410                  GTK_SCROLL_STEP_BACKWARD, newval, &handled);
2411 }
2412 
2413 static void
step_forward(GtkRange * range)2414 step_forward (GtkRange *range)
2415 {
2416   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2417   double newval;
2418   gboolean handled;
2419 
2420   newval = gtk_adjustment_get_value (priv->adjustment) + gtk_adjustment_get_step_increment (priv->adjustment);
2421   apply_marks (range, gtk_adjustment_get_value (priv->adjustment), &newval);
2422   g_signal_emit (range, signals[CHANGE_VALUE], 0,
2423                  GTK_SCROLL_STEP_FORWARD, newval, &handled);
2424 }
2425 
2426 
2427 static void
page_back(GtkRange * range)2428 page_back (GtkRange *range)
2429 {
2430   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2431   double newval;
2432   gboolean handled;
2433 
2434   newval = gtk_adjustment_get_value (priv->adjustment) - gtk_adjustment_get_page_increment (priv->adjustment);
2435   apply_marks (range, gtk_adjustment_get_value (priv->adjustment), &newval);
2436   g_signal_emit (range, signals[CHANGE_VALUE], 0,
2437                  GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
2438 }
2439 
2440 static void
page_forward(GtkRange * range)2441 page_forward (GtkRange *range)
2442 {
2443   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2444   double newval;
2445   gboolean handled;
2446 
2447   newval = gtk_adjustment_get_value (priv->adjustment) + gtk_adjustment_get_page_increment (priv->adjustment);
2448   apply_marks (range, gtk_adjustment_get_value (priv->adjustment), &newval);
2449   g_signal_emit (range, signals[CHANGE_VALUE], 0,
2450                  GTK_SCROLL_PAGE_FORWARD, newval, &handled);
2451 }
2452 
2453 static void
scroll_begin(GtkRange * range)2454 scroll_begin (GtkRange *range)
2455 {
2456   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2457   gboolean handled;
2458 
2459   g_signal_emit (range, signals[CHANGE_VALUE], 0,
2460                  GTK_SCROLL_START, gtk_adjustment_get_lower (priv->adjustment),
2461                  &handled);
2462 }
2463 
2464 static void
scroll_end(GtkRange * range)2465 scroll_end (GtkRange *range)
2466 {
2467   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2468   double newval;
2469   gboolean handled;
2470 
2471   newval = gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment);
2472   g_signal_emit (range, signals[CHANGE_VALUE], 0, GTK_SCROLL_END, newval,
2473                  &handled);
2474 }
2475 
2476 static gboolean
gtk_range_scroll(GtkRange * range,GtkScrollType scroll)2477 gtk_range_scroll (GtkRange     *range,
2478                   GtkScrollType scroll)
2479 {
2480   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2481   double old_value = gtk_adjustment_get_value (priv->adjustment);
2482 
2483   switch (scroll)
2484     {
2485     case GTK_SCROLL_STEP_LEFT:
2486       if (should_invert_move (range, GTK_ORIENTATION_HORIZONTAL))
2487         step_forward (range);
2488       else
2489         step_back (range);
2490       break;
2491 
2492     case GTK_SCROLL_STEP_UP:
2493       if (should_invert_move (range, GTK_ORIENTATION_VERTICAL))
2494         step_forward (range);
2495       else
2496         step_back (range);
2497       break;
2498 
2499     case GTK_SCROLL_STEP_RIGHT:
2500       if (should_invert_move (range, GTK_ORIENTATION_HORIZONTAL))
2501         step_back (range);
2502       else
2503         step_forward (range);
2504       break;
2505 
2506     case GTK_SCROLL_STEP_DOWN:
2507       if (should_invert_move (range, GTK_ORIENTATION_VERTICAL))
2508         step_back (range);
2509       else
2510         step_forward (range);
2511       break;
2512 
2513     case GTK_SCROLL_STEP_BACKWARD:
2514       step_back (range);
2515       break;
2516 
2517     case GTK_SCROLL_STEP_FORWARD:
2518       step_forward (range);
2519       break;
2520 
2521     case GTK_SCROLL_PAGE_LEFT:
2522       if (should_invert_move (range, GTK_ORIENTATION_HORIZONTAL))
2523         page_forward (range);
2524       else
2525         page_back (range);
2526       break;
2527 
2528     case GTK_SCROLL_PAGE_UP:
2529       if (should_invert_move (range, GTK_ORIENTATION_VERTICAL))
2530         page_forward (range);
2531       else
2532         page_back (range);
2533       break;
2534 
2535     case GTK_SCROLL_PAGE_RIGHT:
2536       if (should_invert_move (range, GTK_ORIENTATION_HORIZONTAL))
2537         page_back (range);
2538       else
2539         page_forward (range);
2540       break;
2541 
2542     case GTK_SCROLL_PAGE_DOWN:
2543       if (should_invert_move (range, GTK_ORIENTATION_VERTICAL))
2544         page_back (range);
2545       else
2546         page_forward (range);
2547       break;
2548 
2549     case GTK_SCROLL_PAGE_BACKWARD:
2550       page_back (range);
2551       break;
2552 
2553     case GTK_SCROLL_PAGE_FORWARD:
2554       page_forward (range);
2555       break;
2556 
2557     case GTK_SCROLL_START:
2558       scroll_begin (range);
2559       break;
2560 
2561     case GTK_SCROLL_END:
2562       scroll_end (range);
2563       break;
2564 
2565     case GTK_SCROLL_JUMP:
2566     case GTK_SCROLL_NONE:
2567     default:
2568       break;
2569     }
2570 
2571   return gtk_adjustment_get_value (priv->adjustment) != old_value;
2572 }
2573 
2574 static void
gtk_range_move_slider(GtkRange * range,GtkScrollType scroll)2575 gtk_range_move_slider (GtkRange     *range,
2576                        GtkScrollType scroll)
2577 {
2578   if (! gtk_range_scroll (range, scroll))
2579     gtk_widget_error_bell (GTK_WIDGET (range));
2580 }
2581 
2582 static void
gtk_range_compute_slider_position(GtkRange * range,double adjustment_value,GdkRectangle * slider_rect)2583 gtk_range_compute_slider_position (GtkRange     *range,
2584                                    double        adjustment_value,
2585                                    GdkRectangle *slider_rect)
2586 {
2587   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2588   const double upper = gtk_adjustment_get_upper (priv->adjustment);
2589   const double lower = gtk_adjustment_get_lower (priv->adjustment);
2590   const double page_size = gtk_adjustment_get_page_size (priv->adjustment);
2591   int trough_width, trough_height;
2592   int slider_width, slider_height;
2593 
2594   gtk_widget_measure (priv->slider_widget,
2595                       GTK_ORIENTATION_HORIZONTAL, -1,
2596                       &slider_width, NULL,
2597                       NULL, NULL);
2598   gtk_widget_measure (priv->slider_widget,
2599                       GTK_ORIENTATION_VERTICAL, slider_width,
2600                       &slider_height, NULL,
2601                       NULL, NULL);
2602 
2603   trough_width = gtk_widget_get_width (priv->trough_widget);
2604   trough_height = gtk_widget_get_height (priv->trough_widget);
2605 
2606   if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2607     {
2608       int y, height;
2609 
2610       slider_rect->x = (int) floor ((trough_width - slider_width) / 2);
2611       slider_rect->width = slider_width;
2612 
2613       /* slider height is the fraction (page_size /
2614        * total_adjustment_range) times the trough height in pixels
2615        */
2616 
2617       if (upper - lower != 0)
2618         height = trough_height * (page_size / (upper - lower));
2619       else
2620         height = slider_height;
2621 
2622       if (height < slider_height ||
2623           priv->slider_size_fixed)
2624         height = slider_height;
2625 
2626       height = MIN (height, trough_height);
2627 
2628       if (upper - lower - page_size != 0)
2629         y = (trough_height - height) * ((adjustment_value - lower)  / (upper - lower - page_size));
2630       else
2631         y = 0;
2632 
2633       y = CLAMP (y, 0, trough_height);
2634 
2635       if (should_invert (range))
2636         y = trough_height - y - height;
2637 
2638       slider_rect->y = y;
2639       slider_rect->height = height;
2640     }
2641   else
2642     {
2643       int x, width;
2644 
2645       slider_rect->y = (int) floor ((trough_height - slider_height) / 2);
2646       slider_rect->height = slider_height;
2647 
2648       /* slider width is the fraction (page_size /
2649        * total_adjustment_range) times the trough width in pixels
2650        */
2651 
2652       if (upper - lower != 0)
2653         width = trough_width * (page_size / (upper - lower));
2654       else
2655         width = slider_width;
2656 
2657       if (width < slider_width ||
2658           priv->slider_size_fixed)
2659         width = slider_width;
2660 
2661       width = MIN (width, trough_width);
2662 
2663       if (upper - lower - page_size != 0)
2664         x = (trough_width - width) * ((adjustment_value - lower) / (upper - lower - page_size));
2665       else
2666         x = 0;
2667 
2668       x = CLAMP (x, 0, trough_width);
2669 
2670       if (should_invert (range))
2671         x = trough_width - x - width;
2672 
2673       slider_rect->x = x;
2674       slider_rect->width = width;
2675     }
2676 }
2677 
2678 static void
gtk_range_calc_marks(GtkRange * range)2679 gtk_range_calc_marks (GtkRange *range)
2680 {
2681   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2682   GdkRectangle slider;
2683   double x, y;
2684   int i;
2685 
2686   for (i = 0; i < priv->n_marks; i++)
2687     {
2688       gtk_range_compute_slider_position (range, priv->marks[i], &slider);
2689       gtk_widget_translate_coordinates (priv->trough_widget, GTK_WIDGET (range),
2690                                         slider.x, slider.y, &x, &y);
2691 
2692       if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
2693         priv->mark_pos[i] = x + slider.width / 2;
2694       else
2695         priv->mark_pos[i] = y + slider.height / 2;
2696     }
2697 }
2698 
2699 static gboolean
gtk_range_real_change_value(GtkRange * range,GtkScrollType scroll,double value)2700 gtk_range_real_change_value (GtkRange      *range,
2701                              GtkScrollType  scroll,
2702                              double         value)
2703 {
2704   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2705 
2706   /* potentially adjust the bounds _before_ we clamp */
2707   g_signal_emit (range, signals[ADJUST_BOUNDS], 0, value);
2708 
2709   if (priv->restrict_to_fill_level)
2710     value = MIN (value, MAX (gtk_adjustment_get_lower (priv->adjustment),
2711                              priv->fill_level));
2712 
2713   value = CLAMP (value, gtk_adjustment_get_lower (priv->adjustment),
2714                  (gtk_adjustment_get_upper (priv->adjustment) - gtk_adjustment_get_page_size (priv->adjustment)));
2715 
2716   if (priv->round_digits >= 0)
2717     {
2718       double power;
2719       int i;
2720 
2721       i = priv->round_digits;
2722       power = 1;
2723       while (i--)
2724         power *= 10;
2725 
2726       value = floor ((value * power) + 0.5) / power;
2727     }
2728 
2729   if (priv->in_drag || priv->autoscroll_id)
2730     gtk_adjustment_set_value (priv->adjustment, value);
2731   else
2732     gtk_adjustment_animate_to_value (priv->adjustment, value);
2733 
2734   return FALSE;
2735 }
2736 
2737 struct _GtkRangeStepTimer
2738 {
2739   guint timeout_id;
2740   GtkScrollType step;
2741 };
2742 
2743 static gboolean
second_timeout(gpointer data)2744 second_timeout (gpointer data)
2745 {
2746   GtkRange *range = GTK_RANGE (data);
2747   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2748 
2749   gtk_range_scroll (range, priv->timer->step);
2750 
2751   return G_SOURCE_CONTINUE;
2752 }
2753 
2754 static gboolean
initial_timeout(gpointer data)2755 initial_timeout (gpointer data)
2756 {
2757   GtkRange *range = GTK_RANGE (data);
2758   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2759 
2760   priv->timer->timeout_id = g_timeout_add (TIMEOUT_REPEAT, second_timeout, range);
2761   gdk_source_set_static_name_by_id (priv->timer->timeout_id, "[gtk] second_timeout");
2762   return G_SOURCE_REMOVE;
2763 }
2764 
2765 static void
gtk_range_add_step_timer(GtkRange * range,GtkScrollType step)2766 gtk_range_add_step_timer (GtkRange      *range,
2767                           GtkScrollType  step)
2768 {
2769   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2770 
2771   g_return_if_fail (priv->timer == NULL);
2772   g_return_if_fail (step != GTK_SCROLL_NONE);
2773 
2774   priv->timer = g_new (GtkRangeStepTimer, 1);
2775 
2776   priv->timer->timeout_id = g_timeout_add (TIMEOUT_INITIAL, initial_timeout, range);
2777   gdk_source_set_static_name_by_id (priv->timer->timeout_id, "[gtk] initial_timeout");
2778   priv->timer->step = step;
2779 
2780   gtk_range_scroll (range, priv->timer->step);
2781 }
2782 
2783 static void
gtk_range_remove_step_timer(GtkRange * range)2784 gtk_range_remove_step_timer (GtkRange *range)
2785 {
2786   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2787 
2788   if (priv->timer)
2789     {
2790       if (priv->timer->timeout_id != 0)
2791         g_source_remove (priv->timer->timeout_id);
2792 
2793       g_free (priv->timer);
2794 
2795       priv->timer = NULL;
2796     }
2797 }
2798 
2799 void
_gtk_range_set_has_origin(GtkRange * range,gboolean has_origin)2800 _gtk_range_set_has_origin (GtkRange *range,
2801                            gboolean  has_origin)
2802 {
2803   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2804 
2805   if (has_origin)
2806     {
2807       priv->highlight_widget = gtk_gizmo_new ("highlight", NULL, NULL, NULL, NULL, NULL, NULL);
2808       gtk_widget_insert_before (priv->highlight_widget, priv->trough_widget, priv->slider_widget);
2809 
2810       update_highlight_position (range);
2811     }
2812   else
2813     {
2814       g_clear_pointer (&priv->highlight_widget, gtk_widget_unparent);
2815     }
2816 }
2817 
2818 gboolean
_gtk_range_get_has_origin(GtkRange * range)2819 _gtk_range_get_has_origin (GtkRange *range)
2820 {
2821   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2822 
2823   return priv->highlight_widget != NULL;
2824 }
2825 
2826 void
_gtk_range_set_stop_values(GtkRange * range,double * values,int n_values)2827 _gtk_range_set_stop_values (GtkRange *range,
2828                             double   *values,
2829                             int       n_values)
2830 {
2831   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2832   int i;
2833 
2834   g_free (priv->marks);
2835   priv->marks = g_new (double, n_values);
2836 
2837   g_free (priv->mark_pos);
2838   priv->mark_pos = g_new (int, n_values);
2839 
2840   priv->n_marks = n_values;
2841 
2842   for (i = 0; i < n_values; i++)
2843     priv->marks[i] = values[i];
2844 
2845   gtk_range_calc_marks (range);
2846 }
2847 
2848 int
_gtk_range_get_stop_positions(GtkRange * range,int ** values)2849 _gtk_range_get_stop_positions (GtkRange  *range,
2850                                int      **values)
2851 {
2852   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2853 
2854   gtk_range_calc_marks (range);
2855 
2856   if (values)
2857     *values = g_memdup2 (priv->mark_pos, priv->n_marks * sizeof (int));
2858 
2859   return priv->n_marks;
2860 }
2861 
2862 /**
2863  * gtk_range_set_round_digits: (attributes org.gtk.Method.set_property=round-digits)
2864  * @range: a `GtkRange`
2865  * @round_digits: the precision in digits, or -1
2866  *
2867  * Sets the number of digits to round the value to when
2868  * it changes.
2869  *
2870  * See [signal@Gtk.Range::change-value].
2871  */
2872 void
gtk_range_set_round_digits(GtkRange * range,int round_digits)2873 gtk_range_set_round_digits (GtkRange *range,
2874                             int       round_digits)
2875 {
2876   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2877 
2878   g_return_if_fail (GTK_IS_RANGE (range));
2879   g_return_if_fail (round_digits >= -1);
2880 
2881   if (priv->round_digits != round_digits)
2882     {
2883       priv->round_digits = round_digits;
2884       g_object_notify_by_pspec (G_OBJECT (range), properties[PROP_ROUND_DIGITS]);
2885     }
2886 }
2887 
2888 /**
2889  * gtk_range_get_round_digits: (attributes org.gtk.Method.get_property=round-digits)
2890  * @range: a `GtkRange`
2891  *
2892  * Gets the number of digits to round the value to when
2893  * it changes.
2894  *
2895  * See [signal@Gtk.Range::change-value].
2896  *
2897  * Returns: the number of digits to round to
2898  */
2899 int
gtk_range_get_round_digits(GtkRange * range)2900 gtk_range_get_round_digits (GtkRange *range)
2901 {
2902   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2903 
2904   g_return_val_if_fail (GTK_IS_RANGE (range), -1);
2905 
2906   return priv->round_digits;
2907 }
2908 
2909 GtkWidget *
gtk_range_get_slider_widget(GtkRange * range)2910 gtk_range_get_slider_widget (GtkRange *range)
2911 {
2912   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2913 
2914   return priv->slider_widget;
2915 }
2916 
2917 GtkWidget *
gtk_range_get_trough_widget(GtkRange * range)2918 gtk_range_get_trough_widget (GtkRange *range)
2919 {
2920   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2921 
2922   return priv->trough_widget;
2923 }
2924 
2925 void
gtk_range_start_autoscroll(GtkRange * range,GtkScrollType scroll_type)2926 gtk_range_start_autoscroll (GtkRange      *range,
2927                             GtkScrollType  scroll_type)
2928 {
2929   GtkRangePrivate *priv = gtk_range_get_instance_private (range);
2930 
2931   remove_autoscroll (range);
2932   priv->autoscroll_mode = scroll_type;
2933   add_autoscroll (range);
2934 }
2935 
2936 void
gtk_range_stop_autoscroll(GtkRange * range)2937 gtk_range_stop_autoscroll (GtkRange *range)
2938 {
2939   remove_autoscroll (range);
2940 }
2941