summaryrefslogtreecommitdiff
path: root/gtk/gtklevelbar.c
diff options
context:
space:
mode:
authorCosimo Cecchi <cosimoc@gnome.org>2012-05-29 17:00:33 -0400
committerMatthias Clasen <mclasen@redhat.com>2012-07-10 22:41:12 -0400
commit126a2308ca467744178d4be3309403f6899de987 (patch)
tree66a321f37f4874f89b57772044662bb9cd195cb7 /gtk/gtklevelbar.c
parentf7683b05b1c29ecc715715b95b2011a895917cf4 (diff)
downloadgtk+-126a2308ca467744178d4be3309403f6899de987.tar.gz
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
Diffstat (limited to 'gtk/gtklevelbar.c')
-rw-r--r--gtk/gtklevelbar.c1216
1 files changed, 1216 insertions, 0 deletions
diff --git a/gtk/gtklevelbar.c b/gtk/gtklevelbar.c
new file mode 100644
index 0000000000..4a371042e5
--- /dev/null
+++ b/gtk/gtklevelbar.c
@@ -0,0 +1,1216 @@
+/* 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 level
+ * 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.
+ *
+ * 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);
+
+ /* 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_style_context_remove_class (context, STYLE_CLASS_EMPTY_FILL_BLOCK);
+
+ 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);
+ }
+}
+
+#define OFFSETS_ELEMENT "offsets"
+#define OFFSET_ELEMENT "offset"
+#define OFFSET_NAME "name"
+#define OFFSET_VALUE "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 idx;
+
+ if (strcmp (element_name, OFFSET_ELEMENT) == 0)
+ {
+ for (idx = 0; names[idx] != NULL; idx++)
+ {
+ if (strcmp (names[idx], OFFSET_NAME) == 0)
+ name = values[idx];
+ else if (strcmp (names[idx], OFFSET_VALUE) == 0)
+ value_str = values[idx];
+ }
+
+ 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 if (strcmp (element_name, OFFSETS_ELEMENT) == 0)
+ {
+ return;
+ }
+ else
+ {
+ g_warning ("Unsupported type tag for GtkLevelBar: %s\n",
+ 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_ELEMENT) != 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;
+
+ if (strcmp (tagname, OFFSETS_ELEMENT) != 0)
+ return;
+
+ parser_data = user_data;
+ self = parser_data->self;
+
+ for (l = parser_data->offsets; l != NULL; l = l->next)
+ {
+ offset = l->data;
+ gtk_level_bar_add_offset_value (self, offset->name, offset->value);
+ }
+
+ 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");
+
+ 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:bar-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);
+
+ 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));
+ 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:bar-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:bar-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
+ *
+ * Returns the value specified for the offset marker @name in @self, or
+ * zero if it's not found.
+ *
+ * Returns: a value in the interval between
+ * #GtkLevelBar:min-value and #GtkLevelBar:max-value, or zero.
+ *
+ * Since: 3.6
+ */
+gdouble
+gtk_level_bar_get_offset_value (GtkLevelBar *self,
+ const gchar *name)
+{
+ 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 offset->value;
+
+ return 0.0;
+}