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