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
72typedef struct _GtkRangeStepTimer GtkRangeStepTimer;
73
74typedef struct _GtkRangePrivate GtkRangePrivate;
75struct _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
124enum {
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
136enum {
137 VALUE_CHANGED,
138 ADJUST_BOUNDS,
139 MOVE_SLIDER,
140 CHANGE_VALUE,
141 LAST_SIGNAL
142};
143
144static void gtk_range_set_property (GObject *object,
145 guint prop_id,
146 const GValue *value,
147 GParamSpec *pspec);
148static void gtk_range_get_property (GObject *object,
149 guint prop_id,
150 GValue *value,
151 GParamSpec *pspec);
152static void gtk_range_finalize (GObject *object);
153static void gtk_range_dispose (GObject *object);
154static 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);
161static void gtk_range_size_allocate (GtkWidget *widget,
162 int width,
163 int height,
164 int baseline);
165static void gtk_range_unmap (GtkWidget *widget);
166
167static void gtk_range_click_gesture_pressed (GtkGestureClick *gesture,
168 guint n_press,
169 double x,
170 double y,
171 GtkRange *range);
172static void gtk_range_drag_gesture_begin (GtkGestureDrag *gesture,
173 double offset_x,
174 double offset_y,
175 GtkRange *range);
176static void gtk_range_drag_gesture_update (GtkGestureDrag *gesture,
177 double offset_x,
178 double offset_y,
179 GtkRange *range);
180static void gtk_range_drag_gesture_end (GtkGestureDrag *gesture,
181 double offset_x,
182 double offset_y,
183 GtkRange *range);
184static void gtk_range_long_press_gesture_pressed (GtkGestureLongPress *gesture,
185 double x,
186 double y,
187 GtkRange *range);
188
189
190static void update_slider_position (GtkRange *range,
191 int mouse_x,
192 int mouse_y);
193static void stop_scrolling (GtkRange *range);
194static void add_autoscroll (GtkRange *range);
195static void remove_autoscroll (GtkRange *range);
196
197/* Range methods */
198
199static void gtk_range_move_slider (GtkRange *range,
200 GtkScrollType scroll);
201
202/* Internals */
203static void gtk_range_compute_slider_position (GtkRange *range,
204 double adjustment_value,
205 GdkRectangle *slider_rect);
206static gboolean gtk_range_scroll (GtkRange *range,
207 GtkScrollType scroll);
208static void gtk_range_calc_marks (GtkRange *range);
209static void gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
210 gpointer data);
211static void gtk_range_adjustment_changed (GtkAdjustment *adjustment,
212 gpointer data);
213static void gtk_range_add_step_timer (GtkRange *range,
214 GtkScrollType step);
215static void gtk_range_remove_step_timer (GtkRange *range);
216static gboolean gtk_range_real_change_value (GtkRange *range,
217 GtkScrollType scroll,
218 double value);
219static gboolean gtk_range_key_controller_key_pressed (GtkEventControllerKey *controller,
220 guint keyval,
221 guint keycode,
222 GdkModifierType state,
223 GtkWidget *widget);
224static void gtk_range_direction_changed (GtkWidget *widget,
225 GtkTextDirection previous_direction);
226static 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);
233static void gtk_range_allocate_trough (GtkGizmo *gizmo,
234 int width,
235 int height,
236 int baseline);
237static void gtk_range_render_trough (GtkGizmo *gizmo,
238 GtkSnapshot *snapshot);
239
240static gboolean gtk_range_scroll_controller_scroll (GtkEventControllerScroll *scroll,
241 double dx,
242 double dy,
243 GtkRange *range);
244
245static void gtk_range_set_orientation (GtkRange *range,
246 GtkOrientation orientation);
247
248G_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
253static guint signals[LAST_SIGNAL];
254static GParamSpec *properties[LAST_PROP];
255
256static void
257gtk_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 signal_flags: G_SIGNAL_RUN_LAST,
288 G_STRUCT_OFFSET (GtkRangeClass, value_changed),
289 NULL, NULL,
290 NULL,
291 G_TYPE_NONE, n_params: 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 signal_flags: G_SIGNAL_RUN_LAST,
305 G_STRUCT_OFFSET (GtkRangeClass, adjust_bounds),
306 NULL, NULL,
307 NULL,
308 G_TYPE_NONE, n_params: 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 signal_flags: G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
324 G_STRUCT_OFFSET (GtkRangeClass, move_slider),
325 NULL, NULL,
326 NULL,
327 G_TYPE_NONE, n_params: 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 signal_flags: G_SIGNAL_RUN_LAST,
356 G_STRUCT_OFFSET (GtkRangeClass, change_value),
357 accumulator: _gtk_boolean_handled_accumulator, NULL,
358 c_marshaller: _gtk_marshal_BOOLEAN__ENUM_DOUBLE,
359 G_TYPE_BOOLEAN, n_params: 2,
360 GTK_TYPE_SCROLL_TYPE,
361 G_TYPE_DOUBLE);
362
363 g_object_class_override_property (oclass: gobject_class, property_id: PROP_ORIENTATION, name: "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 (name: "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 (name: "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 (name: "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 (name: "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 (name: "fill-level",
422 P_("Fill Level"),
423 P_("The fill level."),
424 minimum: -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 (name: "round-digits",
438 P_("Round Digits"),
439 P_("The number of digits to round the value to."),
440 minimum: -1, G_MAXINT,
441 default_value: -1,
442 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
443
444 g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_PROP, pspecs: properties);
445
446 gtk_widget_class_set_css_name (widget_class, I_("range"));
447}
448
449static void
450gtk_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, orientation: g_value_get_enum (value));
461 break;
462 case PROP_ADJUSTMENT:
463 gtk_range_set_adjustment (range, adjustment: g_value_get_object (value));
464 break;
465 case PROP_INVERTED:
466 gtk_range_set_inverted (range, setting: g_value_get_boolean (value));
467 break;
468 case PROP_SHOW_FILL_LEVEL:
469 gtk_range_set_show_fill_level (range, show_fill_level: g_value_get_boolean (value));
470 break;
471 case PROP_RESTRICT_TO_FILL_LEVEL:
472 gtk_range_set_restrict_to_fill_level (range, restrict_to_fill_level: g_value_get_boolean (value));
473 break;
474 case PROP_FILL_LEVEL:
475 gtk_range_set_fill_level (range, fill_level: g_value_get_double (value));
476 break;
477 case PROP_ROUND_DIGITS:
478 gtk_range_set_round_digits (range, round_digits: 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
486static void
487gtk_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 (self: range);
494
495 switch (prop_id)
496 {
497 case PROP_ORIENTATION:
498 g_value_set_enum (value, v_enum: priv->orientation);
499 break;
500 case PROP_ADJUSTMENT:
501 g_value_set_object (value, v_object: priv->adjustment);
502 break;
503 case PROP_INVERTED:
504 g_value_set_boolean (value, v_boolean: priv->inverted);
505 break;
506 case PROP_SHOW_FILL_LEVEL:
507 g_value_set_boolean (value, v_boolean: gtk_range_get_show_fill_level (range));
508 break;
509 case PROP_RESTRICT_TO_FILL_LEVEL:
510 g_value_set_boolean (value, v_boolean: gtk_range_get_restrict_to_fill_level (range));
511 break;
512 case PROP_FILL_LEVEL:
513 g_value_set_double (value, v_double: gtk_range_get_fill_level (range));
514 break;
515 case PROP_ROUND_DIGITS:
516 g_value_set_int (value, v_int: 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
524static void
525gtk_range_init (GtkRange *range)
526{
527 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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), orientation: priv->orientation);
542
543 priv->trough_widget = gtk_gizmo_new_with_role (css_name: "trough",
544 role: GTK_ACCESSIBLE_ROLE_NONE,
545 measure_func: gtk_range_measure_trough,
546 allocate_func: gtk_range_allocate_trough,
547 snapshot_func: gtk_range_render_trough,
548 NULL,
549 NULL, NULL);
550
551 gtk_widget_set_parent (widget: priv->trough_widget, GTK_WIDGET (range));
552
553 priv->slider_widget = gtk_gizmo_new (css_name: "slider", NULL, NULL, NULL, NULL, NULL, NULL);
554 gtk_widget_set_parent (widget: priv->slider_widget, parent: 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), button: 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), button: 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 (group_gesture: gesture, 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), delay_factor: 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 (group_gesture: gesture, gesture: priv->drag_gesture);
585
586 controller = gtk_event_controller_scroll_new (flags: 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
597static void
598gtk_range_set_orientation (GtkRange *range,
599 GtkOrientation orientation)
600{
601 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
602
603 if (priv->orientation != orientation)
604 {
605 priv->orientation = orientation;
606
607 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: range),
608 first_property: GTK_ACCESSIBLE_PROPERTY_ORIENTATION, priv->orientation,
609 -1);
610
611 gtk_widget_update_orientation (GTK_WIDGET (range), orientation: priv->orientation);
612 gtk_widget_queue_resize (GTK_WIDGET (range));
613
614 g_object_notify (G_OBJECT (range), property_name: "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 **/
626GtkAdjustment*
627gtk_range_get_adjustment (GtkRange *range)
628{
629 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 */
654void
655gtk_range_set_adjustment (GtkRange *range,
656 GtkAdjustment *adjustment)
657{
658 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
659
660 g_return_if_fail (GTK_IS_RANGE (range));
661
662 if (!adjustment)
663 adjustment = gtk_adjustment_new (value: 0.0, lower: 0.0, upper: 0.0, step_increment: 0.0, page_increment: 0.0, page_size: 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 (object: 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 (self: GTK_ACCESSIBLE (ptr: range),
691 first_property: 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, data: range);
697 gtk_range_adjustment_value_changed (adjustment, data: range);
698
699 g_object_notify_by_pspec (G_OBJECT (range), pspec: properties[PROP_ADJUSTMENT]);
700 }
701}
702
703static gboolean
704should_invert (GtkRange *range)
705{
706 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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
717static gboolean
718should_invert_move (GtkRange *range,
719 GtkOrientation move_orientation)
720{
721 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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
735static void
736update_highlight_position (GtkRange *range)
737{
738 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
739
740 if (!priv->highlight_widget)
741 return;
742
743 if (should_invert (range))
744 {
745 gtk_widget_add_css_class (widget: priv->highlight_widget, css_class: "bottom");
746 gtk_widget_remove_css_class (widget: priv->highlight_widget, css_class: "top");
747 }
748 else
749 {
750 gtk_widget_add_css_class (widget: priv->highlight_widget, css_class: "top");
751 gtk_widget_remove_css_class (widget: priv->highlight_widget, css_class: "bottom");
752 }
753}
754
755static void
756update_fill_position (GtkRange *range)
757{
758 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
759
760 if (!priv->fill_widget)
761 return;
762
763 if (should_invert (range))
764 {
765 gtk_widget_add_css_class (widget: priv->fill_widget, css_class: "bottom");
766 gtk_widget_remove_css_class (widget: priv->fill_widget, css_class: "top");
767 }
768 else
769 {
770 gtk_widget_add_css_class (widget: priv->fill_widget, css_class: "top");
771 gtk_widget_remove_css_class (widget: priv->fill_widget, css_class: "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 */
787void
788gtk_range_set_inverted (GtkRange *range,
789 gboolean setting)
790{
791 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 (widget: priv->trough_widget);
805
806 g_object_notify_by_pspec (G_OBJECT (range), pspec: 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 */
820gboolean
821gtk_range_get_inverted (GtkRange *range)
822{
823 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 */
842void
843gtk_range_set_flippable (GtkRange *range,
844 gboolean flippable)
845{
846 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 */
872gboolean
873gtk_range_get_flippable (GtkRange *range)
874{
875 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 */
892void
893gtk_range_set_slider_size_fixed (GtkRange *range,
894 gboolean size_fixed)
895{
896 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 (widget: 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 */
919gboolean
920gtk_range_get_slider_size_fixed (GtkRange *range)
921{
922 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 */
939void
940gtk_range_get_range_rect (GtkRange *range,
941 GdkRectangle *range_rect)
942{
943 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 (widget: priv->trough_widget, GTK_WIDGET (range), out_bounds: &r))
950 {
951 *range_rect = (GdkRectangle) { 0, 0, 0, 0 };
952 }
953 else
954 {
955 *range_rect = (GdkRectangle) {
956 floorf (x: r.origin.x),
957 floorf (x: r.origin.y),
958 ceilf (x: r.size.width),
959 ceilf (x: 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 */
975void
976gtk_range_get_slider_range (GtkRange *range,
977 int *slider_start,
978 int *slider_end)
979{
980 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
981 graphene_rect_t slider_bounds;
982
983 g_return_if_fail (GTK_IS_RANGE (range));
984
985 if (!gtk_widget_compute_bounds (widget: priv->slider_widget, GTK_WIDGET (range), out_bounds: &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 */
1022void
1023gtk_range_set_increments (GtkRange *range,
1024 double step,
1025 double page)
1026{
1027 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 value: gtk_adjustment_get_value (adjustment),
1036 lower: gtk_adjustment_get_lower (adjustment),
1037 upper: gtk_adjustment_get_upper (adjustment),
1038 step_increment: step,
1039 page_increment: page,
1040 page_size: 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 */
1055void
1056gtk_range_set_range (GtkRange *range,
1057 double min,
1058 double max)
1059{
1060 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 lower: min,
1077 upper: max,
1078 step_increment: gtk_adjustment_get_step_increment (adjustment),
1079 page_increment: gtk_adjustment_get_page_increment (adjustment),
1080 page_size: 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 */
1094void
1095gtk_range_set_value (GtkRange *range,
1096 double value)
1097{
1098 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 (adjustment: 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 */
1117double
1118gtk_range_get_value (GtkRange *range)
1119{
1120 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
1121
1122 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1123
1124 return gtk_adjustment_get_value (adjustment: 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 */
1137void
1138gtk_range_set_show_fill_level (GtkRange *range,
1139 gboolean show_fill_level)
1140{
1141 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 (css_name: "fill", NULL, NULL, NULL, NULL, NULL, NULL);
1155 gtk_widget_insert_after (widget: priv->fill_widget, parent: 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), pspec: 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 */
1175gboolean
1176gtk_range_get_show_fill_level (GtkRange *range)
1177{
1178 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 */
1195void
1196gtk_range_set_restrict_to_fill_level (GtkRange *range,
1197 gboolean restrict_to_fill_level)
1198{
1199 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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), pspec: properties[PROP_RESTRICT_TO_FILL_LEVEL]);
1209
1210 gtk_range_set_value (range, value: 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 **/
1222gboolean
1223gtk_range_get_restrict_to_fill_level (GtkRange *range)
1224{
1225 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 */
1255void
1256gtk_range_set_fill_level (GtkRange *range,
1257 double fill_level)
1258{
1259 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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), pspec: 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, value: 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 */
1284double
1285gtk_range_get_fill_level (GtkRange *range)
1286{
1287 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
1288
1289 g_return_val_if_fail (GTK_IS_RANGE (range), 0.0);
1290
1291 return priv->fill_level;
1292}
1293
1294static void
1295gtk_range_dispose (GObject *object)
1296{
1297 GtkRange *range = GTK_RANGE (object);
1298 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 (object: priv->adjustment);
1311 priv->adjustment = NULL;
1312 }
1313
1314 if (priv->n_marks)
1315 {
1316 g_free (mem: priv->marks);
1317 priv->marks = NULL;
1318 g_free (mem: 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
1326static void
1327gtk_range_finalize (GObject *object)
1328{
1329 GtkRange *range = GTK_RANGE (object);
1330 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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
1340static void
1341gtk_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 (self: range);
1352 int min, nat;
1353
1354 gtk_widget_measure (widget: priv->slider_widget,
1355 orientation, for_size: -1,
1356 minimum, natural,
1357 NULL, NULL);
1358
1359 if (priv->fill_widget)
1360 {
1361 gtk_widget_measure (widget: priv->fill_widget,
1362 orientation, for_size,
1363 minimum: &min, natural: &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 (widget: priv->highlight_widget,
1372 orientation, for_size,
1373 minimum: &min, natural: &nat,
1374 NULL, NULL);
1375 *minimum = MAX (*minimum, min);
1376 *natural = MAX (*natural, nat);
1377 }
1378}
1379
1380static void
1381gtk_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 (self: range);
1391 GtkBorder border = { 0 };
1392
1393 /* Measure the main box */
1394 gtk_widget_measure (widget: priv->trough_widget,
1395 orientation,
1396 for_size: -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
1416static void
1417gtk_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 (self: range);
1425 GtkAllocation slider_alloc;
1426 const double lower = gtk_adjustment_get_lower (adjustment: priv->adjustment);
1427 const double upper = gtk_adjustment_get_upper (adjustment: priv->adjustment);
1428 const double page_size = gtk_adjustment_get_page_size (adjustment: priv->adjustment);
1429 double value;
1430
1431 /* Slider */
1432 gtk_range_calc_marks (range);
1433
1434 gtk_range_compute_slider_position (range,
1435 adjustment_value: gtk_adjustment_get_value (adjustment: priv->adjustment),
1436 slider_rect: &slider_alloc);
1437
1438 gtk_widget_size_allocate (widget: priv->slider_widget, allocation: &slider_alloc, baseline: -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 (adjustment: 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 (widget: priv->fill_widget, allocation: &fill_alloc, baseline: -1);
1475 }
1476
1477 if (priv->highlight_widget)
1478 {
1479 GtkAllocation highlight_alloc;
1480 int min, nat;
1481
1482 gtk_widget_measure (widget: priv->highlight_widget,
1483 orientation: priv->orientation, for_size: -1,
1484 minimum: &min, natural: &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 (widget: priv->highlight_widget, allocation: &highlight_alloc, baseline: -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 */
1518static void
1519clamp_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
1602static void
1603gtk_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 (self: 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 (widget: priv->trough_widget,
1618 orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
1619 minimum: &box_min_width, NULL,
1620 NULL, NULL);
1621 gtk_widget_measure (widget: priv->trough_widget,
1622 orientation: GTK_ORIENTATION_VERTICAL, for_size: -1,
1623 minimum: &box_min_height, NULL,
1624 NULL, NULL);
1625
1626 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
1627 clamp_dimensions (range_width: width, range_height: height, width: &box_min_width, height: &box_min_height, border: &border, TRUE);
1628 else
1629 clamp_dimensions (range_width: width, range_height: height, width: &box_min_width, height: &box_min_height, border: &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 (widget: priv->trough_widget, allocation: &box_alloc, baseline: -1);
1637}
1638
1639static void
1640gtk_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
1649static void
1650gtk_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
1661static void
1662gtk_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 (self: 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 width: gtk_widget_get_width (GTK_WIDGET (gizmo)),
1675 height: gtk_widget_get_height (GTK_WIDGET (gizmo)));
1676
1677 if (priv->show_fill_level &&
1678 gtk_adjustment_get_upper (adjustment: priv->adjustment) - gtk_adjustment_get_page_size (adjustment: priv->adjustment) -
1679 gtk_adjustment_get_lower (adjustment: priv->adjustment) != 0)
1680 gtk_widget_snapshot_child (GTK_WIDGET (gizmo), child: priv->fill_widget, snapshot);
1681
1682 if (priv->highlight_widget)
1683 gtk_widget_snapshot_child (GTK_WIDGET (gizmo), child: priv->highlight_widget, snapshot);
1684
1685 gtk_widget_snapshot_child (GTK_WIDGET (gizmo), child: priv->slider_widget, snapshot);
1686}
1687
1688static void
1689range_grab_add (GtkRange *range,
1690 GtkWidget *location)
1691{
1692 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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), css_class: "dragging");
1702}
1703
1704static void
1705update_zoom_state (GtkRange *range,
1706 gboolean enabled)
1707{
1708 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
1709
1710 if (enabled)
1711 gtk_widget_add_css_class (GTK_WIDGET (range), css_class: "fine-tune");
1712 else
1713 gtk_widget_remove_css_class (GTK_WIDGET (range), css_class: "fine-tune");
1714
1715 priv->zoom = enabled;
1716}
1717
1718static void
1719range_grab_remove (GtkRange *range)
1720{
1721 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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), css_class: "dragging");
1731}
1732
1733static GtkScrollType
1734range_get_scroll_for_grab (GtkRange *range)
1735{
1736 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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
1753static double
1754coord_to_value (GtkRange *range,
1755 double coord)
1756{
1757 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 (widget: priv->slider_widget, target: priv->slider_widget, out_bounds: &slider_bounds))
1765 graphene_rect_init (r: &slider_bounds, x: 0, y: 0, width: gtk_widget_get_width (widget: priv->trough_widget), height: gtk_widget_get_height (widget: priv->trough_widget));
1766
1767 if (priv->orientation == GTK_ORIENTATION_HORIZONTAL)
1768 {
1769 trough_length = gtk_widget_get_width (widget: priv->trough_widget);
1770 slider_length = slider_bounds.size.width;
1771 }
1772 else
1773 {
1774 trough_length = gtk_widget_get_height (widget: 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 (adjustment: priv->adjustment) +
1794 frac * (gtk_adjustment_get_upper (adjustment: priv->adjustment) -
1795 gtk_adjustment_get_lower (adjustment: priv->adjustment) -
1796 gtk_adjustment_get_page_size (adjustment: priv->adjustment));
1797 return value;
1798}
1799
1800static gboolean
1801gtk_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 (self: range);
1809
1810 if (gtk_gesture_is_active (gesture: 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 (widget: priv->slider_widget, target: priv->trough_widget, out_bounds: &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, enabled: !priv->zoom);
1832
1833 return GDK_EVENT_STOP;
1834 }
1835
1836 return GDK_EVENT_PROPAGATE;
1837}
1838
1839static void
1840update_initial_slider_position (GtkRange *range,
1841 double x,
1842 double y)
1843{
1844 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
1845
1846 gtk_widget_translate_coordinates (GTK_WIDGET (range), dest_widget: priv->trough_widget,
1847 src_x: x, src_y: y, dest_x: &x, dest_y: &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
1861static void
1862gtk_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 (self: range);
1868 GtkWidget *mouse_location;
1869
1870 mouse_location = gtk_widget_pick (GTK_WIDGET (range), x, y, flags: 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
1879static void
1880gtk_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 (self: 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 (event: (GdkEvent *) event);
1908 source = gdk_device_get_source (device: source_device);
1909
1910 g_object_get (object: gtk_widget_get_settings (widget),
1911 first_property_name: "gtk-primary-button-warps-slider", &primary_warps,
1912 NULL);
1913
1914 mouse_location = gtk_widget_pick (widget, x, y, flags: 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, location: 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 (src_widget: priv->trough_widget, dest_widget: widget,
1941 src_x: priv->slider_x, src_y: priv->slider_y,
1942 dest_x: &slider_range_x, dest_y: &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 (widget: priv->slider_widget, target: priv->slider_widget, out_bounds: &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, x: slider_range_x, y: slider_range_y);
1953
1954 range_grab_add (range, location: priv->slider_widget);
1955
1956 update_slider_position (range, mouse_x: x, mouse_y: 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 coord: priv->orientation == GTK_ORIENTATION_VERTICAL ?
1969 y : x);
1970
1971 priv->trough_click_forward = click_value > gtk_adjustment_get_value (adjustment: priv->adjustment);
1972 range_grab_add (range, location: priv->trough_widget);
1973
1974 scroll = range_get_scroll_for_grab (range);
1975 gtk_range_add_step_timer (range, step: 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 coord: priv->orientation == GTK_ORIENTATION_VERTICAL ?
1985 y : x);
1986
1987 priv->trough_click_forward = click_value > gtk_adjustment_get_value (adjustment: priv->adjustment);
1988 range_grab_add (range, location: 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), state: GTK_EVENT_SEQUENCE_CLAIMED);
1999}
2000
2001/* During a slide, move the slider as required given new mouse position */
2002static void
2003update_slider_position (GtkRange *range,
2004 int mouse_x,
2005 int mouse_y)
2006{
2007 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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), dest_widget: priv->trough_widget,
2021 src_x: mouse_x, src_y: mouse_y, dest_x: &x, dest_y: &y);
2022
2023 if (priv->zoom &&
2024 gtk_widget_compute_bounds (widget: priv->trough_widget, target: priv->trough_widget, out_bounds: &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 (widget: priv->slider_widget, GTK_WIDGET (range), out_bounds: &slider_bounds))
2046 graphene_rect_init (r: &slider_bounds, x: 0, y: 0, width: 0, height: 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, coord: c);
2067 next_value = coord_to_value (range, coord: c + 1);
2068 mark_delta = fabs (x: 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 (x: gtk_adjustment_get_value (adjustment: priv->adjustment) - mark_value) < 3 * mark_delta)
2075 {
2076 if (fabs (x: 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 (instance: range, signal_id: signals[CHANGE_VALUE], detail: 0, GTK_SCROLL_JUMP, new_value, &handled);
2085}
2086
2087static void
2088remove_autoscroll (GtkRange *range)
2089{
2090 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2091
2092 if (priv->autoscroll_id)
2093 {
2094 gtk_widget_remove_tick_callback (GTK_WIDGET (range),
2095 id: 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
2105static gboolean
2106autoscroll_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 (self: 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 (adjustment: adj);
2119 page = gtk_adjustment_get_page_increment (adjustment: 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: &x, y: &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 (x: y);
2153 else
2154 distance = fabs (x: 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 (adjustment: adj);
2170 value += increment;
2171
2172 g_signal_emit (instance: range, signal_id: signals[CHANGE_VALUE], detail: 0, GTK_SCROLL_JUMP, value, &handled);
2173
2174 return G_SOURCE_CONTINUE;
2175}
2176
2177static void
2178add_autoscroll (GtkRange *range)
2179{
2180 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 callback: autoscroll_cb, user_data: range, NULL);
2188}
2189
2190static void
2191stop_scrolling (GtkRange *range)
2192{
2193 range_grab_remove (range);
2194 gtk_range_remove_step_timer (range);
2195 remove_autoscroll (range);
2196}
2197
2198static gboolean
2199gtk_range_scroll_controller_scroll (GtkEventControllerScroll *scroll,
2200 double dx,
2201 double dy,
2202 GtkRange *range)
2203{
2204 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 (adjustment: 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 (instance: range, signal_id: signals[CHANGE_VALUE], detail: 0,
2230 GTK_SCROLL_JUMP, gtk_adjustment_get_value (adjustment: priv->adjustment) + delta,
2231 &handled);
2232
2233 return GDK_EVENT_STOP;
2234}
2235
2236static void
2237update_autoscroll_mode (GtkRange *range,
2238 int mouse_x,
2239 int mouse_y)
2240{
2241 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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
2277static void
2278gtk_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 (self: 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, x: &start_x, y: &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
2301static void
2302gtk_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 (self: range);
2308
2309 if (priv->grab_location == priv->slider_widget)
2310 gtk_gesture_set_state (gesture: priv->drag_gesture, state: GTK_EVENT_SEQUENCE_CLAIMED);
2311}
2312
2313static void
2314gtk_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 (self: range);
2320
2321 priv->in_drag = FALSE;
2322 stop_scrolling (range);
2323}
2324
2325static void
2326gtk_range_adjustment_changed (GtkAdjustment *adjustment,
2327 gpointer data)
2328{
2329 GtkRange *range = GTK_RANGE (data);
2330 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2331 double upper = gtk_adjustment_get_upper (adjustment: priv->adjustment);
2332 double lower = gtk_adjustment_get_lower (adjustment: priv->adjustment);
2333
2334 if (upper == lower && GTK_IS_SCALE (range))
2335 gtk_widget_hide (widget: priv->slider_widget);
2336 else
2337 gtk_widget_show (widget: priv->slider_widget);
2338
2339 gtk_widget_queue_allocate (widget: priv->trough_widget);
2340
2341 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: range),
2342 first_property: 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
2355static void
2356gtk_range_adjustment_value_changed (GtkAdjustment *adjustment,
2357 gpointer data)
2358{
2359 GtkRange *range = GTK_RANGE (data);
2360 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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 (instance: range, signal_id: signals[VALUE_CHANGED], detail: 0);
2371
2372 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: range),
2373 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, gtk_adjustment_get_value (adjustment),
2374 -1);
2375
2376 gtk_widget_queue_allocate (widget: priv->trough_widget);
2377}
2378
2379static void
2380apply_marks (GtkRange *range,
2381 double oldval,
2382 double *newval)
2383{
2384 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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
2400static void
2401step_back (GtkRange *range)
2402{
2403 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2404 double newval;
2405 gboolean handled;
2406
2407 newval = gtk_adjustment_get_value (adjustment: priv->adjustment) - gtk_adjustment_get_step_increment (adjustment: priv->adjustment);
2408 apply_marks (range, oldval: gtk_adjustment_get_value (adjustment: priv->adjustment), newval: &newval);
2409 g_signal_emit (instance: range, signal_id: signals[CHANGE_VALUE], detail: 0,
2410 GTK_SCROLL_STEP_BACKWARD, newval, &handled);
2411}
2412
2413static void
2414step_forward (GtkRange *range)
2415{
2416 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2417 double newval;
2418 gboolean handled;
2419
2420 newval = gtk_adjustment_get_value (adjustment: priv->adjustment) + gtk_adjustment_get_step_increment (adjustment: priv->adjustment);
2421 apply_marks (range, oldval: gtk_adjustment_get_value (adjustment: priv->adjustment), newval: &newval);
2422 g_signal_emit (instance: range, signal_id: signals[CHANGE_VALUE], detail: 0,
2423 GTK_SCROLL_STEP_FORWARD, newval, &handled);
2424}
2425
2426
2427static void
2428page_back (GtkRange *range)
2429{
2430 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2431 double newval;
2432 gboolean handled;
2433
2434 newval = gtk_adjustment_get_value (adjustment: priv->adjustment) - gtk_adjustment_get_page_increment (adjustment: priv->adjustment);
2435 apply_marks (range, oldval: gtk_adjustment_get_value (adjustment: priv->adjustment), newval: &newval);
2436 g_signal_emit (instance: range, signal_id: signals[CHANGE_VALUE], detail: 0,
2437 GTK_SCROLL_PAGE_BACKWARD, newval, &handled);
2438}
2439
2440static void
2441page_forward (GtkRange *range)
2442{
2443 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2444 double newval;
2445 gboolean handled;
2446
2447 newval = gtk_adjustment_get_value (adjustment: priv->adjustment) + gtk_adjustment_get_page_increment (adjustment: priv->adjustment);
2448 apply_marks (range, oldval: gtk_adjustment_get_value (adjustment: priv->adjustment), newval: &newval);
2449 g_signal_emit (instance: range, signal_id: signals[CHANGE_VALUE], detail: 0,
2450 GTK_SCROLL_PAGE_FORWARD, newval, &handled);
2451}
2452
2453static void
2454scroll_begin (GtkRange *range)
2455{
2456 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2457 gboolean handled;
2458
2459 g_signal_emit (instance: range, signal_id: signals[CHANGE_VALUE], detail: 0,
2460 GTK_SCROLL_START, gtk_adjustment_get_lower (adjustment: priv->adjustment),
2461 &handled);
2462}
2463
2464static void
2465scroll_end (GtkRange *range)
2466{
2467 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2468 double newval;
2469 gboolean handled;
2470
2471 newval = gtk_adjustment_get_upper (adjustment: priv->adjustment) - gtk_adjustment_get_page_size (adjustment: priv->adjustment);
2472 g_signal_emit (instance: range, signal_id: signals[CHANGE_VALUE], detail: 0, GTK_SCROLL_END, newval,
2473 &handled);
2474}
2475
2476static gboolean
2477gtk_range_scroll (GtkRange *range,
2478 GtkScrollType scroll)
2479{
2480 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2481 double old_value = gtk_adjustment_get_value (adjustment: priv->adjustment);
2482
2483 switch (scroll)
2484 {
2485 case GTK_SCROLL_STEP_LEFT:
2486 if (should_invert_move (range, move_orientation: 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, move_orientation: 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, move_orientation: 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, move_orientation: 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, move_orientation: 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, move_orientation: 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, move_orientation: 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, move_orientation: 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 (adjustment: priv->adjustment) != old_value;
2572}
2573
2574static void
2575gtk_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
2582static void
2583gtk_range_compute_slider_position (GtkRange *range,
2584 double adjustment_value,
2585 GdkRectangle *slider_rect)
2586{
2587 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2588 const double upper = gtk_adjustment_get_upper (adjustment: priv->adjustment);
2589 const double lower = gtk_adjustment_get_lower (adjustment: priv->adjustment);
2590 const double page_size = gtk_adjustment_get_page_size (adjustment: priv->adjustment);
2591 int trough_width, trough_height;
2592 int slider_width, slider_height;
2593
2594 gtk_widget_measure (widget: priv->slider_widget,
2595 orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
2596 minimum: &slider_width, NULL,
2597 NULL, NULL);
2598 gtk_widget_measure (widget: priv->slider_widget,
2599 orientation: GTK_ORIENTATION_VERTICAL, for_size: slider_width,
2600 minimum: &slider_height, NULL,
2601 NULL, NULL);
2602
2603 trough_width = gtk_widget_get_width (widget: priv->trough_widget);
2604 trough_height = gtk_widget_get_height (widget: priv->trough_widget);
2605
2606 if (priv->orientation == GTK_ORIENTATION_VERTICAL)
2607 {
2608 int y, height;
2609
2610 slider_rect->x = (int) floor (x: (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 (x: (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
2678static void
2679gtk_range_calc_marks (GtkRange *range)
2680{
2681 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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, adjustment_value: priv->marks[i], slider_rect: &slider);
2689 gtk_widget_translate_coordinates (src_widget: priv->trough_widget, GTK_WIDGET (range),
2690 src_x: slider.x, src_y: slider.y, dest_x: &x, dest_y: &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
2699static gboolean
2700gtk_range_real_change_value (GtkRange *range,
2701 GtkScrollType scroll,
2702 double value)
2703{
2704 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2705
2706 /* potentially adjust the bounds _before_ we clamp */
2707 g_signal_emit (instance: range, signal_id: signals[ADJUST_BOUNDS], detail: 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 (x: (value * power) + 0.5) / power;
2727 }
2728
2729 if (priv->in_drag || priv->autoscroll_id)
2730 gtk_adjustment_set_value (adjustment: priv->adjustment, value);
2731 else
2732 gtk_adjustment_animate_to_value (adjustment: priv->adjustment, value);
2733
2734 return FALSE;
2735}
2736
2737struct _GtkRangeStepTimer
2738{
2739 guint timeout_id;
2740 GtkScrollType step;
2741};
2742
2743static gboolean
2744second_timeout (gpointer data)
2745{
2746 GtkRange *range = GTK_RANGE (data);
2747 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2748
2749 gtk_range_scroll (range, scroll: priv->timer->step);
2750
2751 return G_SOURCE_CONTINUE;
2752}
2753
2754static gboolean
2755initial_timeout (gpointer data)
2756{
2757 GtkRange *range = GTK_RANGE (data);
2758 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2759
2760 priv->timer->timeout_id = g_timeout_add (TIMEOUT_REPEAT, function: second_timeout, data: range);
2761 gdk_source_set_static_name_by_id (tag: priv->timer->timeout_id, name: "[gtk] second_timeout");
2762 return G_SOURCE_REMOVE;
2763}
2764
2765static void
2766gtk_range_add_step_timer (GtkRange *range,
2767 GtkScrollType step)
2768{
2769 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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, function: initial_timeout, data: range);
2777 gdk_source_set_static_name_by_id (tag: priv->timer->timeout_id, name: "[gtk] initial_timeout");
2778 priv->timer->step = step;
2779
2780 gtk_range_scroll (range, scroll: priv->timer->step);
2781}
2782
2783static void
2784gtk_range_remove_step_timer (GtkRange *range)
2785{
2786 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2787
2788 if (priv->timer)
2789 {
2790 if (priv->timer->timeout_id != 0)
2791 g_source_remove (tag: priv->timer->timeout_id);
2792
2793 g_free (mem: priv->timer);
2794
2795 priv->timer = NULL;
2796 }
2797}
2798
2799void
2800_gtk_range_set_has_origin (GtkRange *range,
2801 gboolean has_origin)
2802{
2803 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2804
2805 if (has_origin)
2806 {
2807 priv->highlight_widget = gtk_gizmo_new (css_name: "highlight", NULL, NULL, NULL, NULL, NULL, NULL);
2808 gtk_widget_insert_before (widget: priv->highlight_widget, parent: priv->trough_widget, next_sibling: 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
2818gboolean
2819_gtk_range_get_has_origin (GtkRange *range)
2820{
2821 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2822
2823 return priv->highlight_widget != NULL;
2824}
2825
2826void
2827_gtk_range_set_stop_values (GtkRange *range,
2828 double *values,
2829 int n_values)
2830{
2831 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2832 int i;
2833
2834 g_free (mem: priv->marks);
2835 priv->marks = g_new (double, n_values);
2836
2837 g_free (mem: 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
2848int
2849_gtk_range_get_stop_positions (GtkRange *range,
2850 int **values)
2851{
2852 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2853
2854 gtk_range_calc_marks (range);
2855
2856 if (values)
2857 *values = g_memdup2 (mem: priv->mark_pos, byte_size: 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 */
2872void
2873gtk_range_set_round_digits (GtkRange *range,
2874 int round_digits)
2875{
2876 GtkRangePrivate *priv = gtk_range_get_instance_private (self: 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), pspec: 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 */
2899int
2900gtk_range_get_round_digits (GtkRange *range)
2901{
2902 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2903
2904 g_return_val_if_fail (GTK_IS_RANGE (range), -1);
2905
2906 return priv->round_digits;
2907}
2908
2909GtkWidget *
2910gtk_range_get_slider_widget (GtkRange *range)
2911{
2912 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2913
2914 return priv->slider_widget;
2915}
2916
2917GtkWidget *
2918gtk_range_get_trough_widget (GtkRange *range)
2919{
2920 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2921
2922 return priv->trough_widget;
2923}
2924
2925void
2926gtk_range_start_autoscroll (GtkRange *range,
2927 GtkScrollType scroll_type)
2928{
2929 GtkRangePrivate *priv = gtk_range_get_instance_private (self: range);
2930
2931 remove_autoscroll (range);
2932 priv->autoscroll_mode = scroll_type;
2933 add_autoscroll (range);
2934}
2935
2936void
2937gtk_range_stop_autoscroll (GtkRange *range)
2938{
2939 remove_autoscroll (range);
2940}
2941

source code of gtk/gtk/gtkrange.c