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