* @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
+ * 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
+ * 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
+ * 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
#include <math.h>
#include <stdlib.h>
+#include "a11y/gtklevelbaraccessible.h"
+
+#include "fallback-c89.c"
+
#define DEFAULT_BLOCK_SIZE 3
/* these don't make sense outside of GtkLevelBar, so we don't add
PROP_MIN_VALUE,
PROP_MAX_VALUE,
PROP_MODE,
+ PROP_INVERTED,
LAST_PROPERTY,
PROP_ORIENTATION /* overridden */
};
GList *offsets;
GtkLevelBarMode bar_mode;
+
+ guint inverted : 1;
};
-static void gtk_level_bar_set_value_internal (GtkLevelBar *self,
- gdouble value);
+static void gtk_level_bar_set_value_internal (GtkLevelBar *self,
+ gdouble value);
static GtkLevelBarOffset *
gtk_level_bar_offset_new (const gchar *name,
existing = g_list_find_custom (self->priv->offsets, name, offset_find_func);
if (existing)
offset = existing->data;
-
+
if (offset && (offset->value == value))
return FALSE;
static void
gtk_level_bar_draw_fill_continuous (GtkLevelBar *self,
cairo_t *cr,
+ gboolean inverted,
cairo_rectangle_int_t *fill_area)
{
GtkWidget *widget = GTK_WIDGET (self);
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;
(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);
+ {
+ block_area.width = (gint) floor (block_area.width * fill_percentage);
+
+ if (inverted)
+ block_area.x += base_area.width - block_area.width;
+ }
else
- block_area.height = (gint) floor (block_area.height * fill_percentage);
+ {
+ block_area.height = (gint) floor (block_area.height * fill_percentage);
- gtk_style_context_remove_class (context, STYLE_CLASS_EMPTY_FILL_BLOCK);
+ if (inverted)
+ block_area.y += base_area.height - block_area.height;
+ }
gtk_render_background (context, cr, block_area.x, block_area.y,
block_area.width, block_area.height);
static void
gtk_level_bar_draw_fill_discrete (GtkLevelBar *self,
cairo_t *cr,
+ gboolean inverted,
cairo_rectangle_int_t *fill_area)
{
GtkWidget *widget = GTK_WIDGET (self);
{
block_draw_height = MAX (block_draw_height, block_area.height - block_margin.top - block_margin.bottom);
block_area.y += block_margin.top;
+
+ if (inverted)
+ block_area.x += block_area.width - block_draw_width;
}
else
{
block_draw_width = MAX (block_draw_width, block_area.width - block_margin.left - block_margin.right);
block_area.x += block_margin.left;
+
+ if (inverted)
+ block_area.y += block_area.height - block_draw_height;
}
for (idx = 0; idx < num_blocks; idx++)
{
if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
- block_area.x += block_margin.left;
+ {
+ if (inverted)
+ block_area.x -= block_margin.right;
+ else
+ block_area.x += block_margin.left;
+ }
else
- block_area.y += block_margin.top;
+ {
+ if (inverted)
+ block_area.y -= block_margin.bottom;
+ else
+ block_area.y += block_margin.top;
+ }
if (idx > num_filled - 1)
gtk_style_context_add_class (context, STYLE_CLASS_EMPTY_FILL_BLOCK);
block_draw_width, block_draw_height);
if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
- block_area.x += block_draw_width + block_margin.right;
+ {
+ if (inverted)
+ block_area.x -= block_draw_width + block_margin.left;
+ else
+ block_area.x += block_draw_width + block_margin.right;
+ }
else
- block_area.y += block_draw_height + block_margin.bottom;
+ {
+ if (inverted)
+ block_area.y -= block_draw_height + block_margin.top;
+ else
+ block_area.y += block_draw_height + block_margin.bottom;
+ }
}
gtk_style_context_restore (context);
cairo_t *cr)
{
GtkWidget *widget = GTK_WIDGET (self);
- GtkBorder trough_borders;
+ GtkBorder trough_borders;
+ gboolean inverted;
cairo_rectangle_int_t fill_area;
gtk_level_bar_get_borders (self, &trough_borders);
fill_area.height = gtk_widget_get_allocated_height (widget) -
trough_borders.top - trough_borders.bottom;
+ inverted = self->priv->inverted;
+ if (gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL)
+ {
+ if (self->priv->orientation == GTK_ORIENTATION_HORIZONTAL)
+ inverted = !inverted;
+ }
+
if (self->priv->bar_mode == GTK_LEVEL_BAR_MODE_CONTINUOUS)
- gtk_level_bar_draw_fill_continuous (self, cr, &fill_area);
+ gtk_level_bar_draw_fill_continuous (self, cr, inverted, &fill_area);
else if (self->priv->bar_mode == GTK_LEVEL_BAR_MODE_DISCRETE)
- gtk_level_bar_draw_fill_discrete (self, cr, &fill_area);
+ gtk_level_bar_draw_fill_discrete (self, cr, inverted, &fill_area);
}
static gboolean
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)))
+ if (((l->prev == NULL) && (value <= offset->value)) ||
+ ((l->next == NULL) && (value >= offset->value)))
{
value_class = offset_style_class;
}
}
}
-#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)
+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, OFFSET_ELEMENT) == 0)
+ if (strcmp (element_name, "offsets") == 0)
+ ;
+ else if (strcmp (element_name, "offset") == 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 (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));
+ offset = gtk_level_bar_offset_new (name, g_ascii_strtod (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);
+ 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 gboolean
-gtk_level_bar_buildable_custom_tag_start (GtkBuildable *buildable,
- GtkBuilder *builder,
- GObject *child,
- const gchar *tagname,
+gtk_level_bar_buildable_custom_tag_start (GtkBuildable *buildable,
+ GtkBuilder *builder,
+ GObject *child,
+ const gchar *tagname,
GMarkupParser *parser,
- gpointer *data)
+ gpointer *data)
{
OffsetsParserData *parser_data;
if (child)
return FALSE;
- if (strcmp (tagname, OFFSETS_ELEMENT) != 0)
+ if (strcmp (tagname, "offsets") != 0)
return FALSE;
parser_data = g_slice_new0 (OffsetsParserData);
static void
gtk_level_bar_buildable_custom_finished (GtkBuildable *buildable,
- GtkBuilder *builder,
- GObject *child,
- const gchar *tagname,
- gpointer user_data)
+ 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;
+ 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);
}
case PROP_MODE:
g_value_set_enum (value, gtk_level_bar_get_mode (self));
break;
+ case PROP_INVERTED:
+ g_value_set_boolean (value, gtk_level_bar_get_inverted (self));
+ break;
case PROP_ORIENTATION:
g_value_set_enum (value, self->priv->orientation);
break;
case PROP_MODE:
gtk_level_bar_set_mode (self, g_value_get_enum (value));
break;
+ case PROP_INVERTED:
+ gtk_level_bar_set_inverted (self, g_value_get_boolean (value));
+ break;
case PROP_ORIENTATION:
gtk_level_bar_set_orientation (self, g_value_get_enum (value));
break;
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,
0.0, G_MAXDOUBLE, 1.0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
/**
- * GtkLevelBar:bar-mode:
+ * GtkLevelBar:mode:
*
* The #GtkLevelBar:bar-mode property determines the way #GtkLevelBar
* interprets the value properties to draw the level fill area.
GTK_LEVEL_BAR_MODE_CONTINUOUS,
G_PARAM_READWRITE);
- gtk_widget_class_install_style_property
+ /**
+ * GtkLevelBar:inverted:
+ *
+ * Level bars normally grow from top to bottom or left to right.
+ * Inverted level bars grow in the opposite direction.
+ *
+ * Since: 3.8
+ */
+ properties[PROP_INVERTED] =
+ g_param_spec_boolean ("inverted",
+ P_("Inverted"),
+ P_("Invert the direction in which the level bar grows"),
+ FALSE,
+ 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));
- gtk_widget_class_install_style_property
+ /**
+ * 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"),
g_type_class_add_private (klass, sizeof (GtkLevelBarPrivate));
g_object_class_install_properties (oclass, LAST_PROPERTY, properties);
+
+ gtk_widget_class_set_accessible_type (wclass, GTK_TYPE_LEVEL_BAR_ACCESSIBLE);
}
static void
self->priv->orientation = GTK_ORIENTATION_HORIZONTAL;
_gtk_orientable_set_style_classes (GTK_ORIENTABLE (self));
+ self->priv->inverted = FALSE;
+
gtk_widget_set_has_window (GTK_WIDGET (self), FALSE);
}
/**
* gtk_level_bar_get_min_value:
* @self: a #GtkLevelBar
- *
+ *
* Returns the value of the #GtkLevelBar:min-value property.
*
* Returns: a positive value
/**
* gtk_level_bar_get_max_value:
* @self: a #GtkLevelBar
- *
+ *
* Returns the value of the #GtkLevelBar:max-value property.
*
* Returns: a positive value
* 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
* 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
* gtk_level_bar_get_mode:
* @self: a #GtkLevelBar
*
- * Returns the value of the #GtkLevelBar:bar-mode property
+ * Returns the value of the #GtkLevelBar:mode property.
*
* Returns: a #GtkLevelBarMode
*
* @self: a #GtkLevelBar
* @mode: a #GtkLevelBarMode
*
- * Sets the value of the #GtkLevelBar:bar-mode property
+ * Sets the value of the #GtkLevelBar:mode property.
*
* Since: 3.6
*/
}
}
+/**
+ * gtk_level_bar_get_inverted:
+ * @self: a #GtkLevelBar
+ *
+ * Return the value of the #GtkLevelBar:inverted property.
+ *
+ * Return value: %TRUE if the level bar is inverted
+ *
+ * Since: 3.8
+ */
+gboolean
+gtk_level_bar_get_inverted (GtkLevelBar *self)
+{
+ g_return_val_if_fail (GTK_IS_LEVEL_BAR (self), FALSE);
+
+ return self->priv->inverted;
+}
+
+/**
+ * gtk_level_bar_set_inverted:
+ * @self: a #GtkLevelBar
+ * @inverted: %TRUE to invert the level bar
+ *
+ * Sets the value of the #GtkLevelBar:inverted property.
+ *
+ * Since: 3.8
+ */
+void
+gtk_level_bar_set_inverted (GtkLevelBar *self,
+ gboolean inverted)
+{
+ GtkLevelBarPrivate *priv;
+
+ g_return_if_fail (GTK_IS_LEVEL_BAR (self));
+
+ priv = self->priv;
+
+ if (priv->inverted != inverted)
+ {
+ priv->inverted = inverted;
+
+ gtk_widget_queue_resize (GTK_WIDGET (self));
+
+ g_object_notify (G_OBJECT (self), "inverted");
+ }
+}
+
/**
* gtk_level_bar_remove_offset_value:
* @self: a #GtkLevelBar
* 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
*
- * Returns the value specified for the offset marker @name in @self, or
- * zero if it's not found.
+ * Fetches the value specified for the offset marker @name in @self,
+ * returning %TRUE in case an offset named @name was found.
*
- * Returns: a value in the interval between
- * #GtkLevelBar:min-value and #GtkLevelBar:max-value, or zero.
+ * Returns: %TRUE if the specified offset is found
*
- * Since: 3.6
+ * Since: 3.6
*/
-gdouble
+gboolean
gtk_level_bar_get_offset_value (GtkLevelBar *self,
- const gchar *name)
+ const gchar *name,
+ gdouble *value)
{
GList *existing;
GtkLevelBarOffset *offset = NULL;
if (existing)
offset = existing->data;
- if (offset)
- return offset->value;
+ if (!offset)
+ return FALSE;
+
+ if (value)
+ *value = offset->value;
- return 0.0;
+ return TRUE;
}