diff options
Diffstat (limited to 'src/st/st-theme-node.c')
-rw-r--r-- | src/st/st-theme-node.c | 4325 |
1 files changed, 4325 insertions, 0 deletions
diff --git a/src/st/st-theme-node.c b/src/st/st-theme-node.c new file mode 100644 index 0000000..6e09c39 --- /dev/null +++ b/src/st/st-theme-node.c @@ -0,0 +1,4325 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * st-theme-node.c: style information for one node in a tree of themed objects + * + * Copyright 2008-2010 Red Hat, Inc. + * Copyright 2009 Steve Frécinaux + * Copyright 2009, 2010 Florian Müllner + * Copyright 2010 Adel Gadllah + * Copyright 2010 Giovanni Campagna + * Copyright 2011 Quentin "Sardem FF7" Glidic + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 2.1 of + * the License, or (at your option) any later version. + * + * 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/>. + */ + +#include <stdlib.h> +#include <string.h> + +#include "st-settings.h" +#include "st-theme-private.h" +#include "st-theme-context.h" +#include "st-theme-node-private.h" + +static void st_theme_node_dispose (GObject *object); +static void st_theme_node_finalize (GObject *object); + +static const ClutterColor BLACK_COLOR = { 0, 0, 0, 0xff }; +static const ClutterColor TRANSPARENT_COLOR = { 0, 0, 0, 0 }; +static const ClutterColor DEFAULT_SUCCESS_COLOR = { 0x4e, 0x9a, 0x06, 0xff }; +static const ClutterColor DEFAULT_WARNING_COLOR = { 0xf5, 0x79, 0x3e, 0xff }; +static const ClutterColor DEFAULT_ERROR_COLOR = { 0xcc, 0x00, 0x00, 0xff }; + +G_DEFINE_TYPE (StThemeNode, st_theme_node, G_TYPE_OBJECT) + +static void +st_theme_node_init (StThemeNode *node) +{ + node->transition_duration = -1; + + st_theme_node_paint_state_init (&node->cached_state); +} + +static void +st_theme_node_class_init (StThemeNodeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = st_theme_node_dispose; + object_class->finalize = st_theme_node_finalize; +} + +static void +maybe_free_properties (StThemeNode *node) +{ + 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); + node->inline_properties = NULL; + } +} + +static void +st_theme_node_dispose (GObject *gobject) +{ + StThemeNode *node = ST_THEME_NODE (gobject); + + if (node->parent_node) + { + g_object_unref (node->parent_node); + node->parent_node = NULL; + } + + if (node->border_image) + { + g_object_unref (node->border_image); + node->border_image = NULL; + } + + if (node->icon_colors) + { + st_icon_colors_unref (node->icon_colors); + node->icon_colors = NULL; + } + + st_theme_node_paint_state_free (&node->cached_state); + + g_clear_object (&node->theme); + + G_OBJECT_CLASS (st_theme_node_parent_class)->dispose (gobject); +} + +static void +st_theme_node_finalize (GObject *object) +{ + StThemeNode *node = ST_THEME_NODE (object); + + g_free (node->element_id); + g_strfreev (node->element_classes); + g_strfreev (node->pseudo_classes); + g_free (node->inline_style); + + maybe_free_properties (node); + + g_clear_pointer (&node->font_desc, pango_font_description_free); + + g_clear_pointer (&node->box_shadow, st_shadow_unref); + g_clear_pointer (&node->background_image_shadow, st_shadow_unref); + g_clear_pointer (&node->text_shadow, st_shadow_unref); + + g_clear_object (&node->background_image); + + cogl_clear_object (&node->background_texture); + cogl_clear_object (&node->background_pipeline); + cogl_clear_object (&node->background_shadow_pipeline); + cogl_clear_object (&node->border_slices_texture); + cogl_clear_object (&node->border_slices_pipeline); + cogl_clear_object (&node->color_pipeline); + + G_OBJECT_CLASS (st_theme_node_parent_class)->finalize (object); +} + +static GStrv +split_on_whitespace (const gchar *s) +{ + gchar *cur; + gchar *l; + gchar *temp; + GPtrArray *arr; + + if (s == NULL) + return NULL; + + arr = g_ptr_array_new (); + l = g_strdup (s); + + cur = strtok_r (l, " \t\f\r\n", &temp); + + while (cur != NULL) + { + g_ptr_array_add (arr, g_strdup (cur)); + cur = strtok_r (NULL, " \t\f\r\n", &temp); + } + + g_free (l); + g_ptr_array_add (arr, NULL); + return (GStrv) g_ptr_array_free (arr, FALSE); +} + +/** + * st_theme_node_new: + * @context: the context representing global state for this themed tree + * @parent_node: (nullable): the parent node of this node + * @theme: (nullable): 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: (nullable): the ID to match CSS rules against + * @element_class: (nullable): a whitespace-separated list of classes + * to match CSS rules against + * @pseudo_class: (nullable): 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. If any + * of the attributes of the node (like the @element_class) change the node + * and its child nodes must be destroyed and recreated. + * + * Returns: (transfer full): a new #StThemeNode + */ +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 = 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; + + g_set_object (&node->theme, theme); + node->element_type = element_type; + node->element_id = g_strdup (element_id); + node->element_classes = split_on_whitespace (element_class); + node->pseudo_classes = split_on_whitespace (pseudo_class); + node->inline_style = g_strdup (inline_style); + node->cached_scale_factor = st_theme_context_get_scale_factor (context); + + return node; +} + +/** + * st_theme_node_get_parent: + * @node: a #StThemeNode + * + * Gets the parent themed element node. + * + * Returns: (nullable) (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 + * + * Returns: (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; +} + +/** + * st_theme_node_get_element_type: + * @node: a #StThemeNode + * + * Get the element #GType for @node. + * + * Returns: the element type + */ +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; +} + +/** + * st_theme_node_get_element_id: + * @node: a #StThemeNode + * + * Get the unique element ID for @node. + * + * Returns: (transfer none): the element's ID + */ +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; +} + +/** + * st_theme_node_get_element_classes: + * @node: a #StThemeNode + * + * Get the list of element classes for @node. + * + * Returns: (transfer none): the element's classes + */ +GStrv +st_theme_node_get_element_classes (StThemeNode *node) +{ + g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL); + + return node->element_classes; +} + +/** + * st_theme_node_get_pseudo_classes: + * @node: a #StThemeNode + * + * Get the list of pseudo-classes for @node (eg. `:focused`). + * + * Returns: (transfer none): the element's pseudo-classes + */ +GStrv +st_theme_node_get_pseudo_classes (StThemeNode *node) +{ + g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL); + + return node->pseudo_classes; +} + +/** + * st_theme_node_equal: + * @node_a: first #StThemeNode + * @node_b: second #StThemeNode + * + * Compare two #StThemeNodes. Two nodes which compare equal will match + * the same CSS rules and have the same style properties. However, two + * nodes that have ended up with identical style properties do not + * necessarily compare equal. + * + * In detail, @node_a and @node_b are considered equal if and only if: + * + * - they share the same #StTheme and #StThemeContext + * - they have the same parent + * - they have the same element type + * - their id, class, pseudo-class and inline-style match + * + * Returns: %TRUE if @node_a equals @node_b + */ +gboolean +st_theme_node_equal (StThemeNode *node_a, StThemeNode *node_b) +{ + g_return_val_if_fail (ST_IS_THEME_NODE (node_a), FALSE); + + if (node_a == node_b) + return TRUE; + + g_return_val_if_fail (ST_IS_THEME_NODE (node_b), FALSE); + + if (node_a->parent_node != node_b->parent_node || + node_a->context != node_b->context || + node_a->theme != node_b->theme || + node_a->element_type != node_b->element_type || + node_a->cached_scale_factor != node_b->cached_scale_factor || + g_strcmp0 (node_a->element_id, node_b->element_id) || + g_strcmp0 (node_a->inline_style, node_b->inline_style)) + return FALSE; + + if ((node_a->element_classes == NULL) != (node_b->element_classes == NULL)) + return FALSE; + + if ((node_a->pseudo_classes == NULL) != (node_b->pseudo_classes == NULL)) + return FALSE; + + if (node_a->element_classes != NULL) + { + int i; + + for (i = 0; ; i++) + { + if (g_strcmp0 (node_a->element_classes[i], + node_b->element_classes[i])) + return FALSE; + + if (node_a->element_classes[i] == NULL) + break; + } + } + + if (node_a->pseudo_classes != NULL) + { + int i; + + for (i = 0; ; i++) + { + if (g_strcmp0 (node_a->pseudo_classes[i], + node_b->pseudo_classes[i])) + return FALSE; + + if (node_a->pseudo_classes[i] == NULL) + break; + } + } + + return TRUE; +} + +/** + * st_theme_node_hash: + * @node: a #StThemeNode + * + * Converts @node to a hash value. + * + * Returns: a hash value corresponding to @node + */ +guint +st_theme_node_hash (StThemeNode *node) +{ + guint hash; + + g_return_val_if_fail (ST_IS_THEME_NODE(node), 0); + + hash = GPOINTER_TO_UINT (node->parent_node); + + hash = hash * 33 + GPOINTER_TO_UINT (node->context); + hash = hash * 33 + GPOINTER_TO_UINT (node->theme); + hash = hash * 33 + ((guint) node->element_type); + hash = hash * 33 + ((guint) node->cached_scale_factor); + + if (node->element_id != NULL) + hash = hash * 33 + g_str_hash (node->element_id); + + if (node->inline_style != NULL) + hash = hash * 33 + g_str_hash (node->inline_style); + + if (node->element_classes != NULL) + { + gchar **it; + + for (it = node->element_classes; *it != NULL; it++) + hash = hash * 33 + g_str_hash (*it) + 1; + } + + if (node->pseudo_classes != NULL) + { + gchar **it; + + for (it = node->pseudo_classes; *it != NULL; it++) + hash = hash * 33 + g_str_hash (*it) + 1; + } + + return hash; +} + +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 && *node->inline_style != '\0') + { + 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; + default: + g_assert_not_reached(); + 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; + + if (term_is_inherit (term)) + { + return VALUE_INHERIT; + } + /* Since libcroco doesn't know about rgba colors, it can't handle + * the transparent keyword + */ + else 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. + */ + else if (term->type == TERM_FUNCTION && + term->content.str && + term->content.str->stryng && + term->content.str->stryng->str && + strcmp (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.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_lookup_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: (out caller-allocates): 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. + * + * See also st_theme_node_get_color(), which provides a simpler API. + * + * Returns: %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_lookup_color (StThemeNode *node, + const char *property_name, + gboolean inherit, + ClutterColor *color) +{ + + int i; + + g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE); + g_return_val_if_fail (property_name != NULL, FALSE); + + 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_lookup_color (node->parent_node, property_name, inherit, color); + else + break; + } + } + } + + if (inherit && node->parent_node) + return st_theme_node_lookup_color (node->parent_node, property_name, inherit, color); + + return FALSE; +} + +/** + * st_theme_node_get_color: + * @node: a #StThemeNode + * @property_name: The name of the color property + * @color: (out caller-allocates): location to store the color that + * was determined. + * + * 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. + * + * If @property_name is not found, a warning will be logged and a + * default color returned. + * + * See also st_theme_node_lookup_color(), which provides more options, + * and lets you handle the case where the theme does not specify the + * indicated color. + */ +void +st_theme_node_get_color (StThemeNode *node, + const char *property_name, + ClutterColor *color) +{ + if (!st_theme_node_lookup_color (node, property_name, FALSE, color)) + { + g_warning ("Did not find color property '%s'", property_name); + memset (color, 0, sizeof (ClutterColor)); + } +} + +/** + * st_theme_node_lookup_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: (out): 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. + * + * See also st_theme_node_get_double(), which provides a simpler API. + * + * Returns: %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_lookup_double (StThemeNode *node, + const char *property_name, + gboolean inherit, + double *value) +{ + gboolean result = FALSE; + int i; + + g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE); + g_return_val_if_fail (property_name != NULL, FALSE); + + 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_lookup_double (node->parent_node, property_name, inherit, value); + + return result; +} + +/** + * st_theme_node_lookup_time: + * @node: a #StThemeNode + * @property_name: The name of the time 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: (out): 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 time value, + * which is converted to milliseconds. + * + * Returns: %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_lookup_time (StThemeNode *node, + const char *property_name, + gboolean inherit, + double *value) +{ + gboolean result = FALSE; + int i; + + g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE); + g_return_val_if_fail (property_name != NULL, FALSE); + + 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; + int factor = 1; + + if (term->type != TERM_NUMBER) + continue; + + if (term->content.num->type != NUM_TIME_S && + term->content.num->type != NUM_TIME_MS) + continue; + + if (term->content.num->type == NUM_TIME_S) + factor = 1000; + + *value = factor * term->content.num->val; + result = TRUE; + break; + } + } + + if (!result && inherit && node->parent_node) + result = st_theme_node_lookup_time (node->parent_node, property_name, inherit, value); + + return result; +} + +/** + * st_theme_node_get_double: + * @node: a #StThemeNode + * @property_name: The name of the numeric property + * + * Generically looks up a property containing a single numeric value + * without units. + * + * See also st_theme_node_lookup_double(), which provides more options, + * and lets you handle the case where the theme does not specify the + * indicated value. + * + * Returns: the value found. If @property_name is not + * found, a warning will be logged and 0 will be returned. + */ +gdouble +st_theme_node_get_double (StThemeNode *node, + const char *property_name) +{ + gdouble value; + + if (st_theme_node_lookup_double (node, property_name, FALSE, &value)) + return value; + else + { + g_warning ("Did not find double property '%s'", property_name); + return 0.0; + } +} + +/** + * st_theme_node_lookup_url: + * @node: a #StThemeNode + * @property_name: The name of the string 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. + * @file: (out) (transfer full): location to store the newly allocated value that was + * determined. If the property is not found, the value in this location + * will not be changed. + * + * Looks up a property containing a single URL value. + * + * See also st_theme_node_get_url(), which provides a simpler API. + * + * Returns: %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_lookup_url (StThemeNode *node, + const char *property_name, + gboolean inherit, + GFile **file) +{ + gboolean result = FALSE; + int i; + + g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE); + g_return_val_if_fail (property_name != NULL, FALSE); + + 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; + CRStyleSheet *base_stylesheet; + + if (term->type != TERM_URI && term->type != TERM_STRING) + continue; + + if (decl->parent_statement != NULL) + base_stylesheet = decl->parent_statement->parent_sheet; + else + base_stylesheet = NULL; + + *file = _st_theme_resolve_url (node->theme, + base_stylesheet, + decl->value->content.str->stryng->str); + result = TRUE; + break; + } + } + + if (!result && inherit && node->parent_node) + result = st_theme_node_lookup_url (node->parent_node, property_name, inherit, file); + + return result; +} + +/** + * st_theme_node_get_url: + * @node: a #StThemeNode + * @property_name: The name of the string property + * + * Looks up a property containing a single URL value. + * + * See also st_theme_node_lookup_url(), which provides more options, + * and lets you handle the case where the theme does not specify the + * indicated value. + * + * Returns: (nullable) (transfer full): the newly allocated value if found. + * If @property_name is not found, a warning will be logged and %NULL + * will be returned. + */ +GFile * +st_theme_node_get_url (StThemeNode *node, + const char *property_name) +{ + GFile *file; + + if (st_theme_node_lookup_url (node, property_name, FALSE, &file)) + return file; + else + { + g_warning ("Did not find string property '%s'", property_name); + return NULL; + } +} + +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 at line %d, col %d", + term->location.line, term->location.column); + return VALUE_NOT_FOUND; + } + + num = term->content.num; + + switch (num->type) + { + case NUM_LENGTH_PX: + type = ABSOLUTE; + multiplier = 1 * node->cached_scale_factor; + 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: + { + if (num->val != 0) + { + g_warning ("length values must specify a unit"); + return VALUE_NOT_FOUND; + } + else + { + type = ABSOLUTE; + multiplier = 0; + } + break; + } + + 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: + default: + 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 = clutter_backend_get_resolution (clutter_get_default_backend ()); + *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 = clutter_backend_get_resolution (clutter_get_default_backend ()); + *length = num->val * multiplier * (resolution / 72.) * font_size; + } + } + break; + default: + g_assert_not_reached (); + } + + return VALUE_FOUND; +} + +static GetFromTermResult +get_length_from_term_int (StThemeNode *node, + CRTerm *term, + gboolean use_parent_font, + gint *length) +{ + double value; + GetFromTermResult result; + + result = get_length_from_term (node, term, use_parent_font, &value); + if (result == VALUE_FOUND) + *length = (int) ((value / node->cached_scale_factor) + 0.5) * node->cached_scale_factor; + return result; +} + +static GetFromTermResult +get_length_internal (StThemeNode *node, + const char *property_name, + 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) + { + 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_lookup_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: (out): 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. + * + * See also st_theme_node_get_length(), which provides a simpler API. + * + * Returns: %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_lookup_length (StThemeNode *node, + const char *property_name, + gboolean inherit, + gdouble *length) +{ + GetFromTermResult result; + + g_return_val_if_fail (ST_IS_THEME_NODE(node), FALSE); + g_return_val_if_fail (property_name != NULL, FALSE); + + result = get_length_internal (node, property_name, length); + + if (result == VALUE_FOUND) + return TRUE; + else if (result == VALUE_INHERIT) + inherit = TRUE; + + if (inherit && node->parent_node) + return st_theme_node_lookup_length (node->parent_node, property_name, inherit, length); + + return FALSE; +} + +/** + * st_theme_node_get_length: + * @node: a #StThemeNode + * @property_name: The name of the length property + * + * 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. + * + * Unlike st_theme_node_get_color() and st_theme_node_get_double(), + * this does not print a warning if the property is not found; it just + * returns 0. + * + * See also st_theme_node_lookup_length(), which provides more options. The + * returned value is in physical pixels, as opposed to logical pixels. + * + * Returns: the length, in pixels, or 0 if the property was not found. + */ +gdouble +st_theme_node_get_length (StThemeNode *node, + const char *property_name) +{ + gdouble length; + + if (st_theme_node_lookup_length (node, property_name, FALSE, &length)) + return length; + else + return 0.0; +} + +static void +do_border_radius_term (StThemeNode *node, + CRTerm *term, + gboolean topleft, + gboolean topright, + gboolean bottomright, + gboolean bottomleft) +{ + int value; + + if (get_length_from_term_int (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; + int width = 0; /* suppress warning */ + 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/style 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; + width_set = TRUE; + 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, "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_int (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_int (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_outline_property (StThemeNode *node, + CRDeclaration *decl) +{ + const char *property_name = decl->property->stryng->str + 7; /* Skip 'outline' */ + ClutterColor color; + gboolean color_set = FALSE; + int width = 0; /* suppress warning */ + gboolean width_set = FALSE; + + if (strcmp (property_name, "") == 0) + { + /* Set value for width/color/style 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; + width_set = TRUE; + 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, "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_int (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_int (node, decl->value, FALSE, &width) == VALUE_FOUND) + /* Ignore inherit */ + width_set = TRUE; + } + + if (color_set) + node->outline_color = color; + if (width_set) + node->outline_width = width; +} + +static void +do_padding_property_term (StThemeNode *node, + CRTerm *term, + gboolean left, + gboolean right, + gboolean top, + gboolean bottom) +{ + int value; + + if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND) + return; + + if (left) + node->padding[ST_SIDE_LEFT] = value; + if (right) + node->padding[ST_SIDE_RIGHT] = value; + if (top) + node->padding[ST_SIDE_TOP] = value; + if (bottom) + node->padding[ST_SIDE_BOTTOM] = 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_margin_property_term (StThemeNode *node, + CRTerm *term, + gboolean left, + gboolean right, + gboolean top, + gboolean bottom) +{ + int value; + + if (get_length_from_term_int (node, term, FALSE, &value) != VALUE_FOUND) + return; + + if (left) + node->margin[ST_SIDE_LEFT] = value; + if (right) + node->margin[ST_SIDE_RIGHT] = value; + if (top) + node->margin[ST_SIDE_TOP] = value; + if (bottom) + node->margin[ST_SIDE_BOTTOM] = value; +} + +static void +do_margin_property (StThemeNode *node, + CRDeclaration *decl) +{ + const char *property_name = decl->property->stryng->str + 6; /* Skip 'margin' */ + + 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_margin_property_term (node, decl->value, TRUE, TRUE, TRUE, TRUE); /* left/right/top/bottom */ + return; + } + else if (decl->value->next->next == NULL) /* 2 values */ + { + do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, TRUE); /* top/bottom */ + do_margin_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */ + } + else if (decl->value->next->next->next == NULL) /* 3 values */ + { + do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */ + do_margin_property_term (node, decl->value->next, TRUE, TRUE, FALSE, FALSE); /* left/right */ + do_margin_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */ + } + else if (decl->value->next->next->next->next == NULL) /* 4 values */ + { + do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); /* top */ + do_margin_property_term (node, decl->value->next, FALSE, TRUE, FALSE, FALSE); /* right */ + do_margin_property_term (node, decl->value->next->next, FALSE, FALSE, FALSE, TRUE); /* bottom */ + do_margin_property_term (node, decl->value->next->next->next, TRUE, FALSE, FALSE, FALSE); /* left */ + } + else + { + g_warning ("Too many values for margin property"); + return; + } + } + else + { + if (decl->value == NULL || decl->value->next != NULL) + return; + + if (strcmp (property_name, "-left") == 0) + do_margin_property_term (node, decl->value, TRUE, FALSE, FALSE, FALSE); + else if (strcmp (property_name, "-right") == 0) + do_margin_property_term (node, decl->value, FALSE, TRUE, FALSE, FALSE); + else if (strcmp (property_name, "-top") == 0) + do_margin_property_term (node, decl->value, FALSE, FALSE, TRUE, FALSE); + else if (strcmp (property_name, "-bottom") == 0) + do_margin_property_term (node, decl->value, FALSE, FALSE, FALSE, TRUE); + } +} + +static void +do_size_property (StThemeNode *node, + CRDeclaration *decl, + int *node_value) +{ + CRTerm *term = decl->value; + + if (term->type == TERM_IDENT && + strcmp (term->content.str->stryng->str, "auto") == 0) + *node_value = -1; + else + get_length_from_term_int (node, term, FALSE, node_value); +} + +void +_st_theme_node_ensure_geometry (StThemeNode *node) +{ + int i, j; + int width, height; + + 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->outline_width = 0; + node->outline_color = TRANSPARENT_COLOR; + + width = -1; + height = -1; + node->width = -1; + node->height = -1; + node->min_width = -1; + node->min_height = -1; + node->max_width = -1; + node->max_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, "outline")) + do_outline_property (node, decl); + else if (g_str_has_prefix (property_name, "padding")) + do_padding_property (node, decl); + else if (g_str_has_prefix (property_name, "margin")) + do_margin_property (node, decl); + else if (strcmp (property_name, "width") == 0) + do_size_property (node, decl, &width); + else if (strcmp (property_name, "height") == 0) + do_size_property (node, decl, &height); + else if (strcmp (property_name, "-st-natural-width") == 0) + do_size_property (node, decl, &node->width); + else if (strcmp (property_name, "-st-natural-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); + else if (strcmp (property_name, "max-width") == 0) + do_size_property (node, decl, &node->max_width); + else if (strcmp (property_name, "max-height") == 0) + do_size_property (node, decl, &node->max_height); + } + + /* + * Setting width sets max-width, min-width and -st-natural-width, + * unless one of them is set individually. + * Setting min-width sets natural width too, so that the minimum + * width reported by get_preferred_width() is always not greater + * than the natural width. + * The natural width in node->width is actually a lower bound, the + * actor is allowed to request something greater than that, but + * not greater than max-width. + * We don't need to clamp node->width to be less than max_width, + * that's done by adjust_preferred_width. + */ + if (width != -1) + { + if (node->width == -1) + node->width = width; + if (node->min_width == -1) + node->min_width = width; + if (node->max_width == -1) + node->max_width = width; + } + + if (node->width < node->min_width) + node->width = node->min_width; + + if (height != -1) + { + if (node->height == -1) + node->height = height; + if (node->min_height == -1) + node->min_height = height; + if (node->max_height == -1) + node->max_height = height; + } + + if (node->height < node->min_height) + node->height = node->min_height; +} + +/** + * st_theme_node_get_border_width: + * @node: a #StThemeNode + * @side: a #StCorner + * + * Get the border width for @node on @side, in physical pixels. + * + * Returns: the border width in physical pixels + */ +int +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.); + + _st_theme_node_ensure_geometry (node); + + return node->border_width[side]; +} + +/** + * st_theme_node_get_border_radius: + * @node: a #StThemeNode + * @corner: a #StCorner + * + * Get the border radius for @node at @corner, in physical pixels. + * + * Returns: the border radius in physical pixels + */ +int +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.); + + _st_theme_node_ensure_geometry (node); + + return node->border_radius[corner]; +} + +/** + * st_theme_node_get_outline_width: + * @node: a #StThemeNode + * + * Get the width of the outline for @node, in physical pixels. + * + * Returns: the width in physical pixels + */ +int +st_theme_node_get_outline_width (StThemeNode *node) +{ + g_return_val_if_fail (ST_IS_THEME_NODE (node), 0); + + _st_theme_node_ensure_geometry (node); + + return node->outline_width; +} + +/** + * st_theme_node_get_outline_color: + * @node: a #StThemeNode + * @color: (out caller-allocates): location to store the color + * + * Gets the color of @node's outline. + */ +void +st_theme_node_get_outline_color (StThemeNode *node, + ClutterColor *color) +{ + g_return_if_fail (ST_IS_THEME_NODE (node)); + + _st_theme_node_ensure_geometry (node); + + *color = node->outline_color; +} + +/** + * st_theme_node_get_width: + * @node: a #StThemeNode + * + * Get the width for @node, in physical pixels. + * + * Returns: the width in physical pixels + */ +int +st_theme_node_get_width (StThemeNode *node) +{ + g_return_val_if_fail (ST_IS_THEME_NODE (node), -1); + + _st_theme_node_ensure_geometry (node); + return node->width; +} + +/** + * st_theme_node_get_height: + * @node: a #StThemeNode + * + * Get the height for @node, in physical pixels. + * + * Returns: the height in physical pixels + */ +int +st_theme_node_get_height (StThemeNode *node) +{ + g_return_val_if_fail (ST_IS_THEME_NODE (node), -1); + + _st_theme_node_ensure_geometry (node); + return node->height; +} + +/** + * st_theme_node_get_min_width: + * @node: a #StThemeNode + * + * Get the minimum width for @node, in physical pixels. + * + * Returns: the minimum width in physical pixels + */ +int +st_theme_node_get_min_width (StThemeNode *node) +{ + g_return_val_if_fail (ST_IS_THEME_NODE (node), -1); + + _st_theme_node_ensure_geometry (node); + return node->min_width; +} + +/** + * st_theme_node_get_min_height: + * @node: a #StThemeNode + * + * Get the minimum height for @node, in physical pixels. + * + * Returns: the minimum height in physical pixels + */ +int +st_theme_node_get_min_height (StThemeNode *node) +{ + g_return_val_if_fail (ST_IS_THEME_NODE (node), -1); + + _st_theme_node_ensure_geometry (node); + return node->min_height; +} + +/** + * st_theme_node_get_max_width: + * @node: a #StThemeNode + * + * Get the maximum width for @node, in physical pixels. + * + * Returns: the maximum width in physical pixels + */ +int +st_theme_node_get_max_width (StThemeNode *node) +{ + g_return_val_if_fail (ST_IS_THEME_NODE (node), -1); + + _st_theme_node_ensure_geometry (node); + return node->max_width; +} + +/** + * st_theme_node_get_max_height: + * @node: a #StThemeNode + * + * Get the maximum height for @node, in physical pixels. + * + * Returns: the maximum height in physical pixels + */ +int +st_theme_node_get_max_height (StThemeNode *node) +{ + g_return_val_if_fail (ST_IS_THEME_NODE (node), -1); + + _st_theme_node_ensure_geometry (node); + return node->max_height; +} + +void +_st_theme_node_ensure_background (StThemeNode *node) +{ + int i; + + if (node->background_computed) + return; + + node->background_repeat = FALSE; + node->background_computed = TRUE; + node->background_color = TRANSPARENT_COLOR; + node->background_gradient_type = ST_GRADIENT_NONE; + node->background_position_set = FALSE; + node->background_size = ST_BACKGROUND_SIZE_AUTO; + + 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_clear_object (&node->background_image); + node->background_position_set = FALSE; + node->background_size = ST_BACKGROUND_SIZE_AUTO; + + for (term = decl->value; term; term = term->next) + { + GetFromTermResult result = get_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_object_ref (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) + { + CRStyleSheet *base_stylesheet; + GFile *file; + + if (decl->parent_statement != NULL) + base_stylesheet = decl->parent_statement->parent_sheet; + else + base_stylesheet = NULL; + + file = _st_theme_resolve_url (node->theme, + base_stylesheet, + term->content.str->stryng->str); + + node->background_image = file; + } + } + } + else if (strcmp (property_name, "-position") == 0) + { + GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_position_x); + if (result == VALUE_NOT_FOUND) + { + node->background_position_set = FALSE; + continue; + } + else + node->background_position_set = TRUE; + + result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_position_y); + + if (result == VALUE_NOT_FOUND) + { + node->background_position_set = FALSE; + continue; + } + else + node->background_position_set = TRUE; + } + else if (strcmp (property_name, "-repeat") == 0) + { + if (decl->value->type == TERM_IDENT) + { + if (strcmp (decl->value->content.str->stryng->str, "repeat") == 0) + node->background_repeat = TRUE; + } + } + else if (strcmp (property_name, "-size") == 0) + { + if (decl->value->type == TERM_IDENT) + { + if (strcmp (decl->value->content.str->stryng->str, "contain") == 0) + node->background_size = ST_BACKGROUND_SIZE_CONTAIN; + else if (strcmp (decl->value->content.str->stryng->str, "cover") == 0) + node->background_size = ST_BACKGROUND_SIZE_COVER; + else if ((strcmp (decl->value->content.str->stryng->str, "auto") == 0) && (decl->value->next) && (decl->value->next->type == TERM_NUMBER)) + { + GetFromTermResult result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h); + + node->background_size_w = -1; + node->background_size = (result == VALUE_FOUND) ? ST_BACKGROUND_SIZE_FIXED : ST_BACKGROUND_SIZE_AUTO; + } + else + node->background_size = ST_BACKGROUND_SIZE_AUTO; + } + else if (decl->value->type == TERM_NUMBER) + { + GetFromTermResult result = get_length_from_term_int (node, decl->value, FALSE, &node->background_size_w); + if (result == VALUE_NOT_FOUND) + continue; + + node->background_size = ST_BACKGROUND_SIZE_FIXED; + + if ((decl->value->next) && (decl->value->next->type == TERM_NUMBER)) + { + result = get_length_from_term_int (node, decl->value->next, FALSE, &node->background_size_h); + + if (result == VALUE_FOUND) + continue; + } + node->background_size_h = -1; + } + else + node->background_size = ST_BACKGROUND_SIZE_AUTO; + } + else if (strcmp (property_name, "-color") == 0) + { + GetFromTermResult result; + + if (decl->value == NULL || decl->value->next != NULL) + continue; + + result = get_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) + { + CRStyleSheet *base_stylesheet; + + if (decl->parent_statement != NULL) + base_stylesheet = decl->parent_statement->parent_sheet; + else + base_stylesheet = NULL; + + g_clear_object (&node->background_image); + node->background_image = _st_theme_resolve_url (node->theme, + base_stylesheet, + decl->value->content.str->stryng->str); + } + else if (term_is_inherit (decl->value)) + { + g_clear_object (&node->background_image); + node->background_image = g_object_ref (st_theme_node_get_background_image (node->parent_node)); + } + else if (term_is_none (decl->value)) + { + g_clear_object (&node->background_image); + } + } + else if (strcmp (property_name, "-gradient-direction") == 0) + { + CRTerm *term = decl->value; + if (strcmp (term->content.str->stryng->str, "vertical") == 0) + { + node->background_gradient_type = ST_GRADIENT_VERTICAL; + } + else if (strcmp (term->content.str->stryng->str, "horizontal") == 0) + { + node->background_gradient_type = ST_GRADIENT_HORIZONTAL; + } + else if (strcmp (term->content.str->stryng->str, "radial") == 0) + { + node->background_gradient_type = ST_GRADIENT_RADIAL; + } + else if (strcmp (term->content.str->stryng->str, "none") == 0) + { + node->background_gradient_type = ST_GRADIENT_NONE; + } + else + { + g_warning ("Unrecognized background-gradient-direction \"%s\"", + term->content.str->stryng->str); + } + } + else if (strcmp (property_name, "-gradient-start") == 0) + { + get_color_from_term (node, decl->value, &node->background_color); + } + else if (strcmp (property_name, "-gradient-end") == 0) + { + get_color_from_term (node, decl->value, &node->background_gradient_end); + } + } +} + +/** + * st_theme_node_get_background_color: + * @node: a #StThemeNode + * @color: (out caller-allocates): location to store the color + * + * Gets @node's background color. + */ +void +st_theme_node_get_background_color (StThemeNode *node, + ClutterColor *color) +{ + g_return_if_fail (ST_IS_THEME_NODE (node)); + + _st_theme_node_ensure_background (node); + + *color = node->background_color; +} + +/** + * st_theme_node_get_background_image: + * @node: a #StThemeNode + * + * Returns: (transfer none): @node's background image. + */ +GFile * +st_theme_node_get_background_image (StThemeNode *node) +{ + g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL); + + _st_theme_node_ensure_background (node); + + return node->background_image; +} + +/** + * st_theme_node_get_foreground_color: + * @node: a #StThemeNode + * @color: (out caller-allocates): location to store the color + * + * Gets @node's foreground color. + */ +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; +} + + +/** + * st_theme_node_get_background_gradient: + * @node: A #StThemeNode + * @type: (out): Type of gradient + * @start: (out caller-allocates): Color at start of gradient + * @end: (out caller-allocates): Color at end of gradient + * + * The @start and @end arguments will only be set if @type is not #ST_GRADIENT_NONE. + */ +void +st_theme_node_get_background_gradient (StThemeNode *node, + StGradientType *type, + ClutterColor *start, + ClutterColor *end) +{ + g_return_if_fail (ST_IS_THEME_NODE (node)); + + _st_theme_node_ensure_background (node); + + *type = node->background_gradient_type; + if (*type != ST_GRADIENT_NONE) + { + *start = node->background_color; + *end = node->background_gradient_end; + } +} + +/** + * st_theme_node_get_border_color: + * @node: a #StThemeNode + * @side: a #StSide + * @color: (out caller-allocates): location to store the color + * + * Gets the color of @node's border on @side + */ +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); + + _st_theme_node_ensure_geometry (node); + + *color = node->border_color[side]; +} + +/** + * st_theme_node_get_padding: + * @node: a #StThemeNode + * @side: a #StSide + * + * Get the padding for @node on @side, in physical pixels. This corresponds to + * the CSS properties such as `padding-top`. + * + * Returns: the padding size in physical pixels + */ +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.); + + _st_theme_node_ensure_geometry (node); + + return node->padding[side]; +} + +/** + * st_theme_node_get_margin: + * @node: a #StThemeNode + * @side: a #StSide + * + * Get the margin for @node on @side, in physical pixels. This corresponds to + * the CSS properties such as `margin-top`. + * + * Returns: the margin size in physical pixels + */ +double +st_theme_node_get_margin (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.); + + _st_theme_node_ensure_geometry (node); + + return node->margin[side]; +} + +/** + * st_theme_node_get_transition_duration: + * @node: an #StThemeNode + * + * Get the value of the transition-duration property, which + * specifies the transition time between the previous #StThemeNode + * and @node. + * + * Returns: the node's transition duration in milliseconds + */ +int +st_theme_node_get_transition_duration (StThemeNode *node) +{ + StSettings *settings; + gdouble value = 0.0; + gdouble factor; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), 0); + + settings = st_settings_get (); + g_object_get (settings, "slow-down-factor", &factor, NULL); + + if (node->transition_duration > -1) + return factor * node->transition_duration; + + st_theme_node_lookup_time (node, "transition-duration", FALSE, &value); + + node->transition_duration = (int)value; + + return factor * node->transition_duration; +} + +/** + * st_theme_node_get_icon_style: + * @node: a #StThemeNode + * + * Get the icon style for @node (eg. symbolic, regular). This corresponds to the + * special `-st-icon-style` CSS property. + * + * Returns: the icon style for @node + */ +StIconStyle +st_theme_node_get_icon_style (StThemeNode *node) +{ + int i; + + g_return_val_if_fail (ST_IS_THEME_NODE(node), ST_ICON_STYLE_REQUESTED); + + ensure_properties (node); + + for (i = node->n_properties - 1; i >= 0; i--) + { + CRDeclaration *decl = node->properties[i]; + + if (strcmp (decl->property->stryng->str, "-st-icon-style") == 0) + { + CRTerm *term; + + for (term = decl->value; term; term = term->next) + { + if (term->type != TERM_IDENT) + goto next_decl; + + if (strcmp (term->content.str->stryng->str, "requested") == 0) + return ST_ICON_STYLE_REQUESTED; + else if (strcmp (term->content.str->stryng->str, "regular") == 0) + return ST_ICON_STYLE_REGULAR; + else if (strcmp (term->content.str->stryng->str, "symbolic") == 0) + return ST_ICON_STYLE_SYMBOLIC; + else + g_warning ("Unknown -st-icon-style \"%s\"", + term->content.str->stryng->str); + } + } + + next_decl: + ; + } + + if (node->parent_node) + return st_theme_node_get_icon_style (node->parent_node); + + return ST_ICON_STYLE_REQUESTED; +} + +/** + * st_theme_node_get_text_decoration + * @node: a #StThemeNode + * + * Get the text decoration for @node (eg. underline, line-through, etc). + * + * Returns: the text decoration for @node + */ +StTextDecoration +st_theme_node_get_text_decoration (StThemeNode *node) +{ + int i; + + g_return_val_if_fail (ST_IS_THEME_NODE(node), 0); + + 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; +} + +/** + * st_theme_node_get_text_align: + * @node: a #StThemeNode + * + * Get the text alignment of @node. + * + * Returns: the alignment of text for @node + */ +StTextAlign +st_theme_node_get_text_align(StThemeNode *node) +{ + int i; + + g_return_val_if_fail (ST_IS_THEME_NODE(node), ST_TEXT_ALIGN_LEFT); + + ensure_properties(node); + + for (i = node->n_properties - 1; i >= 0; i--) + { + CRDeclaration *decl = node->properties[i]; + + if (strcmp(decl->property->stryng->str, "text-align") == 0) + { + CRTerm *term = decl->value; + + if (term->type != TERM_IDENT || term->next) + continue; + + if (strcmp(term->content.str->stryng->str, "inherit") == 0) + { + if (node->parent_node) + return st_theme_node_get_text_align(node->parent_node); + return ST_TEXT_ALIGN_LEFT; + } + else if (strcmp(term->content.str->stryng->str, "left") == 0) + { + return ST_TEXT_ALIGN_LEFT; + } + else if (strcmp(term->content.str->stryng->str, "right") == 0) + { + return ST_TEXT_ALIGN_RIGHT; + } + else if (strcmp(term->content.str->stryng->str, "center") == 0) + { + return ST_TEXT_ALIGN_CENTER; + } + else if (strcmp(term->content.str->stryng->str, "justify") == 0) + { + return ST_TEXT_ALIGN_JUSTIFY; + } + } + } + if(node->parent_node) + return st_theme_node_get_text_align(node->parent_node); + + if (clutter_get_default_text_direction () == CLUTTER_TEXT_DIRECTION_RTL) + return ST_TEXT_ALIGN_RIGHT; + return ST_TEXT_ALIGN_LEFT; +} + +/** + * st_theme_node_get_letter_spacing: + * @node: a #StThemeNode + * + * Gets the value for the letter-spacing style property, in physical pixels. + * + * Returns: the value of the letter-spacing property, if + * found, or zero if such property has not been found. + */ +gdouble +st_theme_node_get_letter_spacing (StThemeNode *node) +{ + gdouble spacing = 0.; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), spacing); + + ensure_properties (node); + + st_theme_node_lookup_length (node, "letter-spacing", FALSE, &spacing); + return spacing; +} + +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 = clutter_backend_get_resolution (clutter_get_default_backend ()); + /* 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; +} + +/** + * st_theme_node_get_font: + * @node: a #StThemeNode + * + * Get the current font of @node as a #PangoFontDescription + * + * Returns: (transfer none): the current font + */ +const PangoFontDescription * +st_theme_node_get_font (StThemeNode *node) +{ + /* Initialized despite _set flags to suppress compiler warnings */ + PangoStyle font_style = PANGO_STYLE_NORMAL; + gboolean font_style_set = FALSE; + PangoVariant variant = PANGO_VARIANT_NORMAL; + gboolean variant_set = FALSE; + PangoWeight weight = PANGO_WEIGHT_NORMAL; + gboolean weight_absolute = TRUE; + gboolean weight_set = FALSE; + double size = 0.; + gboolean size_set = FALSE; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL); + + char *family = NULL; + double parent_size; + 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 = clutter_backend_get_resolution (clutter_get_default_backend ()); + 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); + g_free (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_font_features: + * @node: a #StThemeNode + * + * Get the CSS font-features for @node. + * + * Returns: (transfer full): font-features as a string + */ +gchar * +st_theme_node_get_font_features (StThemeNode *node) +{ + int i; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL); + + ensure_properties (node); + + for (i = node->n_properties - 1; i >= 0; i--) + { + CRDeclaration *decl = node->properties[i]; + + if (strcmp (decl->property->stryng->str, "font-feature-settings") == 0) + { + CRTerm *term = decl->value; + + if (!term->next && term->type == TERM_IDENT) + { + gchar *ident = term->content.str->stryng->str; + + if (strcmp (ident, "inherit") == 0) + break; + + if (strcmp (ident, "normal") == 0) + return NULL; + } + + return (gchar *)cr_term_to_string (term); + } + } + + return node->parent_node ? st_theme_node_get_font_features (node->parent_node) : NULL; +} + +/** + * st_theme_node_get_border_image: + * @node: a #StThemeNode + * + * Gets the value for the border-image style property + * + * Returns: (transfer none): the border image, or %NULL + * if there is no border image. + */ +StBorderImage * +st_theme_node_get_border_image (StThemeNode *node) +{ + int i; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL); + + 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; + CRStyleSheet *base_stylesheet; + int borders[4]; + int n_borders = 0; + int j; + + const char *url; + int border_top; + int border_right; + int border_bottom; + int border_left; + + GFile *file; + + /* Support border-image: none; to suppress a previously specified border image */ + if (term_is_none (term)) + { + if (term->next == NULL) + return NULL; + else + goto next_property; + } + + /* 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 (j = 0; j < 4; j++) + { + 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; + } + + if (decl->parent_statement != NULL) + base_stylesheet = decl->parent_statement->parent_sheet; + else + base_stylesheet = NULL; + + file = _st_theme_resolve_url (node->theme, base_stylesheet, url); + + if (file == NULL) + goto next_property; + + node->border_image = st_border_image_new (file, + border_top, border_right, border_bottom, border_left, + node->cached_scale_factor); + + g_object_unref (file); + + return node->border_image; + } + + next_property: + ; + } + + return NULL; +} + +/** + * st_theme_node_get_horizontal_padding: + * @node: a #StThemeNode + * + * Gets the total horizontal padding (left + right padding), in physical pixels. + * + * Returns: the total horizontal padding in physical pixels + */ +double +st_theme_node_get_horizontal_padding (StThemeNode *node) +{ + double padding = 0.0; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), padding); + + padding += st_theme_node_get_padding (node, ST_SIDE_LEFT); + padding += st_theme_node_get_padding (node, ST_SIDE_RIGHT); + + return padding; +} + +/** + * st_theme_node_get_vertical_padding: + * @node: a #StThemeNode + * + * Gets the total vertical padding (top + bottom padding), in physical pixels. + * + * Returns: the total vertical padding in physical pixels + */ +double +st_theme_node_get_vertical_padding (StThemeNode *node) +{ + double padding = 0.0; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), padding); + + padding += st_theme_node_get_padding (node, ST_SIDE_TOP); + padding += st_theme_node_get_padding (node, ST_SIDE_BOTTOM); + + return padding; +} + +void +_st_theme_node_apply_margins (StThemeNode *node, + ClutterActor *actor) +{ + g_return_if_fail (ST_IS_THEME_NODE (node)); + + _st_theme_node_ensure_geometry (node); + + clutter_actor_set_margin_left (actor, st_theme_node_get_margin(node, ST_SIDE_LEFT)); + clutter_actor_set_margin_right (actor, st_theme_node_get_margin(node, ST_SIDE_RIGHT)); + clutter_actor_set_margin_top (actor, st_theme_node_get_margin(node, ST_SIDE_TOP)); + clutter_actor_set_margin_bottom (actor, st_theme_node_get_margin(node, ST_SIDE_BOTTOM)); +} + +static GetFromTermResult +parse_shadow_property (StThemeNode *node, + CRDeclaration *decl, + ClutterColor *color, + gdouble *xoffset, + gdouble *yoffset, + gdouble *blur, + gdouble *spread, + gboolean *inset, + gboolean *is_none) +{ + GetFromTermResult result; + CRTerm *term; + int n_offsets = 0; + *is_none = FALSE; + + /* default values */ + color->red = 0x0; color->green = 0x0; color->blue = 0x0; color->alpha = 0xff; + *xoffset = 0.; + *yoffset = 0.; + *blur = 0.; + *spread = 0.; + *inset = FALSE; + + /* The CSS3 draft of the box-shadow property[0] is a lot stricter + * regarding the order of terms: + * If the 'inset' keyword is specified, it has to be first or last, + * and the color may not be mixed with the lengths; while we parse + * length values in the correct order, we allow for arbitrary + * placement of the color and 'inset' keyword. + * + * [0] http://www.w3.org/TR/css3-background/#box-shadow + */ + for (term = decl->value; term; term = term->next) + { + /* if we found "none", we're all set with the default values */ + if (term_is_none (term)) { + *is_none = TRUE; + return VALUE_FOUND; + } + + if (term->type == TERM_NUMBER) + { + gdouble value; + gdouble multiplier; + + multiplier = (term->unary_op == MINUS_UOP) ? -1. : 1.; + result = get_length_from_term (node, term, FALSE, &value); + + if (result == VALUE_INHERIT) + { + /* we only allow inherit on the line by itself */ + if (n_offsets > 0) + return VALUE_NOT_FOUND; + else + return VALUE_INHERIT; + } + else if (result == VALUE_FOUND) + { + switch (n_offsets++) + { + case 0: + *xoffset = multiplier * value; + break; + case 1: + *yoffset = multiplier * value; + break; + case 2: + if (multiplier < 0) + g_warning ("Negative blur values are " + "not allowed"); + *blur = value; + break; + case 3: + if (multiplier < 0) + g_warning ("Negative spread values are " + "not allowed"); + *spread = value; + break; + default: + g_warning ("Ignoring excess values in shadow definition"); + break; + } + continue; + } + } + else if (term->type == TERM_IDENT && + strcmp (term->content.str->stryng->str, "inset") == 0) + { + *inset = TRUE; + continue; + } + + result = get_color_from_term (node, term, color); + + if (result == VALUE_INHERIT) + { + if (n_offsets > 0) + return VALUE_NOT_FOUND; + else + return VALUE_INHERIT; + } + else if (result == VALUE_FOUND) + { + continue; + } + } + + /* The only required terms are the x and y offsets + */ + if (n_offsets >= 2) + return VALUE_FOUND; + else + return VALUE_NOT_FOUND; +} + +/** + * st_theme_node_lookup_shadow: + * @node: a #StThemeNode + * @property_name: The name of the shadow 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. + * @shadow: (out): location to store the shadow + * + * If the property is not found, the value in the shadow variable will not + * be changed. + * + * Generically looks up a property containing a set of shadow values. When + * specific getters (like st_theme_node_get_box_shadow ()) exist, they + * should be used instead. They are cached, so more efficient, and have + * handling for shortcut properties and other details of CSS. + * + * See also st_theme_node_get_shadow(), which provides a simpler API. + * + * Returns: %TRUE if the property was found in the properties for this + * theme node (or in the properties of parent nodes when inheriting.), %FALSE + * if the property was not found, or was explicitly set to 'none'. + */ +gboolean +st_theme_node_lookup_shadow (StThemeNode *node, + const char *property_name, + gboolean inherit, + StShadow **shadow) +{ + ClutterColor color = { 0., }; + gdouble xoffset = 0.; + gdouble yoffset = 0.; + gdouble blur = 0.; + gdouble spread = 0.; + gboolean inset = FALSE; + gboolean is_none = FALSE; + + int i; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), FALSE); + g_return_val_if_fail (property_name != NULL, FALSE); + + 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 = parse_shadow_property (node, + decl, + &color, + &xoffset, + &yoffset, + &blur, + &spread, + &inset, + &is_none); + if (result == VALUE_FOUND) + { + if (is_none) + return FALSE; + + *shadow = st_shadow_new (&color, + xoffset, yoffset, + blur, spread, + inset); + return TRUE; + } + else if (result == VALUE_INHERIT) + { + if (node->parent_node) + return st_theme_node_lookup_shadow (node->parent_node, + property_name, + inherit, + shadow); + else + break; + } + } + } + + if (inherit && node->parent_node) + return st_theme_node_lookup_shadow (node->parent_node, + property_name, + inherit, + shadow); + + return FALSE; +} + +/** + * st_theme_node_get_shadow: + * @node: a #StThemeNode + * @property_name: The name of the shadow property + * + * Generically looks up a property containing a set of shadow values. When + * specific getters (like st_theme_node_get_box_shadow()) exist, they + * should be used instead. They are cached, so more efficient, and have + * handling for shortcut properties and other details of CSS. + * + * Like st_theme_get_length(), this does not print a warning if the property is + * not found; it just returns %NULL + * + * See also st_theme_node_lookup_shadow (), which provides more options. + * + * Returns: (nullable) (transfer full): the shadow, or %NULL if the property was + * not found. + */ +StShadow * +st_theme_node_get_shadow (StThemeNode *node, + const char *property_name) +{ + StShadow *shadow; + + if (st_theme_node_lookup_shadow (node, property_name, FALSE, &shadow)) + return shadow; + else + return NULL; +} + +/** + * st_theme_node_get_box_shadow: + * @node: a #StThemeNode + * + * Gets the value for the box-shadow style property + * + * Returns: (nullable) (transfer none): the node's shadow, or %NULL + * if node has no shadow + */ +StShadow * +st_theme_node_get_box_shadow (StThemeNode *node) +{ + StShadow *shadow; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL); + + if (node->box_shadow_computed) + return node->box_shadow; + + node->box_shadow = NULL; + node->box_shadow_computed = TRUE; + + if (st_theme_node_lookup_shadow (node, + "box-shadow", + FALSE, + &shadow)) + { + node->box_shadow = shadow; + + return node->box_shadow; + } + + return NULL; +} + +/** + * st_theme_node_get_background_image_shadow: + * @node: a #StThemeNode + * + * Gets the value for the -st-background-image-shadow style property + * + * Returns: (nullable) (transfer none): the node's background image shadow, or + * %NULL if node has no such shadow + */ +StShadow * +st_theme_node_get_background_image_shadow (StThemeNode *node) +{ + StShadow *shadow; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL); + + if (node->background_image_shadow_computed) + return node->background_image_shadow; + + node->background_image_shadow = NULL; + node->background_image_shadow_computed = TRUE; + + if (st_theme_node_lookup_shadow (node, + "-st-background-image-shadow", + FALSE, + &shadow)) + { + if (shadow->inset) + { + g_warning ("The -st-background-image-shadow property does not " + "support inset shadows"); + st_shadow_unref (shadow); + shadow = NULL; + } + + node->background_image_shadow = shadow; + + return node->background_image_shadow; + } + + return NULL; +} + +/** + * st_theme_node_get_text_shadow: + * @node: a #StThemeNode + * + * Gets the value for the text-shadow style property + * + * Returns: (nullable) (transfer none): the node's text-shadow, or %NULL + * if node has no text-shadow + */ +StShadow * +st_theme_node_get_text_shadow (StThemeNode *node) +{ + StShadow *result = NULL; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL); + + if (node->text_shadow_computed) + return node->text_shadow; + + ensure_properties (node); + + if (!st_theme_node_lookup_shadow (node, + "text-shadow", + FALSE, + &result)) + { + if (node->parent_node) + { + result = st_theme_node_get_text_shadow (node->parent_node); + if (result) + st_shadow_ref (result); + } + } + + if (result && result->inset) + { + g_warning ("The text-shadow property does not support inset shadows"); + st_shadow_unref (result); + result = NULL; + } + + node->text_shadow = result; + node->text_shadow_computed = TRUE; + + return result; +} + +/** + * st_theme_node_get_icon_colors: + * @node: a #StThemeNode + * + * Gets the colors that should be used for colorizing symbolic icons according + * the style of this node. + * + * Returns: (transfer none): the icon colors to use for this theme node + */ +StIconColors * +st_theme_node_get_icon_colors (StThemeNode *node) +{ + /* Foreground here will always be the same as st_theme_node_get_foreground_color(), + * but there's a loss of symmetry and little efficiency win if we try to exploit + * that. */ + + enum { + FOREGROUND = 1 << 0, + WARNING = 1 << 1, + ERROR = 1 << 2, + SUCCESS = 1 << 3 + }; + + gboolean shared_with_parent; + int i; + ClutterColor color = { 0, }; + + guint still_need = FOREGROUND | WARNING | ERROR | SUCCESS; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL); + + if (node->icon_colors) + return node->icon_colors; + + if (node->parent_node) + { + node->icon_colors = st_theme_node_get_icon_colors (node->parent_node); + shared_with_parent = TRUE; + } + else + { + node->icon_colors = st_icon_colors_new (); + node->icon_colors->foreground = BLACK_COLOR; + node->icon_colors->warning = DEFAULT_WARNING_COLOR; + node->icon_colors->error = DEFAULT_ERROR_COLOR; + node->icon_colors->success = DEFAULT_SUCCESS_COLOR; + shared_with_parent = FALSE; + } + + ensure_properties (node); + + for (i = node->n_properties - 1; i >= 0 && still_need != 0; i--) + { + CRDeclaration *decl = node->properties[i]; + GetFromTermResult result = VALUE_NOT_FOUND; + guint found = 0; + + if ((still_need & FOREGROUND) != 0 && + strcmp (decl->property->stryng->str, "color") == 0) + { + found = FOREGROUND; + result = get_color_from_term (node, decl->value, &color); + } + else if ((still_need & WARNING) != 0 && + strcmp (decl->property->stryng->str, "warning-color") == 0) + { + found = WARNING; + result = get_color_from_term (node, decl->value, &color); + } + else if ((still_need & ERROR) != 0 && + strcmp (decl->property->stryng->str, "error-color") == 0) + { + found = ERROR; + result = get_color_from_term (node, decl->value, &color); + } + else if ((still_need & SUCCESS) != 0 && + strcmp (decl->property->stryng->str, "success-color") == 0) + { + found = SUCCESS; + result = get_color_from_term (node, decl->value, &color); + } + + if (result == VALUE_INHERIT) + { + still_need &= ~found; + } + else if (result == VALUE_FOUND) + { + still_need &= ~found; + if (shared_with_parent) + { + node->icon_colors = st_icon_colors_copy (node->icon_colors); + shared_with_parent = FALSE; + } + + switch (found) + { + case FOREGROUND: + node->icon_colors->foreground = color; + break; + case WARNING: + node->icon_colors->warning = color; + break; + case ERROR: + node->icon_colors->error = color; + break; + case SUCCESS: + node->icon_colors->success = color; + break; + default: + g_assert_not_reached(); + break; + } + } + } + + if (shared_with_parent) + st_icon_colors_ref (node->icon_colors); + + return node->icon_colors; +} + +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) (nullable): the minimum width to adjust + * @natural_width_p: (inout): the natural width to adjust + * + * Adjusts the minimum and natural width computed for an actor by + * adding on the necessary space for borders and padding and taking + * into account any minimum or maximum width. 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)); + + _st_theme_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 = MAX (*natural_width_p, node->width); + if (node->max_width != -1) + *natural_width_p = MIN (*natural_width_p, node->max_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 adjustment 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) (nullable): the minimum height to adjust + * @natural_height_p: (inout): the natural height to adjust + * + * Adjusts the minimum and natural height computed for an actor by + * adding on the necessary space for borders and padding and taking + * into account any minimum or maximum height. 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)); + + _st_theme_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 = MAX (*natural_height_p, node->height); + if (node->max_height != -1) + *natural_height_p = MIN (*natural_height_p, node->max_height); + *natural_height_p += height_inc; + } +} + +/** + * st_theme_node_get_content_box: + * @node: a #StThemeNode + * @allocation: the box allocated to a #ClutterAlctor + * @content_box: (out caller-allocates): 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)); + + _st_theme_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_get_background_paint_box: + * @node: a #StThemeNode + * @allocation: the box allocated to a #ClutterActor + * @paint_box: (out caller-allocates): computed box occupied when painting the actor's background + * + * Gets the box used to paint the actor's background, including the area + * occupied by properties which paint outside the actor's assigned allocation. + */ +void +st_theme_node_get_background_paint_box (StThemeNode *node, + const ClutterActorBox *actor_box, + ClutterActorBox *paint_box) +{ + StShadow *background_image_shadow; + ClutterActorBox shadow_box; + + g_return_if_fail (ST_IS_THEME_NODE (node)); + g_return_if_fail (actor_box != NULL); + g_return_if_fail (paint_box != NULL); + + background_image_shadow = st_theme_node_get_background_image_shadow (node); + + *paint_box = *actor_box; + + if (!background_image_shadow) + return; + + st_shadow_get_box (background_image_shadow, actor_box, &shadow_box); + + paint_box->x1 = MIN (paint_box->x1, shadow_box.x1); + paint_box->x2 = MAX (paint_box->x2, shadow_box.x2); + paint_box->y1 = MIN (paint_box->y1, shadow_box.y1); + paint_box->y2 = MAX (paint_box->y2, shadow_box.y2); +} + +/** + * st_theme_node_get_paint_box: + * @node: a #StThemeNode + * @allocation: the box allocated to a #ClutterActor + * @paint_box: (out caller-allocates): computed box occupied when painting the actor + * + * Gets the box used to paint the actor, including the area occupied + * by properties which paint outside the actor's assigned allocation. + * When painting @node to an offscreen buffer, this function can be + * used to determine the necessary size of the buffer. + */ +void +st_theme_node_get_paint_box (StThemeNode *node, + const ClutterActorBox *actor_box, + ClutterActorBox *paint_box) +{ + StShadow *box_shadow; + ClutterActorBox shadow_box; + int outline_width; + + g_return_if_fail (ST_IS_THEME_NODE (node)); + g_return_if_fail (actor_box != NULL); + g_return_if_fail (paint_box != NULL); + + box_shadow = st_theme_node_get_box_shadow (node); + outline_width = st_theme_node_get_outline_width (node); + + st_theme_node_get_background_paint_box (node, actor_box, paint_box); + + if (!box_shadow && !outline_width) + return; + + paint_box->x1 -= outline_width; + paint_box->x2 += outline_width; + paint_box->y1 -= outline_width; + paint_box->y2 += outline_width; + + if (box_shadow) + { + st_shadow_get_box (box_shadow, actor_box, &shadow_box); + + paint_box->x1 = MIN (paint_box->x1, shadow_box.x1); + paint_box->x2 = MAX (paint_box->x2, shadow_box.x2); + paint_box->y1 = MIN (paint_box->y1, shadow_box.y1); + paint_box->y2 = MAX (paint_box->y2, shadow_box.y2); + } +} + +/** + * st_theme_node_geometry_equal: + * @node: a #StThemeNode + * @other: 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. + * + * Returns: %TRUE if equal, %FALSE otherwise + */ +gboolean +st_theme_node_geometry_equal (StThemeNode *node, + StThemeNode *other) +{ + StSide side; + + g_return_val_if_fail (ST_IS_THEME_NODE (node), FALSE); + + if (node == other) + return TRUE; + + g_return_val_if_fail (ST_IS_THEME_NODE (other), FALSE); + + if (node->cached_scale_factor != other->cached_scale_factor) + return FALSE; + + _st_theme_node_ensure_geometry (node); + _st_theme_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; + if (node->max_width != other->max_width || node->max_height != other->max_height) + return FALSE; + + return TRUE; +} + +/** + * st_theme_node_paint_equal: + * @node: (nullable): a #StThemeNode + * @other: (nullable): a different #StThemeNode + * + * Check if st_theme_node_paint() will paint identically for @node as it does + * for @other. Note that in some cases this function may return %TRUE even + * if there is no visible difference in the painting. + * + * Returns: %TRUE if the two theme nodes paint identically. %FALSE if the + * two nodes potentially paint differently. + */ +gboolean +st_theme_node_paint_equal (StThemeNode *node, + StThemeNode *other) +{ + StBorderImage *border_image, *other_border_image; + StShadow *shadow, *other_shadow; + int i; + + /* Make sure NULL != NULL */ + if (node == NULL || other == NULL) + return FALSE; + + if (node == other) + return TRUE; + + _st_theme_node_ensure_background (node); + _st_theme_node_ensure_background (other); + + if (!clutter_color_equal (&node->background_color, &other->background_color)) + return FALSE; + + if (node->background_gradient_type != other->background_gradient_type) + return FALSE; + + if (node->background_gradient_type != ST_GRADIENT_NONE && + !clutter_color_equal (&node->background_gradient_end, &other->background_gradient_end)) + return FALSE; + + if ((node->background_image != NULL) && + (other->background_image != NULL) && + !g_file_equal (node->background_image, other->background_image)) + return FALSE; + + _st_theme_node_ensure_geometry (node); + _st_theme_node_ensure_geometry (other); + + for (i = 0; i < 4; i++) + { + if (node->border_width[i] != other->border_width[i]) + return FALSE; + + if (node->border_width[i] > 0 && + !clutter_color_equal (&node->border_color[i], &other->border_color[i])) + return FALSE; + + if (node->border_radius[i] != other->border_radius[i]) + return FALSE; + } + + if (node->outline_width != other->outline_width) + return FALSE; + + if (node->outline_width > 0 && + !clutter_color_equal (&node->outline_color, &other->outline_color)) + return FALSE; + + border_image = st_theme_node_get_border_image (node); + other_border_image = st_theme_node_get_border_image (other); + + if ((border_image == NULL) != (other_border_image == NULL)) + return FALSE; + + if (border_image != NULL && !st_border_image_equal (border_image, other_border_image)) + return FALSE; + + shadow = st_theme_node_get_box_shadow (node); + other_shadow = st_theme_node_get_box_shadow (other); + + if ((shadow == NULL) != (other_shadow == NULL)) + return FALSE; + + if (shadow != NULL && !st_shadow_equal (shadow, other_shadow)) + return FALSE; + + shadow = st_theme_node_get_background_image_shadow (node); + other_shadow = st_theme_node_get_background_image_shadow (other); + + if ((shadow == NULL) != (other_shadow == NULL)) + return FALSE; + + if (shadow != NULL && !st_shadow_equal (shadow, other_shadow)) + return FALSE; + + return TRUE; +} + +/** + * st_theme_node_to_string: + * @node: a #StThemeNode + * + * Serialize @node to a string of its #GType name, CSS ID, classes and + * pseudo-classes. + * + * Returns: the serialized theme node + */ +gchar * +st_theme_node_to_string (StThemeNode *node) +{ + GString *desc; + gchar **it; + + if (!node) + return g_strdup ("[null]"); + + desc = g_string_new (NULL); + g_string_append_printf (desc, + "[%p %s#%s", + node, + g_type_name (node->element_type), + node->element_id); + + for (it = node->element_classes; it && *it; it++) + g_string_append_printf (desc, ".%s", *it); + + for (it = node->pseudo_classes; it && *it; it++) + g_string_append_printf (desc, ":%s", *it); + + g_string_append_c (desc, ']'); + + return g_string_free (desc, FALSE); +} |