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-2000. 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 "gtkscale.h"
29
30#include "gtkadjustment.h"
31#include "gtkbuildable.h"
32#include "gtkbuilderprivate.h"
33#include "gtkgizmoprivate.h"
34#include "gtkicontheme.h"
35#include "gtkintl.h"
36#include "gtklabel.h"
37#include "gtkmarshalers.h"
38#include "gtkorientable.h"
39#include "gtkprivate.h"
40#include "gtkrangeprivate.h"
41#include "gtktypebuiltins.h"
42#include "gtkwidgetprivate.h"
43
44#include <math.h>
45#include <stdlib.h>
46
47
48/**
49 * GtkScale:
50 *
51 * A `GtkScale` is a slider control used to select a numeric value.
52 *
53 * ![An example GtkScale](scales.png)
54 *
55 * To use it, you’ll probably want to investigate the methods on its base
56 * class, [class@GtkRange], in addition to the methods for `GtkScale` itself.
57 * To set the value of a scale, you would normally use [method@Gtk.Range.set_value].
58 * To detect changes to the value, you would normally use the
59 * [signal@Gtk.Range::value-changed] signal.
60 *
61 * Note that using the same upper and lower bounds for the `GtkScale` (through
62 * the `GtkRange` methods) will hide the slider itself. This is useful for
63 * applications that want to show an undeterminate value on the scale, without
64 * changing the layout of the application (such as movie or music players).
65 *
66 * # GtkScale as GtkBuildable
67 *
68 * `GtkScale` supports a custom <marks> element, which can contain multiple
69 * <mark\> elements. The “value” and “position” attributes have the same
70 * meaning as [method@Gtk.Scale.add_mark] parameters of the same name. If
71 * the element is not empty, its content is taken as the markup to show at
72 * the mark. It can be translated with the usual ”translatable” and
73 * “context” attributes.
74 *
75 * # CSS nodes
76 *
77 * ```
78 * scale[.fine-tune][.marks-before][.marks-after]
79 * ├── [value][.top][.right][.bottom][.left]
80 * ├── marks.top
81 * │ ├── mark
82 * │ ┊ ├── [label]
83 * │ ┊ ╰── indicator
84 * ┊ ┊
85 * │ ╰── mark
86 * ├── marks.bottom
87 * │ ├── mark
88 * │ ┊ ├── indicator
89 * │ ┊ ╰── [label]
90 * ┊ ┊
91 * │ ╰── mark
92 * ╰── trough
93 * ├── [fill]
94 * ├── [highlight]
95 * ╰── slider
96 * ```
97 *
98 * `GtkScale` has a main CSS node with name scale and a subnode for its contents,
99 * with subnodes named trough and slider.
100 *
101 * The main node gets the style class .fine-tune added when the scale is in
102 * 'fine-tuning' mode.
103 *
104 * If the scale has an origin (see [method@Gtk.Scale.set_has_origin]), there is
105 * a subnode with name highlight below the trough node that is used for rendering
106 * the highlighted part of the trough.
107 *
108 * If the scale is showing a fill level (see [method@Gtk.Range.set_show_fill_level]),
109 * there is a subnode with name fill below the trough node that is used for
110 * rendering the filled in part of the trough.
111 *
112 * If marks are present, there is a marks subnode before or after the trough
113 * node, below which each mark gets a node with name mark. The marks nodes get
114 * either the .top or .bottom style class.
115 *
116 * The mark node has a subnode named indicator. If the mark has text, it also
117 * has a subnode named label. When the mark is either above or left of the
118 * scale, the label subnode is the first when present. Otherwise, the indicator
119 * subnode is the first.
120 *
121 * The main CSS node gets the 'marks-before' and/or 'marks-after' style classes
122 * added depending on what marks are present.
123 *
124 * If the scale is displaying the value (see [property@Gtk.Scale:draw-value]),
125 * there is subnode with name value. This node will get the .top or .bottom style
126 * classes similar to the marks node.
127 *
128 * # Accessibility
129 *
130 * `GtkScale` uses the %GTK_ACCESSIBLE_ROLE_SLIDER role.
131 */
132
133
134#define MAX_DIGITS (64) /* don't change this,
135 * a) you don't need to and
136 * b) you might cause buffer owerflows in
137 * unrelated code portions otherwise
138 */
139
140typedef struct _GtkScaleMark GtkScaleMark;
141
142typedef struct _GtkScalePrivate GtkScalePrivate;
143struct _GtkScalePrivate
144{
145 GSList *marks;
146
147 GtkWidget *value_widget;
148 GtkWidget *top_marks_widget;
149 GtkWidget *bottom_marks_widget;
150
151 int digits;
152
153 guint draw_value : 1;
154 guint value_pos : 2;
155
156 GtkScaleFormatValueFunc format_value_func;
157 gpointer format_value_func_user_data;
158 GDestroyNotify format_value_func_destroy_notify;
159};
160
161struct _GtkScaleMark
162{
163 double value;
164 int stop_position;
165 GtkPositionType position; /* always GTK_POS_TOP or GTK_POS_BOTTOM */
166 char *markup;
167 GtkWidget *label_widget;
168 GtkWidget *indicator_widget;
169 GtkWidget *widget;
170};
171
172enum {
173 PROP_0,
174 PROP_DIGITS,
175 PROP_DRAW_VALUE,
176 PROP_HAS_ORIGIN,
177 PROP_VALUE_POS,
178 LAST_PROP
179};
180
181
182static GParamSpec *properties[LAST_PROP];
183
184static void gtk_scale_set_property (GObject *object,
185 guint prop_id,
186 const GValue *value,
187 GParamSpec *pspec);
188static void gtk_scale_get_property (GObject *object,
189 guint prop_id,
190 GValue *value,
191 GParamSpec *pspec);
192static void gtk_scale_measure (GtkWidget *widget,
193 GtkOrientation orientation,
194 int for_size,
195 int *minimum,
196 int *natural,
197 int *minimum_baseline,
198 int *natural_baseline);
199static void gtk_scale_get_range_border (GtkRange *range,
200 GtkBorder *border);
201static void gtk_scale_finalize (GObject *object);
202static void gtk_scale_real_get_layout_offsets (GtkScale *scale,
203 int *x,
204 int *y);
205static void gtk_scale_buildable_interface_init (GtkBuildableIface *iface);
206static gboolean gtk_scale_buildable_custom_tag_start (GtkBuildable *buildable,
207 GtkBuilder *builder,
208 GObject *child,
209 const char *tagname,
210 GtkBuildableParser *parser,
211 gpointer *data);
212static void gtk_scale_buildable_custom_finished (GtkBuildable *buildable,
213 GtkBuilder *builder,
214 GObject *child,
215 const char *tagname,
216 gpointer user_data);
217static char * gtk_scale_format_value (GtkScale *scale,
218 double value);
219
220
221G_DEFINE_TYPE_WITH_CODE (GtkScale, gtk_scale, GTK_TYPE_RANGE,
222 G_ADD_PRIVATE (GtkScale)
223 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
224 gtk_scale_buildable_interface_init))
225
226static int
227compare_marks (gconstpointer a, gconstpointer b, gpointer data)
228{
229 gboolean inverted = GPOINTER_TO_INT (data);
230 int val;
231 const GtkScaleMark *ma, *mb;
232
233 val = inverted ? -1 : 1;
234
235 ma = a; mb = b;
236
237 return (ma->value > mb->value) ? val : ((ma->value < mb->value) ? -val : 0);
238}
239
240static void
241update_label_request (GtkScale *scale)
242{
243 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
244 GtkAdjustment *adjustment = gtk_range_get_adjustment (GTK_RANGE (scale));
245 double lowest_value, highest_value;
246 char *old_text;
247 char *text;
248 int size = 0;
249 int min;
250
251 g_assert (priv->value_widget != NULL);
252
253 lowest_value = gtk_adjustment_get_lower (adjustment);
254 highest_value = gtk_adjustment_get_upper (adjustment);
255
256 old_text = g_strdup (str: gtk_label_get_label (GTK_LABEL (priv->value_widget)));
257 gtk_widget_set_size_request (widget: priv->value_widget, width: -1, height: -1);
258
259 text = gtk_scale_format_value (scale, value: lowest_value);
260 gtk_label_set_label (GTK_LABEL (priv->value_widget), str: text);
261
262 gtk_widget_measure (widget: priv->value_widget, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
263 minimum: &min, NULL, NULL, NULL);
264 size = MAX (size, min);
265 g_free (mem: text);
266
267 text = gtk_scale_format_value (scale, value: highest_value);
268 gtk_label_set_label (GTK_LABEL (priv->value_widget), str: text);
269
270 gtk_widget_measure (widget: priv->value_widget, orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
271 minimum: &min, NULL, NULL, NULL);
272 size = MAX (size, min);
273 g_free (mem: text);
274
275 gtk_widget_set_size_request (widget: priv->value_widget, width: size, height: -1);
276 gtk_label_set_label (GTK_LABEL (priv->value_widget), str: old_text);
277 g_free (mem: old_text);
278}
279
280static void
281gtk_scale_notify (GObject *object,
282 GParamSpec *pspec)
283{
284 GtkScale *scale = GTK_SCALE (object);
285 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
286
287 if (strcmp (s1: pspec->name, s2: "inverted") == 0)
288 {
289 GtkScaleMark *mark;
290 GSList *m;
291 int i, n;
292 double *values;
293
294 priv->marks = g_slist_sort_with_data (list: priv->marks,
295 compare_func: compare_marks,
296 GINT_TO_POINTER (gtk_range_get_inverted (GTK_RANGE (scale))));
297
298 n = g_slist_length (list: priv->marks);
299 values = g_new (double, n);
300 for (m = priv->marks, i = 0; m; m = m->next, i++)
301 {
302 mark = m->data;
303 values[i] = mark->value;
304 }
305
306 _gtk_range_set_stop_values (GTK_RANGE (scale), values, n_values: n);
307
308 if (priv->top_marks_widget)
309 gtk_widget_queue_resize (widget: priv->top_marks_widget);
310
311 if (priv->bottom_marks_widget)
312 gtk_widget_queue_resize (widget: priv->bottom_marks_widget);
313
314 g_free (mem: values);
315 }
316 else if (strcmp (s1: pspec->name, s2: "adjustment"))
317 {
318 if (priv->value_widget)
319 update_label_request (scale);
320 }
321
322 if (G_OBJECT_CLASS (gtk_scale_parent_class)->notify)
323 G_OBJECT_CLASS (gtk_scale_parent_class)->notify (object, pspec);
324}
325
326static void
327gtk_scale_allocate_value (GtkScale *scale)
328{
329 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
330 GtkWidget *widget = GTK_WIDGET (scale);
331 GtkRange *range = GTK_RANGE (widget);
332 GtkWidget *slider_widget;
333 GtkAllocation value_alloc;
334 int range_width, range_height;
335 graphene_rect_t slider_bounds;
336
337 range_width = gtk_widget_get_width (widget);
338 range_height = gtk_widget_get_height (widget);
339
340 slider_widget = gtk_range_get_slider_widget (range);
341 if (!gtk_widget_compute_bounds (widget: slider_widget, target: widget, out_bounds: &slider_bounds))
342 graphene_rect_init (r: &slider_bounds, x: 0, y: 0, width: gtk_widget_get_width (widget), height: gtk_widget_get_height (widget));
343
344 gtk_widget_measure (widget: priv->value_widget,
345 orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
346 minimum: &value_alloc.width, NULL,
347 NULL, NULL);
348 gtk_widget_measure (widget: priv->value_widget,
349 orientation: GTK_ORIENTATION_VERTICAL, for_size: -1,
350 minimum: &value_alloc.height, NULL,
351 NULL, NULL);
352
353 if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
354 {
355 switch (priv->value_pos)
356 {
357 case GTK_POS_LEFT:
358 value_alloc.x = 0;
359 value_alloc.y = (range_height - value_alloc.height) / 2;
360 break;
361
362 case GTK_POS_RIGHT:
363 value_alloc.x = range_width - value_alloc.width;
364 value_alloc.y = (range_height - value_alloc.height) / 2;
365 break;
366
367 case GTK_POS_TOP:
368 value_alloc.x = slider_bounds.origin.x + (slider_bounds.size.width - value_alloc.width) / 2;
369 value_alloc.y = slider_bounds.origin.y - value_alloc.height;
370 break;
371
372 case GTK_POS_BOTTOM:
373 value_alloc.x = slider_bounds.origin.x + (slider_bounds.size.width - value_alloc.width) / 2;
374 value_alloc.y = range_height - value_alloc.height;
375 break;
376
377 default:
378 g_return_if_reached ();
379 break;
380 }
381 }
382 else /* VERTICAL */
383 {
384 switch (priv->value_pos)
385 {
386 case GTK_POS_LEFT:
387 value_alloc.x = 0;
388 value_alloc.y = (slider_bounds.origin.y + (slider_bounds.size.height / 2)) - value_alloc.height / 2;
389 break;
390
391 case GTK_POS_RIGHT:
392 value_alloc.x = range_width - value_alloc.width;
393 value_alloc.y = (slider_bounds.origin.y + (slider_bounds.size.height / 2)) - value_alloc.height / 2;
394 break;
395
396 case GTK_POS_TOP:
397 value_alloc.x = (range_width - value_alloc.width) / 2;
398 value_alloc.y = 0;
399 break;
400
401 case GTK_POS_BOTTOM:
402 value_alloc.x = (range_width - value_alloc.width) / 2;
403 value_alloc.y = range_height - value_alloc.height;
404 break;
405
406 default:
407 g_return_if_reached ();
408 }
409 }
410
411 gtk_widget_size_allocate (widget: priv->value_widget, allocation: &value_alloc, baseline: -1);
412}
413
414static void
415gtk_scale_allocate_mark (GtkGizmo *gizmo,
416 int width,
417 int height,
418 int baseline)
419{
420 GtkWidget *widget = GTK_WIDGET (gizmo);
421 GtkScale *scale = GTK_SCALE (gtk_widget_get_parent (gtk_widget_get_parent (widget)));
422 GtkScaleMark *mark = g_object_get_data (G_OBJECT (gizmo), key: "mark");
423 int indicator_width, indicator_height;
424 GtkAllocation indicator_alloc;
425 GtkOrientation orientation;
426
427 orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (scale));
428 gtk_widget_measure (widget: mark->indicator_widget,
429 orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
430 minimum: &indicator_width, NULL,
431 NULL, NULL);
432 gtk_widget_measure (widget: mark->indicator_widget,
433 orientation: GTK_ORIENTATION_VERTICAL, for_size: -1,
434 minimum: &indicator_height, NULL,
435 NULL, NULL);
436
437 if (orientation == GTK_ORIENTATION_HORIZONTAL)
438 {
439 indicator_alloc.x = (width - indicator_width) / 2;
440 if (mark->position == GTK_POS_TOP)
441 indicator_alloc.y = height - indicator_height;
442 else
443 indicator_alloc.y = 0;
444
445 indicator_alloc.width = indicator_width;
446 indicator_alloc.height = indicator_height;
447 }
448 else
449 {
450 if (mark->position == GTK_POS_TOP)
451 indicator_alloc.x = width - indicator_width;
452 else
453 indicator_alloc.x = 0;
454 indicator_alloc.y = (height - indicator_height) / 2;
455 indicator_alloc.width = indicator_width;
456 indicator_alloc.height = indicator_height;
457 }
458
459 gtk_widget_size_allocate (widget: mark->indicator_widget, allocation: &indicator_alloc, baseline);
460
461 if (mark->label_widget)
462 {
463 GtkAllocation label_alloc;
464
465 label_alloc = (GtkAllocation) {0, 0, width, height};
466
467 if (orientation == GTK_ORIENTATION_HORIZONTAL)
468 {
469 label_alloc.height = height - indicator_alloc.height;
470 if (mark->position == GTK_POS_BOTTOM)
471 label_alloc.y = indicator_alloc.y + indicator_alloc.height;
472 }
473 else
474 {
475 label_alloc.width = width - indicator_alloc.width;
476 if (mark->position == GTK_POS_BOTTOM)
477 label_alloc.x = indicator_alloc.x + indicator_alloc.width;
478 }
479
480 gtk_widget_size_allocate (widget: mark->label_widget, allocation: &label_alloc, baseline);
481 }
482}
483
484static void
485gtk_scale_allocate_marks (GtkGizmo *gizmo,
486 int width,
487 int height,
488 int baseline)
489{
490 GtkWidget *widget = GTK_WIDGET (gizmo);
491 GtkScale *scale = GTK_SCALE (gtk_widget_get_parent (widget));
492 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
493 GtkOrientation orientation;
494 int *marks;
495 int i;
496 GSList *m;
497
498 orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (scale));
499 _gtk_range_get_stop_positions (GTK_RANGE (scale), values: &marks);
500
501 for (m = priv->marks, i = 0; m; m = m->next, i++)
502 {
503 GtkScaleMark *mark = m->data;
504 GtkAllocation mark_alloc;
505 int mark_size;
506
507 if ((mark->position == GTK_POS_TOP && widget == priv->bottom_marks_widget) ||
508 (mark->position == GTK_POS_BOTTOM && widget == priv->top_marks_widget))
509 continue;
510
511 gtk_widget_measure (widget: mark->widget,
512 orientation, for_size: -1,
513 minimum: &mark_size, NULL,
514 NULL, NULL);
515 mark->stop_position = marks[i];
516
517 if (orientation == GTK_ORIENTATION_HORIZONTAL)
518 {
519 mark_alloc.x = mark->stop_position;
520 mark_alloc.y = 0;
521 mark_alloc.width = mark_size;
522 mark_alloc.height = height;
523
524 mark_alloc.x -= mark_size / 2;
525 }
526 else
527 {
528 mark_alloc.x = 0;
529 mark_alloc.y = mark->stop_position;
530 mark_alloc.width = width;
531 mark_alloc.height = mark_size;
532
533 mark_alloc.y -= mark_size / 2;
534 }
535
536 gtk_widget_size_allocate (widget: mark->widget, allocation: &mark_alloc, baseline);
537 }
538
539 g_free (mem: marks);
540}
541
542static void
543gtk_scale_size_allocate (GtkWidget *widget,
544 int width,
545 int height,
546 int baseline)
547{
548 GtkScale *scale = GTK_SCALE (widget);
549 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
550 GtkAllocation range_rect, marks_rect;
551 GtkOrientation orientation;
552
553 GTK_WIDGET_CLASS (gtk_scale_parent_class)->size_allocate (widget, width, height, baseline);
554
555 orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
556 gtk_range_get_range_rect (GTK_RANGE (scale), range_rect: &range_rect);
557
558 if (orientation == GTK_ORIENTATION_HORIZONTAL)
559 {
560 int marks_height = 0;
561
562 if (priv->top_marks_widget)
563 {
564 gtk_widget_measure (widget: priv->top_marks_widget,
565 orientation: GTK_ORIENTATION_VERTICAL, for_size: -1,
566 minimum: &marks_height, NULL,
567 NULL, NULL);
568 marks_rect.x = 0;
569 marks_rect.y = 0;
570 marks_rect.width = range_rect.width;
571 marks_rect.height = marks_height;
572 gtk_widget_size_allocate (widget: priv->top_marks_widget, allocation: &marks_rect, baseline: -1);
573 }
574
575 if (priv->bottom_marks_widget)
576 {
577 gtk_widget_measure (widget: priv->bottom_marks_widget,
578 orientation: GTK_ORIENTATION_VERTICAL, for_size: -1,
579 minimum: &marks_height, NULL,
580 NULL, NULL);
581 marks_rect.x = 0;
582 marks_rect.y = range_rect.y + range_rect.height;
583 marks_rect.width = range_rect.width;
584 marks_rect.height = marks_height;
585 gtk_widget_size_allocate (widget: priv->bottom_marks_widget, allocation: &marks_rect, baseline: -1);
586 }
587 }
588 else
589 {
590 int marks_width = 0;
591
592 if (priv->top_marks_widget)
593 {
594 gtk_widget_measure (widget: priv->top_marks_widget,
595 orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
596 minimum: &marks_width, NULL,
597 NULL, NULL);
598 marks_rect.x = range_rect.x - marks_width;
599 marks_rect.y = 0;
600 marks_rect.width = marks_width;
601 marks_rect.height = range_rect.height;
602 gtk_widget_size_allocate (widget: priv->top_marks_widget, allocation: &marks_rect, baseline: -1);
603 }
604
605 if (priv->bottom_marks_widget)
606 {
607 gtk_widget_measure (widget: priv->bottom_marks_widget,
608 orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
609 minimum: &marks_width, NULL,
610 NULL, NULL);
611 marks_rect = range_rect;
612 marks_rect.x = range_rect.x + range_rect.width;
613 marks_rect.y = 0;
614 marks_rect.width = marks_width;
615 marks_rect.height = range_rect.height;
616 gtk_widget_size_allocate (widget: priv->bottom_marks_widget, allocation: &marks_rect, baseline: -1);
617 }
618 }
619
620 if (priv->value_widget)
621 {
622 gtk_scale_allocate_value (scale);
623 }
624}
625
626#define add_slider_binding(binding_set, keyval, mask, scroll) \
627 gtk_widget_class_add_binding_signal (widget_class, \
628 keyval, mask, \
629 I_("move-slider"), \
630 "(i)", scroll)
631
632static void
633gtk_scale_value_changed (GtkRange *range)
634{
635 GtkScalePrivate *priv = gtk_scale_get_instance_private (GTK_SCALE (range));
636 GtkAdjustment *adjustment = gtk_range_get_adjustment (range);
637
638 if (priv->value_widget)
639 {
640 char *text = gtk_scale_format_value (GTK_SCALE (range),
641 value: gtk_adjustment_get_value (adjustment));
642 gtk_label_set_label (GTK_LABEL (priv->value_widget), str: text);
643
644 g_free (mem: text);
645 }
646}
647
648static void
649gtk_scale_class_init (GtkScaleClass *class)
650{
651 GObjectClass *gobject_class;
652 GtkWidgetClass *widget_class;
653 GtkRangeClass *range_class;
654
655 gobject_class = G_OBJECT_CLASS (class);
656 range_class = (GtkRangeClass*) class;
657 widget_class = (GtkWidgetClass*) class;
658
659 gobject_class->set_property = gtk_scale_set_property;
660 gobject_class->get_property = gtk_scale_get_property;
661 gobject_class->notify = gtk_scale_notify;
662 gobject_class->finalize = gtk_scale_finalize;
663
664 widget_class->size_allocate = gtk_scale_size_allocate;
665 widget_class->measure = gtk_scale_measure;
666 widget_class->grab_focus = gtk_widget_grab_focus_self;
667 widget_class->focus = gtk_widget_focus_self;
668
669 range_class->get_range_border = gtk_scale_get_range_border;
670 range_class->value_changed = gtk_scale_value_changed;
671
672 class->get_layout_offsets = gtk_scale_real_get_layout_offsets;
673
674 /**
675 * GtkScale:digits: (attributes org.gtk.Method.get=gtk_scale_get_digits org.gtk.Method.set=gtk_scale_set_digits)
676 *
677 * The number of decimal places that are displayed in the value.
678 */
679 properties[PROP_DIGITS] =
680 g_param_spec_int (name: "digits",
681 P_("Digits"),
682 P_("The number of decimal places that are displayed in the value"),
683 minimum: -1, MAX_DIGITS,
684 default_value: 1,
685 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
686
687 /**
688 * GtkScale:draw-value: (attributes org.gtk.Method.get=gtk_scale_get_draw_value org.gtk.Method.set=gtk_scale_set_draw_value)
689 *
690 * Whether the current value is displayed as a string next to the slider.
691 */
692 properties[PROP_DRAW_VALUE] =
693 g_param_spec_boolean (name: "draw-value",
694 P_("Draw Value"),
695 P_("Whether the current value is displayed as a string next to the slider"),
696 FALSE,
697 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
698
699 /**
700 * GtkScale:has-origin: (attributes org.gtk.Method.get=gtk_scale_get_has_origin org.gtk.Method.set=gtk_scale_set_has_origin)
701 *
702 * Whether the scale has an origin.
703 */
704 properties[PROP_HAS_ORIGIN] =
705 g_param_spec_boolean (name: "has-origin",
706 P_("Has Origin"),
707 P_("Whether the scale has an origin"),
708 TRUE,
709 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
710
711 /**
712 * GtkScale:value-pos: (attributes org.gtk.Method.get=gtk_scale_get_value_pos org.gtk.Method.set=gtk_scale_set_value_pos)
713 *
714 * The position in which the current value is displayed.
715 */
716 properties[PROP_VALUE_POS] =
717 g_param_spec_enum (name: "value-pos",
718 P_("Value Position"),
719 P_("The position in which the current value is displayed"),
720 enum_type: GTK_TYPE_POSITION_TYPE,
721 default_value: GTK_POS_TOP,
722 GTK_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
723
724 g_object_class_install_properties (oclass: gobject_class, n_pspecs: LAST_PROP, pspecs: properties);
725
726 /* All bindings (even arrow keys) are on both h/v scale, because
727 * blind users etc. don't care about scale orientation.
728 */
729
730 add_slider_binding (binding_set, GDK_KEY_Left, 0,
731 GTK_SCROLL_STEP_LEFT);
732
733 add_slider_binding (binding_set, GDK_KEY_Left, GDK_CONTROL_MASK,
734 GTK_SCROLL_PAGE_LEFT);
735
736 add_slider_binding (binding_set, GDK_KEY_KP_Left, 0,
737 GTK_SCROLL_STEP_LEFT);
738
739 add_slider_binding (binding_set, GDK_KEY_KP_Left, GDK_CONTROL_MASK,
740 GTK_SCROLL_PAGE_LEFT);
741
742 add_slider_binding (binding_set, GDK_KEY_Right, 0,
743 GTK_SCROLL_STEP_RIGHT);
744
745 add_slider_binding (binding_set, GDK_KEY_Right, GDK_CONTROL_MASK,
746 GTK_SCROLL_PAGE_RIGHT);
747
748 add_slider_binding (binding_set, GDK_KEY_KP_Right, 0,
749 GTK_SCROLL_STEP_RIGHT);
750
751 add_slider_binding (binding_set, GDK_KEY_KP_Right, GDK_CONTROL_MASK,
752 GTK_SCROLL_PAGE_RIGHT);
753
754 add_slider_binding (binding_set, GDK_KEY_Up, 0,
755 GTK_SCROLL_STEP_UP);
756
757 add_slider_binding (binding_set, GDK_KEY_Up, GDK_CONTROL_MASK,
758 GTK_SCROLL_PAGE_UP);
759
760 add_slider_binding (binding_set, GDK_KEY_KP_Up, 0,
761 GTK_SCROLL_STEP_UP);
762
763 add_slider_binding (binding_set, GDK_KEY_KP_Up, GDK_CONTROL_MASK,
764 GTK_SCROLL_PAGE_UP);
765
766 add_slider_binding (binding_set, GDK_KEY_Down, 0,
767 GTK_SCROLL_STEP_DOWN);
768
769 add_slider_binding (binding_set, GDK_KEY_Down, GDK_CONTROL_MASK,
770 GTK_SCROLL_PAGE_DOWN);
771
772 add_slider_binding (binding_set, GDK_KEY_KP_Down, 0,
773 GTK_SCROLL_STEP_DOWN);
774
775 add_slider_binding (binding_set, GDK_KEY_KP_Down, GDK_CONTROL_MASK,
776 GTK_SCROLL_PAGE_DOWN);
777
778 add_slider_binding (binding_set, GDK_KEY_Page_Up, GDK_CONTROL_MASK,
779 GTK_SCROLL_PAGE_LEFT);
780
781 add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, GDK_CONTROL_MASK,
782 GTK_SCROLL_PAGE_LEFT);
783
784 add_slider_binding (binding_set, GDK_KEY_Page_Up, 0,
785 GTK_SCROLL_PAGE_UP);
786
787 add_slider_binding (binding_set, GDK_KEY_KP_Page_Up, 0,
788 GTK_SCROLL_PAGE_UP);
789
790 add_slider_binding (binding_set, GDK_KEY_Page_Down, GDK_CONTROL_MASK,
791 GTK_SCROLL_PAGE_RIGHT);
792
793 add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, GDK_CONTROL_MASK,
794 GTK_SCROLL_PAGE_RIGHT);
795
796 add_slider_binding (binding_set, GDK_KEY_Page_Down, 0,
797 GTK_SCROLL_PAGE_DOWN);
798
799 add_slider_binding (binding_set, GDK_KEY_KP_Page_Down, 0,
800 GTK_SCROLL_PAGE_DOWN);
801
802 /* Logical bindings (vs. visual bindings above) */
803
804 add_slider_binding (binding_set, GDK_KEY_plus, 0,
805 GTK_SCROLL_STEP_FORWARD);
806
807 add_slider_binding (binding_set, GDK_KEY_minus, 0,
808 GTK_SCROLL_STEP_BACKWARD);
809
810 add_slider_binding (binding_set, GDK_KEY_plus, GDK_CONTROL_MASK,
811 GTK_SCROLL_PAGE_FORWARD);
812
813 add_slider_binding (binding_set, GDK_KEY_minus, GDK_CONTROL_MASK,
814 GTK_SCROLL_PAGE_BACKWARD);
815
816
817 add_slider_binding (binding_set, GDK_KEY_KP_Add, 0,
818 GTK_SCROLL_STEP_FORWARD);
819
820 add_slider_binding (binding_set, GDK_KEY_KP_Subtract, 0,
821 GTK_SCROLL_STEP_BACKWARD);
822
823 add_slider_binding (binding_set, GDK_KEY_KP_Add, GDK_CONTROL_MASK,
824 GTK_SCROLL_PAGE_FORWARD);
825
826 add_slider_binding (binding_set, GDK_KEY_KP_Subtract, GDK_CONTROL_MASK,
827 GTK_SCROLL_PAGE_BACKWARD);
828
829 add_slider_binding (binding_set, GDK_KEY_Home, 0,
830 GTK_SCROLL_START);
831
832 add_slider_binding (binding_set, GDK_KEY_KP_Home, 0,
833 GTK_SCROLL_START);
834
835 add_slider_binding (binding_set, GDK_KEY_End, 0,
836 GTK_SCROLL_END);
837
838 add_slider_binding (binding_set, GDK_KEY_KP_End, 0,
839 GTK_SCROLL_END);
840
841 gtk_widget_class_set_css_name (widget_class, I_("scale"));
842
843 gtk_widget_class_set_accessible_role (widget_class, accessible_role: GTK_ACCESSIBLE_ROLE_SLIDER);
844}
845
846static void
847gtk_scale_init (GtkScale *scale)
848{
849 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
850 GtkRange *range = GTK_RANGE (scale);
851
852 gtk_widget_set_focusable (GTK_WIDGET (scale), TRUE);
853
854 priv->value_pos = GTK_POS_TOP;
855 priv->digits = 1;
856
857 gtk_range_set_slider_size_fixed (range, TRUE);
858
859 _gtk_range_set_has_origin (range, TRUE);
860
861 gtk_range_set_round_digits (range, round_digits: -1);
862
863 gtk_range_set_flippable (range, TRUE);
864}
865
866static void
867gtk_scale_set_property (GObject *object,
868 guint prop_id,
869 const GValue *value,
870 GParamSpec *pspec)
871{
872 GtkScale *scale;
873
874 scale = GTK_SCALE (object);
875
876 switch (prop_id)
877 {
878 case PROP_DIGITS:
879 gtk_scale_set_digits (scale, digits: g_value_get_int (value));
880 break;
881 case PROP_DRAW_VALUE:
882 gtk_scale_set_draw_value (scale, draw_value: g_value_get_boolean (value));
883 break;
884 case PROP_HAS_ORIGIN:
885 gtk_scale_set_has_origin (scale, has_origin: g_value_get_boolean (value));
886 break;
887 case PROP_VALUE_POS:
888 gtk_scale_set_value_pos (scale, pos: g_value_get_enum (value));
889 break;
890 default:
891 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
892 break;
893 }
894}
895
896static void
897gtk_scale_get_property (GObject *object,
898 guint prop_id,
899 GValue *value,
900 GParamSpec *pspec)
901{
902 GtkScale *scale = GTK_SCALE (object);
903 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
904
905 switch (prop_id)
906 {
907 case PROP_DIGITS:
908 g_value_set_int (value, v_int: priv->digits);
909 break;
910 case PROP_DRAW_VALUE:
911 g_value_set_boolean (value, v_boolean: priv->draw_value);
912 break;
913 case PROP_HAS_ORIGIN:
914 g_value_set_boolean (value, v_boolean: gtk_scale_get_has_origin (scale));
915 break;
916 case PROP_VALUE_POS:
917 g_value_set_enum (value, v_enum: priv->value_pos);
918 break;
919 default:
920 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
921 break;
922 }
923}
924
925/**
926 * gtk_scale_new:
927 * @orientation: the scale’s orientation.
928 * @adjustment: (nullable): the [class@Gtk.Adjustment] which sets
929 * the range of the scale, or %NULL to create a new adjustment.
930 *
931 * Creates a new `GtkScale`.
932 *
933 * Returns: a new `GtkScale`
934 */
935GtkWidget *
936gtk_scale_new (GtkOrientation orientation,
937 GtkAdjustment *adjustment)
938{
939 g_return_val_if_fail (adjustment == NULL || GTK_IS_ADJUSTMENT (adjustment),
940 NULL);
941
942 return g_object_new (GTK_TYPE_SCALE,
943 first_property_name: "orientation", orientation,
944 "adjustment", adjustment,
945 NULL);
946}
947
948/**
949 * gtk_scale_new_with_range:
950 * @orientation: the scale’s orientation.
951 * @min: minimum value
952 * @max: maximum value
953 * @step: step increment (tick size) used with keyboard shortcuts
954 *
955 * Creates a new scale widget with a range from @min to @max.
956 *
957 * The returns scale will have the given orientation and will let the
958 * user input a number between @min and @max (including @min and @max)
959 * with the increment @step. @step must be nonzero; it’s the distance
960 * the slider moves when using the arrow keys to adjust the scale
961 * value.
962 *
963 * Note that the way in which the precision is derived works best if
964 * @step is a power of ten. If the resulting precision is not suitable
965 * for your needs, use [method@Gtk.Scale.set_digits] to correct it.
966 *
967 * Returns: a new `GtkScale`
968 */
969GtkWidget *
970gtk_scale_new_with_range (GtkOrientation orientation,
971 double min,
972 double max,
973 double step)
974{
975 GtkAdjustment *adj;
976 int digits;
977
978 g_return_val_if_fail (min < max, NULL);
979 g_return_val_if_fail (step != 0.0, NULL);
980
981 adj = gtk_adjustment_new (value: min, lower: min, upper: max, step_increment: step, page_increment: 10 * step, page_size: 0);
982
983 if (fabs (x: step) >= 1.0 || step == 0.0)
984 {
985 digits = 0;
986 }
987 else
988 {
989 digits = abs (x: (int) floor (x: log10 (x: fabs (x: step))));
990 if (digits > 5)
991 digits = 5;
992 }
993
994 return g_object_new (GTK_TYPE_SCALE,
995 first_property_name: "orientation", orientation,
996 "adjustment", adj,
997 "digits", digits,
998 NULL);
999}
1000
1001/**
1002 * gtk_scale_set_digits: (attributes org.gtk.Method.set_property=digits)
1003 * @scale: a `GtkScale`
1004 * @digits: the number of decimal places to display,
1005 * e.g. use 1 to display 1.0, 2 to display 1.00, etc
1006 *
1007 * Sets the number of decimal places that are displayed in the value.
1008 *
1009 * Also causes the value of the adjustment to be rounded to this number
1010 * of digits, so the retrieved value matches the displayed one, if
1011 * [property@GtkScale:draw-value] is %TRUE when the value changes. If
1012 * you want to enforce rounding the value when [property@GtkScale:draw-value]
1013 * is %FALSE, you can set [property@GtkRange:round-digits] instead.
1014 *
1015 * Note that rounding to a small number of digits can interfere with
1016 * the smooth autoscrolling that is built into `GtkScale`. As an alternative,
1017 * you can use [method@Gtk.Scale.set_format_value_func] to format the displayed
1018 * value yourself.
1019 */
1020void
1021gtk_scale_set_digits (GtkScale *scale,
1022 int digits)
1023{
1024 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1025 GtkRange *range;
1026
1027 g_return_if_fail (GTK_IS_SCALE (scale));
1028
1029 range = GTK_RANGE (scale);
1030
1031 digits = CLAMP (digits, -1, MAX_DIGITS);
1032
1033 if (priv->digits != digits)
1034 {
1035 priv->digits = digits;
1036 if (priv->draw_value)
1037 gtk_range_set_round_digits (range, round_digits: digits);
1038
1039 gtk_widget_queue_resize (GTK_WIDGET (scale));
1040
1041 g_object_notify_by_pspec (G_OBJECT (scale), pspec: properties[PROP_DIGITS]);
1042 }
1043}
1044
1045/**
1046 * gtk_scale_get_digits: (attributes org.gtk.Method.get_property=digits)
1047 * @scale: a `GtkScale`
1048 *
1049 * Gets the number of decimal places that are displayed in the value.
1050 *
1051 * Returns: the number of decimal places that are displayed
1052 */
1053int
1054gtk_scale_get_digits (GtkScale *scale)
1055{
1056 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1057
1058 g_return_val_if_fail (GTK_IS_SCALE (scale), -1);
1059
1060 return priv->digits;
1061}
1062
1063static void
1064update_value_position (GtkScale *scale)
1065{
1066 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1067
1068 if (!priv->value_widget)
1069 return;
1070
1071 gtk_widget_remove_css_class (widget: priv->value_widget, css_class: "top");
1072 gtk_widget_remove_css_class (widget: priv->value_widget, css_class: "right");
1073 gtk_widget_remove_css_class (widget: priv->value_widget, css_class: "bottom");
1074 gtk_widget_remove_css_class (widget: priv->value_widget, css_class: "left");
1075
1076 switch (priv->value_pos)
1077 {
1078 case GTK_POS_TOP:
1079 gtk_widget_add_css_class (widget: priv->value_widget, css_class: "top");
1080 break;
1081 case GTK_POS_RIGHT:
1082 gtk_widget_add_css_class (widget: priv->value_widget, css_class: "right");
1083 break;
1084 case GTK_POS_BOTTOM:
1085 gtk_widget_add_css_class (widget: priv->value_widget, css_class: "bottom");
1086 break;
1087 case GTK_POS_LEFT:
1088 gtk_widget_add_css_class (widget: priv->value_widget, css_class: "left");
1089 break;
1090 default:
1091 g_assert_not_reached ();
1092 break;
1093 }
1094}
1095
1096/**
1097 * gtk_scale_set_draw_value: (attributes org.gtk.Method.set_property=draw-value)
1098 * @scale: a `GtkScale`
1099 * @draw_value: %TRUE to draw the value
1100 *
1101 * Specifies whether the current value is displayed as a string next
1102 * to the slider.
1103 */
1104void
1105gtk_scale_set_draw_value (GtkScale *scale,
1106 gboolean draw_value)
1107{
1108 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1109
1110 g_return_if_fail (GTK_IS_SCALE (scale));
1111
1112 draw_value = draw_value != FALSE;
1113
1114 if (priv->draw_value != draw_value)
1115 {
1116 priv->draw_value = draw_value;
1117 if (draw_value)
1118 {
1119 char *txt;
1120
1121 txt = gtk_scale_format_value (scale,
1122 value: gtk_adjustment_get_value (adjustment: gtk_range_get_adjustment (GTK_RANGE (scale))));
1123
1124 priv->value_widget = g_object_new (GTK_TYPE_LABEL,
1125 first_property_name: "css-name", "value",
1126 "label", txt,
1127 NULL);
1128 g_free (mem: txt);
1129
1130 gtk_widget_insert_after (widget: priv->value_widget, GTK_WIDGET (scale), NULL);
1131 gtk_range_set_round_digits (GTK_RANGE (scale), round_digits: priv->digits);
1132 update_value_position (scale);
1133 update_label_request (scale);
1134 }
1135 else if (priv->value_widget)
1136 {
1137 g_clear_pointer (&priv->value_widget, gtk_widget_unparent);
1138 gtk_range_set_round_digits (GTK_RANGE (scale), round_digits: -1);
1139 }
1140
1141 g_object_notify_by_pspec (G_OBJECT (scale), pspec: properties[PROP_DRAW_VALUE]);
1142 }
1143}
1144
1145/**
1146 * gtk_scale_get_draw_value: (attributes org.gtk.Method.get_property=draw-value)
1147 * @scale: a `GtkScale`
1148 *
1149 * Returns whether the current value is displayed as a string
1150 * next to the slider.
1151 *
1152 * Returns: whether the current value is displayed as a string
1153 */
1154gboolean
1155gtk_scale_get_draw_value (GtkScale *scale)
1156{
1157 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1158
1159 g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);
1160
1161 return priv->draw_value;
1162}
1163
1164/**
1165 * gtk_scale_set_has_origin: (attributes org.gtk.Method.set_property=has-origin)
1166 * @scale: a `GtkScale`
1167 * @has_origin: %TRUE if the scale has an origin
1168 *
1169 * Sets whether the scale has an origin.
1170 *
1171 * If [property@GtkScale:has-origin] is set to %TRUE (the default),
1172 * the scale will highlight the part of the trough between the origin
1173 * (bottom or left side) and the current value.
1174 */
1175void
1176gtk_scale_set_has_origin (GtkScale *scale,
1177 gboolean has_origin)
1178{
1179 g_return_if_fail (GTK_IS_SCALE (scale));
1180
1181 has_origin = has_origin != FALSE;
1182
1183 if (_gtk_range_get_has_origin (GTK_RANGE (scale)) != has_origin)
1184 {
1185 _gtk_range_set_has_origin (GTK_RANGE (scale), has_origin);
1186
1187 gtk_widget_queue_draw (GTK_WIDGET (scale));
1188
1189 g_object_notify_by_pspec (G_OBJECT (scale), pspec: properties[PROP_HAS_ORIGIN]);
1190 }
1191}
1192
1193/**
1194 * gtk_scale_get_has_origin: (attributes org.gtk.Method.get_property=has-origin)
1195 * @scale: a `GtkScale`
1196 *
1197 * Returns whether the scale has an origin.
1198 *
1199 * Returns: %TRUE if the scale has an origin.
1200 */
1201gboolean
1202gtk_scale_get_has_origin (GtkScale *scale)
1203{
1204 g_return_val_if_fail (GTK_IS_SCALE (scale), FALSE);
1205
1206 return _gtk_range_get_has_origin (GTK_RANGE (scale));
1207}
1208
1209/**
1210 * gtk_scale_set_value_pos: (attributes org.gtk.Method.set_property=value-pos)
1211 * @scale: a `GtkScale`
1212 * @pos: the position in which the current value is displayed
1213 *
1214 * Sets the position in which the current value is displayed.
1215 */
1216void
1217gtk_scale_set_value_pos (GtkScale *scale,
1218 GtkPositionType pos)
1219{
1220 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1221
1222 g_return_if_fail (GTK_IS_SCALE (scale));
1223
1224 if (priv->value_pos != pos)
1225 {
1226 priv->value_pos = pos;
1227
1228 update_value_position (scale);
1229 gtk_widget_queue_resize (GTK_WIDGET (scale));
1230
1231 g_object_notify_by_pspec (G_OBJECT (scale), pspec: properties[PROP_VALUE_POS]);
1232 }
1233}
1234
1235/**
1236 * gtk_scale_get_value_pos: (attributes org.gtk.Method.get_property=value-pos)
1237 * @scale: a `GtkScale`
1238 *
1239 * Gets the position in which the current value is displayed.
1240 *
1241 * Returns: the position in which the current value is displayed
1242 */
1243GtkPositionType
1244gtk_scale_get_value_pos (GtkScale *scale)
1245{
1246 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1247
1248 g_return_val_if_fail (GTK_IS_SCALE (scale), 0);
1249
1250 return priv->value_pos;
1251}
1252
1253static void
1254gtk_scale_get_range_border (GtkRange *range,
1255 GtkBorder *border)
1256{
1257 GtkScale *scale = GTK_SCALE (range);
1258 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1259
1260 border->left = 0;
1261 border->right = 0;
1262 border->top = 0;
1263 border->bottom = 0;
1264
1265 if (priv->value_widget)
1266 {
1267 int value_size;
1268 GtkOrientation value_orientation;
1269
1270 if (priv->value_pos == GTK_POS_LEFT || priv->value_pos == GTK_POS_RIGHT)
1271 value_orientation = GTK_ORIENTATION_HORIZONTAL;
1272 else
1273 value_orientation = GTK_ORIENTATION_VERTICAL;
1274
1275 gtk_widget_measure (widget: priv->value_widget,
1276 orientation: value_orientation, for_size: -1,
1277 minimum: &value_size, NULL,
1278 NULL, NULL);
1279
1280 switch (priv->value_pos)
1281 {
1282 case GTK_POS_LEFT:
1283 border->left += value_size;
1284 break;
1285 case GTK_POS_RIGHT:
1286 border->right += value_size;
1287 break;
1288 case GTK_POS_TOP:
1289 border->top += value_size;
1290 break;
1291 case GTK_POS_BOTTOM:
1292 border->bottom += value_size;
1293 break;
1294 default:
1295 g_assert_not_reached ();
1296 break;
1297 }
1298 }
1299
1300 if (gtk_orientable_get_orientation (GTK_ORIENTABLE (range)) == GTK_ORIENTATION_HORIZONTAL)
1301 {
1302 int height;
1303
1304 if (priv->top_marks_widget)
1305 {
1306 gtk_widget_measure (widget: priv->top_marks_widget,
1307 orientation: GTK_ORIENTATION_VERTICAL, for_size: -1,
1308 minimum: &height, NULL,
1309 NULL, NULL);
1310 if (height > 0)
1311 border->top += height;
1312 }
1313
1314 if (priv->bottom_marks_widget)
1315 {
1316 gtk_widget_measure (widget: priv->bottom_marks_widget,
1317 orientation: GTK_ORIENTATION_VERTICAL, for_size: -1,
1318 minimum: &height, NULL,
1319 NULL, NULL);
1320 if (height > 0)
1321 border->bottom += height;
1322 }
1323 }
1324 else
1325 {
1326 int width;
1327
1328 if (priv->top_marks_widget)
1329 {
1330 gtk_widget_measure (widget: priv->top_marks_widget,
1331 orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
1332 minimum: &width, NULL,
1333 NULL, NULL);
1334 if (width > 0)
1335 border->left += width;
1336 }
1337
1338 if (priv->bottom_marks_widget)
1339 {
1340 gtk_widget_measure (widget: priv->bottom_marks_widget,
1341 orientation: GTK_ORIENTATION_HORIZONTAL, for_size: -1,
1342 minimum: &width, NULL,
1343 NULL, NULL);
1344 if (width > 0)
1345 border->right += width;
1346 }
1347 }
1348}
1349
1350static void
1351gtk_scale_measure_mark (GtkGizmo *gizmo,
1352 GtkOrientation orientation,
1353 int for_size,
1354 int *minimum,
1355 int *natural,
1356 int *minimum_baseline,
1357 int *natural_baseline)
1358{
1359 GtkScaleMark *mark = g_object_get_data (G_OBJECT (gizmo), key: "mark");
1360
1361 gtk_widget_measure (widget: mark->indicator_widget,
1362 orientation, for_size: -1,
1363 minimum, natural,
1364 NULL, NULL);
1365
1366 if (mark->label_widget)
1367 {
1368 int label_min, label_nat;
1369
1370 gtk_widget_measure (widget: mark->label_widget,
1371 orientation, for_size: -1,
1372 minimum: &label_min, natural: &label_nat,
1373 NULL, NULL);
1374 *minimum += label_min;
1375 *natural += label_nat;
1376 }
1377}
1378
1379static void
1380gtk_scale_measure_marks (GtkGizmo *gizmo,
1381 GtkOrientation orientation,
1382 int for_size,
1383 int *minimum,
1384 int *natural,
1385 int *minimum_baseline,
1386 int *natural_baseline)
1387{
1388 GtkWidget *widget = GTK_WIDGET (gizmo);
1389 GtkScale *scale = GTK_SCALE (gtk_widget_get_parent (widget));;
1390 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1391 GtkOrientation scale_orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (scale));
1392 GSList *m;
1393
1394 *minimum = *natural = 0;
1395
1396 for (m = priv->marks; m; m = m->next)
1397 {
1398 GtkScaleMark *mark = m->data;
1399 int mark_size;
1400
1401 if ((mark->position == GTK_POS_TOP && widget == priv->bottom_marks_widget) ||
1402 (mark->position == GTK_POS_BOTTOM && widget == priv->top_marks_widget))
1403 continue;
1404
1405 gtk_widget_measure (widget: mark->widget,
1406 orientation, for_size: -1,
1407 minimum: &mark_size, NULL,
1408 NULL, NULL);
1409
1410 if (scale_orientation == orientation)
1411 {
1412 *minimum += mark_size;
1413 *natural += mark_size;
1414 }
1415 else
1416 {
1417 *minimum = MAX (*minimum, mark_size);
1418 *natural = MAX (*natural, mark_size);
1419 }
1420 }
1421}
1422
1423static void
1424gtk_scale_measure (GtkWidget *widget,
1425 GtkOrientation orientation,
1426 int for_size,
1427 int *minimum,
1428 int *natural,
1429 int *minimum_baseline,
1430 int *natural_baseline)
1431{
1432 GtkScale *scale = GTK_SCALE (widget);
1433 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1434 GtkOrientation scale_orientation;
1435
1436 GTK_WIDGET_CLASS (gtk_scale_parent_class)->measure (widget,
1437 orientation,
1438 for_size,
1439 minimum, natural,
1440 minimum_baseline, natural_baseline);
1441
1442 scale_orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (widget));
1443
1444 if (scale_orientation == orientation)
1445 {
1446 int top_marks_size = 0, bottom_marks_size = 0, marks_size;
1447
1448 if (priv->top_marks_widget)
1449 gtk_widget_measure (widget: priv->top_marks_widget,
1450 orientation, for_size,
1451 minimum: &top_marks_size, NULL,
1452 NULL, NULL);
1453 if (priv->bottom_marks_widget)
1454 gtk_widget_measure (widget: priv->bottom_marks_widget,
1455 orientation, for_size,
1456 minimum: &bottom_marks_size, NULL,
1457 NULL, NULL);
1458
1459 marks_size = MAX (top_marks_size, bottom_marks_size);
1460
1461 *minimum = MAX (*minimum, marks_size);
1462 *natural = MAX (*natural, marks_size);
1463 }
1464
1465 if (priv->value_widget)
1466 {
1467 int min, nat;
1468
1469 gtk_widget_measure (widget: priv->value_widget, orientation, for_size: -1, minimum: &min, natural: &nat, NULL, NULL);
1470
1471 if (priv->value_pos == GTK_POS_TOP ||
1472 priv->value_pos == GTK_POS_BOTTOM)
1473 {
1474 if (orientation == GTK_ORIENTATION_HORIZONTAL)
1475 {
1476 *minimum = MAX (*minimum, min);
1477 *natural = MAX (*natural, nat);
1478 }
1479 else
1480 {
1481 *minimum += min;
1482 *natural += nat;
1483 }
1484 }
1485 else
1486 {
1487 if (orientation == GTK_ORIENTATION_HORIZONTAL)
1488 {
1489 *minimum += min;
1490 *natural += nat;
1491 }
1492 else
1493 {
1494 *minimum = MAX (*minimum, min);
1495 *natural = MAX (*natural, nat);
1496 }
1497 }
1498 }
1499}
1500
1501static void
1502gtk_scale_real_get_layout_offsets (GtkScale *scale,
1503 int *x,
1504 int *y)
1505{
1506 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1507 graphene_rect_t value_bounds;
1508
1509 if (!priv->value_widget ||
1510 !gtk_widget_compute_bounds (widget: priv->value_widget, GTK_WIDGET (scale), out_bounds: &value_bounds))
1511 {
1512 *x = 0;
1513 *y = 0;
1514
1515 return;
1516 }
1517
1518
1519 *x = value_bounds.origin.x;
1520 *y = value_bounds.origin.y;
1521}
1522
1523static char *
1524weed_out_neg_zero (char *str,
1525 int digits)
1526{
1527 if (str[0] == '-')
1528 {
1529 char neg_zero[8];
1530 g_snprintf (string: neg_zero, n: 8, format: "%0.*f", digits, -0.0);
1531 if (strcmp (s1: neg_zero, s2: str) == 0)
1532 memmove (dest: str, src: str + 1, n: strlen (s: str));
1533 }
1534 return str;
1535}
1536
1537static char *
1538gtk_scale_format_value (GtkScale *scale,
1539 double value)
1540{
1541 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1542
1543 if (priv->format_value_func)
1544 {
1545 return priv->format_value_func (scale, value, priv->format_value_func_user_data);
1546 }
1547 else
1548 {
1549 char *fmt = g_strdup_printf (format: "%0.*f", priv->digits, value);
1550 return weed_out_neg_zero (str: fmt, digits: priv->digits);
1551 }
1552}
1553
1554static void
1555gtk_scale_finalize (GObject *object)
1556{
1557 GtkScale *scale = GTK_SCALE (object);
1558 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1559
1560 gtk_scale_clear_marks (scale);
1561
1562 g_clear_pointer (&priv->value_widget, gtk_widget_unparent);
1563
1564 if (priv->format_value_func_destroy_notify)
1565 priv->format_value_func_destroy_notify (priv->format_value_func_user_data);
1566
1567 G_OBJECT_CLASS (gtk_scale_parent_class)->finalize (object);
1568}
1569
1570/**
1571 * gtk_scale_get_layout:
1572 * @scale: A `GtkScale`
1573 *
1574 * Gets the `PangoLayout` used to display the scale.
1575 *
1576 * The returned object is owned by the scale so does not need
1577 * to be freed by the caller.
1578 *
1579 * Returns: (transfer none) (nullable): the [class@Pango.Layout]
1580 * for this scale, or %NULL if the [property@GtkScale:draw-value]
1581 * property is %FALSE.
1582 */
1583PangoLayout *
1584gtk_scale_get_layout (GtkScale *scale)
1585{
1586 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1587
1588 g_return_val_if_fail (GTK_IS_SCALE (scale), NULL);
1589
1590 if (priv->value_widget)
1591 return gtk_label_get_layout (GTK_LABEL (priv->value_widget));
1592
1593 return NULL;
1594}
1595
1596/**
1597 * gtk_scale_get_layout_offsets:
1598 * @scale: a `GtkScale`
1599 * @x: (out) (optional): location to store X offset of layout
1600 * @y: (out) (optional): location to store Y offset of layout
1601 *
1602 * Obtains the coordinates where the scale will draw the
1603 * `PangoLayout` representing the text in the scale.
1604 *
1605 * Remember when using the `PangoLayout` function you need to
1606 * convert to and from pixels using `PANGO_PIXELS()` or `PANGO_SCALE`.
1607 *
1608 * If the [property@GtkScale:draw-value] property is %FALSE, the return
1609 * values are undefined.
1610 */
1611void
1612gtk_scale_get_layout_offsets (GtkScale *scale,
1613 int *x,
1614 int *y)
1615{
1616 int local_x = 0;
1617 int local_y = 0;
1618
1619 g_return_if_fail (GTK_IS_SCALE (scale));
1620
1621 if (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets)
1622 (GTK_SCALE_GET_CLASS (scale)->get_layout_offsets) (scale, &local_x, &local_y);
1623
1624 if (x)
1625 *x = local_x;
1626
1627 if (y)
1628 *y = local_y;
1629}
1630
1631static void
1632gtk_scale_mark_free (gpointer data)
1633{
1634 GtkScaleMark *mark = data;
1635
1636 if (mark->label_widget)
1637 gtk_widget_unparent (widget: mark->label_widget);
1638
1639 gtk_widget_unparent (widget: mark->indicator_widget);
1640 gtk_widget_unparent (widget: mark->widget);
1641 g_free (mem: mark->markup);
1642 g_free (mem: mark);
1643}
1644
1645/**
1646 * gtk_scale_clear_marks:
1647 * @scale: a `GtkScale`
1648 *
1649 * Removes any marks that have been added.
1650 */
1651void
1652gtk_scale_clear_marks (GtkScale *scale)
1653{
1654 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1655
1656 g_return_if_fail (GTK_IS_SCALE (scale));
1657
1658 g_slist_free_full (list: priv->marks, free_func: gtk_scale_mark_free);
1659 priv->marks = NULL;
1660
1661 g_clear_pointer (&priv->top_marks_widget, gtk_widget_unparent);
1662 g_clear_pointer (&priv->bottom_marks_widget, gtk_widget_unparent);
1663
1664 gtk_widget_remove_css_class (GTK_WIDGET (scale), css_class: "marks-before");
1665 gtk_widget_remove_css_class (GTK_WIDGET (scale), css_class: "marks-after");
1666
1667 _gtk_range_set_stop_values (GTK_RANGE (scale), NULL, n_values: 0);
1668
1669 gtk_widget_queue_resize (GTK_WIDGET (scale));
1670}
1671
1672/**
1673 * gtk_scale_add_mark:
1674 * @scale: a `GtkScale`
1675 * @value: the value at which the mark is placed, must be between
1676 * the lower and upper limits of the scales’ adjustment
1677 * @position: where to draw the mark. For a horizontal scale, %GTK_POS_TOP
1678 * and %GTK_POS_LEFT are drawn above the scale, anything else below.
1679 * For a vertical scale, %GTK_POS_LEFT and %GTK_POS_TOP are drawn to
1680 * the left of the scale, anything else to the right.
1681 * @markup: (nullable): Text to be shown at the mark, using Pango markup
1682 *
1683 * Adds a mark at @value.
1684 *
1685 * A mark is indicated visually by drawing a tick mark next to the scale,
1686 * and GTK makes it easy for the user to position the scale exactly at the
1687 * marks value.
1688 *
1689 * If @markup is not %NULL, text is shown next to the tick mark.
1690 *
1691 * To remove marks from a scale, use [method@Gtk.Scale.clear_marks].
1692 */
1693void
1694gtk_scale_add_mark (GtkScale *scale,
1695 double value,
1696 GtkPositionType position,
1697 const char *markup)
1698{
1699 GtkWidget *widget;
1700 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
1701 GtkScaleMark *mark;
1702 GSList *m;
1703 double *values;
1704 int n, i;
1705 GtkWidget *marks_widget;
1706
1707 g_return_if_fail (GTK_IS_SCALE (scale));
1708
1709 widget = GTK_WIDGET (scale);
1710
1711 mark = g_new0 (GtkScaleMark, 1);
1712 mark->value = value;
1713 mark->markup = g_strdup (str: markup);
1714 if (position == GTK_POS_LEFT ||
1715 position == GTK_POS_TOP)
1716 mark->position = GTK_POS_TOP;
1717 else
1718 mark->position = GTK_POS_BOTTOM;
1719
1720 priv->marks = g_slist_insert_sorted_with_data (list: priv->marks, data: mark,
1721 func: compare_marks,
1722 GINT_TO_POINTER (gtk_range_get_inverted (GTK_RANGE (scale))));
1723
1724 if (mark->position == GTK_POS_TOP)
1725 {
1726 if (!priv->top_marks_widget)
1727 {
1728 priv->top_marks_widget = gtk_gizmo_new_with_role (css_name: "marks",
1729 role: GTK_ACCESSIBLE_ROLE_NONE,
1730 measure_func: gtk_scale_measure_marks,
1731 allocate_func: gtk_scale_allocate_marks,
1732 NULL,
1733 NULL,
1734 NULL, NULL);
1735
1736 gtk_widget_insert_after (widget: priv->top_marks_widget,
1737 GTK_WIDGET (scale),
1738 previous_sibling: priv->value_widget);
1739 gtk_widget_add_css_class (widget: priv->top_marks_widget, css_class: "top");
1740 }
1741 marks_widget = priv->top_marks_widget;
1742 }
1743 else
1744 {
1745 if (!priv->bottom_marks_widget)
1746 {
1747 priv->bottom_marks_widget = gtk_gizmo_new_with_role (css_name: "marks",
1748 role: GTK_ACCESSIBLE_ROLE_NONE,
1749 measure_func: gtk_scale_measure_marks,
1750 allocate_func: gtk_scale_allocate_marks,
1751 NULL,
1752 NULL,
1753 NULL, NULL);
1754
1755 gtk_widget_insert_before (widget: priv->bottom_marks_widget,
1756 GTK_WIDGET (scale),
1757 next_sibling: gtk_range_get_trough_widget (GTK_RANGE (scale)));
1758 gtk_widget_add_css_class (widget: priv->bottom_marks_widget, css_class: "bottom");
1759 }
1760 marks_widget = priv->bottom_marks_widget;
1761 }
1762
1763 mark->widget = gtk_gizmo_new (css_name: "mark", measure_func: gtk_scale_measure_mark, allocate_func: gtk_scale_allocate_mark, NULL, NULL, NULL, NULL);
1764 g_object_set_data (G_OBJECT (mark->widget), key: "mark", data: mark);
1765
1766 mark->indicator_widget = gtk_gizmo_new (css_name: "indicator", NULL, NULL, NULL, NULL, NULL, NULL);
1767 gtk_widget_set_parent (widget: mark->indicator_widget, parent: mark->widget);
1768 if (mark->markup && *mark->markup)
1769 {
1770 mark->label_widget = g_object_new (GTK_TYPE_LABEL,
1771 first_property_name: "use-markup", TRUE,
1772 "label", mark->markup,
1773 NULL);
1774 if (marks_widget == priv->top_marks_widget)
1775 gtk_widget_insert_after (widget: mark->label_widget, parent: mark->widget, NULL);
1776 else
1777 gtk_widget_insert_before (widget: mark->label_widget, parent: mark->widget, NULL);
1778 }
1779
1780 m = g_slist_find (list: priv->marks, data: mark);
1781 m = m->next;
1782 while (m)
1783 {
1784 GtkScaleMark *next = m->data;
1785 if (next->position == mark->position)
1786 break;
1787 m = m->next;
1788 }
1789
1790 if (m)
1791 {
1792 GtkScaleMark *next = m->data;
1793 gtk_widget_insert_before (widget: mark->widget, parent: marks_widget, next_sibling: next->widget);
1794 }
1795 else
1796 {
1797 gtk_widget_set_parent (widget: mark->widget, parent: marks_widget);
1798 }
1799
1800 n = g_slist_length (list: priv->marks);
1801 values = g_new (double, n);
1802 for (m = priv->marks, i = 0; m; m = m->next, i++)
1803 {
1804 mark = m->data;
1805 values[i] = mark->value;
1806 }
1807
1808 _gtk_range_set_stop_values (GTK_RANGE (scale), values, n_values: n);
1809
1810 g_free (mem: values);
1811
1812 if (priv->top_marks_widget)
1813 gtk_widget_add_css_class (GTK_WIDGET (scale), css_class: "marks-before");
1814
1815 if (priv->bottom_marks_widget)
1816 gtk_widget_add_css_class (GTK_WIDGET (scale), css_class: "marks-after");
1817
1818 gtk_widget_queue_resize (widget);
1819}
1820
1821static GtkBuildableIface *parent_buildable_iface;
1822
1823static void
1824gtk_scale_buildable_interface_init (GtkBuildableIface *iface)
1825{
1826 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
1827 iface->custom_tag_start = gtk_scale_buildable_custom_tag_start;
1828 iface->custom_finished = gtk_scale_buildable_custom_finished;
1829}
1830
1831typedef struct
1832{
1833 GtkScale *scale;
1834 GtkBuilder *builder;
1835 GSList *marks;
1836} MarksSubparserData;
1837
1838typedef struct
1839{
1840 double value;
1841 GtkPositionType position;
1842 GString *markup;
1843 char *context;
1844 gboolean translatable;
1845} MarkData;
1846
1847static void
1848mark_data_free (MarkData *data)
1849{
1850 g_string_free (string: data->markup, TRUE);
1851 g_free (mem: data->context);
1852 g_slice_free (MarkData, data);
1853}
1854
1855static void
1856marks_start_element (GtkBuildableParseContext *context,
1857 const char *element_name,
1858 const char **names,
1859 const char **values,
1860 gpointer user_data,
1861 GError **error)
1862{
1863 MarksSubparserData *data = (MarksSubparserData*)user_data;
1864
1865 if (strcmp (s1: element_name, s2: "marks") == 0)
1866 {
1867 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "object", error))
1868 return;
1869
1870 if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error,
1871 first_type: G_MARKUP_COLLECT_INVALID, NULL, NULL,
1872 G_MARKUP_COLLECT_INVALID))
1873 _gtk_builder_prefix_error (builder: data->builder, context, error);
1874 }
1875 else if (strcmp (s1: element_name, s2: "mark") == 0)
1876 {
1877 const char *value_str;
1878 double value = 0;
1879 const char *position_str = NULL;
1880 GtkPositionType position = GTK_POS_BOTTOM;
1881 const char *msg_context = NULL;
1882 gboolean translatable = FALSE;
1883 MarkData *mark;
1884
1885 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "marks", error))
1886 return;
1887
1888 if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error,
1889 first_type: G_MARKUP_COLLECT_STRING, first_attr: "value", &value_str,
1890 G_MARKUP_COLLECT_BOOLEAN|G_MARKUP_COLLECT_OPTIONAL, "translatable", &translatable,
1891 G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "comments", NULL,
1892 G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "context", &msg_context,
1893 G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "position", &position_str,
1894 G_MARKUP_COLLECT_INVALID))
1895 {
1896 _gtk_builder_prefix_error (builder: data->builder, context, error);
1897 return;
1898 }
1899
1900 if (value_str != NULL)
1901 {
1902 GValue gvalue = G_VALUE_INIT;
1903
1904 if (!gtk_builder_value_from_string_type (builder: data->builder, G_TYPE_DOUBLE, string: value_str, value: &gvalue, error))
1905 {
1906 _gtk_builder_prefix_error (builder: data->builder, context, error);
1907 return;
1908 }
1909
1910 value = g_value_get_double (value: &gvalue);
1911 }
1912
1913 if (position_str != NULL)
1914 {
1915 GValue gvalue = G_VALUE_INIT;
1916
1917 if (!gtk_builder_value_from_string_type (builder: data->builder, type: GTK_TYPE_POSITION_TYPE, string: position_str, value: &gvalue, error))
1918 {
1919 _gtk_builder_prefix_error (builder: data->builder, context, error);
1920 return;
1921 }
1922
1923 position = g_value_get_enum (value: &gvalue);
1924 }
1925
1926 mark = g_slice_new (MarkData);
1927 mark->value = value;
1928 if (position == GTK_POS_LEFT || position == GTK_POS_TOP)
1929 mark->position = GTK_POS_TOP;
1930 else
1931 mark->position = GTK_POS_BOTTOM;
1932 mark->markup = g_string_new (init: "");
1933 mark->context = g_strdup (str: msg_context);
1934 mark->translatable = translatable;
1935
1936 data->marks = g_slist_prepend (list: data->marks, data: mark);
1937 }
1938 else
1939 {
1940 _gtk_builder_error_unhandled_tag (builder: data->builder, context,
1941 object: "GtkScale", element_name,
1942 error);
1943 }
1944}
1945
1946static void
1947marks_text (GtkBuildableParseContext *context,
1948 const char *text,
1949 gsize text_len,
1950 gpointer user_data,
1951 GError **error)
1952{
1953 MarksSubparserData *data = (MarksSubparserData*)user_data;
1954
1955 if (strcmp (s1: gtk_buildable_parse_context_get_element (context), s2: "mark") == 0)
1956 {
1957 MarkData *mark = data->marks->data;
1958
1959 g_string_append_len (string: mark->markup, val: text, len: text_len);
1960 }
1961}
1962
1963static const GtkBuildableParser marks_parser =
1964 {
1965 marks_start_element,
1966 NULL,
1967 marks_text,
1968 };
1969
1970
1971static gboolean
1972gtk_scale_buildable_custom_tag_start (GtkBuildable *buildable,
1973 GtkBuilder *builder,
1974 GObject *child,
1975 const char *tagname,
1976 GtkBuildableParser *parser,
1977 gpointer *parser_data)
1978{
1979 MarksSubparserData *data;
1980
1981 if (child)
1982 return FALSE;
1983
1984 if (strcmp (s1: tagname, s2: "marks") == 0)
1985 {
1986 data = g_slice_new0 (MarksSubparserData);
1987 data->scale = GTK_SCALE (buildable);
1988 data->builder = builder;
1989 data->marks = NULL;
1990
1991 *parser = marks_parser;
1992 *parser_data = data;
1993
1994 return TRUE;
1995 }
1996
1997 return parent_buildable_iface->custom_tag_start (buildable, builder, child,
1998 tagname, parser, parser_data);
1999}
2000
2001static void
2002gtk_scale_buildable_custom_finished (GtkBuildable *buildable,
2003 GtkBuilder *builder,
2004 GObject *child,
2005 const char *tagname,
2006 gpointer user_data)
2007{
2008 GtkScale *scale = GTK_SCALE (buildable);
2009 MarksSubparserData *marks_data;
2010
2011 if (strcmp (s1: tagname, s2: "marks") == 0)
2012 {
2013 GSList *m;
2014 const char *markup;
2015
2016 marks_data = (MarksSubparserData *)user_data;
2017
2018 for (m = marks_data->marks; m; m = m->next)
2019 {
2020 MarkData *mdata = m->data;
2021
2022 if (mdata->translatable && mdata->markup->len)
2023 markup = _gtk_builder_parser_translate (domain: gtk_builder_get_translation_domain (builder),
2024 context: mdata->context,
2025 text: mdata->markup->str);
2026 else
2027 markup = mdata->markup->str;
2028
2029 gtk_scale_add_mark (scale, value: mdata->value, position: mdata->position, markup);
2030
2031 mark_data_free (data: mdata);
2032 }
2033
2034 g_slist_free (list: marks_data->marks);
2035 g_slice_free (MarksSubparserData, marks_data);
2036 }
2037 else
2038 {
2039 parent_buildable_iface->custom_finished (buildable, builder, child,
2040 tagname, user_data);
2041 }
2042
2043}
2044
2045/**
2046 * gtk_scale_set_format_value_func:
2047 * @scale: a `GtkScale`
2048 * @func: (nullable): function that formats the value
2049 * @user_data: (closure): user data to pass to @func
2050 * @destroy_notify: (nullable): destroy function for @user_data
2051 *
2052 * @func allows you to change how the scale value is displayed.
2053 *
2054 * The given function will return an allocated string representing
2055 * @value. That string will then be used to display the scale's value.
2056 *
2057 * If #NULL is passed as @func, the value will be displayed on
2058 * its own, rounded according to the value of the
2059 * [property@GtkScale:digits] property.
2060 */
2061void
2062gtk_scale_set_format_value_func (GtkScale *scale,
2063 GtkScaleFormatValueFunc func,
2064 gpointer user_data,
2065 GDestroyNotify destroy_notify)
2066{
2067 GtkScalePrivate *priv = gtk_scale_get_instance_private (self: scale);
2068 GtkAdjustment *adjustment;
2069 char *text;
2070
2071 g_return_if_fail (GTK_IS_SCALE (scale));
2072
2073 if (priv->format_value_func_destroy_notify)
2074 priv->format_value_func_destroy_notify (priv->format_value_func_user_data);
2075
2076 priv->format_value_func = func;
2077 priv->format_value_func_user_data = user_data;
2078 priv->format_value_func_destroy_notify = destroy_notify;
2079
2080 if (!priv->value_widget)
2081 return;
2082
2083 update_label_request (scale);
2084
2085 adjustment = gtk_range_get_adjustment (GTK_RANGE (scale));
2086 text = gtk_scale_format_value (scale,
2087 value: gtk_adjustment_get_value (adjustment));
2088 gtk_label_set_label (GTK_LABEL (priv->value_widget), str: text);
2089
2090 g_free (mem: text);
2091}
2092

source code of gtk/gtk/gtkscale.c