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