1/* GTK - The GIMP Toolkit
2 * Copyright © 2012 Red Hat, Inc.
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 *
17 * Author: Cosimo Cecchi <cosimoc@gnome.org>
18 *
19 */
20
21/**
22 * GtkLevelBar:
23 *
24 * `GtkLevelBar` is a widget that can be used as a level indicator.
25 *
26 * Typical use cases are displaying the strength of a password, or
27 * showing the charge level of a battery.
28 *
29 * ![An example GtkLevelBar](levelbar.png)
30 *
31 * Use [method@Gtk.LevelBar.set_value] to set the current value, and
32 * [method@Gtk.LevelBar.add_offset_value] to set the value offsets at which
33 * the bar will be considered in a different state. GTK will add a few
34 * offsets by default on the level bar: %GTK_LEVEL_BAR_OFFSET_LOW,
35 * %GTK_LEVEL_BAR_OFFSET_HIGH and %GTK_LEVEL_BAR_OFFSET_FULL, with
36 * values 0.25, 0.75 and 1.0 respectively.
37 *
38 * Note that it is your responsibility to update preexisting offsets
39 * when changing the minimum or maximum value. GTK will simply clamp
40 * them to the new range.
41 *
42 * ## Adding a custom offset on the bar
43 *
44 * ```c
45 * static GtkWidget *
46 * create_level_bar (void)
47 * {
48 * GtkWidget *widget;
49 * GtkLevelBar *bar;
50 *
51 * widget = gtk_level_bar_new ();
52 * bar = GTK_LEVEL_BAR (widget);
53 *
54 * // This changes the value of the default low offset
55 *
56 * gtk_level_bar_add_offset_value (bar,
57 * GTK_LEVEL_BAR_OFFSET_LOW,
58 * 0.10);
59 *
60 * // This adds a new offset to the bar; the application will
61 * // be able to change its color CSS like this:
62 * //
63 * // levelbar block.my-offset {
64 * // background-color: magenta;
65 * // border-style: solid;
66 * // border-color: black;
67 * // border-style: 1px;
68 * // }
69 *
70 * gtk_level_bar_add_offset_value (bar, "my-offset", 0.60);
71 *
72 * return widget;
73 * }
74 * ```
75 *
76 * The default interval of values is between zero and one, but it’s possible
77 * to modify the interval using [method@Gtk.LevelBar.set_min_value] and
78 * [method@Gtk.LevelBar.set_max_value]. The value will be always drawn in
79 * proportion to the admissible interval, i.e. a value of 15 with a specified
80 * interval between 10 and 20 is equivalent to a value of 0.5 with an interval
81 * between 0 and 1. When %GTK_LEVEL_BAR_MODE_DISCRETE is used, the bar level
82 * is rendered as a finite number of separated blocks instead of a single one.
83 * The number of blocks that will be rendered is equal to the number of units
84 * specified by the admissible interval.
85 *
86 * For instance, to build a bar rendered with five blocks, it’s sufficient to
87 * set the minimum value to 0 and the maximum value to 5 after changing the
88 * indicator mode to discrete.
89 *
90 * # GtkLevelBar as GtkBuildable
91 *
92 * The `GtkLevelBar` implementation of the `GtkBuildable` interface supports a
93 * custom <offsets> element, which can contain any number of <offset> elements,
94 * each of which must have name and value attributes.
95 *
96 * # CSS nodes
97 *
98 * ```
99 * levelbar[.discrete]
100 * ╰── trough
101 * ├── block.filled.level-name
102 * ┊
103 * ├── block.empty
104 * ┊
105 * ```
106 *
107 * `GtkLevelBar` has a main CSS node with name levelbar and one of the style
108 * classes .discrete or .continuous and a subnode with name trough. Below the
109 * trough node are a number of nodes with name block and style class .filled
110 * or .empty. In continuous mode, there is exactly one node of each, in discrete
111 * mode, the number of filled and unfilled nodes corresponds to blocks that are
112 * drawn. The block.filled nodes also get a style class .level-name corresponding
113 * to the level for the current value.
114 *
115 * In horizontal orientation, the nodes are always arranged from left to right,
116 * regardless of text direction.
117 *
118 * # Accessibility
119 *
120 * `GtkLevelBar` uses the %GTK_ACCESSIBLE_ROLE_METER role.
121 */
122#include "config.h"
123
124#include "gtkbinlayout.h"
125#include "gtkbuildable.h"
126#include "gtkbuilderprivate.h"
127#include "gtkgizmoprivate.h"
128#include "gtkintl.h"
129#include "gtklevelbar.h"
130#include "gtkmarshalers.h"
131#include "gtkorientable.h"
132#include "gtkcssnodeprivate.h"
133#include "gtktypebuiltins.h"
134#include "gtkwidgetprivate.h"
135
136#include <math.h>
137#include <stdlib.h>
138
139enum {
140 PROP_VALUE = 1,
141 PROP_MIN_VALUE,
142 PROP_MAX_VALUE,
143 PROP_MODE,
144 PROP_INVERTED,
145 LAST_PROPERTY,
146 PROP_ORIENTATION /* overridden */
147};
148
149enum {
150 SIGNAL_OFFSET_CHANGED,
151 NUM_SIGNALS
152};
153
154static GParamSpec *properties[LAST_PROPERTY] = { NULL, };
155static guint signals[NUM_SIGNALS] = { 0, };
156
157typedef struct _GtkLevelBarClass GtkLevelBarClass;
158
159typedef struct {
160 char *name;
161 double value;
162} GtkLevelBarOffset;
163
164struct _GtkLevelBar {
165 GtkWidget parent_instance;
166
167 GtkOrientation orientation;
168
169 GtkLevelBarMode bar_mode;
170
171 double min_value;
172 double max_value;
173 double cur_value;
174
175 GList *offsets;
176
177 GtkWidget *trough_widget;
178 GtkWidget **block_widget;
179 guint n_blocks;
180
181 guint inverted : 1;
182};
183
184struct _GtkLevelBarClass {
185 GtkWidgetClass parent_class;
186
187 void (* offset_changed) (GtkLevelBar *self,
188 const char *name);
189};
190
191static void gtk_level_bar_set_value_internal (GtkLevelBar *self,
192 double value);
193
194static void gtk_level_bar_buildable_init (GtkBuildableIface *iface);
195
196G_DEFINE_TYPE_WITH_CODE (GtkLevelBar, gtk_level_bar, GTK_TYPE_WIDGET,
197 G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
198 G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
199 gtk_level_bar_buildable_init))
200
201static GtkLevelBarOffset *
202gtk_level_bar_offset_new (const char *name,
203 double value)
204{
205 GtkLevelBarOffset *offset = g_slice_new0 (GtkLevelBarOffset);
206
207 offset->name = g_strdup (str: name);
208 offset->value = value;
209
210 return offset;
211}
212
213static void
214gtk_level_bar_offset_free (GtkLevelBarOffset *offset)
215{
216 g_free (mem: offset->name);
217 g_slice_free (GtkLevelBarOffset, offset);
218}
219
220static int
221offset_find_func (gconstpointer data,
222 gconstpointer user_data)
223{
224 const GtkLevelBarOffset *offset = data;
225 const char *name = user_data;
226
227 return g_strcmp0 (str1: name, str2: offset->name);
228}
229
230static int
231offset_sort_func (gconstpointer a,
232 gconstpointer b)
233{
234 const GtkLevelBarOffset *offset_a = a;
235 const GtkLevelBarOffset *offset_b = b;
236
237 return (offset_a->value > offset_b->value);
238}
239
240static gboolean
241gtk_level_bar_ensure_offset (GtkLevelBar *self,
242 const char *name,
243 double value)
244{
245 GList *existing;
246 GtkLevelBarOffset *offset = NULL;
247 GtkLevelBarOffset *new_offset;
248
249 existing = g_list_find_custom (list: self->offsets, data: name, func: offset_find_func);
250 if (existing)
251 offset = existing->data;
252
253 if (offset && (offset->value == value))
254 return FALSE;
255
256 new_offset = gtk_level_bar_offset_new (name, value);
257
258 if (offset)
259 {
260 gtk_level_bar_offset_free (offset);
261 self->offsets = g_list_delete_link (list: self->offsets, link_: existing);
262 }
263
264 self->offsets = g_list_insert_sorted (list: self->offsets, data: new_offset, func: offset_sort_func);
265
266 return TRUE;
267}
268
269#ifndef G_DISABLE_CHECKS
270static gboolean
271gtk_level_bar_value_in_interval (GtkLevelBar *self,
272 double value)
273{
274 return ((value >= self->min_value) &&
275 (value <= self->max_value));
276}
277#endif
278
279static int
280gtk_level_bar_get_num_blocks (GtkLevelBar *self)
281{
282 if (self->bar_mode == GTK_LEVEL_BAR_MODE_CONTINUOUS)
283 return 1;
284 else if (self->bar_mode == GTK_LEVEL_BAR_MODE_DISCRETE)
285 return MAX (1, (int) (round (self->max_value) - round (self->min_value)));
286
287 return 0;
288}
289
290static int
291gtk_level_bar_get_num_block_nodes (GtkLevelBar *self)
292{
293 if (self->bar_mode == GTK_LEVEL_BAR_MODE_CONTINUOUS)
294 return 2;
295 else
296 return gtk_level_bar_get_num_blocks (self);
297}
298
299static void
300gtk_level_bar_get_min_block_size (GtkLevelBar *self,
301 int *block_width,
302 int *block_height)
303{
304 guint i, n_blocks;
305 int width, height;
306
307 *block_width = *block_height = 0;
308 n_blocks = gtk_level_bar_get_num_block_nodes (self);
309
310 for (i = 0; i < n_blocks; i++)
311 {
312 gtk_widget_measure (widget: self->block_widget[i],
313 orientation: GTK_ORIENTATION_HORIZONTAL,
314 for_size: -1,
315 minimum: &width, NULL,
316 NULL, NULL);
317 gtk_widget_measure (widget: self->block_widget[i],
318 orientation: GTK_ORIENTATION_VERTICAL,
319 for_size: -1,
320 minimum: &height, NULL,
321 NULL, NULL);
322
323 *block_width = MAX (width, *block_width);
324 *block_height = MAX (height, *block_height);
325 }
326}
327
328static gboolean
329gtk_level_bar_get_real_inverted (GtkLevelBar *self)
330{
331 if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL &&
332 self->orientation == GTK_ORIENTATION_HORIZONTAL)
333 return !self->inverted;
334
335 return self->inverted;
336}
337
338static void
339gtk_level_bar_render_trough (GtkGizmo *gizmo,
340 GtkSnapshot *snapshot)
341{
342 GtkWidget *widget = GTK_WIDGET (gizmo);
343 GtkLevelBar *self = GTK_LEVEL_BAR (gtk_widget_get_parent (GTK_WIDGET (gizmo)));
344
345 if (self->bar_mode == GTK_LEVEL_BAR_MODE_CONTINUOUS)
346 {
347 gboolean inverted;
348
349 inverted = gtk_level_bar_get_real_inverted (self);
350
351 /* render the empty (unfilled) part */
352 gtk_widget_snapshot_child (widget, child: self->block_widget[inverted ? 0 : 1], snapshot);
353
354 /* now render the filled part on top of it */
355 if (self->cur_value != 0)
356 gtk_widget_snapshot_child (widget, child: self->block_widget[inverted ? 1 : 0], snapshot);
357 }
358 else
359 {
360 int num_blocks, i;
361
362 num_blocks = gtk_level_bar_get_num_blocks (self);
363
364 for (i = 0; i < num_blocks; i++)
365 gtk_widget_snapshot_child (widget, child: self->block_widget[i], snapshot);
366 }
367}
368
369static void
370gtk_level_bar_measure_trough (GtkGizmo *gizmo,
371 GtkOrientation orientation,
372 int for_size,
373 int *minimum,
374 int *natural,
375 int *minimum_baseline,
376 int *natural_baseline)
377{
378 GtkWidget *widget = GTK_WIDGET (gizmo);
379 GtkLevelBar *self = GTK_LEVEL_BAR (gtk_widget_get_parent (widget));
380 int num_blocks, size;
381 int block_width, block_height;
382
383 num_blocks = gtk_level_bar_get_num_blocks (self);
384 gtk_level_bar_get_min_block_size (self, block_width: &block_width, block_height: &block_height);
385
386 if (orientation == GTK_ORIENTATION_HORIZONTAL)
387 {
388 if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
389 size = num_blocks * block_width;
390 else
391 size = block_width;
392 }
393 else
394 {
395 if (self->orientation == GTK_ORIENTATION_VERTICAL)
396 size = num_blocks * block_height;
397 else
398 size = block_height;
399 }
400
401 *minimum = size;
402 *natural = size;
403}
404
405static void
406gtk_level_bar_allocate_trough_continuous (GtkLevelBar *self,
407 int width,
408 int height,
409 int baseline)
410{
411 GtkAllocation block_area;
412 double fill_percentage;
413 gboolean inverted;
414 int block_min;
415
416 inverted = gtk_level_bar_get_real_inverted (self);
417
418 /* allocate the empty (unfilled) part */
419 gtk_widget_size_allocate (widget: self->block_widget[inverted ? 0 : 1],
420 allocation: &(GtkAllocation) {0, 0, width, height},
421 baseline);
422
423 if (self->cur_value == 0)
424 return;
425
426 /* now allocate the filled part */
427 block_area = (GtkAllocation) {0, 0, width, height};
428 fill_percentage = (self->cur_value - self->min_value) /
429 (self->max_value - self->min_value);
430
431 gtk_widget_measure (widget: self->block_widget[inverted ? 1 : 0],
432 orientation: self->orientation, for_size: -1,
433 minimum: &block_min, NULL,
434 NULL, NULL);
435
436 if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
437 {
438 block_area.width = (int) floor (x: block_area.width * fill_percentage);
439 block_area.width = MAX (block_area.width, block_min);
440
441 if (inverted)
442 block_area.x += width - block_area.width;
443 }
444 else
445 {
446 block_area.height = (int) floor (x: block_area.height * fill_percentage);
447 block_area.height = MAX (block_area.height, block_min);
448
449 if (inverted)
450 block_area.y += height - block_area.height;
451 }
452
453 gtk_widget_size_allocate (widget: self->block_widget[inverted ? 1 : 0],
454 allocation: &block_area,
455 baseline);
456}
457
458static void
459gtk_level_bar_allocate_trough_discrete (GtkLevelBar *self,
460 int width,
461 int height,
462 int baseline)
463{
464 GtkAllocation block_area;
465 int num_blocks, i;
466 int block_width, block_height;
467 int extra_space;
468
469 gtk_level_bar_get_min_block_size (self, block_width: &block_width, block_height: &block_height);
470 num_blocks = gtk_level_bar_get_num_blocks (self);
471
472 if (num_blocks == 0)
473 return;
474
475 if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
476 {
477 block_width = MAX (block_width, (int) floor ((double) width / num_blocks));
478 block_height = height;
479 extra_space = width - block_width * num_blocks;
480
481 if (extra_space > 0)
482 block_width++;
483 }
484 else
485 {
486 block_width = width;
487 block_height = MAX (block_height, (int) floor ((double) height / num_blocks));
488 extra_space = height - block_height * num_blocks;
489
490 if (extra_space > 0)
491 block_height++;
492 }
493
494 block_area.x = 0;
495 block_area.y = 0;
496 block_area.width = block_width;
497 block_area.height = block_height;
498
499 for (i = 0; i < num_blocks; i++)
500 {
501 if (extra_space > 0 && i == extra_space)
502 {
503 if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
504 block_area.width--;
505 else
506 block_area.height--;
507 }
508
509 gtk_widget_size_allocate (widget: self->block_widget[i],
510 allocation: &block_area,
511 baseline);
512
513 if (self->orientation == GTK_ORIENTATION_HORIZONTAL)
514 block_area.x += block_area.width;
515 else
516 block_area.y += block_area.height;
517 }
518}
519
520static void
521gtk_level_bar_allocate_trough (GtkGizmo *gizmo,
522 int width,
523 int height,
524 int baseline)
525{
526 GtkWidget *widget = GTK_WIDGET (gizmo);
527 GtkLevelBar *self = GTK_LEVEL_BAR (gtk_widget_get_parent (widget));
528
529 if (self->bar_mode == GTK_LEVEL_BAR_MODE_CONTINUOUS)
530 gtk_level_bar_allocate_trough_continuous (self, width, height, baseline);
531 else
532 gtk_level_bar_allocate_trough_discrete (self, width, height, baseline);
533}
534
535static void
536update_block_nodes (GtkLevelBar *self)
537{
538 guint n_blocks;
539 guint i;
540
541 n_blocks = gtk_level_bar_get_num_block_nodes (self);
542
543 if (self->n_blocks == n_blocks)
544 return;
545 else if (n_blocks < self->n_blocks)
546 {
547 for (i = n_blocks; i < self->n_blocks; i++)
548 {
549 gtk_widget_unparent (widget: self->block_widget[i]);
550 }
551 self->block_widget = g_renew (GtkWidget*, self->block_widget, n_blocks);
552 self->n_blocks = n_blocks;
553 }
554 else
555 {
556 self->block_widget = g_renew (GtkWidget*, self->block_widget, n_blocks);
557 for (i = self->n_blocks; i < n_blocks; i++)
558 {
559 self->block_widget[i] = gtk_gizmo_new_with_role (css_name: "block",
560 role: GTK_ACCESSIBLE_ROLE_NONE,
561 NULL, NULL, NULL, NULL, NULL, NULL);
562 gtk_widget_insert_before (widget: self->block_widget[i], GTK_WIDGET (self->trough_widget), NULL);
563 }
564 self->n_blocks = n_blocks;
565 }
566}
567
568static void
569update_mode_style_classes (GtkLevelBar *self)
570{
571 GtkCssNode *widget_node;
572
573 widget_node = gtk_widget_get_css_node (GTK_WIDGET (self));
574 if (self->bar_mode == GTK_LEVEL_BAR_MODE_CONTINUOUS)
575 {
576 gtk_css_node_remove_class (cssnode: widget_node, style_class: g_quark_from_static_string (string: "discrete"));
577 gtk_css_node_add_class (cssnode: widget_node, style_class: g_quark_from_static_string (string: "continuous"));
578 }
579 else if (self->bar_mode == GTK_LEVEL_BAR_MODE_DISCRETE)
580 {
581 gtk_css_node_add_class (cssnode: widget_node, style_class: g_quark_from_static_string (string: "discrete"));
582 gtk_css_node_remove_class (cssnode: widget_node, style_class: g_quark_from_static_string (string: "continuous"));
583 }
584}
585
586static void
587update_level_style_classes (GtkLevelBar *self)
588{
589 double value;
590 const char *value_class = NULL;
591 GtkLevelBarOffset *offset, *prev_offset;
592 GList *l;
593 int num_filled, num_blocks, i;
594 gboolean inverted;
595
596 value = gtk_level_bar_get_value (self);
597
598 for (l = self->offsets; l != NULL; l = l->next)
599 {
600 offset = l->data;
601
602 /* find the right offset for our style class */
603 if (value <= offset->value)
604 {
605 if (l->prev == NULL)
606 {
607 value_class = offset->name;
608 }
609 else
610 {
611 prev_offset = l->prev->data;
612 if (prev_offset->value < value)
613 value_class = offset->name;
614 }
615 }
616
617 if (value_class)
618 break;
619 }
620
621 inverted = gtk_level_bar_get_real_inverted (self);
622 num_blocks = gtk_level_bar_get_num_block_nodes (self);
623
624 if (self->bar_mode == GTK_LEVEL_BAR_MODE_CONTINUOUS)
625 num_filled = 1;
626 else
627 num_filled = MIN (num_blocks, (int) round (self->cur_value) - (int) round (self->min_value));
628
629 for (i = 0; i < num_filled; i++)
630 {
631 GtkCssNode *node = gtk_widget_get_css_node (widget: self->block_widget[inverted ? num_blocks - 1 - i : i]);
632
633 gtk_css_node_set_classes (cssnode: node, NULL);
634 gtk_css_node_add_class (cssnode: node, style_class: g_quark_from_static_string (string: "filled"));
635
636 if (value_class)
637 gtk_css_node_add_class (cssnode: node, style_class: g_quark_from_string (string: value_class));
638 }
639
640 for (; i < num_blocks; i++)
641 {
642 GtkCssNode *node = gtk_widget_get_css_node (widget: self->block_widget[inverted ? num_blocks - 1 - i : i]);
643
644 gtk_css_node_set_classes (cssnode: node, NULL);
645 gtk_css_node_add_class (cssnode: node, style_class: g_quark_from_static_string (string: "empty"));
646 }
647}
648
649static void
650gtk_level_bar_direction_changed (GtkWidget *widget,
651 GtkTextDirection previous_dir)
652{
653 GtkLevelBar *self = GTK_LEVEL_BAR (widget);
654
655 update_level_style_classes (self);
656
657 GTK_WIDGET_CLASS (gtk_level_bar_parent_class)->direction_changed (widget, previous_dir);
658}
659
660static void
661gtk_level_bar_ensure_offsets_in_range (GtkLevelBar *self)
662{
663 GtkLevelBarOffset *offset;
664 GList *l = self->offsets;
665
666 while (l != NULL)
667 {
668 offset = l->data;
669 l = l->next;
670
671 if (offset->value < self->min_value)
672 gtk_level_bar_ensure_offset (self, name: offset->name, value: self->min_value);
673 else if (offset->value > self->max_value)
674 gtk_level_bar_ensure_offset (self, name: offset->name, value: self->max_value);
675 }
676}
677
678typedef struct {
679 GtkLevelBar *self;
680 GtkBuilder *builder;
681 GList *offsets;
682} OffsetsParserData;
683
684static void
685offset_start_element (GtkBuildableParseContext *context,
686 const char *element_name,
687 const char **names,
688 const char **values,
689 gpointer user_data,
690 GError **error)
691{
692 OffsetsParserData *data = user_data;
693
694 if (strcmp (s1: element_name, s2: "offsets") == 0)
695 {
696 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "object", error))
697 return;
698
699 if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error,
700 first_type: G_MARKUP_COLLECT_INVALID, NULL, NULL,
701 G_MARKUP_COLLECT_INVALID))
702 _gtk_builder_prefix_error (builder: data->builder, context, error);
703 }
704 else if (strcmp (s1: element_name, s2: "offset") == 0)
705 {
706 const char *name;
707 const char *value;
708 GValue gvalue = G_VALUE_INIT;
709 GtkLevelBarOffset *offset;
710
711 if (!_gtk_builder_check_parent (builder: data->builder, context, parent_name: "offsets", error))
712 return;
713
714 if (!g_markup_collect_attributes (element_name, attribute_names: names, attribute_values: values, error,
715 first_type: G_MARKUP_COLLECT_STRING, first_attr: "name", &name,
716 G_MARKUP_COLLECT_STRING, "value", &value,
717 G_MARKUP_COLLECT_INVALID))
718 {
719 _gtk_builder_prefix_error (builder: data->builder, context, error);
720 return;
721 }
722
723 if (!gtk_builder_value_from_string_type (builder: data->builder, G_TYPE_DOUBLE, string: value, value: &gvalue, error))
724 {
725 _gtk_builder_prefix_error (builder: data->builder, context, error);
726 return;
727 }
728
729 offset = gtk_level_bar_offset_new (name, value: g_value_get_double (value: &gvalue));
730 data->offsets = g_list_prepend (list: data->offsets, data: offset);
731 }
732 else
733 {
734 _gtk_builder_error_unhandled_tag (builder: data->builder, context,
735 object: "GtkLevelBar", element_name,
736 error);
737 }
738}
739
740static const GtkBuildableParser offset_parser =
741{
742 offset_start_element
743};
744
745static GtkBuildableIface *parent_buildable_iface;
746
747static gboolean
748gtk_level_bar_buildable_custom_tag_start (GtkBuildable *buildable,
749 GtkBuilder *builder,
750 GObject *child,
751 const char *tagname,
752 GtkBuildableParser *parser,
753 gpointer *parser_data)
754{
755 OffsetsParserData *data;
756
757 if (parent_buildable_iface->custom_tag_start (buildable, builder, child,
758 tagname, parser, parser_data))
759 return TRUE;
760
761 if (child)
762 return FALSE;
763
764 if (strcmp (s1: tagname, s2: "offsets") != 0)
765 return FALSE;
766
767 data = g_slice_new0 (OffsetsParserData);
768 data->self = GTK_LEVEL_BAR (buildable);
769 data->builder = builder;
770 data->offsets = NULL;
771
772 *parser = offset_parser;
773 *parser_data = data;
774
775 return TRUE;
776}
777
778static void
779gtk_level_bar_buildable_custom_finished (GtkBuildable *buildable,
780 GtkBuilder *builder,
781 GObject *child,
782 const char *tagname,
783 gpointer user_data)
784{
785 OffsetsParserData *data = user_data;
786 GtkLevelBar *self;
787 GtkLevelBarOffset *offset;
788 GList *l;
789
790 self = data->self;
791
792 if (strcmp (s1: tagname, s2: "offsets") != 0)
793 {
794 parent_buildable_iface->custom_finished (buildable, builder, child,
795 tagname, user_data);
796 return;
797 }
798
799 for (l = data->offsets; l != NULL; l = l->next)
800 {
801 offset = l->data;
802 gtk_level_bar_add_offset_value (self, name: offset->name, value: offset->value);
803 }
804
805 g_list_free_full (list: data->offsets, free_func: (GDestroyNotify) gtk_level_bar_offset_free);
806 g_slice_free (OffsetsParserData, data);
807}
808
809static void
810gtk_level_bar_buildable_init (GtkBuildableIface *iface)
811{
812 parent_buildable_iface = g_type_interface_peek_parent (g_iface: iface);
813
814 iface->custom_tag_start = gtk_level_bar_buildable_custom_tag_start;
815 iface->custom_finished = gtk_level_bar_buildable_custom_finished;
816}
817
818static void
819gtk_level_bar_set_orientation (GtkLevelBar *self,
820 GtkOrientation orientation)
821{
822 if (self->orientation != orientation)
823 {
824 self->orientation = orientation;
825 gtk_widget_update_orientation (GTK_WIDGET (self), orientation: self->orientation);
826 gtk_widget_queue_resize (GTK_WIDGET (self));
827 g_object_notify (G_OBJECT (self), property_name: "orientation");
828 }
829}
830
831static void
832gtk_level_bar_get_property (GObject *obj,
833 guint property_id,
834 GValue *value,
835 GParamSpec *pspec)
836{
837 GtkLevelBar *self = GTK_LEVEL_BAR (obj);
838
839 switch (property_id)
840 {
841 case PROP_VALUE:
842 g_value_set_double (value, v_double: gtk_level_bar_get_value (self));
843 break;
844 case PROP_MIN_VALUE:
845 g_value_set_double (value, v_double: gtk_level_bar_get_min_value (self));
846 break;
847 case PROP_MAX_VALUE:
848 g_value_set_double (value, v_double: gtk_level_bar_get_max_value (self));
849 break;
850 case PROP_MODE:
851 g_value_set_enum (value, v_enum: gtk_level_bar_get_mode (self));
852 break;
853 case PROP_INVERTED:
854 g_value_set_boolean (value, v_boolean: gtk_level_bar_get_inverted (self));
855 break;
856 case PROP_ORIENTATION:
857 g_value_set_enum (value, v_enum: self->orientation);
858 break;
859 default:
860 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
861 break;
862 }
863}
864
865static void
866gtk_level_bar_set_property (GObject *obj,
867 guint property_id,
868 const GValue *value,
869 GParamSpec *pspec)
870{
871 GtkLevelBar *self = GTK_LEVEL_BAR (obj);
872
873 switch (property_id)
874 {
875 case PROP_VALUE:
876 gtk_level_bar_set_value (self, value: g_value_get_double (value));
877 break;
878 case PROP_MIN_VALUE:
879 gtk_level_bar_set_min_value (self, value: g_value_get_double (value));
880 break;
881 case PROP_MAX_VALUE:
882 gtk_level_bar_set_max_value (self, value: g_value_get_double (value));
883 break;
884 case PROP_MODE:
885 gtk_level_bar_set_mode (self, mode: g_value_get_enum (value));
886 break;
887 case PROP_INVERTED:
888 gtk_level_bar_set_inverted (self, inverted: g_value_get_boolean (value));
889 break;
890 case PROP_ORIENTATION:
891 gtk_level_bar_set_orientation (self, orientation: g_value_get_enum (value));
892 break;
893 default:
894 G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
895 break;
896 }
897}
898
899static void
900gtk_level_bar_finalize (GObject *obj)
901{
902 GtkLevelBar *self = GTK_LEVEL_BAR (obj);
903 int i;
904
905 g_list_free_full (list: self->offsets, free_func: (GDestroyNotify) gtk_level_bar_offset_free);
906
907 for (i = 0; i < self->n_blocks; i++)
908 gtk_widget_unparent (widget: self->block_widget[i]);
909
910 g_free (mem: self->block_widget);
911
912 gtk_widget_unparent (widget: self->trough_widget);
913
914 G_OBJECT_CLASS (gtk_level_bar_parent_class)->finalize (obj);
915}
916
917static void
918gtk_level_bar_class_init (GtkLevelBarClass *klass)
919{
920 GObjectClass *oclass = G_OBJECT_CLASS (klass);
921 GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
922
923 oclass->get_property = gtk_level_bar_get_property;
924 oclass->set_property = gtk_level_bar_set_property;
925 oclass->finalize = gtk_level_bar_finalize;
926
927 wclass->direction_changed = gtk_level_bar_direction_changed;
928
929 g_object_class_override_property (oclass, property_id: PROP_ORIENTATION, name: "orientation");
930
931 /**
932 * GtkLevelBar::offset-changed:
933 * @self: a `GtkLevelBar`
934 * @name: the name of the offset that changed value
935 *
936 * Emitted when an offset specified on the bar changes value.
937 *
938 * This typically is the result of a [method@Gtk.LevelBar.add_offset_value]
939 * call.
940 *
941 * The signal supports detailed connections; you can connect to the
942 * detailed signal "changed::x" in order to only receive callbacks when
943 * the value of offset "x" changes.
944 */
945 signals[SIGNAL_OFFSET_CHANGED] =
946 g_signal_new (I_("offset-changed"),
947 GTK_TYPE_LEVEL_BAR,
948 signal_flags: G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
949 G_STRUCT_OFFSET (GtkLevelBarClass, offset_changed),
950 NULL, NULL,
951 NULL,
952 G_TYPE_NONE,
953 n_params: 1, G_TYPE_STRING);
954
955 /**
956 * GtkLevelBar:value: (attributes org.gtk.Property.get=gtk_level_bar_get_value org.gtk.Property.set=gtk_level_bar_set_value)
957 *
958 * Determines the currently filled value of the level bar.
959 */
960 properties[PROP_VALUE] =
961 g_param_spec_double (name: "value",
962 P_("Currently filled value level"),
963 P_("Currently filled value level of the level bar"),
964 minimum: 0.0, G_MAXDOUBLE, default_value: 0.0,
965 flags: G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY);
966
967 /**
968 * GtkLevelBar:min-value: (attributes org.gtk.Property.get=gtk_level_bar_get_min_value org.gtk.Property.set=gtk_level_bar_set_min_value)
969 *
970 * Determines the minimum value of the interval that can be displayed by the bar.
971 */
972 properties[PROP_MIN_VALUE] =
973 g_param_spec_double (name: "min-value",
974 P_("Minimum value level for the bar"),
975 P_("Minimum value level that can be displayed by the bar"),
976 minimum: 0.0, G_MAXDOUBLE, default_value: 0.0,
977 flags: G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY);
978
979 /**
980 * GtkLevelBar:max-value: (attributes org.gtk.Property.get=gtk_level_bar_get_max_value org.gtk.Property.set=gtk_level_bar_set_max_value)
981 *
982 * Determines the maximum value of the interval that can be displayed by the bar.
983 */
984 properties[PROP_MAX_VALUE] =
985 g_param_spec_double (name: "max-value",
986 P_("Maximum value level for the bar"),
987 P_("Maximum value level that can be displayed by the bar"),
988 minimum: 0.0, G_MAXDOUBLE, default_value: 1.0,
989 flags: G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY);
990
991 /**
992 * GtkLevelBar:mode: (attributes org.gtk.Property.get=gtk_level_bar_get_mode org.gtk.Property.set=gtk_level_bar_set_mode)
993 *
994 * Determines the way `GtkLevelBar` interprets the value properties to draw the
995 * level fill area.
996 *
997 * Specifically, when the value is %GTK_LEVEL_BAR_MODE_CONTINUOUS,
998 * `GtkLevelBar` will draw a single block representing the current value in
999 * that area; when the value is %GTK_LEVEL_BAR_MODE_DISCRETE,
1000 * the widget will draw a succession of separate blocks filling the
1001 * draw area, with the number of blocks being equal to the units separating
1002 * the integral roundings of [property@Gtk.LevelBar:min-value] and
1003 * [property@Gtk.LevelBar:max-value].
1004 */
1005 properties[PROP_MODE] =
1006 g_param_spec_enum (name: "mode",
1007 P_("The mode of the value indicator"),
1008 P_("The mode of the value indicator displayed by the bar"),
1009 enum_type: GTK_TYPE_LEVEL_BAR_MODE,
1010 default_value: GTK_LEVEL_BAR_MODE_CONTINUOUS,
1011 flags: G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY);
1012
1013 /**
1014 * GtkLevelBar:inverted: (attributes org.gtk.Property.get=gtk_level_bar_get_inverted org.gtk.Property.set=gtk_level_bar_set_inverted)
1015 *
1016 * Whether the `GtkLeveBar` is inverted.
1017 *
1018 * Level bars normally grow from top to bottom or left to right.
1019 * Inverted level bars grow in the opposite direction.
1020 */
1021 properties[PROP_INVERTED] =
1022 g_param_spec_boolean (name: "inverted",
1023 P_("Inverted"),
1024 P_("Invert the direction in which the level bar grows"),
1025 FALSE,
1026 flags: G_PARAM_READWRITE|G_PARAM_STATIC_STRINGS|G_PARAM_EXPLICIT_NOTIFY);
1027
1028 g_object_class_install_properties (oclass, n_pspecs: LAST_PROPERTY, pspecs: properties);
1029
1030 gtk_widget_class_set_layout_manager_type (widget_class: wclass, GTK_TYPE_BIN_LAYOUT);
1031 gtk_widget_class_set_css_name (widget_class: wclass, I_("levelbar"));
1032 gtk_widget_class_set_accessible_role (widget_class: wclass, accessible_role: GTK_ACCESSIBLE_ROLE_METER);
1033}
1034
1035static void
1036gtk_level_bar_init (GtkLevelBar *self)
1037{
1038 self->cur_value = 0.0;
1039 self->min_value = 0.0;
1040 self->max_value = 1.0;
1041
1042 /* set initial orientation and style classes */
1043 self->orientation = GTK_ORIENTATION_HORIZONTAL;
1044 gtk_widget_update_orientation (GTK_WIDGET (self), orientation: self->orientation);
1045
1046 self->inverted = FALSE;
1047
1048 self->trough_widget = gtk_gizmo_new_with_role (css_name: "trough",
1049 role: GTK_ACCESSIBLE_ROLE_NONE,
1050 measure_func: gtk_level_bar_measure_trough,
1051 allocate_func: gtk_level_bar_allocate_trough,
1052 snapshot_func: gtk_level_bar_render_trough,
1053 NULL,
1054 NULL, NULL);
1055 gtk_widget_set_parent (widget: self->trough_widget, GTK_WIDGET (self));
1056
1057 gtk_level_bar_ensure_offset (self, GTK_LEVEL_BAR_OFFSET_LOW, value: 0.25);
1058 gtk_level_bar_ensure_offset (self, GTK_LEVEL_BAR_OFFSET_HIGH, value: 0.75);
1059 gtk_level_bar_ensure_offset (self, GTK_LEVEL_BAR_OFFSET_FULL, value: 1.0);
1060
1061 self->block_widget = NULL;
1062 self->n_blocks = 0;
1063
1064 self->bar_mode = GTK_LEVEL_BAR_MODE_CONTINUOUS;
1065 update_mode_style_classes (self);
1066 update_block_nodes (self);
1067 update_level_style_classes (self);
1068
1069 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self),
1070 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, 1.0,
1071 GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, 0.0,
1072 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, 0.0,
1073 -1);
1074}
1075
1076/**
1077 * gtk_level_bar_new:
1078 *
1079 * Creates a new `GtkLevelBar`.
1080 *
1081 * Returns: a `GtkLevelBar`.
1082 */
1083GtkWidget *
1084gtk_level_bar_new (void)
1085{
1086 return g_object_new (GTK_TYPE_LEVEL_BAR, NULL);
1087}
1088
1089/**
1090 * gtk_level_bar_new_for_interval:
1091 * @min_value: a positive value
1092 * @max_value: a positive value
1093 *
1094 * Creates a new `GtkLevelBar` for the specified interval.
1095 *
1096 * Returns: a `GtkLevelBar`
1097 */
1098GtkWidget *
1099gtk_level_bar_new_for_interval (double min_value,
1100 double max_value)
1101{
1102 return g_object_new (GTK_TYPE_LEVEL_BAR,
1103 first_property_name: "min-value", min_value,
1104 "max-value", max_value,
1105 NULL);
1106}
1107
1108/**
1109 * gtk_level_bar_get_min_value: (attributes org.gtk.Method.get_property=min-value)
1110 * @self: a `GtkLevelBar`
1111 *
1112 * Returns the `min-value of the `GtkLevelBar`.
1113 *
1114 * Returns: a positive value
1115 */
1116double
1117gtk_level_bar_get_min_value (GtkLevelBar *self)
1118{
1119 g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), 0.0);
1120
1121 return self->min_value;
1122}
1123
1124/**
1125 * gtk_level_bar_get_max_value: (attributes org.gtk.Method.get_property=max-value)
1126 * @self: a `GtkLevelBar`
1127 *
1128 * Returns the `max-value` of the `GtkLevelBar`.
1129 *
1130 * Returns: a positive value
1131 */
1132double
1133gtk_level_bar_get_max_value (GtkLevelBar *self)
1134{
1135 g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), 0.0);
1136
1137 return self->max_value;
1138}
1139
1140/**
1141 * gtk_level_bar_get_value: (attributes org.gtk.Method.get_property=value)
1142 * @self: a `GtkLevelBar`
1143 *
1144 * Returns the `value` of the `GtkLevelBar`.
1145 *
1146 * Returns: a value in the interval between
1147 * [property@Gtk.LevelBar:min-value[ and [property@Gtk.LevelBar:max-value]
1148 */
1149double
1150gtk_level_bar_get_value (GtkLevelBar *self)
1151{
1152 g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), 0.0);
1153
1154 return self->cur_value;
1155}
1156
1157static void
1158gtk_level_bar_set_value_internal (GtkLevelBar *self,
1159 double value)
1160{
1161 self->cur_value = value;
1162
1163 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_VALUE]);
1164
1165 gtk_widget_queue_allocate (GTK_WIDGET (self->trough_widget));
1166}
1167
1168/**
1169 * gtk_level_bar_set_min_value: (attributes org.gtk.Method.set_property=min-value)
1170 * @self: a `GtkLevelBar`
1171 * @value: a positive value
1172 *
1173 * Sets the `min-value` of the `GtkLevelBar`.
1174 *
1175 * You probably want to update preexisting level offsets after calling
1176 * this function.
1177 */
1178void
1179gtk_level_bar_set_min_value (GtkLevelBar *self,
1180 double value)
1181{
1182 g_return_if_fail (GTK_IS_LEVEL_BAR (self));
1183 g_return_if_fail (value >= 0.0);
1184
1185 if (value == self->min_value)
1186 return;
1187
1188 self->min_value = value;
1189
1190 if (self->min_value > self->cur_value)
1191 gtk_level_bar_set_value_internal (self, value: self->min_value);
1192
1193 update_block_nodes (self);
1194 update_level_style_classes (self);
1195
1196 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self),
1197 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MIN, self->min_value,
1198 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, self->cur_value,
1199 -1);
1200
1201 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MIN_VALUE]);
1202}
1203
1204/**
1205 * gtk_level_bar_set_max_value: (attributes org.gtk.Method.set_property=max-value)
1206 * @self: a `GtkLevelBar`
1207 * @value: a positive value
1208 *
1209 * Sets the `max-value` of the `GtkLevelBar`.
1210 *
1211 * You probably want to update preexisting level offsets after calling
1212 * this function.
1213 */
1214void
1215gtk_level_bar_set_max_value (GtkLevelBar *self,
1216 double value)
1217{
1218 g_return_if_fail (GTK_IS_LEVEL_BAR (self));
1219 g_return_if_fail (value >= 0.0);
1220
1221 if (value == self->max_value)
1222 return;
1223
1224 self->max_value = value;
1225
1226 if (self->max_value < self->cur_value)
1227 gtk_level_bar_set_value_internal (self, value: self->max_value);
1228
1229 gtk_level_bar_ensure_offsets_in_range (self);
1230 update_block_nodes (self);
1231 update_level_style_classes (self);
1232
1233 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self),
1234 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_MAX, self->max_value,
1235 GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, self->cur_value,
1236 -1);
1237
1238 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MAX_VALUE]);
1239}
1240
1241/**
1242 * gtk_level_bar_set_value: (attributes org.gtk.Method.set_property=value)
1243 * @self: a `GtkLevelBar`
1244 * @value: a value in the interval between
1245 * [property@Gtk.LevelBar:min-value] and [property@Gtk.LevelBar:max-value]
1246 *
1247 * Sets the value of the `GtkLevelBar`.
1248 */
1249void
1250gtk_level_bar_set_value (GtkLevelBar *self,
1251 double value)
1252{
1253 g_return_if_fail (GTK_IS_LEVEL_BAR (self));
1254
1255 if (value == self->cur_value)
1256 return;
1257
1258 gtk_level_bar_set_value_internal (self, value);
1259 update_level_style_classes (self);
1260
1261 gtk_accessible_update_property (self: GTK_ACCESSIBLE (ptr: self),
1262 first_property: GTK_ACCESSIBLE_PROPERTY_VALUE_NOW, self->cur_value,
1263 -1);
1264}
1265
1266/**
1267 * gtk_level_bar_get_mode: (attributes org.gtk.Method.get_property=mode)
1268 * @self: a `GtkLevelBar`
1269 *
1270 * Returns the `mode` of the `GtkLevelBar`.
1271 *
1272 * Returns: a `GtkLevelBarMode`
1273 */
1274GtkLevelBarMode
1275gtk_level_bar_get_mode (GtkLevelBar *self)
1276{
1277 g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), 0);
1278
1279 return self->bar_mode;
1280}
1281
1282/**
1283 * gtk_level_bar_set_mode: (attributes org.gtk.Method.set_property=mode)
1284 * @self: a `GtkLevelBar`
1285 * @mode: a `GtkLevelBarMode`
1286 *
1287 * Sets the `mode` of the `GtkLevelBar`.
1288 */
1289void
1290gtk_level_bar_set_mode (GtkLevelBar *self,
1291 GtkLevelBarMode mode)
1292{
1293 g_return_if_fail (GTK_IS_LEVEL_BAR (self));
1294
1295 if (self->bar_mode == mode)
1296 return;
1297
1298 self->bar_mode = mode;
1299
1300 update_mode_style_classes (self);
1301 update_block_nodes (self);
1302 update_level_style_classes (self);
1303 gtk_widget_queue_resize (GTK_WIDGET (self));
1304 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_MODE]);
1305
1306}
1307
1308/**
1309 * gtk_level_bar_get_inverted: (attributes org.gtk.Method.get_property=inverted)
1310 * @self: a `GtkLevelBar`
1311 *
1312 * Returns whether the levelbar is inverted.
1313 *
1314 * Returns: %TRUE if the level bar is inverted
1315 */
1316gboolean
1317gtk_level_bar_get_inverted (GtkLevelBar *self)
1318{
1319 g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), FALSE);
1320
1321 return self->inverted;
1322}
1323
1324/**
1325 * gtk_level_bar_set_inverted: (attributes org.gtk.Method.set_property=inverted)
1326 * @self: a `GtkLevelBar`
1327 * @inverted: %TRUE to invert the level bar
1328 *
1329 * Sets whether the `GtkLevelBar` is inverted.
1330 */
1331void
1332gtk_level_bar_set_inverted (GtkLevelBar *self,
1333 gboolean inverted)
1334{
1335 g_return_if_fail (GTK_IS_LEVEL_BAR (self));
1336
1337 if (self->inverted == inverted)
1338 return;
1339
1340 self->inverted = inverted;
1341 gtk_widget_queue_resize (GTK_WIDGET (self));
1342 update_level_style_classes (self);
1343 g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_INVERTED]);
1344}
1345
1346/**
1347 * gtk_level_bar_remove_offset_value:
1348 * @self: a `GtkLevelBar`
1349 * @name: (nullable): the name of an offset in the bar
1350 *
1351 * Removes an offset marker from a `GtkLevelBar`.
1352 *
1353 * The marker must have been previously added with
1354 * [method@Gtk.LevelBar.add_offset_value].
1355 */
1356void
1357gtk_level_bar_remove_offset_value (GtkLevelBar *self,
1358 const char *name)
1359{
1360 GList *existing;
1361
1362 g_return_if_fail (GTK_IS_LEVEL_BAR (self));
1363
1364 existing = g_list_find_custom (list: self->offsets, data: name, func: offset_find_func);
1365 if (existing)
1366 {
1367 gtk_level_bar_offset_free (offset: existing->data);
1368 self->offsets = g_list_delete_link (list: self->offsets, link_: existing);
1369
1370 update_level_style_classes (self);
1371 }
1372}
1373
1374/**
1375 * gtk_level_bar_add_offset_value:
1376 * @self: a `GtkLevelBar`
1377 * @name: the name of the new offset
1378 * @value: the value for the new offset
1379 *
1380 * Adds a new offset marker on @self at the position specified by @value.
1381 *
1382 * When the bar value is in the interval topped by @value (or between @value
1383 * and [property@Gtk.LevelBar:max-value] in case the offset is the last one
1384 * on the bar) a style class named `level-`@name will be applied
1385 * when rendering the level bar fill.
1386 *
1387 * If another offset marker named @name exists, its value will be
1388 * replaced by @value.
1389 */
1390void
1391gtk_level_bar_add_offset_value (GtkLevelBar *self,
1392 const char *name,
1393 double value)
1394{
1395 GQuark name_quark;
1396
1397 g_return_if_fail (GTK_IS_LEVEL_BAR (self));
1398 g_return_if_fail (gtk_level_bar_value_in_interval (self, value));
1399
1400 if (!gtk_level_bar_ensure_offset (self, name, value))
1401 return;
1402
1403 update_level_style_classes (self);
1404 name_quark = g_quark_from_string (string: name);
1405 g_signal_emit (instance: self, signal_id: signals[SIGNAL_OFFSET_CHANGED], detail: name_quark, name);
1406}
1407
1408/**
1409 * gtk_level_bar_get_offset_value:
1410 * @self: a `GtkLevelBar`
1411 * @name: (nullable): the name of an offset in the bar
1412 * @value: (out): location where to store the value
1413 *
1414 * Fetches the value specified for the offset marker @name in @self.
1415 *
1416 * Returns: %TRUE if the specified offset is found
1417 */
1418gboolean
1419gtk_level_bar_get_offset_value (GtkLevelBar *self,
1420 const char *name,
1421 double *value)
1422{
1423 GList *existing;
1424 GtkLevelBarOffset *offset = NULL;
1425
1426 g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), FALSE);
1427
1428 existing = g_list_find_custom (list: self->offsets, data: name, func: offset_find_func);
1429 if (existing)
1430 offset = existing->data;
1431
1432 if (!offset)
1433 return FALSE;
1434
1435 if (value)
1436 *value = offset->value;
1437
1438 return TRUE;
1439}
1440

source code of gtk/gtk/gtklevelbar.c