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