Florian Müllner 329a286303 st/label: Don't guard against NULL text
Commit 9168f6055e marked the parameter as nullable, which is in
conflict with the precondition check. But given that NULL is
valid for the underlying ClutterText (and GtkLabel accepts it
as well), there's no strong argument for disallowing NULL, so
remove the precondition check.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2465>
2022-09-02 18:59:07 +02:00

550 lines
15 KiB
C

/* -*- 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;
}