diff options
Diffstat (limited to 'src/st/st-label.c')
-rw-r--r-- | src/st/st-label.c | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/src/st/st-label.c b/src/st/st-label.c new file mode 100644 index 0000000..fe77743 --- /dev/null +++ b/src/st/st-label.c @@ -0,0 +1,549 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * st-label.c: Plain label actor + * + * Copyright 2008,2009 Intel Corporation + * Copyright 2009 Red Hat, Inc. + * Copyright 2010 Florian Müllner + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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 program. If not, see <http://www.gnu.org/licenses/>. + */ + +/** + * SECTION:st-label + * @short_description: Widget for displaying text + * + * #StLabel is a simple widget for displaying text. It derives from + * #StWidget to add extra style and placement functionality over + * #ClutterText. The internal #ClutterText is publicly accessibly to allow + * applications to set further properties. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> + +#include <glib.h> + +#include <clutter/clutter.h> + +#include "st-label.h" +#include "st-private.h" +#include "st-widget.h" + +#include <st/st-widget-accessible.h> + +enum +{ + PROP_0, + + PROP_CLUTTER_TEXT, + PROP_TEXT, + + N_PROPS +}; + +static GParamSpec *props[N_PROPS] = { NULL, }; + +struct _StLabelPrivate +{ + ClutterActor *label; + + StShadow *shadow_spec; + + CoglPipeline *text_shadow_pipeline; + float shadow_width; + float shadow_height; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (StLabel, st_label, ST_TYPE_WIDGET); + +static GType st_label_accessible_get_type (void) G_GNUC_CONST; + +static void +st_label_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + StLabel *label = ST_LABEL (gobject); + + switch (prop_id) + { + case PROP_TEXT: + st_label_set_text (label, g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_label_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + StLabelPrivate *priv = ST_LABEL (gobject)->priv; + + switch (prop_id) + { + case PROP_CLUTTER_TEXT: + g_value_set_object (value, priv->label); + break; + + case PROP_TEXT: + g_value_set_string (value, clutter_text_get_text (CLUTTER_TEXT (priv->label))); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +st_label_style_changed (StWidget *self) +{ + StLabelPrivate *priv = ST_LABEL(self)->priv; + StThemeNode *theme_node; + StShadow *shadow_spec; + + theme_node = st_widget_get_theme_node (self); + + shadow_spec = st_theme_node_get_text_shadow (theme_node); + if (!priv->shadow_spec || !shadow_spec || + !st_shadow_equal (shadow_spec, priv->shadow_spec)) + { + g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref); + + g_clear_pointer (&priv->shadow_spec, st_shadow_unref); + if (shadow_spec) + priv->shadow_spec = st_shadow_ref (shadow_spec); + } + + _st_set_text_from_style ((ClutterText *)priv->label, st_widget_get_theme_node (self)); + + ST_WIDGET_CLASS (st_label_parent_class)->style_changed (self); +} + +static void +st_label_get_preferred_width (ClutterActor *actor, + gfloat for_height, + gfloat *min_width_p, + gfloat *natural_width_p) +{ + StLabelPrivate *priv = ST_LABEL (actor)->priv; + StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); + + st_theme_node_adjust_for_height (theme_node, &for_height); + + clutter_actor_get_preferred_width (priv->label, for_height, + min_width_p, + natural_width_p); + + st_theme_node_adjust_preferred_width (theme_node, min_width_p, natural_width_p); +} + +static void +st_label_get_preferred_height (ClutterActor *actor, + gfloat for_width, + gfloat *min_height_p, + gfloat *natural_height_p) +{ + StLabelPrivate *priv = ST_LABEL (actor)->priv; + StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); + + st_theme_node_adjust_for_width (theme_node, &for_width); + + clutter_actor_get_preferred_height (priv->label, for_width, + min_height_p, + natural_height_p); + + st_theme_node_adjust_preferred_height (theme_node, min_height_p, natural_height_p); +} + +static void +st_label_allocate (ClutterActor *actor, + const ClutterActorBox *box) +{ + StLabelPrivate *priv = ST_LABEL (actor)->priv; + StThemeNode *theme_node = st_widget_get_theme_node (ST_WIDGET (actor)); + ClutterActorBox content_box; + + clutter_actor_set_allocation (actor, box); + + st_theme_node_get_content_box (theme_node, box, &content_box); + + clutter_actor_allocate (priv->label, &content_box); +} + +static void +st_label_dispose (GObject *object) +{ + StLabelPrivate *priv = ST_LABEL (object)->priv; + + priv->label = NULL; + g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref); + + G_OBJECT_CLASS (st_label_parent_class)->dispose (object); +} + +static void +st_label_paint (ClutterActor *actor, + ClutterPaintContext *paint_context) +{ + StLabelPrivate *priv = ST_LABEL (actor)->priv; + + st_widget_paint_background (ST_WIDGET (actor), paint_context); + + if (priv->shadow_spec) + { + ClutterActorBox allocation; + float width, height; + float resource_scale; + + clutter_actor_get_allocation_box (priv->label, &allocation); + clutter_actor_box_get_size (&allocation, &width, &height); + + resource_scale = clutter_actor_get_resource_scale (priv->label); + + width *= resource_scale; + height *= resource_scale; + + if (priv->text_shadow_pipeline == NULL || + width != priv->shadow_width || + height != priv->shadow_height) + { + g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref); + + priv->shadow_width = width; + priv->shadow_height = height; + priv->text_shadow_pipeline = + _st_create_shadow_pipeline_from_actor (priv->shadow_spec, + priv->label); + } + + if (priv->text_shadow_pipeline != NULL) + { + CoglFramebuffer *framebuffer; + + framebuffer = + clutter_paint_context_get_framebuffer (paint_context); + _st_paint_shadow_with_opacity (priv->shadow_spec, + framebuffer, + priv->text_shadow_pipeline, + &allocation, + clutter_actor_get_paint_opacity (priv->label)); + } + } + + clutter_actor_paint (priv->label, paint_context); +} + +static void +st_label_resource_scale_changed (ClutterActor *actor) +{ + StLabelPrivate *priv = ST_LABEL (actor)->priv; + + g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref); + + if (CLUTTER_ACTOR_CLASS (st_label_parent_class)->resource_scale_changed) + CLUTTER_ACTOR_CLASS (st_label_parent_class)->resource_scale_changed (actor); +} + +static void +st_label_class_init (StLabelClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass); + StWidgetClass *widget_class = ST_WIDGET_CLASS (klass); + + gobject_class->set_property = st_label_set_property; + gobject_class->get_property = st_label_get_property; + gobject_class->dispose = st_label_dispose; + + actor_class->paint = st_label_paint; + actor_class->allocate = st_label_allocate; + actor_class->get_preferred_width = st_label_get_preferred_width; + actor_class->get_preferred_height = st_label_get_preferred_height; + actor_class->resource_scale_changed = st_label_resource_scale_changed; + + widget_class->style_changed = st_label_style_changed; + widget_class->get_accessible_type = st_label_accessible_get_type; + + /** + * StLabel:clutter-text: + * + * The internal #ClutterText actor supporting the label + */ + props[PROP_CLUTTER_TEXT] = + g_param_spec_object ("clutter-text", + "Clutter Text", + "Internal ClutterText actor", + CLUTTER_TYPE_TEXT, + ST_PARAM_READABLE); + + /** + * StLabel:text: + * + * The current text being display in the #StLabel. + */ + props[PROP_TEXT] = + g_param_spec_string ("text", + "Text", + "Text of the label", + NULL, + ST_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (gobject_class, N_PROPS, props); +} + +static void +invalidate_shadow_pipeline (GObject *object, + GParamSpec *pspec, + StLabel *label) +{ + StLabelPrivate *priv = st_label_get_instance_private (label); + + g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref); +} + +static void +st_label_init (StLabel *label) +{ + ClutterActor *actor = CLUTTER_ACTOR (label); + StLabelPrivate *priv; + + label->priv = priv = st_label_get_instance_private (label); + + label->priv->label = g_object_new (CLUTTER_TYPE_TEXT, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + label->priv->text_shadow_pipeline = NULL; + label->priv->shadow_width = -1.; + label->priv->shadow_height = -1.; + + /* These properties might get set from CSS using _st_set_text_from_style */ + g_signal_connect (priv->label, "notify::font-description", + G_CALLBACK (invalidate_shadow_pipeline), label); + + g_signal_connect (priv->label, "notify::attributes", + G_CALLBACK (invalidate_shadow_pipeline), label); + + g_signal_connect (priv->label, "notify::justify", + G_CALLBACK (invalidate_shadow_pipeline), label); + + g_signal_connect (priv->label, "notify::line-alignment", + G_CALLBACK (invalidate_shadow_pipeline), label); + + clutter_actor_add_child (actor, priv->label); + + clutter_actor_set_offscreen_redirect (actor, + CLUTTER_OFFSCREEN_REDIRECT_ALWAYS); +} + +/** + * st_label_new: + * @text: (nullable): text to set the label to + * + * Create a new #StLabel with the label specified by @text. + * + * Returns: a new #StLabel + */ +StWidget * +st_label_new (const gchar *text) +{ + if (text == NULL || *text == '\0') + return g_object_new (ST_TYPE_LABEL, NULL); + else + return g_object_new (ST_TYPE_LABEL, + "text", text, + NULL); +} + +/** + * st_label_get_text: + * @label: a #StLabel + * + * Get the text displayed on the label. + * + * Returns: (transfer none): the text for the label. This must not be freed by + * the application + */ +const gchar * +st_label_get_text (StLabel *label) +{ + g_return_val_if_fail (ST_IS_LABEL (label), NULL); + + return clutter_text_get_text (CLUTTER_TEXT (label->priv->label)); +} + +/** + * st_label_set_text: + * @label: a #StLabel + * @text: (nullable): text to set the label to + * + * Sets the text displayed by the label. + */ +void +st_label_set_text (StLabel *label, + const gchar *text) +{ + StLabelPrivate *priv; + ClutterText *ctext; + + g_return_if_fail (ST_IS_LABEL (label)); + + priv = label->priv; + ctext = CLUTTER_TEXT (priv->label); + + if (clutter_text_get_editable (ctext) || + g_strcmp0 (clutter_text_get_text (ctext), text) != 0) + { + g_clear_pointer (&priv->text_shadow_pipeline, cogl_object_unref); + + clutter_text_set_text (ctext, text); + + g_object_notify_by_pspec (G_OBJECT (label), props[PROP_TEXT]); + } +} + +/** + * st_label_get_clutter_text: + * @label: a #StLabel + * + * Retrieve the internal #ClutterText used by @label so that extra parameters + * can be set. + * + * Returns: (transfer none): the #ClutterText used by #StLabel. The actor + * is owned by the #StLabel and should not be destroyed by the application. + */ +ClutterActor* +st_label_get_clutter_text (StLabel *label) +{ + g_return_val_if_fail (ST_LABEL (label), NULL); + + return label->priv->label; +} + + +/******************************************************************************/ +/*************************** ACCESSIBILITY SUPPORT ****************************/ +/******************************************************************************/ + +#define ST_TYPE_LABEL_ACCESSIBLE st_label_accessible_get_type () + +#define ST_LABEL_ACCESSIBLE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + ST_TYPE_LABEL_ACCESSIBLE, StLabelAccessible)) + +#define ST_IS_LABEL_ACCESSIBLE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + ST_TYPE_LABEL_ACCESSIBLE)) + +#define ST_LABEL_ACCESSIBLE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + ST_TYPE_LABEL_ACCESSIBLE, StLabelAccessibleClass)) + +#define ST_IS_LABEL_ACCESSIBLE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + ST_TYPE_LABEL_ACCESSIBLE)) + +#define ST_LABEL_ACCESSIBLE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + ST_TYPE_LABEL_ACCESSIBLE, StLabelAccessibleClass)) + +typedef struct _StLabelAccessible StLabelAccessible; +typedef struct _StLabelAccessibleClass StLabelAccessibleClass; + +struct _StLabelAccessible +{ + StWidgetAccessible parent; +}; + +struct _StLabelAccessibleClass +{ + StWidgetAccessibleClass parent_class; +}; + +/* AtkObject */ +static void st_label_accessible_initialize (AtkObject *obj, + gpointer data); +static const gchar * st_label_accessible_get_name (AtkObject *obj); + +G_DEFINE_TYPE (StLabelAccessible, st_label_accessible, ST_TYPE_WIDGET_ACCESSIBLE) + +static void +st_label_accessible_class_init (StLabelAccessibleClass *klass) +{ + AtkObjectClass *atk_class = ATK_OBJECT_CLASS (klass); + + atk_class->initialize = st_label_accessible_initialize; + atk_class->get_name = st_label_accessible_get_name; +} + +static void +st_label_accessible_init (StLabelAccessible *self) +{ + /* initialization done on AtkObject->initialize */ +} + +static void +label_text_notify_cb (StLabel *label, + GParamSpec *pspec, + AtkObject *accessible) +{ + g_object_notify (G_OBJECT (accessible), "accessible-name"); +} + +static void +st_label_accessible_initialize (AtkObject *obj, + gpointer data) +{ + ATK_OBJECT_CLASS (st_label_accessible_parent_class)->initialize (obj, data); + + g_signal_connect (data, "notify::text", + G_CALLBACK (label_text_notify_cb), + obj); + + obj->role = ATK_ROLE_LABEL; +} + +static const gchar * +st_label_accessible_get_name (AtkObject *obj) +{ + const gchar *name = NULL; + + g_return_val_if_fail (ST_IS_LABEL_ACCESSIBLE (obj), NULL); + + name = ATK_OBJECT_CLASS (st_label_accessible_parent_class)->get_name (obj); + if (name == NULL) + { + ClutterActor *actor = NULL; + + actor = CLUTTER_ACTOR (atk_gobject_accessible_get_object (ATK_GOBJECT_ACCESSIBLE (obj))); + + if (actor == NULL || st_widget_has_style_class_name (ST_WIDGET (actor), "hidden")) + name = NULL; + else + name = st_label_get_text (ST_LABEL (actor)); + } + + return name; +} |