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