[gtk+] level-bar: introduce GtkLevelBar



commit 68acf78c5d6bf6c76b8912cf28eb70e597558a10
Author: Cosimo Cecchi <cosimoc gnome org>
Date:   Tue May 29 17:00:33 2012 -0400

    level-bar: introduce GtkLevelBar
    
    Similar to CcStrengthBar from gnome-control-center, but more generic and
    with thorough CSS styling support.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=677892

 docs/reference/gtk/gtk-docs.sgml        |    1 +
 docs/reference/gtk/gtk3-sections.txt    |   33 +
 docs/reference/gtk/gtk3.types.in        |    1 +
 docs/reference/gtk/objects_grouped.sgml |    1 +
 gtk/Makefile.am                         |    2 +
 gtk/gtk.h                               |    1 +
 gtk/gtk.symbols                         |   15 +
 gtk/gtkenums.h                          |   16 +
 gtk/gtklevelbar.c                       | 1305 +++++++++++++++++++++++++++++++
 gtk/gtklevelbar.h                       |  127 +++
 gtk/gtkstylecontext.h                   |   10 +
 11 files changed, 1512 insertions(+), 0 deletions(-)
---
diff --git a/docs/reference/gtk/gtk-docs.sgml b/docs/reference/gtk/gtk-docs.sgml
index b5de514..6eca4d0 100644
--- a/docs/reference/gtk/gtk-docs.sgml
+++ b/docs/reference/gtk/gtk-docs.sgml
@@ -102,6 +102,7 @@
       <xi:include href="xml/gtklabel.xml" />
       <xi:include href="xml/gtkprogressbar.xml" />
       <xi:include href="xml/gtkstatusbar.xml" />
+      <xi:include href="xml/gtklevelbar.xml" />
       <xi:include href="xml/gtkinfobar.xml" />
       <xi:include href="xml/gtkstatusicon.xml" />
       <xi:include href="xml/gtkspinner.xml" />
diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt
index 491e651..0d16ef4 100644
--- a/docs/reference/gtk/gtk3-sections.txt
+++ b/docs/reference/gtk/gtk3-sections.txt
@@ -3248,6 +3248,38 @@ gtk_status_icon_get_type
 </SECTION>
 
 <SECTION>
+<FILE>gtklevelbar</FILE>
+<TITLE>GtkLevelBar</TITLE>
+GTK_LEVEL_BAR_OFFSET_LOW
+GTK_LEVEL_BAR_OFFSET_HIGH
+GtkLevelBarMode
+GtkLevelBar
+gtk_level_bar_new
+gtk_level_bar_new_for_interval
+gtk_level_bar_set_mode
+gtk_level_bar_get_mode
+gtk_level_bar_set_value
+gtk_level_bar_get_value
+gtk_level_bar_set_min_value
+gtk_level_bar_get_min_value
+gtk_level_bar_set_max_value
+gtk_level_bar_get_max_value
+gtk_level_bar_add_offset_value
+gtk_level_bar_remove_offset_value
+gtk_level_bar_get_offset_value
+<SUBSECTION Standard>
+GTK_LEVEL_BAR
+GTK_IS_LEVEL_BAR
+GTK_TYPE_LEVEL_BAR
+GTK_LEVEL_BAR_CLASS
+GTK_IS_LEVEL_BAR_CLASS
+GTK_LEVEL_BAR_GET_CLASS
+<SUBSECTION Private>
+GtkLevelBarPrivate
+gtk_level_bar_get_type
+</SECTION>
+
+<SECTION>
 <FILE>gtktable</FILE>
 <TITLE>GtkTable</TITLE>
 GtkTable
@@ -5721,6 +5753,7 @@ GTK_STYLE_CLASS_RIGHT
 GTK_STYLE_CLASS_LINKED
 GTK_STYLE_CLASS_ARROW
 GTK_STYLE_CLASS_OSD
+GTK_STYLE_CLASS_LEVEL_BAR
 GTK_STYLE_REGION_COLUMN
 GTK_STYLE_REGION_COLUMN_HEADER
 GTK_STYLE_REGION_ROW
diff --git a/docs/reference/gtk/gtk3.types.in b/docs/reference/gtk/gtk3.types.in
index 238ca7f..3f378b3 100644
--- a/docs/reference/gtk/gtk3.types.in
+++ b/docs/reference/gtk/gtk3.types.in
@@ -157,6 +157,7 @@ gtk_spinner_get_type
 gtk_statusbar_get_type
 gtk_status_icon_get_type
 gtk_switch_get_type
+gtk_level_bar_get_type
 gtk_style_get_type
 gtk_style_context_get_type
 gtk_style_provider_get_type
diff --git a/docs/reference/gtk/objects_grouped.sgml b/docs/reference/gtk/objects_grouped.sgml
index 78dc95b..56bfd95 100644
--- a/docs/reference/gtk/objects_grouped.sgml
+++ b/docs/reference/gtk/objects_grouped.sgml
@@ -90,6 +90,7 @@
     <link linkend="GtkCalendar">GtkCalendar</link>
     <link linkend="GtkProgressBar">GtkProgressBar</link>
     <link linkend="GtkStatusbar">GtkStatusbar</link>
+    <link linkend="GtkStrengthBar">GtkStrengthBar</link>
     <link linkend="GtkHRuler">GtkHRuler</link>
     <link linkend="GtkVRuler">GtkVRuler</link>
     <link linkend="GtkHScrollbar">GtkHScrollbar</link>
diff --git a/gtk/Makefile.am b/gtk/Makefile.am
index 4f4cda0..41c61b8 100644
--- a/gtk/Makefile.am
+++ b/gtk/Makefile.am
@@ -265,6 +265,7 @@ gtk_public_h_sources = 		\
 	gtkinvisible.h		\
 	gtklabel.h		\
 	gtklayout.h		\
+	gtklevelbar.h		\
 	gtklinkbutton.h		\
 	gtkliststore.h		\
 	gtklockbutton.h		\
@@ -729,6 +730,7 @@ gtk_base_c_sources = 		\
 	gtkkeyhash.c		\
 	gtklabel.c		\
 	gtklayout.c		\
+	gtklevelbar.c		\
 	gtklinkbutton.c		\
 	gtkliststore.c		\
 	gtklockbutton.c		\
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 0c9b727..bd9d5f4 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -122,6 +122,7 @@
 #include <gtk/gtkinvisible.h>
 #include <gtk/gtklabel.h>
 #include <gtk/gtklayout.h>
+#include <gtk/gtklevelbar.h>
 #include <gtk/gtklinkbutton.h>
 #include <gtk/gtkliststore.h>
 #include <gtk/gtklockbutton.h>
diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols
index 8b6f902..82f6f6f 100644
--- a/gtk/gtk.symbols
+++ b/gtk/gtk.symbols
@@ -2566,6 +2566,21 @@ gtk_switch_get_active
 gtk_switch_get_type
 gtk_switch_new
 gtk_switch_set_active
+gtk_level_bar_get_type
+gtk_level_bar_new
+gtk_level_bar_new_for_interval
+gtk_level_bar_set_mode
+gtk_level_bar_get_mode
+gtk_level_bar_set_value
+gtk_level_bar_get_value
+gtk_level_bar_set_min_value
+gtk_level_bar_get_min_value
+gtk_level_bar_set_max_value
+gtk_level_bar_get_max_value
+gtk_level_bar_add_offset_value
+gtk_level_bar_remove_offset_value
+gtk_level_bar_get_offset_value
+gtk_level_bar_mode_get_type
 gtk_style_apply_default_background
 gtk_style_attach
 gtk_style_context_add_class
diff --git a/gtk/gtkenums.h b/gtk/gtkenums.h
index e409d79..e2cdb2d 100644
--- a/gtk/gtkenums.h
+++ b/gtk/gtkenums.h
@@ -932,6 +932,22 @@ typedef enum {
   GTK_BORDER_STYLE_RIDGE
 } GtkBorderStyle;
 
+/**
+ * GtkLevelBarMode:
+ * @GTK_LEVEL_BAR_MODE_CONTINUOUS: the bar has a continuous mode
+ * @GTK_LEVEL_BAR_MODE_DISCRETE: the bar has a discrete mode
+ *
+ * Describes how #GtkLevelBar contents should be rendered.
+ * Note that this enumeration could be extended with additional modes
+ * in the future.
+ *
+ * Since: 3.6
+ */
+typedef enum {
+  GTK_LEVEL_BAR_MODE_CONTINUOUS,
+  GTK_LEVEL_BAR_MODE_DISCRETE
+} GtkLevelBarMode;
+
 G_END_DECLS
 
 
diff --git a/gtk/gtklevelbar.c b/gtk/gtklevelbar.c
new file mode 100644
index 0000000..7207d01
--- /dev/null
+++ b/gtk/gtklevelbar.c
@@ -0,0 +1,1305 @@
+/* GTK - The GIMP Toolkit
+ * Copyright  2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc gnome org>
+ *
+ */
+
+/**
+ * SECTION:gtklevelbar
+ * @Title: GtkLevelBar
+ * @Short_description: A bar that can used as a level indicator
+ *
+ * The #GtkLevelBar is a bar widget that can be used
+ * as a level indicator. Typical use cases are displaying the strength
+ * of a password, or showing the charge level of a battery.
+ *
+ * Use gtk_level_bar_set_value() to set the current value, and
+ * gtk_level_bar_add_offset_value() to set the value offsets at which
+ * the bar will be considered in a different state. GTK will add two offsets
+ * by default on the level bar: #GTK_LEVEL_BAR_OFFSET_LOW and
+ * #GTK_LEVEL_BAR_OFFSET_HIGH, with values 0.25 and 0.75 respectively.
+ *
+ * <example>
+ * <title>Adding a custom offset on the bar</title>
+ * <programlisting>
+ *
+ * static GtkWidget *
+ * create_level_bar (void)
+ * {
+ *   GtkWidget *level_bar;
+ *
+ *   level_bar = gtk_level_bar_new ();
+ *
+ *   /<!---->* This changes the value of the default low offset *<!---->/
+ *   gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (level_bar),
+ *                                   GTK_LEVEL_BAR_OFFSET_LOW, 0.10);
+ *
+ *   /<!---->* This adds a new offset to the bar; the application will
+ *    * be able to change its color by using the following selector,
+ *    * either by adding it to its CSS file or using
+ *    * gtk_css_provider_load_from_data() and gtk_style_context_add_provider()
+ *    *
+ *    * .level-bar.fill-block.level-my-offset {
+ *    *   background-color: green;
+ *    *   border-style: solid;
+ *    *   border-color: black;
+ *    *   border-style: 1px;
+ *    * }
+ *    *<!---->/
+ *   gtk_level_bar_add_offset_value (GTK_LEVEL_BAR (level_bar),
+ *                                   "my-offset", 0.60);
+ *
+ *   return level_bar;
+ * }
+ * </programlisting>
+ * </example>
+ *
+ * The default interval of values is between zero and one, but it's possible to
+ * modify the interval using gtk_level_bar_set_min_value() and
+ * gtk_level_bar_set_max_value(). The value will be always drawn in proportion to
+ * the admissible interval, i.e. a value of 15 with a specified interval between
+ * 10 and 20 is equivalent to a value of 0.5 with an interval between 0 and 1.
+ * When #GTK_LEVEL_BAR_MODE_DISCRETE is used, the bar level is rendered
+ * as a finite and number of separated blocks instead of a single one. The number
+ * of blocks that will be rendered is equal to the number of units specified by
+ * the admissible interval.
+ * For instance, to build a bar rendered with five blocks, it's sufficient to
+ * set the minimum value to 0 and the maximum value to 5 after changing the indicator
+ * mode to discrete.
+ *
+ * Since: 3.6
+ */
+#include "config.h"
+
+#include "gtkbuildable.h"
+#include "gtkintl.h"
+#include "gtkorientableprivate.h"
+#include "gtklevelbar.h"
+#include "gtkmarshalers.h"
+#include "gtkstylecontext.h"
+#include "gtktypebuiltins.h"
+#include "gtkwidget.h"
+
+#include <math.h>
+#include <stdlib.h>
+
+#define DEFAULT_BLOCK_SIZE 3
+
+/* these don't make sense outside of GtkLevelBar, so we don't add
+ * global defines */
+#define STYLE_CLASS_INDICATOR_CONTINUOUS "indicator-continuous"
+#define STYLE_CLASS_INDICATOR_DISCRETE   "indicator-discrete"
+#define STYLE_CLASS_FILL_BLOCK           "fill-block"
+#define STYLE_CLASS_EMPTY_FILL_BLOCK     "empty-fill-block"
+
+static void gtk_level_bar_buildable_init (GtkBuildableIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkLevelBar, gtk_level_bar, GTK_TYPE_WIDGET,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_ORIENTABLE, NULL)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE,
+                                                gtk_level_bar_buildable_init))
+
+enum {
+  PROP_VALUE = 1,
+  PROP_MIN_VALUE,
+  PROP_MAX_VALUE,
+  PROP_MODE,
+  LAST_PROPERTY,
+  PROP_ORIENTATION /* overridden */
+};
+
+enum {
+  SIGNAL_OFFSET_CHANGED,
+  NUM_SIGNALS
+};
+
+static GParamSpec *properties[LAST_PROPERTY] = { NULL, };
+static guint signals[NUM_SIGNALS] = { 0, };
+
+typedef struct {
+  gchar *name;
+  gdouble value;
+} GtkLevelBarOffset;
+
+struct _GtkLevelBarPrivate {
+  GtkOrientation orientation;
+
+  gdouble min_value;
+  gdouble max_value;
+  gdouble cur_value;
+
+  GList *offsets;
+
+  GtkLevelBarMode bar_mode;
+};
+
+static void gtk_level_bar_set_value_internal (GtkLevelBar *self,
+                                              gdouble      value);
+
+static GtkLevelBarOffset *
+gtk_level_bar_offset_new (const gchar *name,
+                          gdouble      value)
+{
+  GtkLevelBarOffset *offset = g_slice_new0 (GtkLevelBarOffset);
+
+  offset->name = g_strdup (name);
+  offset->value = value;
+
+  return offset;
+}
+
+static void
+gtk_level_bar_offset_free (GtkLevelBarOffset *offset)
+{
+  g_free (offset->name);
+  g_slice_free (GtkLevelBarOffset, offset);
+}
+
+static gint
+offset_find_func (gconstpointer data,
+                  gconstpointer user_data)
+{
+  const GtkLevelBarOffset *offset = data;
+  const gchar *name = user_data;
+
+  return g_strcmp0 (name, offset->name);
+}
+
+static gint
+offset_sort_func (gconstpointer a,
+                  gconstpointer b)
+{
+  const GtkLevelBarOffset *offset_a = a;
+  const GtkLevelBarOffset *offset_b = b;
+
+  return (offset_a->value > offset_b->value);
+}
+
+static gboolean
+gtk_level_bar_ensure_offset (GtkLevelBar *self,
+                             const gchar *name,
+                             gdouble      value)
+{
+  GList *existing;
+  GtkLevelBarOffset *offset = NULL;
+
+  existing = g_list_find_custom (self->priv->offsets, name, offset_find_func);
+  if (existing)
+    offset = existing->data;
+
+  if (offset && (offset->value == value))
+    return FALSE;
+
+  if (offset)
+    {
+      gtk_level_bar_offset_free (offset);
+      self->priv->offsets = g_list_delete_link (self->priv->offsets, existing);
+    }
+
+  offset = gtk_level_bar_offset_new (name, value);
+  self->priv->offsets = g_list_insert_sorted (self->priv->offsets, offset, offset_sort_func);
+
+  return TRUE;
+}
+
+static gboolean
+gtk_level_bar_value_in_interval (GtkLevelBar *self,
+                                 gdouble      value)
+{
+  return ((value >= self->priv->min_value) &&
+          (value <= self->priv->max_value));
+}
+
+static void
+gtk_level_bar_get_min_block_size (GtkLevelBar *self,
+                                  gint        *block_width,
+                                  gint        *block_height)
+{
+  GtkWidget *widget = GTK_WIDGET (self);
+  GtkStyleContext *context = gtk_widget_get_style_context (widget);
+  GtkStateFlags flags = gtk_widget_get_state_flags (widget);
+  GtkBorder border, tmp, tmp2;
+  gint min_width, min_height;
+
+  gtk_style_context_save (context);
+  gtk_style_context_add_class (context, STYLE_CLASS_FILL_BLOCK);
+  gtk_style_context_get_border (context, flags, &border);
+  gtk_style_context_get_padding (context, flags, &tmp);
+  gtk_style_context_get_margin (context, flags, &tmp2);
+  gtk_style_context_restore (context);
+
+  gtk_style_context_get_style (context,
+                               "min-block-width", &min_width,
+                               "min-block-height", &min_height,
+                               NULL);
+
+  border.top += tmp.top;
+  border.right += tmp.right;
+  border.bottom += tmp.bottom;
+  border.left += tmp.left;
+
+  border.top += tmp2.top;
+  border.right += tmp2.right;
+  border.bottom += tmp2.bottom;
+  border.left += tmp2.left;
+
+  if (block_width)
+    *block_width = MAX (border.left + border.right, min_width);
+  if (block_height)
+    *block_height = MAX (border.top + border.bottom, min_height);
+}
+
+static gint
+gtk_level_bar_get_num_blocks (GtkLevelBar *self)
+{
+  if (self->priv->bar_mode == GTK_LEVEL_BAR_MODE_CONTINUOUS)
+    return 1;
+  else if (self->priv->bar_mode == GTK_LEVEL_BAR_MODE_DISCRETE)
+    return MAX (1, (gint) (round (self->priv->max_value) - round (self->priv->min_value)));
+
+  return 0;
+}
+
+static void
+gtk_level_bar_get_borders (GtkLevelBar *self,
+                           GtkBorder   *borders_out)
+{
+  GtkWidget *widget = GTK_WIDGET (self);
+  GtkStyleContext *context = gtk_widget_get_style_context (widget);
+  GtkStateFlags flags = gtk_widget_get_state_flags (widget);
+  GtkBorder border, tmp;
+
+  gtk_style_context_save (context);
+  gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
+  gtk_style_context_get_border (context, flags, &border);
+  gtk_style_context_get_padding (context, flags, &tmp);
+  gtk_style_context_restore (context);
+
+  border.top += tmp.top;
+  border.right += tmp.right;
+  border.bottom += tmp.bottom;
+  border.left += tmp.left;
+
+  if (borders_out)
+    *borders_out = border;
+}
+
+static void
+gtk_level_bar_draw_fill_continuous (GtkLevelBar           *self,
+                                    cairo_t               *cr,
+                                    cairo_rectangle_int_t *fill_area)
+{
+  GtkWidget *widget = GTK_WIDGET (self);
+  GtkStyleContext *context = gtk_widget_get_style_context (widget);
+  GtkStateFlags flags = gtk_widget_get_state_flags (widget);
+  cairo_rectangle_int_t base_area, block_area;
+  GtkBorder block_margin;
+  gdouble fill_percentage;
+
+  gtk_style_context_save (context);
+  gtk_style_context_add_class (context, STYLE_CLASS_FILL_BLOCK);
+  gtk_style_context_get_margin (context, flags, &block_margin);
+
+  /* render the empty (unfilled) part */
+  base_area = *fill_area;
+  base_area.x += block_margin.left;
+  base_area.y += block_margin.top;
+  base_area.width -= block_margin.left + block_margin.right;
+  base_area.height -= block_margin.top + block_margin.bottom;
+
+  gtk_style_context_add_class (context, STYLE_CLASS_EMPTY_FILL_BLOCK);
+
+  gtk_render_background (context, cr, base_area.x, base_area.y,
+                         base_area.width, base_area.height);
+  gtk_render_frame (context, cr, base_area.x, base_area.y,
+                    base_area.width, base_area.height);
+
+  gtk_style_context_remove_class (context, STYLE_CLASS_EMPTY_FILL_BLOCK);
+
+  /* now render the filled part on top of it */
+  block_area = base_area;
+
+  fill_percentage = (self->priv->cur_value - self->priv->min_value) /
+    (self->priv->max_value - self->priv->min_value);
+
+  if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    block_area.width = (gint) floor (block_area.width * fill_percentage);
+  else
+    block_area.height = (gint) floor (block_area.height * fill_percentage);
+
+  gtk_render_background (context, cr, block_area.x, block_area.y,
+                         block_area.width, block_area.height);
+  gtk_render_frame (context, cr, block_area.x, block_area.y,
+                    block_area.width, block_area.height);
+
+  gtk_style_context_restore (context);
+}
+
+static void
+gtk_level_bar_draw_fill_discrete (GtkLevelBar           *self,
+                                  cairo_t               *cr,
+                                  cairo_rectangle_int_t *fill_area)
+{
+  GtkWidget *widget = GTK_WIDGET (self);
+  GtkStyleContext *context = gtk_widget_get_style_context (widget);
+  GtkStateFlags flags = gtk_widget_get_state_flags (widget);
+  gint num_blocks, num_filled, idx;
+  gint block_width, block_height;
+  gint block_draw_width, block_draw_height;
+  GtkBorder block_margin;
+  cairo_rectangle_int_t block_area;
+
+  gtk_level_bar_get_min_block_size (self, &block_width, &block_height);
+
+  block_area = *fill_area;
+  num_blocks = gtk_level_bar_get_num_blocks (self);
+  num_filled = (gint) round (self->priv->cur_value) - (gint) round (self->priv->min_value);
+
+  if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    block_width = MAX (block_width, (gint) floor (block_area.width / num_blocks));
+  else
+    block_height = MAX (block_height, (gint) floor (block_area.height / num_blocks));
+
+  gtk_style_context_save (context);
+  gtk_style_context_add_class (context, STYLE_CLASS_FILL_BLOCK);
+  gtk_style_context_get_margin (context, flags, &block_margin);
+
+  block_draw_width = block_width - block_margin.left - block_margin.right;
+  block_draw_height = block_height - block_margin.top - block_margin.bottom;
+
+  if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    {
+      block_draw_height = MAX (block_draw_height, block_area.height - block_margin.top - block_margin.bottom);
+      block_area.y += block_margin.top;
+    }
+  else
+    {
+      block_draw_width = MAX (block_draw_width, block_area.width - block_margin.left - block_margin.right);
+      block_area.x += block_margin.left;
+    }
+
+  for (idx = 0; idx < num_blocks; idx++)
+    {
+      if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+        block_area.x += block_margin.left;
+      else
+        block_area.y += block_margin.top;
+
+      if (idx > num_filled - 1)
+        gtk_style_context_add_class (context, STYLE_CLASS_EMPTY_FILL_BLOCK);
+
+      gtk_render_background (context, cr,
+                             block_area.x, block_area.y,
+                             block_draw_width, block_draw_height);
+      gtk_render_frame (context, cr,
+                        block_area.x, block_area.y,
+                        block_draw_width, block_draw_height);
+
+      if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+        block_area.x += block_draw_width + block_margin.right;
+      else
+        block_area.y += block_draw_height + block_margin.bottom;
+    }
+
+  gtk_style_context_restore (context);
+}
+
+static void
+gtk_level_bar_draw_fill (GtkLevelBar *self,
+                         cairo_t     *cr)
+{
+  GtkWidget *widget = GTK_WIDGET (self);
+  GtkBorder trough_borders;
+  cairo_rectangle_int_t fill_area;
+
+  gtk_level_bar_get_borders (self, &trough_borders);
+
+  fill_area.x = trough_borders.left;
+  fill_area.y = trough_borders.top;
+  fill_area.width = gtk_widget_get_allocated_width (widget) -
+    trough_borders.left - trough_borders.right;
+  fill_area.height = gtk_widget_get_allocated_height (widget) -
+    trough_borders.top - trough_borders.bottom;
+
+  if (self->priv->bar_mode == GTK_LEVEL_BAR_MODE_CONTINUOUS)
+    gtk_level_bar_draw_fill_continuous (self, cr, &fill_area);
+  else if (self->priv->bar_mode == GTK_LEVEL_BAR_MODE_DISCRETE)
+    gtk_level_bar_draw_fill_discrete (self, cr, &fill_area);
+}
+
+static gboolean
+gtk_level_bar_draw (GtkWidget *widget,
+                    cairo_t   *cr)
+{
+  GtkLevelBar *self = GTK_LEVEL_BAR (widget);
+  GtkStyleContext *context = gtk_widget_get_style_context (widget);
+  gint width, height;
+
+  width = gtk_widget_get_allocated_width (widget);
+  height = gtk_widget_get_allocated_height (widget);
+
+  gtk_style_context_save (context);
+  gtk_style_context_add_class (context, GTK_STYLE_CLASS_TROUGH);
+
+  gtk_render_background (context, cr, 0, 0, width, height);
+  gtk_render_frame (context, cr, 0, 0, width, height);
+
+  gtk_style_context_restore (context);
+
+  gtk_level_bar_draw_fill (self, cr);
+
+  return FALSE;
+}
+
+static void
+gtk_level_bar_get_preferred_width (GtkWidget *widget,
+                                   gint      *minimum,
+                                   gint      *natural)
+{
+  GtkLevelBar *self = GTK_LEVEL_BAR (widget);
+  GtkBorder borders;
+  gint num_blocks;
+  gint width, block_width;
+
+  num_blocks = gtk_level_bar_get_num_blocks (self);
+  gtk_level_bar_get_min_block_size (self, &block_width, NULL);
+
+  gtk_level_bar_get_borders (self, &borders);
+  width = borders.left + borders.right;
+
+  if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+    width += num_blocks * block_width;
+  else
+    width += block_width;
+
+  if (minimum)
+    *minimum = width;
+  if (natural)
+    *natural = width;
+}
+
+static void
+gtk_level_bar_get_preferred_height (GtkWidget *widget,
+                                    gint      *minimum,
+                                    gint      *natural)
+{
+  GtkLevelBar *self = GTK_LEVEL_BAR (widget);
+  GtkBorder borders;
+  gint num_blocks;
+  gint height, block_height;
+
+  num_blocks = gtk_level_bar_get_num_blocks (self);
+  gtk_level_bar_get_min_block_size (self, NULL, &block_height);
+
+  gtk_level_bar_get_borders (self, &borders);
+  height = borders.top + borders.bottom;
+
+  if (self->priv->orientation == GTK_ORIENTATION_VERTICAL)
+    height += num_blocks * block_height;
+  else
+    height += block_height;
+
+  if (minimum)
+    *minimum = height;
+  if (natural)
+    *natural = height;
+}
+
+static void
+gtk_level_bar_update_mode_style_classes (GtkLevelBar *self)
+{
+  GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
+
+  if (self->priv->bar_mode == GTK_LEVEL_BAR_MODE_CONTINUOUS)
+    {
+      gtk_style_context_remove_class (context, STYLE_CLASS_INDICATOR_DISCRETE);
+      gtk_style_context_add_class (context, STYLE_CLASS_INDICATOR_CONTINUOUS);
+    }
+  else if (self->priv->bar_mode == GTK_LEVEL_BAR_MODE_DISCRETE)
+    {
+      gtk_style_context_remove_class (context, STYLE_CLASS_INDICATOR_CONTINUOUS);
+      gtk_style_context_add_class (context, STYLE_CLASS_INDICATOR_DISCRETE);
+    }
+}
+
+static void
+gtk_level_bar_update_level_style_classes (GtkLevelBar *self)
+{
+  GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET (self));
+  gdouble value = gtk_level_bar_get_value (self);
+  gchar *offset_style_class, *value_class = NULL;
+  GtkLevelBarOffset *offset, *prev_offset;
+  GList *l;
+
+  for (l = self->priv->offsets; l != NULL; l = l->next)
+    {
+      offset = l->data;
+
+      offset_style_class = g_strconcat ("level-", offset->name, NULL);
+      gtk_style_context_remove_class (context, offset_style_class);
+
+      /* find the right offset for our style class */
+      if (((l->prev == NULL) && (value <= offset->value)) ||
+          ((l->next == NULL) && (value >= offset->value)))
+        {
+          value_class = offset_style_class;
+        }
+      else if ((l->next != NULL) && (l->prev != NULL))
+        {
+          prev_offset = l->prev->data;
+          if ((prev_offset->value <= value) && (value < offset->value))
+            value_class = offset_style_class;
+        }
+      else
+        {
+          g_free (offset_style_class);
+        }
+    }
+
+  if (value_class != NULL)
+    {
+      gtk_style_context_add_class (context, value_class);
+      g_free (value_class);
+    }
+}
+
+static void
+gtk_level_bar_ensure_offsets_in_range (GtkLevelBar *self)
+{
+  GtkLevelBarOffset *offset;
+  GList *l = self->priv->offsets;
+
+  while (l != NULL)
+    {
+      offset = l->data;
+      l = l->next;
+
+      if (offset->value < self->priv->min_value)
+        gtk_level_bar_ensure_offset (self, offset->name, self->priv->min_value);
+      else if (offset->value > self->priv->max_value)
+        gtk_level_bar_ensure_offset (self, offset->name, self->priv->max_value);
+    }
+}
+
+typedef struct {
+  GtkLevelBar *self;
+  GList *offsets;
+} OffsetsParserData;
+
+static void
+offset_start_element (GMarkupParseContext  *context,
+                      const gchar          *element_name,
+                      const gchar         **names,
+                      const gchar         **values,
+                      gpointer              user_data,
+                      GError              **error)
+{
+  OffsetsParserData *parser_data = user_data;
+  const gchar *name = NULL;
+  const gchar *value_str = NULL;
+  GtkLevelBarOffset *offset;
+  gint line_number, char_number;
+  gint idx;
+
+  if (strcmp (element_name, "offsets") == 0)
+    ;
+  else if (strcmp (element_name, "offset") == 0)
+    {
+      for (idx = 0; names[idx] != NULL; idx++)
+        {
+          if (strcmp (names[idx], "name") == 0)
+            {
+              name = values[idx];
+            }
+          else if (strcmp (names[idx], "value") == 0)
+            {
+              value_str = values[idx];
+            }
+          else
+            {
+              g_markup_parse_context_get_position (context,
+                                                   &line_number,
+                                                   &char_number);
+              g_set_error (error,
+                           GTK_BUILDER_ERROR,
+                           GTK_BUILDER_ERROR_INVALID_ATTRIBUTE,
+                           "%s:%d:%d '%s' is not a valid attribute of <%s>",
+                           "<input>",
+                           line_number, char_number, names[idx], "offset");
+
+              return;
+            }
+        }
+
+      if (name && value_str)
+        {
+          offset = gtk_level_bar_offset_new (name, strtof (value_str, NULL));
+          parser_data->offsets = g_list_prepend (parser_data->offsets, offset);
+        }
+    }
+  else
+    {
+      g_markup_parse_context_get_position (context,
+                                           &line_number,
+                                           &char_number);
+      g_set_error (error,
+                   GTK_BUILDER_ERROR,
+                   GTK_BUILDER_ERROR_UNHANDLED_TAG,
+                   "%s:%d:%d unsupported tag for GtkLevelBar: \"%s\"",
+                   "<input>",
+                   line_number, char_number, element_name);
+    }
+}
+
+static const GMarkupParser offset_parser =
+{
+  offset_start_element
+};
+
+static gboolean
+gtk_level_bar_buildable_custom_tag_start (GtkBuildable  *buildable,
+                                          GtkBuilder    *builder,
+                                          GObject       *child,
+                                          const gchar   *tagname,
+                                          GMarkupParser *parser,
+                                          gpointer      *data)
+{
+  OffsetsParserData *parser_data;
+
+  if (child)
+    return FALSE;
+
+  if (strcmp (tagname, "offsets") != 0)
+    return FALSE;
+
+  parser_data = g_slice_new0 (OffsetsParserData);
+  parser_data->self = GTK_LEVEL_BAR (buildable);
+  parser_data->offsets = NULL;
+
+  *parser = offset_parser;
+  *data = parser_data;
+
+  return TRUE;
+}
+
+static void
+gtk_level_bar_buildable_custom_finished (GtkBuildable *buildable,
+                                         GtkBuilder   *builder,
+                                         GObject      *child,
+                                         const gchar  *tagname,
+                                         gpointer      user_data)
+{
+  OffsetsParserData *parser_data;
+  GtkLevelBar *self;
+  GtkLevelBarOffset *offset;
+  GList *l;
+
+  parser_data = user_data;
+  self = parser_data->self;
+
+  if (strcmp (tagname, "offsets") != 0)
+    goto out;
+
+  for (l = parser_data->offsets; l != NULL; l = l->next)
+    {
+      offset = l->data;
+      gtk_level_bar_add_offset_value (self, offset->name, offset->value);
+    }
+
+ out:
+  g_list_free_full (parser_data->offsets, (GDestroyNotify) gtk_level_bar_offset_free);
+  g_slice_free (OffsetsParserData, parser_data);
+}
+
+static void
+gtk_level_bar_buildable_init (GtkBuildableIface *iface)
+{
+  iface->custom_tag_start = gtk_level_bar_buildable_custom_tag_start;
+  iface->custom_finished = gtk_level_bar_buildable_custom_finished;
+}
+
+static void
+gtk_level_bar_set_orientation (GtkLevelBar *self,
+                                  GtkOrientation  orientation)
+{
+  if (self->priv->orientation != orientation)
+    {
+      self->priv->orientation = orientation;
+      _gtk_orientable_set_style_classes (GTK_ORIENTABLE (self));
+
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+    }
+}
+
+static void
+gtk_level_bar_get_property (GObject    *obj,
+                            guint       property_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  GtkLevelBar *self = GTK_LEVEL_BAR (obj);
+
+  switch (property_id)
+    {
+    case PROP_VALUE:
+      g_value_set_double (value, gtk_level_bar_get_value (self));
+      break;
+    case PROP_MIN_VALUE:
+      g_value_set_double (value, gtk_level_bar_get_min_value (self));
+      break;
+    case PROP_MAX_VALUE:
+      g_value_set_double (value, gtk_level_bar_get_max_value (self));
+      break;
+    case PROP_MODE:
+      g_value_set_enum (value, gtk_level_bar_get_mode (self));
+      break;
+    case PROP_ORIENTATION:
+      g_value_set_enum (value, self->priv->orientation);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_level_bar_set_property (GObject      *obj,
+                            guint         property_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  GtkLevelBar *self = GTK_LEVEL_BAR (obj);
+
+  switch (property_id)
+    {
+    case PROP_VALUE:
+      gtk_level_bar_set_value (self, g_value_get_double (value));
+      break;
+    case PROP_MIN_VALUE:
+      gtk_level_bar_set_min_value (self, g_value_get_double (value));
+      break;
+    case PROP_MAX_VALUE:
+      gtk_level_bar_set_max_value (self, g_value_get_double (value));
+      break;
+    case PROP_MODE:
+      gtk_level_bar_set_mode (self, g_value_get_enum (value));
+      break;
+    case PROP_ORIENTATION:
+      gtk_level_bar_set_orientation (self, g_value_get_enum (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gtk_level_bar_finalize (GObject *obj)
+{
+  GtkLevelBar *self = GTK_LEVEL_BAR (obj);
+
+  g_list_free_full (self->priv->offsets, (GDestroyNotify) gtk_level_bar_offset_free);
+
+  G_OBJECT_CLASS (gtk_level_bar_parent_class)->finalize (obj);
+}
+
+static void
+gtk_level_bar_class_init (GtkLevelBarClass *klass)
+{
+  GObjectClass *oclass = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *wclass = GTK_WIDGET_CLASS (klass);
+
+  oclass->get_property = gtk_level_bar_get_property;
+  oclass->set_property = gtk_level_bar_set_property;
+  oclass->finalize = gtk_level_bar_finalize;
+
+  wclass->draw = gtk_level_bar_draw;
+  wclass->get_preferred_width = gtk_level_bar_get_preferred_width;
+  wclass->get_preferred_height = gtk_level_bar_get_preferred_height;
+
+  g_object_class_override_property (oclass, PROP_ORIENTATION, "orientation");
+
+  /**
+   * GtkLevelBar::offset-changed:
+   * @self: a #GtkLevelBar
+   * @name: the name of the offset that changed value
+   *
+   * Emitted when an offset specified on the bar changes value as an
+   * effect to gtk_level_bar_add_offset_value() being called.
+   *
+   * The signal supports detailed connections; you can connect to the
+   * detailed signal "changed::x" in order to only receive callbacks when
+   * the value of offset "x" changes.
+   *
+   * Since: 3.6
+   */
+  signals[SIGNAL_OFFSET_CHANGED] =
+    g_signal_new ("offset-changed",
+                  GTK_TYPE_LEVEL_BAR,
+                  G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED,
+                  G_STRUCT_OFFSET (GtkLevelBarClass, offset_changed),
+                  NULL, NULL,
+                  _gtk_marshal_VOID__STRING,
+                  G_TYPE_NONE,
+                  1, G_TYPE_STRING);
+
+  /**
+   * GtkLevelBar:value:
+   *
+   * The #GtkLevelBar:value property determines the currently
+   * filled value of the level bar.
+   *
+   * Since: 3.6
+   */
+  properties[PROP_VALUE] =
+    g_param_spec_double ("value",
+                         P_("Currently filled value level"),
+                         P_("Currently filled value level of the level bar"),
+                         0.0, G_MAXDOUBLE, 0.0,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  /**
+   * GtkLevelBar:min-value:
+   *
+   * The #GtkLevelBar:min-value property determines the minimum value of
+   * the interval that can be displayed by the bar.
+   *
+   * Since: 3.6
+   */
+  properties[PROP_MIN_VALUE] =
+    g_param_spec_double ("min-value",
+                         P_("Minimum value level for the bar"),
+                         P_("Minimum value level that can be displayed by the bar"),
+                         0.0, G_MAXDOUBLE, 0.0,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  /**
+   * GtkLevelBar:max-value:
+   *
+   * The #GtkLevelBar:max-value property determaxes the maximum value of
+   * the interval that can be displayed by the bar.
+   *
+   * Since: 3.6
+   */
+  properties[PROP_MAX_VALUE] =
+    g_param_spec_double ("max-value",
+                         P_("Maximum value level for the bar"),
+                         P_("Maximum value level that can be displayed by the bar"),
+                         0.0, G_MAXDOUBLE, 1.0,
+                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  /**
+   * GtkLevelBar:mode:
+   *
+   * The #GtkLevelBar:bar-mode property determines the way #GtkLevelBar
+   * interprets the value properties to draw the level fill area.
+   * Specifically, when the value is #GTK_LEVEL_BAR_MODE_CONTINUOUS,
+   * #GtkLevelBar will draw a single block representing the current value in
+   * that area; when the value is #GTK_LEVEL_BAR_MODE_DISCRETE,
+   * the widget will draw a succession of separate blocks filling the
+   * draw area, with the number of blocks being equal to the units separating
+   * the integral roundings of #GtkLevelBar:min-value and #GtkLevelBar:max-value.
+   *
+   * Since: 3.6
+   */
+  properties[PROP_MODE] =
+    g_param_spec_enum ("mode",
+                       P_("The mode of the value indicator"),
+                       P_("The mode of the value indicator displayed by the bar"),
+                       GTK_TYPE_LEVEL_BAR_MODE,
+                       GTK_LEVEL_BAR_MODE_CONTINUOUS,
+                       G_PARAM_READWRITE);
+
+  /**
+   * GtkLevelBar:min-block-height:
+   *
+   * The min-block-height style property determines the minimum
+   * height for blocks filling the #GtkLevelBar widget.
+   *
+   * Since: 3.6
+   */
+  gtk_widget_class_install_style_property
+    (wclass, g_param_spec_int ("min-block-height",
+                               P_("Minimum height for filling blocks"),
+                               P_("Minimum height for blocks that fill the bar"),
+                               1, G_MAXINT, DEFAULT_BLOCK_SIZE,
+                               G_PARAM_READWRITE));
+  /**
+   * GtkLevelBar:min-block-width:
+   *
+   * The min-block-width style property determines the minimum
+   * width for blocks filling the #GtkLevelBar widget.
+   *
+   * Since: 3.6
+   */
+  gtk_widget_class_install_style_property
+    (wclass, g_param_spec_int ("min-block-width",
+                               P_("Minimum width for filling blocks"),
+                               P_("Minimum width for blocks that fill the bar"),
+                               1, G_MAXINT, DEFAULT_BLOCK_SIZE,
+                               G_PARAM_READWRITE));
+
+  g_type_class_add_private (klass, sizeof (GtkLevelBarPrivate));
+  g_object_class_install_properties (oclass, LAST_PROPERTY, properties);
+}
+
+static void
+gtk_level_bar_init (GtkLevelBar *self)
+{
+  GtkStyleContext *context;
+
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTK_TYPE_LEVEL_BAR, GtkLevelBarPrivate);
+
+  context = gtk_widget_get_style_context (GTK_WIDGET (self));
+  gtk_style_context_add_class (context, GTK_STYLE_CLASS_LEVEL_BAR);
+
+  self->priv->cur_value = 0.0;
+  self->priv->min_value = 0.0;
+  self->priv->max_value = 1.0;
+
+  gtk_level_bar_ensure_offset (self, GTK_LEVEL_BAR_OFFSET_LOW, 0.25);
+  gtk_level_bar_ensure_offset (self, GTK_LEVEL_BAR_OFFSET_HIGH, 0.75);
+  gtk_level_bar_update_level_style_classes (self);
+
+  self->priv->bar_mode = GTK_LEVEL_BAR_MODE_CONTINUOUS;
+  gtk_level_bar_update_mode_style_classes (self);
+
+  /* set initial orientation and style classes */
+  self->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
+  _gtk_orientable_set_style_classes (GTK_ORIENTABLE (self));
+
+  gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
+}
+
+/**
+ * gtk_level_bar_new:
+ *
+ * Creates a new #GtkLevelBar.
+ *
+ * Returns: a #GtkLevelBar.
+ *
+ * Since: 3.6
+ */
+GtkWidget *
+gtk_level_bar_new (void)
+{
+  return g_object_new (GTK_TYPE_LEVEL_BAR, NULL);
+}
+
+/**
+ * gtk_level_bar_new_for_interval:
+ * @min_value: a positive value
+ * @max_value: a positive value
+ *
+ * Utility constructor that creates a new #GtkLevelBar for the specified
+ * interval.
+ *
+ * Returns: a #GtkLevelBar
+ *
+ * Since: 3.6
+ */
+GtkWidget *
+gtk_level_bar_new_for_interval (gdouble min_value,
+                                gdouble max_value)
+{
+  return g_object_new (GTK_TYPE_LEVEL_BAR,
+                       "min-value", min_value,
+                       "max-value", max_value,
+                       NULL);
+}
+
+/**
+ * gtk_level_bar_get_min_value:
+ * @self: a #GtkLevelBar
+ *
+ * Returns the value of the #GtkLevelBar:min-value property.
+ *
+ * Returns: a positive value
+ *
+ * Since: 3.6
+ */
+gdouble
+gtk_level_bar_get_min_value (GtkLevelBar *self)
+{
+  g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), 0.0);
+
+  return self->priv->min_value;
+}
+
+/**
+ * gtk_level_bar_get_max_value:
+ * @self: a #GtkLevelBar
+ *
+ * Returns the value of the #GtkLevelBar:max-value property.
+ *
+ * Returns: a positive value
+ *
+ * Since: 3.6
+ */
+gdouble
+gtk_level_bar_get_max_value (GtkLevelBar *self)
+{
+  g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), 0.0);
+
+  return self->priv->max_value;
+}
+
+/**
+ * gtk_level_bar_get_value:
+ * @self: a #GtkLevelBar
+ *
+ * Returns the value of the #GtkLevelBar:value property.
+ *
+ * Returns: a value in the interval between
+ *     #GtkLevelBar:min-value and #GtkLevelBar:max-value
+ *
+ * Since: 3.6
+ */
+gdouble
+gtk_level_bar_get_value (GtkLevelBar *self)
+{
+  g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), 0.0);
+
+  return self->priv->cur_value;
+}
+
+static void
+gtk_level_bar_set_value_internal (GtkLevelBar *self,
+                                  gdouble      value)
+{
+  self->priv->cur_value = value;
+  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VALUE]);
+  gtk_widget_queue_draw (GTK_WIDGET (self));
+}
+
+/**
+ * gtk_level_bar_set_min_value:
+ * @self: a #GtkLevelBar
+ * @value: a positive value
+ *
+ * Sets the value of the #GtkLevelBar:min-value property.
+ *
+ * Since: 3.6
+ */
+void
+gtk_level_bar_set_min_value (GtkLevelBar *self,
+                             gdouble      value)
+{
+  g_return_if_fail (GTK_IS_LEVEL_BAR (self));
+  g_return_if_fail (value >= 0.0);
+
+  if (value != self->priv->min_value)
+    {
+      self->priv->min_value = value;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MIN_VALUE]);
+
+      if (self->priv->min_value > self->priv->cur_value)
+        gtk_level_bar_set_value_internal (self, self->priv->min_value);
+
+      gtk_level_bar_update_level_style_classes (self);
+    }
+}
+
+/**
+ * gtk_level_bar_set_max_value:
+ * @self: a #GtkLevelBar
+ * @value: a positive value
+ *
+ * Sets the value of the #GtkLevelBar:max-value property.
+ *
+ * Since: 3.6
+ */
+void
+gtk_level_bar_set_max_value (GtkLevelBar *self,
+                             gdouble      value)
+{
+  g_return_if_fail (GTK_IS_LEVEL_BAR (self));
+  g_return_if_fail (value >= 0.0);
+
+  if (value != self->priv->max_value)
+    {
+      self->priv->max_value = value;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MAX_VALUE]);
+
+      if (self->priv->max_value < self->priv->cur_value)
+        gtk_level_bar_set_value_internal (self, self->priv->max_value);
+
+      gtk_level_bar_ensure_offsets_in_range (self);
+      gtk_level_bar_update_level_style_classes (self);
+    }
+}
+
+/**
+ * gtk_level_bar_set_value:
+ * @self: a #GtkLevelBar
+ * @value: a value in the interval between
+ *     #GtkLevelBar:min-value and #GtkLevelBar:max-value
+ *
+ * Sets the value of the #GtkLevelBar:value property.
+ *
+ * Since: 3.6
+ */
+void
+gtk_level_bar_set_value (GtkLevelBar *self,
+                         gdouble      value)
+{
+  g_return_if_fail (GTK_IS_LEVEL_BAR (self));
+
+  if (value != self->priv->cur_value)
+    {
+      gtk_level_bar_set_value_internal (self, value);
+      gtk_level_bar_update_level_style_classes (self);
+    }
+}
+
+/**
+ * gtk_level_bar_get_mode:
+ * @self: a #GtkLevelBar
+ *
+ * Returns the value of the #GtkLevelBar:mode property.
+ *
+ * Returns: a #GtkLevelBarMode
+ *
+ * Since: 3.6
+ */
+GtkLevelBarMode
+gtk_level_bar_get_mode (GtkLevelBar *self)
+{
+  g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), 0);
+
+  return self->priv->bar_mode;
+}
+
+/**
+ * gtk_level_bar_set_mode:
+ * @self: a #GtkLevelBar
+ * @mode: a #GtkLevelBarMode
+ *
+ * Sets the value of the #GtkLevelBar:mode property.
+ *
+ * Since: 3.6
+ */
+void
+gtk_level_bar_set_mode (GtkLevelBar     *self,
+                        GtkLevelBarMode  mode)
+{
+  g_return_if_fail (GTK_IS_LEVEL_BAR (self));
+
+  if (self->priv->bar_mode != mode)
+    {
+      self->priv->bar_mode = mode;
+      g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MODE]);
+
+      gtk_level_bar_update_mode_style_classes (self);
+      gtk_widget_queue_resize (GTK_WIDGET (self));
+    }
+}
+
+/**
+ * gtk_level_bar_remove_offset_value:
+ * @self: a #GtkLevelBar
+ * @name: (allow-none): the name of an offset in the bar
+ *
+ * Removes an offset marker previously added with
+ * gtk_level_bar_add_offset_value().
+ *
+ * Since: 3.6
+ */
+void
+gtk_level_bar_remove_offset_value (GtkLevelBar *self,
+                                   const gchar *name)
+{
+  GList *existing;
+
+  g_return_if_fail (GTK_IS_LEVEL_BAR (self));
+
+  existing = g_list_find_custom (self->priv->offsets, name, offset_find_func);
+  if (existing)
+    {
+      gtk_level_bar_offset_free (existing->data);
+      self->priv->offsets = g_list_delete_link (self->priv->offsets, existing);
+
+      gtk_level_bar_update_level_style_classes (self);
+    }
+}
+
+/**
+ * gtk_level_bar_add_offset_value:
+ * @self: a #GtkLevelBar
+ * @name: the name of the new offset
+ * @value: the value for the new offset
+ *
+ * Adds a new offset marker on @self at the position specified by @value.
+ * When the bar value is in the interval topped by @value (or between @value
+ * and #GtkLevelBar:max-value in case the offset is the last one on the bar)
+ * a style class named <literal>level-</literal>@name will be applied
+ * when rendering the level bar fill.
+ * If another offset marker named @name exists, its value will be
+ * replaced by @value.
+ *
+ * Since: 3.6
+ */
+void
+gtk_level_bar_add_offset_value (GtkLevelBar *self,
+                                const gchar *name,
+                                gdouble      value)
+{
+  GQuark name_quark;
+
+  g_return_if_fail (GTK_IS_LEVEL_BAR (self));
+  g_return_if_fail (gtk_level_bar_value_in_interval (self, value));
+
+  if (!gtk_level_bar_ensure_offset (self, name, value))
+    return;
+
+  gtk_level_bar_update_level_style_classes (self);
+  name_quark = g_quark_from_string (name);
+  g_signal_emit (self, signals[SIGNAL_OFFSET_CHANGED], name_quark, name);
+}
+
+/**
+ * gtk_level_bar_get_offset_value:
+ * @self: a #GtkLevelBar
+ * @name: (allow-none): the name of an offset in the bar
+ * @value: (out): location where to store the value
+ *
+ * Fetches the value specified for the offset marker @name in @self,
+ * returning %TRUE in case an offset named @name was found.
+ *
+ * Returns: %TRUE if the specified offset is found
+ *
+ * Since: 3.6
+ */
+gboolean
+gtk_level_bar_get_offset_value (GtkLevelBar *self,
+                                const gchar *name,
+                                gdouble     *value)
+{
+  GList *existing;
+  GtkLevelBarOffset *offset = NULL;
+
+  g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), 0.0);
+
+  existing = g_list_find_custom (self->priv->offsets, name, offset_find_func);
+  if (existing)
+    offset = existing->data;
+
+  if (!offset)
+    return FALSE;
+
+  if (value)
+    *value = offset->value;
+
+  return TRUE;
+}
diff --git a/gtk/gtklevelbar.h b/gtk/gtklevelbar.h
new file mode 100644
index 0000000..60dae3b
--- /dev/null
+++ b/gtk/gtklevelbar.h
@@ -0,0 +1,127 @@
+/* GTK - The GIMP Toolkit
+ * Copyright  2012 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Cosimo Cecchi <cosimoc gnome org>
+ *
+ */
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#ifndef __GTK_LEVEL_BAR_H__
+#define __GTK_LEVEL_BAR_H__
+
+#include <gtk/gtkwidget.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_LEVEL_BAR            (gtk_level_bar_get_type ())
+#define GTK_LEVEL_BAR(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_LEVEL_BAR, GtkLevelBar))
+#define GTK_LEVEL_BAR_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_LEVEL_BAR, GtkLevelBarClass))
+#define GTK_IS_LEVEL_BAR(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_LEVEL_BAR))
+#define GTK_IS_LEVEL_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_LEVEL_BAR))
+#define GTK_LEVEL_BAR_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_LEVEL_BAR, GtkLevelBarClass))
+
+/**
+ * GTK_LEVEL_BAR_OFFSET_LOW:
+ *
+ * The name used for the stock low offset included by #GtkLevelBar.
+ *
+ * Since: 3.6
+ */
+#define GTK_LEVEL_BAR_OFFSET_LOW  "low"
+
+/**
+ * GTK_LEVEL_BAR_OFFSET_HIGH:
+ *
+ * The name used for the stock high offset included by #GtkLevelBar.
+ *
+ * Since: 3.6
+ */
+#define GTK_LEVEL_BAR_OFFSET_HIGH "high"
+
+typedef struct _GtkLevelBarClass   GtkLevelBarClass;
+typedef struct _GtkLevelBar        GtkLevelBar;
+typedef struct _GtkLevelBarPrivate GtkLevelBarPrivate;
+
+struct _GtkLevelBar {
+  /*< private >*/
+  GtkWidget parent;
+
+  GtkLevelBarPrivate *priv;
+};
+
+struct _GtkLevelBarClass {
+  /*< private >*/
+  GtkWidgetClass parent_class;
+
+  void (* offset_changed) (GtkLevelBar *self,
+                           const gchar *name);
+
+  /* padding for future class expansion */
+  gpointer padding[16];
+};
+
+GDK_AVAILABLE_IN_3_6
+GType      gtk_level_bar_get_type           (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_3_6
+GtkWidget *gtk_level_bar_new                (void);
+
+GDK_AVAILABLE_IN_3_6
+GtkWidget *gtk_level_bar_new_for_interval   (gdouble      min_value,
+                                             gdouble      max_value);
+
+GDK_AVAILABLE_IN_3_6
+void       gtk_level_bar_set_mode           (GtkLevelBar *self,
+                                             GtkLevelBarMode mode);
+GDK_AVAILABLE_IN_3_6
+GtkLevelBarMode gtk_level_bar_get_mode      (GtkLevelBar *self);
+
+GDK_AVAILABLE_IN_3_6
+void       gtk_level_bar_set_value          (GtkLevelBar *self,
+                                             gdouble      value);
+GDK_AVAILABLE_IN_3_6
+gdouble    gtk_level_bar_get_value          (GtkLevelBar *self);
+
+GDK_AVAILABLE_IN_3_6
+void       gtk_level_bar_set_min_value      (GtkLevelBar *self,
+                                             gdouble      value);
+GDK_AVAILABLE_IN_3_6
+gdouble    gtk_level_bar_get_min_value      (GtkLevelBar *self);
+
+GDK_AVAILABLE_IN_3_6
+void       gtk_level_bar_set_max_value      (GtkLevelBar *self,
+                                             gdouble      value);
+GDK_AVAILABLE_IN_3_6
+gdouble    gtk_level_bar_get_max_value      (GtkLevelBar *self);
+
+GDK_AVAILABLE_IN_3_6
+void       gtk_level_bar_add_offset_value   (GtkLevelBar *self,
+                                             const gchar *name,
+                                             gdouble      value);
+GDK_AVAILABLE_IN_3_6
+void       gtk_level_bar_remove_offset_value (GtkLevelBar *self,
+                                              const gchar *name);
+GDK_AVAILABLE_IN_3_6
+gboolean   gtk_level_bar_get_offset_value   (GtkLevelBar *self,
+                                             const gchar *name,
+                                             gdouble     *value);
+
+G_END_DECLS
+
+#endif /* __GTK_LEVEL_BAR_H__ */
diff --git a/gtk/gtkstylecontext.h b/gtk/gtkstylecontext.h
index 4975862..456b5a1 100644
--- a/gtk/gtkstylecontext.h
+++ b/gtk/gtkstylecontext.h
@@ -691,6 +691,16 @@ struct _GtkStyleContextClass
  */
 #define GTK_STYLE_CLASS_OSD "osd"
 
+/**
+ * GTK_STYLE_CLASS_LEVEL_BAR:
+ *
+ * A CSS class used when rendering a level indicator, such
+ * as a battery charge level, or a password strength.
+ *
+ * This is used by #GtkLevelBar.
+ */
+#define GTK_STYLE_CLASS_LEVEL_BAR "level-bar"
+
 /* Predefined set of widget regions */
 
 /**



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]