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