gnome-shell/src/st/st-theme-node.c
Colin Walters dcd7762253 [StThemeNode] Don't give negative width/height for content boxes
If the space we're allocated is too small for our border + padding
constraints, don't give negative allocations to callers.  Squash
to zero.

It isn't really useful for callers to get negative content sizes,
and certainly breaks most allocation code.

https://bugzilla.gnome.org/show_bug.cgi?id=600734
2009-11-05 15:28:56 -05:00

2277 lines
65 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#include <stdlib.h>
#include <string.h>
#include "st-theme-private.h"
#include "st-theme-context.h"
#include "st-theme-node.h"
static void st_theme_node_init (StThemeNode *node);
static void st_theme_node_class_init (StThemeNodeClass *klass);
static void st_theme_node_finalize (GObject *object);
struct _StThemeNode {
GObject parent;
StThemeContext *context;
StThemeNode *parent_node;
StTheme *theme;
PangoFontDescription *font_desc;
ClutterColor background_color;
ClutterColor foreground_color;
ClutterColor border_color[4];
double border_width[4];
double border_radius[4];
guint padding[4];
int width;
int height;
int min_width;
int min_height;
char *background_image;
StBorderImage *border_image;
GType element_type;
char *element_id;
char *element_class;
char *pseudo_class;
char *inline_style;
CRDeclaration **properties;
int n_properties;
/* We hold onto these separately so we can destroy them on finalize */
CRDeclaration *inline_properties;
guint properties_computed : 1;
guint geometry_computed : 1;
guint background_computed : 1;
guint foreground_computed : 1;
guint border_image_computed : 1;
guint link_type : 2;
};
struct _StThemeNodeClass {
GObjectClass parent_class;
};
static const ClutterColor BLACK_COLOR = { 0, 0, 0, 0xff };
static const ClutterColor TRANSPARENT_COLOR = { 0, 0, 0, 0 };
G_DEFINE_TYPE (StThemeNode, st_theme_node, G_TYPE_OBJECT)
static void
st_theme_node_init (StThemeNode *node)
{
}
static void
st_theme_node_class_init (StThemeNodeClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = st_theme_node_finalize;
}
static void
st_theme_node_finalize (GObject *object)
{
StThemeNode *node = ST_THEME_NODE (object);
g_free (node->element_id);
g_free (node->element_class);
g_free (node->pseudo_class);
g_free (node->inline_style);
if (node->properties)
{
g_free (node->properties);
node->properties = NULL;
node->n_properties = 0;
}
if (node->inline_properties)
{
/* This destroys the list, not just the head of the list */
cr_declaration_destroy (node->inline_properties);
}
if (node->font_desc)
{
pango_font_description_free (node->font_desc);
node->font_desc = NULL;
}
if (node->border_image)
{
g_object_unref (node->border_image);
node->border_image = NULL;
}
if (node->background_image)
g_free (node->background_image);
G_OBJECT_CLASS (st_theme_node_parent_class)->finalize (object);
}
/**
* st_theme_node_new:
* @context: the context representing global state for this themed tree
* @parent_node: (allow-none): the parent node of this node
* @theme: (allow-none): a theme (stylesheet set) that overrides the
* theme inherited from the parent node
* @element_type: the type of the GObject represented by this node
* in the tree (corresponding to an element if we were theming an XML
* document. %G_TYPE_NONE means this style was created for the stage
* actor and matches a selector element name of 'stage'.
* @element_id: (allow-none): the ID to match CSS rules against
* @element_class: (allow-none): a whitespace-separated list of classes
* to match CSS rules against
* @pseudo_class: (allow-none): a whitespace-separated list of pseudo-classes
* (like 'hover' or 'visited') to match CSS rules against
*
* Creates a new #StThemeNode. Once created, a node is immutable. Of any
* of the attributes of the node (like the @element_class) change the node
* and its child nodes must be destroyed and recreated.
*
* Return value: (transfer full): the theme node
*/
StThemeNode *
st_theme_node_new (StThemeContext *context,
StThemeNode *parent_node,
StTheme *theme,
GType element_type,
const char *element_id,
const char *element_class,
const char *pseudo_class,
const char *inline_style)
{
StThemeNode *node;
g_return_val_if_fail (ST_IS_THEME_CONTEXT (context), NULL);
g_return_val_if_fail (parent_node == NULL || ST_IS_THEME_NODE (parent_node), NULL);
node = g_object_new (ST_TYPE_THEME_NODE, NULL);
node->context = g_object_ref (context);
if (parent_node != NULL)
node->parent_node = g_object_ref (parent_node);
else
node->parent_node = NULL;
if (theme == NULL && parent_node != NULL)
theme = parent_node->theme;
if (theme != NULL)
node->theme = g_object_ref (theme);
node->element_type = element_type;
node->element_id = g_strdup (element_id);
node->element_class = g_strdup (element_class);
node->pseudo_class = g_strdup (pseudo_class);
node->inline_style = g_strdup (inline_style);
return node;
}
/**
* st_theme_node_get_parent:
* @node: a #StThemeNode
*
* Gets the parent themed element node.
*
* Return value: (transfer none): the parent #StThemeNode, or %NULL if this
* is the root node of the tree of theme elements.
*/
StThemeNode *
st_theme_node_get_parent (StThemeNode *node)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
return node->parent_node;
}
/**
* st_theme_node_get_theme:
* @node: a #StThemeNode
*
* Gets the theme stylesheet set that styles this node
*
* Return value: (transfer none): the theme stylesheet set
*/
StTheme *
st_theme_node_get_theme (StThemeNode *node)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
return node->theme;
}
GType
st_theme_node_get_element_type (StThemeNode *node)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), G_TYPE_NONE);
return node->element_type;
}
const char *
st_theme_node_get_element_id (StThemeNode *node)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
return node->element_id;
}
const char *
st_theme_node_get_element_class (StThemeNode *node)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
return node->element_class;
}
const char *
st_theme_node_get_pseudo_class (StThemeNode *node)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
return node->pseudo_class;
}
static void
ensure_properties (StThemeNode *node)
{
if (!node->properties_computed)
{
GPtrArray *properties = NULL;
node->properties_computed = TRUE;
if (node->theme)
properties = _st_theme_get_matched_properties (node->theme, node);
if (node->inline_style)
{
CRDeclaration *cur_decl;
if (!properties)
properties = g_ptr_array_new ();
node->inline_properties = _st_theme_parse_declaration_list (node->inline_style);
for (cur_decl = node->inline_properties; cur_decl; cur_decl = cur_decl->next)
g_ptr_array_add (properties, cur_decl);
}
if (properties)
{
node->n_properties = properties->len;
node->properties = (CRDeclaration **)g_ptr_array_free (properties, FALSE);
}
}
}
typedef enum {
VALUE_FOUND,
VALUE_NOT_FOUND,
VALUE_INHERIT
} GetFromTermResult;
static gboolean
term_is_inherit (CRTerm *term)
{
return (term->type == TERM_IDENT &&
strcmp (term->content.str->stryng->str, "inherit") == 0);
}
static gboolean
term_is_none (CRTerm *term)
{
return (term->type == TERM_IDENT &&
strcmp (term->content.str->stryng->str, "none") == 0);
}
static gboolean
term_is_transparent (CRTerm *term)
{
return (term->type == TERM_IDENT &&
strcmp (term->content.str->stryng->str, "transparent") == 0);
}
static int
color_component_from_double (double component)
{
/* We want to spread the range 0-1 equally over 0..255, but
* 1.0 should map to 255 not 256, so we need to special-case it.
* See http://people.redhat.com/otaylor/pixel-converting.html
* for (very) detailed discussion of related issues. */
if (component >= 1.0)
return 255;
else
return (int)(component * 256);
}
static GetFromTermResult
get_color_from_rgba_term (CRTerm *term,
ClutterColor *color)
{
CRTerm *arg = term->ext_content.func_param;
CRNum *num;
double r = 0, g = 0, b = 0, a = 0;
int i;
for (i = 0; i < 4; i++)
{
double value;
if (arg == NULL)
return VALUE_NOT_FOUND;
if ((i == 0 && arg->the_operator != NO_OP) ||
(i > 0 && arg->the_operator != COMMA))
return VALUE_NOT_FOUND;
if (arg->type != TERM_NUMBER)
return VALUE_NOT_FOUND;
num = arg->content.num;
/* For simplicity, we convert a,r,g,b to [0,1.0] floats and then
* convert them back below. Then when we set them on a cairo content
* we convert them back to floats, and then cairo converts them
* back to integers to pass them to X, and so forth...
*/
if (i < 3)
{
if (num->type == NUM_PERCENTAGE)
value = num->val / 100;
else if (num->type == NUM_GENERIC)
value = num->val / 255;
else
return VALUE_NOT_FOUND;
}
else
{
if (num->type != NUM_GENERIC)
return VALUE_NOT_FOUND;
value = num->val;
}
value = CLAMP (value, 0, 1);
switch (i)
{
case 0:
r = value;
break;
case 1:
g = value;
break;
case 2:
b = value;
break;
case 3:
a = value;
break;
}
arg = arg->next;
}
color->red = color_component_from_double (r);
color->green = color_component_from_double (g);
color->blue = color_component_from_double (b);
color->alpha = color_component_from_double (a);
return VALUE_FOUND;
}
static GetFromTermResult
get_color_from_term (StThemeNode *node,
CRTerm *term,
ClutterColor *color)
{
CRRgb rgb;
enum CRStatus status;
/* Since libcroco doesn't know about rgba colors, it can't handle
* the transparent keyword
*/
if (term_is_transparent (term))
{
*color = TRANSPARENT_COLOR;
return VALUE_FOUND;
}
/* rgba () colors - a CSS3 addition, are not supported by libcroco,
* but they are parsed as a "function", so we can emulate the
* functionality.
*
* libcroco < 0.6.2 has a bug where functions starting with 'r' are
* misparsed. We workaround this by pre-converting 'rgba' to 'RGBA'
* before parsing the stylesheet. Since libcroco isn't
* case-insensitive (a bug), it's fine with functions starting with
* 'R'. (In theory, we should be doing a case-insensitive compare
* everywhere, not just here, but that doesn't make much sense when
* the built-in parsing of libcroco is case-sensitive and things
* like 10PX don't work.)
*/
else if (term->type == TERM_FUNCTION &&
term->content.str &&
term->content.str->stryng &&
term->content.str->stryng->str &&
g_ascii_strcasecmp (term->content.str->stryng->str, "rgba") == 0)
{
return get_color_from_rgba_term (term, color);
}
status = cr_rgb_set_from_term (&rgb, term);
if (status != CR_OK)
return VALUE_NOT_FOUND;
if (rgb.inherit)
return VALUE_INHERIT;
if (rgb.is_percentage)
cr_rgb_compute_from_percentage (&rgb);
color->red = rgb.red;
color->green = rgb.green;
color->blue = rgb.blue;
color->alpha = 0xff;
return VALUE_FOUND;
}
/**
* st_theme_node_get_color:
* @node: a #StThemeNode
* @property_name: The name of the color property
* @inherit: if %TRUE, if a value is not found for the property on the
* node, then it will be looked up on the parent node, and then on the
* parent's parent, and so forth. Note that if the property has a
* value of 'inherit' it will be inherited even if %FALSE is passed
* in for @inherit; this only affects the default behavior for inheritance.
* @color: location to store the color that was determined.
* If the property is not found, the value in this location
* will not be changed.
*
* Generically looks up a property containing a single color value. When
* specific getters (like st_theme_node_get_background_color()) exist, they
* should be used instead. They are cached, so more efficient, and have
* handling for shortcut properties and other details of CSS.
*
* Return value: %TRUE if the property was found in the properties for this
* theme node (or in the properties of parent nodes when inheriting.)
*/
gboolean
st_theme_node_get_color (StThemeNode *node,
const char *property_name,
gboolean inherit,
ClutterColor *color)
{
int i;
ensure_properties (node);
for (i = node->n_properties - 1; i >= 0; i--)
{
CRDeclaration *decl = node->properties[i];
if (strcmp (decl->property->stryng->str, property_name) == 0)
{
GetFromTermResult result = get_color_from_term (node, decl->value, color);
if (result == VALUE_FOUND)
{
return TRUE;
}
else if (result == VALUE_INHERIT)
{
if (node->parent_node)
return st_theme_node_get_color (node->parent_node, property_name, inherit, color);
else
break;
}
}
}
return FALSE;
}
/**
* st_theme_node_get_double:
* @node: a #StThemeNode
* @property_name: The name of the numeric property
* @inherit: if %TRUE, if a value is not found for the property on the
* node, then it will be looked up on the parent node, and then on the
* parent's parent, and so forth. Note that if the property has a
* value of 'inherit' it will be inherited even if %FALSE is passed
* in for @inherit; this only affects the default behavior for inheritance.
* @value: location to store the value that was determined.
* If the property is not found, the value in this location
* will not be changed.
*
* Generically looks up a property containing a single numeric value
* without units.
*
* Return value: %TRUE if the property was found in the properties for this
* theme node (or in the properties of parent nodes when inheriting.)
*/
gboolean
st_theme_node_get_double (StThemeNode *node,
const char *property_name,
gboolean inherit,
double *value)
{
gboolean result = FALSE;
int i;
ensure_properties (node);
for (i = node->n_properties - 1; i >= 0; i--)
{
CRDeclaration *decl = node->properties[i];
if (strcmp (decl->property->stryng->str, property_name) == 0)
{
CRTerm *term = decl->value;
if (term->type != TERM_NUMBER || term->content.num->type != NUM_GENERIC)
continue;
*value = term->content.num->val;
result = TRUE;
break;
}
}
if (!result && inherit && node->parent_node)
result = st_theme_node_get_double (node->parent_node, property_name, inherit, value);
return result;
}
static const PangoFontDescription *
get_parent_font (StThemeNode *node)
{
if (node->parent_node)
return st_theme_node_get_font (node->parent_node);
else
return st_theme_context_get_font (node->context);
}
static GetFromTermResult
get_length_from_term (StThemeNode *node,
CRTerm *term,
gboolean use_parent_font,
gdouble *length)
{
CRNum *num;
enum {
ABSOLUTE,
POINTS,
FONT_RELATIVE,
} type = ABSOLUTE;
double multiplier = 1.0;
if (term->type != TERM_NUMBER)
{
g_warning ("Ignoring length property that isn't a number");
return FALSE;
}
num = term->content.num;
switch (num->type)
{
case NUM_LENGTH_PX:
type = ABSOLUTE;
multiplier = 1;
break;
case NUM_LENGTH_PT:
type = POINTS;
multiplier = 1;
break;
case NUM_LENGTH_IN:
type = POINTS;
multiplier = 72;
break;
case NUM_LENGTH_CM:
type = POINTS;
multiplier = 72. / 2.54;
break;
case NUM_LENGTH_MM:
type = POINTS;
multiplier = 72. / 25.4;
break;
case NUM_LENGTH_PC:
type = POINTS;
multiplier = 12. / 25.4;
break;
case NUM_LENGTH_EM:
{
type = FONT_RELATIVE;
multiplier = 1;
break;
}
case NUM_LENGTH_EX:
{
/* Doing better would require actually resolving the font description
* to a specific font, and Pango doesn't have an ex metric anyways,
* so we'd have to try and synthesize it by complicated means.
*
* The 0.5em is the CSS spec suggested thing to use when nothing
* better is available.
*/
type = FONT_RELATIVE;
multiplier = 0.5;
break;
}
case NUM_INHERIT:
return VALUE_INHERIT;
case NUM_AUTO:
g_warning ("'auto' not supported for lengths");
return VALUE_NOT_FOUND;
case NUM_GENERIC:
g_warning ("length values must specify a unit");
return VALUE_NOT_FOUND;
case NUM_PERCENTAGE:
g_warning ("percentage lengths not currently supported");
return VALUE_NOT_FOUND;
case NUM_ANGLE_DEG:
case NUM_ANGLE_RAD:
case NUM_ANGLE_GRAD:
case NUM_TIME_MS:
case NUM_TIME_S:
case NUM_FREQ_HZ:
case NUM_FREQ_KHZ:
case NUM_UNKNOWN_TYPE:
case NB_NUM_TYPE:
g_warning ("Ignoring invalid type of number of length property");
return VALUE_NOT_FOUND;
}
switch (type)
{
case ABSOLUTE:
*length = num->val * multiplier;
break;
case POINTS:
{
double resolution = st_theme_context_get_resolution (node->context);
*length = num->val * multiplier * (resolution / 72.);
}
break;
case FONT_RELATIVE:
{
const PangoFontDescription *desc;
double font_size;
if (use_parent_font)
desc = get_parent_font (node);
else
desc = st_theme_node_get_font (node);
font_size = (double)pango_font_description_get_size (desc) / PANGO_SCALE;
if (pango_font_description_get_size_is_absolute (desc))
{
*length = num->val * multiplier * font_size;
}
else
{
double resolution = st_theme_context_get_resolution (node->context);
*length = num->val * multiplier * (resolution / 72.) * font_size;
}
}
break;
default:
g_assert_not_reached ();
}
return VALUE_FOUND;
}
static GetFromTermResult
get_length_internal (StThemeNode *node,
const char *property_name,
const char *suffixed,
gdouble *length)
{
int i;
ensure_properties (node);
for (i = node->n_properties - 1; i >= 0; i--)
{
CRDeclaration *decl = node->properties[i];
if (strcmp (decl->property->stryng->str, property_name) == 0 ||
(suffixed != NULL && strcmp (decl->property->stryng->str, suffixed) == 0))
{
GetFromTermResult result = get_length_from_term (node, decl->value, FALSE, length);
if (result != VALUE_NOT_FOUND)
return result;
}
}
return VALUE_NOT_FOUND;
}
/**
* st_theme_node_get_length:
* @node: a #StThemeNode
* @property_name: The name of the length property
* @inherit: if %TRUE, if a value is not found for the property on the
* node, then it will be looked up on the parent node, and then on the
* parent's parent, and so forth. Note that if the property has a
* value of 'inherit' it will be inherited even if %FALSE is passed
* in for @inherit; this only affects the default behavior for inheritance.
* @length: location to store the length that was determined.
* If the property is not found, the value in this location
* will not be changed. The returned length is resolved
* to pixels.
*
* Generically looks up a property containing a single length value. When
* specific getters (like st_theme_node_get_border_width()) exist, they
* should be used instead. They are cached, so more efficient, and have
* handling for shortcut properties and other details of CSS.
*
* Return value: %TRUE if the property was found in the properties for this
* theme node (or in the properties of parent nodes when inheriting.)
*/
gboolean
st_theme_node_get_length (StThemeNode *node,
const char *property_name,
gboolean inherit,
gdouble *length)
{
GetFromTermResult result = get_length_internal (node, property_name, NULL, length);
if (result == VALUE_FOUND)
return TRUE;
else if (result == VALUE_INHERIT)
inherit = TRUE;
if (inherit && node->parent_node &&
st_theme_node_get_length (node->parent_node, property_name, inherit, length))
return TRUE;
else
return FALSE;
}
static void
do_border_radius_term (StThemeNode *node,
CRTerm *term,
gboolean topleft,
gboolean topright,
gboolean bottomright,
gboolean bottomleft)
{
gdouble value;
if (get_length_from_term (node, term, FALSE, &value) != VALUE_FOUND)
return;
if (topleft)
node->border_radius[ST_CORNER_TOPLEFT] = value;
if (topright)
node->border_radius[ST_CORNER_TOPRIGHT] = value;
if (bottomright)
node->border_radius[ST_CORNER_BOTTOMRIGHT] = value;
if (bottomleft)
node->border_radius[ST_CORNER_BOTTOMLEFT] = value;
}
static void
do_border_radius (StThemeNode *node,
CRDeclaration *decl)
{
const char *property_name = decl->property->stryng->str + 13; /* Skip 'border-radius' */
if (strcmp (property_name, "") == 0)
{
/* Slight deviation ... if we don't understand some of the terms and understand others,
* then we set the ones we understand and ignore the others instead of ignoring the
* whole thing
*/
if (decl->value == NULL) /* 0 values */
return;
else if (decl->value->next == NULL) /* 1 value */
{
do_border_radius_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* all corners */
return;
}
else if (decl->value->next->next == NULL) /* 2 values */
{
do_border_radius_term (node, decl->value, TRUE, FALSE, TRUE, FALSE); /* topleft/bottomright */
do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, TRUE); /* topright/bottomleft */
}
else if (decl->value->next->next->next == NULL) /* 3 values */
{
do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE); /* topleft */
do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, TRUE); /* topright/bottomleft */
do_border_radius_term (node, decl->value->next->next, FALSE, FALSE, TRUE, FALSE); /* bottomright */
}
else if (decl->value->next->next->next->next == NULL) /* 4 values */
{
do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE); /* topleft */
do_border_radius_term (node, decl->value->next, FALSE, TRUE, FALSE, FALSE); /* topright */
do_border_radius_term (node, decl->value->next->next, FALSE, FALSE, TRUE, FALSE); /* bottomright */
do_border_radius_term (node, decl->value->next->next->next, FALSE, FALSE, FALSE, TRUE); /* bottomleft */
}
else
{
g_warning ("Too many values for border-radius property");
return;
}
}
else
{
if (decl->value == NULL || decl->value->next != NULL)
return;
if (strcmp (property_name, "-topleft") == 0)
do_border_radius_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
else if (strcmp (property_name, "-topright") == 0)
do_border_radius_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
else if (strcmp (property_name, "-bottomright") == 0)
do_border_radius_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
else if (strcmp (property_name, "-bottomleft") == 0)
do_border_radius_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
}
}
static void
do_border_property (StThemeNode *node,
CRDeclaration *decl)
{
const char *property_name = decl->property->stryng->str + 6; /* Skip 'border' */
StSide side = (StSide)-1;
ClutterColor color;
gboolean color_set = FALSE;
double width;
gboolean width_set = FALSE;
int j;
if (g_str_has_prefix (property_name, "-radius"))
{
do_border_radius (node, decl);
return;
}
if (g_str_has_prefix (property_name, "-left"))
{
side = ST_SIDE_LEFT;
property_name += 5;
}
else if (g_str_has_prefix (property_name, "-right"))
{
side = ST_SIDE_RIGHT;
property_name += 6;
}
else if (g_str_has_prefix (property_name, "-top"))
{
side = ST_SIDE_TOP;
property_name += 4;
}
else if (g_str_has_prefix (property_name, "-bottom"))
{
side = ST_SIDE_BOTTOM;
property_name += 7;
}
if (strcmp (property_name, "") == 0)
{
/* Set value for width/color/node in any order */
CRTerm *term;
for (term = decl->value; term; term = term->next)
{
GetFromTermResult result;
if (term->type == TERM_IDENT)
{
const char *ident = term->content.str->stryng->str;
if (strcmp (ident, "none") == 0 || strcmp (ident, "hidden") == 0)
{
width = 0.;
continue;
}
else if (strcmp (ident, "solid") == 0)
{
/* The only thing we support */
continue;
}
else if (strcmp (ident, "dotted") == 0 ||
strcmp (ident, "dashed") == 0 ||
strcmp (ident, "solid") == 0 ||
strcmp (ident, "double") == 0 ||
strcmp (ident, "groove") == 0 ||
strcmp (ident, "ridge") == 0 ||
strcmp (ident, "inset") == 0 ||
strcmp (ident, "outset") == 0)
{
/* Treat the same as solid */
continue;
}
/* Presumably a color, fall through */
}
if (term->type == TERM_NUMBER)
{
result = get_length_from_term (node, term, FALSE, &width);
if (result != VALUE_NOT_FOUND)
{
width_set = result == VALUE_FOUND;
continue;
}
}
result = get_color_from_term (node, term, &color);
if (result != VALUE_NOT_FOUND)
{
color_set = result == VALUE_FOUND;
continue;
}
}
}
else if (strcmp (property_name, "-color") == 0)
{
if (decl->value == NULL || decl->value->next != NULL)
return;
if (get_color_from_term (node, decl->value, &color) == VALUE_FOUND)
/* Ignore inherit */
color_set = TRUE;
}
else if (strcmp (property_name, "-width") == 0)
{
if (decl->value == NULL || decl->value->next != NULL)
return;
if (get_length_from_term (node, decl->value, FALSE, &width) == VALUE_FOUND)
/* Ignore inherit */
width_set = TRUE;
}
if (side == (StSide)-1)
{
for (j = 0; j < 4; j++)
{
if (color_set)
node->border_color[j] = color;
if (width_set)
node->border_width[j] = width;
}
}
else
{
if (color_set)
node->border_color[side] = color;
if (width_set)
node->border_width[side] = width;
}
}
static void
do_padding_property_term (StThemeNode *node,
CRTerm *term,
gboolean left,
gboolean right,
gboolean top,
gboolean bottom)
{
gdouble value;
int int_value;
if (get_length_from_term (node, term, FALSE, &value) != VALUE_FOUND)
return;
/* Round the value */
int_value = (int) (0.5 + value);
if (left)
node->padding[ST_SIDE_LEFT] = int_value;
if (right)
node->padding[ST_SIDE_RIGHT] = int_value;
if (top)
node->padding[ST_SIDE_TOP] = int_value;
if (bottom)
node->padding[ST_SIDE_BOTTOM] = int_value;
}
static void
do_padding_property (StThemeNode *node,
CRDeclaration *decl)
{
const char *property_name = decl->property->stryng->str + 7; /* Skip 'padding' */
if (strcmp (property_name, "") == 0)
{
/* Slight deviation ... if we don't understand some of the terms and understand others,
* then we set the ones we understand and ignore the others instead of ignoring the
* whole thing
*/
if (decl->value == NULL) /* 0 values */
return;
else if (decl->value->next == NULL) /* 1 value */
{
do_padding_property_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* left/right/top/bottom */
return;
}
else if (decl->value->next->next == NULL) /* 2 values */
{
do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, TRUE); /* top/bottom */
do_padding_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
}
else if (decl->value->next->next->next == NULL) /* 3 values */
{
do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
do_padding_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */
do_padding_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
}
else if (decl->value->next->next->next->next == NULL) /* 4 values */
{
do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */
do_padding_property_term (node, decl->value->next, FALSE, TRUE, FALSE, FALSE); /* right */
do_padding_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */
do_padding_property_term (node, decl->value->next->next->next, TRUE, FALSE, FALSE, FALSE); /* left */
}
else
{
g_warning ("Too many values for padding property");
return;
}
}
else
{
if (decl->value == NULL || decl->value->next != NULL)
return;
if (strcmp (property_name, "-left") == 0)
do_padding_property_term (node, decl->value, TRUE, FALSE, FALSE, FALSE);
else if (strcmp (property_name, "-right") == 0)
do_padding_property_term (node, decl->value, FALSE, TRUE, FALSE, FALSE);
else if (strcmp (property_name, "-top") == 0)
do_padding_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE);
else if (strcmp (property_name, "-bottom") == 0)
do_padding_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE);
}
}
static void
do_size_property (StThemeNode *node,
CRDeclaration *decl,
int *node_value)
{
gdouble value;
if (get_length_from_term (node, decl->value, FALSE, &value) == VALUE_FOUND)
*node_value = (int) (0.5 + value);
}
static void
ensure_geometry (StThemeNode *node)
{
int i, j;
if (node->geometry_computed)
return;
node->geometry_computed = TRUE;
ensure_properties (node);
for (j = 0; j < 4; j++)
{
node->border_width[j] = 0;
node->border_color[j] = TRANSPARENT_COLOR;
}
node->width = -1;
node->height = -1;
node->min_width = -1;
node->min_height = -1;
for (i = 0; i < node->n_properties; i++)
{
CRDeclaration *decl = node->properties[i];
const char *property_name = decl->property->stryng->str;
if (g_str_has_prefix (property_name, "border"))
do_border_property (node, decl);
else if (g_str_has_prefix (property_name, "padding"))
do_padding_property (node, decl);
else if (strcmp (property_name, "width") == 0)
do_size_property (node, decl, &node->width);
else if (strcmp (property_name, "height") == 0)
do_size_property (node, decl, &node->height);
else if (strcmp (property_name, "min-width") == 0)
do_size_property (node, decl, &node->min_width);
else if (strcmp (property_name, "min-height") == 0)
do_size_property (node, decl, &node->min_height);
}
if (node->width != -1)
{
if (node->min_width == -1)
node->min_width = node->width;
else if (node->width <= node->min_width)
node->width = node->min_width;
}
else
node->width = node->min_width;
if (node->height != -1)
{
if (node->min_height == -1)
node->min_height = node->height;
else if (node->height <= node->min_height)
node->height = node->min_height;
}
else
node->height = node->min_height;
}
double
st_theme_node_get_border_width (StThemeNode *node,
StSide side)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
ensure_geometry (node);
return node->border_width[side];
}
double
st_theme_node_get_border_radius (StThemeNode *node,
StCorner corner)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
g_return_val_if_fail (corner >= ST_CORNER_TOPLEFT && corner <= ST_CORNER_BOTTOMLEFT, 0.);
ensure_geometry (node);
return node->border_radius[corner];
}
int
st_theme_node_get_width (StThemeNode *node)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
ensure_geometry (node);
return node->width;
}
int
st_theme_node_get_height (StThemeNode *node)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
ensure_geometry (node);
return node->height;
}
int
st_theme_node_get_min_width (StThemeNode *node)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
ensure_geometry (node);
return node->min_width;
}
int
st_theme_node_get_min_height (StThemeNode *node)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), -1);
ensure_geometry (node);
return node->min_height;
}
static GetFromTermResult
get_background_color_from_term (StThemeNode *node,
CRTerm *term,
ClutterColor *color)
{
GetFromTermResult result = get_color_from_term (node, term, color);
if (result == VALUE_NOT_FOUND)
{
if (term_is_transparent (term))
{
*color = TRANSPARENT_COLOR;
return VALUE_FOUND;
}
}
return result;
}
static void
ensure_background (StThemeNode *node)
{
int i;
if (node->background_computed)
return;
node->background_computed = TRUE;
node->background_color = TRANSPARENT_COLOR;
ensure_properties (node);
for (i = 0; i < node->n_properties; i++)
{
CRDeclaration *decl = node->properties[i];
const char *property_name = decl->property->stryng->str;
if (g_str_has_prefix (property_name, "background"))
property_name += 10;
else
continue;
if (strcmp (property_name, "") == 0)
{
/* We're very liberal here ... if we recognize any term in the expression we take it, and
* we ignore the rest. The actual specification is:
*
* background: [<'background-color'> || <'background-image'> || <'background-repeat'> || <'background-attachment'> || <'background-position'>] | inherit
*/
CRTerm *term;
/* background: property sets all terms to specified or default values */
node->background_color = TRANSPARENT_COLOR;
g_free (node->background_image);
node->background_image = NULL;
for (term = decl->value; term; term = term->next)
{
GetFromTermResult result = get_background_color_from_term (node, term, &node->background_color);
if (result == VALUE_FOUND)
{
/* color stored in node->background_color */
}
else if (result == VALUE_INHERIT)
{
if (node->parent_node)
{
st_theme_node_get_background_color (node->parent_node, &node->background_color);
node->background_image = g_strdup (st_theme_node_get_background_image (node->parent_node));
}
}
else if (term_is_none (term))
{
/* leave node->background_color as transparent */
}
else if (term->type == TERM_URI)
{
node->background_image = _st_theme_resolve_url (node->theme,
decl->parent_statement->parent_sheet,
term->content.str->stryng->str);
}
}
}
else if (strcmp (property_name, "-color") == 0)
{
GetFromTermResult result;
if (decl->value == NULL || decl->value->next != NULL)
continue;
result = get_background_color_from_term (node, decl->value, &node->background_color);
if (result == VALUE_FOUND)
{
/* color stored in node->background_color */
}
else if (result == VALUE_INHERIT)
{
if (node->parent_node)
st_theme_node_get_background_color (node->parent_node, &node->background_color);
}
}
else if (strcmp (property_name, "-image") == 0)
{
if (decl->value == NULL || decl->value->next != NULL)
continue;
if (decl->value->type == TERM_URI)
{
g_free (node->background_image);
node->background_image = _st_theme_resolve_url (node->theme,
decl->parent_statement->parent_sheet,
decl->value->content.str->stryng->str);
}
else if (term_is_inherit (decl->value))
{
g_free (node->background_image);
node->background_image = g_strdup (st_theme_node_get_background_image (node->parent_node));
}
else if (term_is_none (decl->value))
{
g_free (node->background_image);
node->background_image = NULL;
}
}
}
}
void
st_theme_node_get_background_color (StThemeNode *node,
ClutterColor *color)
{
g_return_if_fail (ST_IS_THEME_NODE (node));
ensure_background (node);
*color = node->background_color;
}
const char *
st_theme_node_get_background_image (StThemeNode *node)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL);
ensure_background (node);
return node->background_image;
}
void
st_theme_node_get_foreground_color (StThemeNode *node,
ClutterColor *color)
{
g_return_if_fail (ST_IS_THEME_NODE (node));
if (!node->foreground_computed)
{
int i;
node->foreground_computed = TRUE;
ensure_properties (node);
for (i = node->n_properties - 1; i >= 0; i--)
{
CRDeclaration *decl = node->properties[i];
if (strcmp (decl->property->stryng->str, "color") == 0)
{
GetFromTermResult result = get_color_from_term (node, decl->value, &node->foreground_color);
if (result == VALUE_FOUND)
goto out;
else if (result == VALUE_INHERIT)
break;
}
}
if (node->parent_node)
st_theme_node_get_foreground_color (node->parent_node, &node->foreground_color);
else
node->foreground_color = BLACK_COLOR; /* default to black */
}
out:
*color = node->foreground_color;
}
void
st_theme_node_get_border_color (StThemeNode *node,
StSide side,
ClutterColor *color)
{
g_return_if_fail (ST_IS_THEME_NODE (node));
g_return_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT);
ensure_geometry (node);
*color = node->border_color[side];
}
double
st_theme_node_get_padding (StThemeNode *node,
StSide side)
{
g_return_val_if_fail (ST_IS_THEME_NODE (node), 0.);
g_return_val_if_fail (side >= ST_SIDE_TOP && side <= ST_SIDE_LEFT, 0.);
ensure_geometry (node);
return node->padding[side];
}
StTextDecoration
st_theme_node_get_text_decoration (StThemeNode *node)
{
int i;
ensure_properties (node);
for (i = node->n_properties - 1; i >= 0; i--)
{
CRDeclaration *decl = node->properties[i];
if (strcmp (decl->property->stryng->str, "text-decoration") == 0)
{
CRTerm *term = decl->value;
StTextDecoration decoration = 0;
/* Specification is none | [ underline || overline || line-through || blink ] | inherit
*
* We're a bit more liberal, and for example treat 'underline none' as the same as
* none.
*/
for (; term; term = term->next)
{
if (term->type != TERM_IDENT)
goto next_decl;
if (strcmp (term->content.str->stryng->str, "none") == 0)
{
return 0;
}
else if (strcmp (term->content.str->stryng->str, "inherit") == 0)
{
if (node->parent_node)
return st_theme_node_get_text_decoration (node->parent_node);
}
else if (strcmp (term->content.str->stryng->str, "underline") == 0)
{
decoration |= ST_TEXT_DECORATION_UNDERLINE;
}
else if (strcmp (term->content.str->stryng->str, "overline") == 0)
{
decoration |= ST_TEXT_DECORATION_OVERLINE;
}
else if (strcmp (term->content.str->stryng->str, "line-through") == 0)
{
decoration |= ST_TEXT_DECORATION_LINE_THROUGH;
}
else if (strcmp (term->content.str->stryng->str, "blink") == 0)
{
decoration |= ST_TEXT_DECORATION_BLINK;
}
else
{
goto next_decl;
}
}
return decoration;
}
next_decl:
;
}
return 0;
}
static gboolean
font_family_from_terms (CRTerm *term,
char **family)
{
GString *family_string;
gboolean result = FALSE;
gboolean last_was_quoted = FALSE;
if (!term)
return FALSE;
family_string = g_string_new (NULL);
while (term)
{
if (term->type != TERM_STRING && term->type != TERM_IDENT)
{
goto out;
}
if (family_string->len > 0)
{
if (term->the_operator != COMMA && term->the_operator != NO_OP)
goto out;
/* Can concatenate two bare words, but not two quoted strings */
if ((term->the_operator == NO_OP && last_was_quoted) || term->type == TERM_STRING)
goto out;
if (term->the_operator == NO_OP)
g_string_append (family_string, " ");
else
g_string_append (family_string, ", ");
}
else
{
if (term->the_operator != NO_OP)
goto out;
}
g_string_append (family_string, term->content.str->stryng->str);
term = term->next;
}
result = TRUE;
out:
if (result)
{
*family = g_string_free (family_string, FALSE);
return TRUE;
}
else
{
*family = g_string_free (family_string, TRUE);
return FALSE;
}
}
/* In points */
static int font_sizes[] = {
6 * 1024, /* xx-small */
8 * 1024, /* x-small */
10 * 1024, /* small */
12 * 1024, /* medium */
16 * 1024, /* large */
20 * 1024, /* x-large */
24 * 1024, /* xx-large */
};
static gboolean
font_size_from_term (StThemeNode *node,
CRTerm *term,
double *size)
{
if (term->type == TERM_IDENT)
{
double resolution = st_theme_context_get_resolution (node->context);
/* We work in integers to avoid double comparisons when converting back
* from a size in pixels to a logical size.
*/
int size_points = (int)(0.5 + *size * (72. / resolution));
if (strcmp (term->content.str->stryng->str, "xx-small") == 0)
size_points = font_sizes[0];
else if (strcmp (term->content.str->stryng->str, "x-small") == 0)
size_points = font_sizes[1];
else if (strcmp (term->content.str->stryng->str, "small") == 0)
size_points = font_sizes[2];
else if (strcmp (term->content.str->stryng->str, "medium") == 0)
size_points = font_sizes[3];
else if (strcmp (term->content.str->stryng->str, "large") == 0)
size_points = font_sizes[4];
else if (strcmp (term->content.str->stryng->str, "x-large") == 0)
size_points = font_sizes[5];
else if (strcmp (term->content.str->stryng->str, "xx-large") == 0)
size_points = font_sizes[6];
else if (strcmp (term->content.str->stryng->str, "smaller") == 0)
{
/* Find the standard size equal to or smaller than the current size */
int i = 0;
while (i <= 6 && font_sizes[i] < size_points)
i++;
if (i > 6)
{
/* original size greater than any standard size */
size_points = (int)(0.5 + size_points / 1.2);
}
else
{
/* Go one smaller than that, if possible */
if (i > 0)
i--;
size_points = font_sizes[i];
}
}
else if (strcmp (term->content.str->stryng->str, "larger") == 0)
{
/* Find the standard size equal to or larger than the current size */
int i = 6;
while (i >= 0 && font_sizes[i] > size_points)
i--;
if (i < 0) /* original size smaller than any standard size */
i = 0;
/* Go one larger than that, if possible */
if (i < 6)
i++;
size_points = font_sizes[i];
}
else
{
return FALSE;
}
*size = size_points * (resolution / 72.);
return TRUE;
}
else if (term->type == TERM_NUMBER && term->content.num->type == NUM_PERCENTAGE)
{
*size *= term->content.num->val / 100.;
return TRUE;
}
else if (get_length_from_term (node, term, TRUE, size) == VALUE_FOUND)
{
/* Convert from pixels to Pango units */
*size *= 1024;
return TRUE;
}
return FALSE;
}
static gboolean
font_weight_from_term (CRTerm *term,
PangoWeight *weight,
gboolean *weight_absolute)
{
if (term->type == TERM_NUMBER)
{
int weight_int;
/* The spec only allows numeric weights from 100-900, though Pango
* will handle any number. We just let anything through.
*/
if (term->content.num->type != NUM_GENERIC)
return FALSE;
weight_int = (int)(0.5 + term->content.num->val);
*weight = weight_int;
*weight_absolute = TRUE;
}
else if (term->type == TERM_IDENT)
{
/* FIXME: handle INHERIT */
if (strcmp (term->content.str->stryng->str, "bold") == 0)
{
*weight = PANGO_WEIGHT_BOLD;
*weight_absolute = TRUE;
}
else if (strcmp (term->content.str->stryng->str, "normal") == 0)
{
*weight = PANGO_WEIGHT_NORMAL;
*weight_absolute = TRUE;
}
else if (strcmp (term->content.str->stryng->str, "bolder") == 0)
{
*weight = PANGO_WEIGHT_BOLD;
*weight_absolute = FALSE;
}
else if (strcmp (term->content.str->stryng->str, "lighter") == 0)
{
*weight = PANGO_WEIGHT_LIGHT;
*weight_absolute = FALSE;
}
else
{
return FALSE;
}
}
else
{
return FALSE;
}
return TRUE;
}
static gboolean
font_style_from_term (CRTerm *term,
PangoStyle *style)
{
if (term->type != TERM_IDENT)
return FALSE;
/* FIXME: handle INHERIT */
if (strcmp (term->content.str->stryng->str, "normal") == 0)
*style = PANGO_STYLE_NORMAL;
else if (strcmp (term->content.str->stryng->str, "oblique") == 0)
*style = PANGO_STYLE_OBLIQUE;
else if (strcmp (term->content.str->stryng->str, "italic") == 0)
*style = PANGO_STYLE_ITALIC;
else
return FALSE;
return TRUE;
}
static gboolean
font_variant_from_term (CRTerm *term,
PangoVariant *variant)
{
if (term->type != TERM_IDENT)
return FALSE;
/* FIXME: handle INHERIT */
if (strcmp (term->content.str->stryng->str, "normal") == 0)
*variant = PANGO_VARIANT_NORMAL;
else if (strcmp (term->content.str->stryng->str, "small-caps") == 0)
*variant = PANGO_VARIANT_SMALL_CAPS;
else
return FALSE;
return TRUE;
}
const PangoFontDescription *
st_theme_node_get_font (StThemeNode *node)
{
PangoStyle font_style;
gboolean font_style_set = FALSE;
PangoVariant variant;
gboolean variant_set = FALSE;
PangoWeight weight;
gboolean weight_absolute;
gboolean weight_set = FALSE;
double parent_size;
double size = 0.; /* Suppress warning */
gboolean size_set = FALSE;
char *family = NULL;
int i;
if (node->font_desc)
return node->font_desc;
node->font_desc = pango_font_description_copy (get_parent_font (node));
parent_size = pango_font_description_get_size (node->font_desc);
if (!pango_font_description_get_size_is_absolute (node->font_desc))
{
double resolution = st_theme_context_get_resolution (node->context);
parent_size *= (resolution / 72.);
}
ensure_properties (node);
for (i = 0; i < node->n_properties; i++)
{
CRDeclaration *decl = node->properties[i];
if (strcmp (decl->property->stryng->str, "font") == 0)
{
PangoStyle tmp_style = PANGO_STYLE_NORMAL;
PangoVariant tmp_variant = PANGO_VARIANT_NORMAL;
PangoWeight tmp_weight = PANGO_WEIGHT_NORMAL;
gboolean tmp_weight_absolute = TRUE;
double tmp_size;
CRTerm *term = decl->value;
/* A font specification starts with node/variant/weight
* in any order. Each is allowed to be specified only once,
* but we don't enforce that.
*/
for (; term; term = term->next)
{
if (font_style_from_term (term, &tmp_style))
continue;
if (font_variant_from_term (term, &tmp_variant))
continue;
if (font_weight_from_term (term, &tmp_weight, &tmp_weight_absolute))
continue;
break;
}
/* The size is mandatory */
if (term == NULL || term->type != TERM_NUMBER)
{
g_warning ("Size missing from font property");
continue;
}
tmp_size = parent_size;
if (!font_size_from_term (node, term, &tmp_size))
{
g_warning ("Couldn't parse size in font property");
continue;
}
term = term->next;
if (term != NULL && term->type && TERM_NUMBER && term->the_operator == DIVIDE)
{
/* Ignore line-height specification */
term = term->next;
}
/* the font family is mandatory - it is a comma-separated list of
* names.
*/
if (!font_family_from_terms (term, &family))
{
g_warning ("Couldn't parse family in font property");
continue;
}
font_style = tmp_style;
font_style_set = TRUE;
weight = tmp_weight;
weight_absolute = tmp_weight_absolute;
weight_set = TRUE;
variant = tmp_variant;
variant_set = TRUE;
size = tmp_size;
size_set = TRUE;
}
else if (strcmp (decl->property->stryng->str, "font-family") == 0)
{
if (!font_family_from_terms (decl->value, &family))
{
g_warning ("Couldn't parse family in font property");
continue;
}
}
else if (strcmp (decl->property->stryng->str, "font-weight") == 0)
{
if (decl->value == NULL || decl->value->next != NULL)
continue;
if (font_weight_from_term (decl->value, &weight, &weight_absolute))
weight_set = TRUE;
}
else if (strcmp (decl->property->stryng->str, "font-style") == 0)
{
if (decl->value == NULL || decl->value->next != NULL)
continue;
if (font_style_from_term (decl->value, &font_style))
font_style_set = TRUE;
}
else if (strcmp (decl->property->stryng->str, "font-variant") == 0)
{
if (decl->value == NULL || decl->value->next != NULL)
continue;
if (font_variant_from_term (decl->value, &variant))
variant_set = TRUE;
}
else if (strcmp (decl->property->stryng->str, "font-size") == 0)
{
gdouble tmp_size;
if (decl->value == NULL || decl->value->next != NULL)
continue;
tmp_size = parent_size;
if (font_size_from_term (node, decl->value, &tmp_size))
{
size = tmp_size;
size_set = TRUE;
}
}
}
if (family)
pango_font_description_set_family (node->font_desc, family);
if (size_set)
pango_font_description_set_absolute_size (node->font_desc, size);
if (weight_set)
{
if (!weight_absolute)
{
/* bolder/lighter are supposed to switch between available styles, but with
* font substitution, that gets to be a pretty fuzzy concept. So we use
* a fixed step of 200. (The spec says 100, but that might not take us from
* normal to bold.
*/
PangoWeight old_weight = pango_font_description_get_weight (node->font_desc);
if (weight == PANGO_WEIGHT_BOLD)
weight = old_weight + 200;
else
weight = old_weight - 200;
if (weight < 100)
weight = 100;
if (weight > 900)
weight = 900;
}
pango_font_description_set_weight (node->font_desc, weight);
}
if (font_style_set)
pango_font_description_set_style (node->font_desc, font_style);
if (variant_set)
pango_font_description_set_variant (node->font_desc, variant);
return node->font_desc;
}
/**
* st_theme_node_get_border_image:
* @node: a #StThemeNode
*
* Gets the value for the border-image style property
*
* Return value: (transfer none): the border image, or %NULL
* if there is no border image.
*/
StBorderImage *
st_theme_node_get_border_image (StThemeNode *node)
{
int i;
if (node->border_image_computed)
return node->border_image;
node->border_image = NULL;
node->border_image_computed = TRUE;
ensure_properties (node);
for (i = node->n_properties - 1; i >= 0; i--)
{
CRDeclaration *decl = node->properties[i];
if (strcmp (decl->property->stryng->str, "border-image") == 0)
{
CRTerm *term = decl->value;
int borders[4];
int n_borders = 0;
int i;
const char *url;
int border_top;
int border_right;
int border_bottom;
int border_left;
char *filename;
/* First term must be the URL to the image */
if (term->type != TERM_URI)
goto next_property;
url = term->content.str->stryng->str;
term = term->next;
/* Followed by 0 to 4 numbers or percentages. *Not lengths*. The interpretation
* of a number is supposed to be pixels if the image is pixel based, otherwise CSS pixels.
*/
for (i = 0; i < 4; i++)
{
if (term == NULL)
break;
if (term->type != TERM_NUMBER)
goto next_property;
if (term->content.num->type == NUM_GENERIC)
{
borders[n_borders] = (int)(0.5 + term->content.num->val);
n_borders++;
}
else if (term->content.num->type == NUM_PERCENTAGE)
{
/* This would be easiest to support if we moved image handling into StBorderImage */
g_warning ("Percentages not supported for border-image");
goto next_property;
}
else
goto next_property;
term = term->next;
}
switch (n_borders)
{
case 0:
border_top = border_right = border_bottom = border_left = 0;
break;
case 1:
border_top = border_right = border_bottom = border_left = borders[0];
break;
case 2:
border_top = border_bottom = borders[0];
border_left = border_right = borders[1];
break;
case 3:
border_top = borders[0];
border_left = border_right = borders[1];
border_bottom = borders[2];
break;
case 4:
default:
border_top = borders[0];
border_right = borders[1];
border_bottom = borders[2];
border_left = borders[3];
break;
}
filename = _st_theme_resolve_url (node->theme, decl->parent_statement->parent_sheet, url);
if (filename == NULL)
goto next_property;
node->border_image = st_border_image_new (filename,
border_top, border_right, border_bottom, border_left);
g_free (filename);
return node->border_image;
}
next_property:
;
}
return NULL;
}
static float
get_width_inc (StThemeNode *node)
{
return ((int)(0.5 + node->border_width[ST_SIDE_LEFT]) + node->padding[ST_SIDE_LEFT] +
(int)(0.5 + node->border_width[ST_SIDE_RIGHT]) + node->padding[ST_SIDE_RIGHT]);
}
static float
get_height_inc (StThemeNode *node)
{
return ((int)(0.5 + node->border_width[ST_SIDE_TOP]) + node->padding[ST_SIDE_TOP] +
(int)(0.5 + node->border_width[ST_SIDE_BOTTOM]) + node->padding[ST_SIDE_BOTTOM]);
}
/**
* st_theme_node_adjust_for_height:
* @node: a #StThemeNode
* @for_height: (inout): the "for height" to adjust
*
* Adjusts a "for height" passed to clutter_actor_get_preferred_width() to
* account for borders and padding. This is a convenience function meant
* to be called from a get_preferred_width() method of a #ClutterActor
* subclass. The value after adjustment is the height available for the actor's
* content.
*/
void
st_theme_node_adjust_for_height (StThemeNode *node,
float *for_height)
{
g_return_if_fail (ST_IS_THEME_NODE (node));
g_return_if_fail (for_height != NULL);
if (*for_height >= 0)
{
float height_inc = get_height_inc (node);
*for_height = MAX (0, *for_height - height_inc);
}
}
/**
* st_theme_node_adjust_preferred_width:
* @node: a #StThemeNode
* @min_width_p: (inout) (allow-none): the width to adjust
* @for_height: (inout): the height to adjust
*
* Adjusts the minimum and natural width computed for an actor by
* adding on the necessary space for borders and padding. This is a
* convenience function meant to be called from the get_preferred_width()
* method of a #ClutterActor subclass
*/
void
st_theme_node_adjust_preferred_width (StThemeNode *node,
float *min_width_p,
float *natural_width_p)
{
float width_inc;
g_return_if_fail (ST_IS_THEME_NODE (node));
ensure_geometry (node);
width_inc = get_width_inc (node);
if (min_width_p)
{
if (node->min_width != -1)
*min_width_p = node->min_width;
*min_width_p += width_inc;
}
if (natural_width_p)
{
if (node->width != -1)
*natural_width_p = node->width;
*natural_width_p += width_inc;
}
}
/**
* st_theme_node_adjust_for_width:
* @node: a #StThemeNode
* @for_width: (inout): the "for width" to adjust
*
* Adjusts a "for width" passed to clutter_actor_get_preferred_height() to
* account for borders and padding. This is a convenience function meant
* to be called from a get_preferred_height() method of a #ClutterActor
* subclass. The value after adjustmnet is the width available for the actor's
* content.
*/
void
st_theme_node_adjust_for_width (StThemeNode *node,
float *for_width)
{
g_return_if_fail (ST_IS_THEME_NODE (node));
g_return_if_fail (for_width != NULL);
if (*for_width >= 0)
{
float width_inc = get_width_inc (node);
*for_width = MAX (0, *for_width - width_inc);
}
}
/**
* st_theme_node_adjust_preferred_height:
* @node: a #StThemeNode
* @min_height_p: (inout) (allow-none): the height to adjust
* @for_height: (inout): the height to adjust
*
* Adjusts the minimum and natural height computed for an actor by
* adding on the necessary space for borders and padding. This is a
* convenience function meant to be called from the get_preferred_height()
* method of a #ClutterActor subclass
*/
void
st_theme_node_adjust_preferred_height (StThemeNode *node,
float *min_height_p,
float *natural_height_p)
{
float height_inc;
g_return_if_fail (ST_IS_THEME_NODE (node));
ensure_geometry (node);
height_inc = get_height_inc (node);
if (min_height_p)
{
if (node->min_height != -1)
*min_height_p = node->min_height;
*min_height_p += height_inc;
}
if (natural_height_p)
{
if (node->height != -1)
*natural_height_p = node->height;
*natural_height_p += height_inc;
}
}
/**
* st_theme_node_get_content_box:
* @node: a #StThemeNode
* @allocation: the box allocated to a #ClutterAlctor
* @content_box: computed box occupied by the actor's content
*
* Gets the box within an actor's allocation that contents the content
* of an actor (excluding borders and padding). This is a convenience function
* meant to be used from the allocate() or paint() methods of a #ClutterActor
* subclass.
*/
void
st_theme_node_get_content_box (StThemeNode *node,
const ClutterActorBox *allocation,
ClutterActorBox *content_box)
{
double noncontent_left, noncontent_top, noncontent_right, noncontent_bottom;
double avail_width, avail_height, content_width, content_height;
g_return_if_fail (ST_IS_THEME_NODE (node));
ensure_geometry (node);
avail_width = allocation->x2 - allocation->x1;
avail_height = allocation->y2 - allocation->y1;
noncontent_left = node->border_width[ST_SIDE_LEFT] + node->padding[ST_SIDE_LEFT];
noncontent_top = node->border_width[ST_SIDE_TOP] + node->padding[ST_SIDE_TOP];
noncontent_right = node->border_width[ST_SIDE_RIGHT] + node->padding[ST_SIDE_RIGHT];
noncontent_bottom = node->border_width[ST_SIDE_BOTTOM] + node->padding[ST_SIDE_BOTTOM];
content_box->x1 = (int)(0.5 + noncontent_left);
content_box->y1 = (int)(0.5 + noncontent_top);
content_width = avail_width - noncontent_left - noncontent_right;
if (content_width < 0)
content_width = 0;
content_height = avail_height - noncontent_top - noncontent_bottom;
if (content_height < 0)
content_height = 0;
content_box->x2 = (int)(0.5 + content_box->x1 + content_width);
content_box->y2 = (int)(0.5 + content_box->y1 + content_height);
}
/**
* st_theme_node_geometry_equal:
* @node: a #StThemeNode
* @node: a different #StThemeNode
*
* Tests if two theme nodes have the same borders and padding; this can be
* used to optimize having to relayout when the style applied to a Clutter
* actor changes colors without changing the geometry.
*/
gboolean
st_theme_node_geometry_equal (StThemeNode *node,
StThemeNode *other)
{
StSide side;
ensure_geometry (node);
ensure_geometry (other);
for (side = ST_SIDE_TOP; side <= ST_SIDE_LEFT; side++)
{
if (node->border_width[side] != other->border_width[side])
return FALSE;
if (node->padding[side] != other->padding[side])
return FALSE;
}
if (node->width != other->width || node->height != other->height)
return FALSE;
if (node->min_width != other->min_width || node->min_height != other->min_height)
return FALSE;
return TRUE;
}