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