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