diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 15:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 15:07:22 +0000 |
commit | f9d480cfe50ca1d7a0f0b5a2b8bb9932962bfbe7 (patch) | |
tree | ce9e8db2d4e8799780fa72ae8f1953039373e2ee /src/st/st-theme.c | |
parent | Initial commit. (diff) | |
download | gnome-shell-f9d480cfe50ca1d7a0f0b5a2b8bb9932962bfbe7.tar.xz gnome-shell-f9d480cfe50ca1d7a0f0b5a2b8bb9932962bfbe7.zip |
Adding upstream version 3.38.6.upstream/3.38.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/st/st-theme.c')
-rw-r--r-- | src/st/st-theme.c | 1085 |
1 files changed, 1085 insertions, 0 deletions
diff --git a/src/st/st-theme.c b/src/st/st-theme.c new file mode 100644 index 0000000..20d42fd --- /dev/null +++ b/src/st/st-theme.c @@ -0,0 +1,1085 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* + * st-theme.c: A set of CSS stylesheets used for rule matching + * + * Copyright 2003-2004 Dodji Seketeli + * Copyright 2008, 2009 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for + * more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * This file started as a cut-and-paste of cr-sel-eng.c from libcroco. + * + * In moving it to hippo-canvas: + * - Reformatted and otherwise edited to match our coding style + * - Switched from handling xmlNode to handling HippoStyle + * - Simplified by removing things that we don't need or that don't + * make sense in our context. + * - The code to get a list of matching properties works quite differently; + * we order things in priority order, but we don't actually try to + * coalesce properties with the same name. + * + * In moving it to GNOME Shell: + * - Renamed again to StTheme + * - Reformatted to match the gnome-shell coding style + * - Removed notion of "theme engine" from hippo-canvas + * - pseudo-class matching changed from link enum to strings + * - Some code simplification + */ + + +#include <stdlib.h> +#include <string.h> + +#include <gio/gio.h> + +#include "st-private.h" +#include "st-theme-node.h" +#include "st-theme-private.h" + +static void st_theme_constructed (GObject *object); +static void st_theme_finalize (GObject *object); +static void st_theme_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void st_theme_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +struct _StTheme +{ + GObject parent; + + GFile *application_stylesheet; + GFile *default_stylesheet; + GFile *theme_stylesheet; + GSList *custom_stylesheets; + + GHashTable *stylesheets_by_file; + GHashTable *files_by_stylesheet; + + CRCascade *cascade; +}; + +enum +{ + PROP_0, + PROP_APPLICATION_STYLESHEET, + PROP_THEME_STYLESHEET, + PROP_DEFAULT_STYLESHEET +}; + +enum +{ + STYLESHEETS_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = { 0, }; + +G_DEFINE_TYPE (StTheme, st_theme, G_TYPE_OBJECT) + +/* Quick strcmp. Test only for == 0 or != 0, not < 0 or > 0. */ +#define strqcmp(str,lit,lit_len) \ + (strlen (str) != (lit_len) || memcmp (str, lit, lit_len)) + +static gboolean +file_equal0 (GFile *file1, + GFile *file2) +{ + if (file1 == file2) + return TRUE; + + if ((file1 == NULL) || (file2 == NULL)) + return FALSE; + + return g_file_equal (file1, file2); +} + +static void +st_theme_init (StTheme *theme) +{ + theme->stylesheets_by_file = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, + (GDestroyNotify)g_object_unref, (GDestroyNotify)cr_stylesheet_unref); + theme->files_by_stylesheet = g_hash_table_new (g_direct_hash, g_direct_equal); +} + +static void +st_theme_class_init (StThemeClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = st_theme_constructed; + object_class->finalize = st_theme_finalize; + object_class->set_property = st_theme_set_property; + object_class->get_property = st_theme_get_property; + + /** + * StTheme:application-stylesheet: + * + * The highest priority stylesheet, representing application-specific + * styling; this is associated with the CSS "author" stylesheet. + */ + g_object_class_install_property (object_class, + PROP_APPLICATION_STYLESHEET, + g_param_spec_object ("application-stylesheet", + "Application Stylesheet", + "Stylesheet with application-specific styling", + G_TYPE_FILE, + ST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /** + * StTheme:theme-stylesheet: + * + * The second priority stylesheet, representing theme-specific styling; + * this is associated with the CSS "user" stylesheet. + */ + g_object_class_install_property (object_class, + PROP_THEME_STYLESHEET, + g_param_spec_object ("theme-stylesheet", + "Theme Stylesheet", + "Stylesheet with theme-specific styling", + G_TYPE_FILE, + ST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + /** + * StTheme:default-stylesheet: + * + * The lowest priority stylesheet, representing global default + * styling; this is associated with the CSS "user agent" stylesheet. + */ + g_object_class_install_property (object_class, + PROP_DEFAULT_STYLESHEET, + g_param_spec_object ("default-stylesheet", + "Default Stylesheet", + "Stylesheet with global default styling", + G_TYPE_FILE, + ST_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + signals[STYLESHEETS_CHANGED] = + g_signal_new ("custom-stylesheets-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, /* no default handler slot */ + NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + +static CRStyleSheet * +parse_stylesheet (GFile *file, + GError **error) +{ + enum CRStatus status; + CRStyleSheet *stylesheet; + char *contents; + gsize length; + + if (file == NULL) + return NULL; + + if (!g_file_load_contents (file, NULL, &contents, &length, NULL, error)) + return NULL; + + status = cr_om_parser_simply_parse_buf ((const guchar *) contents, + length, + CR_UTF_8, + &stylesheet); + g_free (contents); + + if (status != CR_OK) + { + char *uri = g_file_get_uri (file); + g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, + "Error parsing stylesheet '%s'; errcode:%d", uri, status); + g_free (uri); + return NULL; + } + + /* Extension stylesheet */ + stylesheet->app_data = GUINT_TO_POINTER (FALSE); + + return stylesheet; +} + +CRDeclaration * +_st_theme_parse_declaration_list (const char *str) +{ + return cr_declaration_parse_list_from_buf ((const guchar *)str, + CR_UTF_8); +} + +/* Just g_warning for now until we have something nicer to do */ +static CRStyleSheet * +parse_stylesheet_nofail (GFile *file) +{ + GError *error = NULL; + CRStyleSheet *result; + + result = parse_stylesheet (file, &error); + if (error) + { + g_warning ("%s", error->message); + g_clear_error (&error); + } + return result; +} + +static void +insert_stylesheet (StTheme *theme, + GFile *file, + CRStyleSheet *stylesheet) +{ + if (stylesheet == NULL) + return; + + g_object_ref (file); + cr_stylesheet_ref (stylesheet); + + g_hash_table_insert (theme->stylesheets_by_file, file, stylesheet); + g_hash_table_insert (theme->files_by_stylesheet, stylesheet, file); +} + +/** + * st_theme_load_stylesheet: + * @theme: a #StTheme + * @file: a #GFile + * @error: a #GError + * + * Load the stylesheet associated with @file. + * + * Returns: %TRUE if successful + */ +gboolean +st_theme_load_stylesheet (StTheme *theme, + GFile *file, + GError **error) +{ + CRStyleSheet *stylesheet; + + stylesheet = parse_stylesheet (file, error); + if (!stylesheet) + return FALSE; + + stylesheet->app_data = GUINT_TO_POINTER (TRUE); + + insert_stylesheet (theme, file, stylesheet); + cr_stylesheet_ref (stylesheet); + theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet); + g_signal_emit (theme, signals[STYLESHEETS_CHANGED], 0); + + return TRUE; +} + +/** + * st_theme_unload_stylesheet: + * @theme: a #StTheme + * @file: a #GFile + * + * Unload the stylesheet associated with @file. If @file was not loaded this + * function does nothing. + */ +void +st_theme_unload_stylesheet (StTheme *theme, + GFile *file) +{ + CRStyleSheet *stylesheet; + + stylesheet = g_hash_table_lookup (theme->stylesheets_by_file, file); + if (!stylesheet) + return; + + if (!g_slist_find (theme->custom_stylesheets, stylesheet)) + return; + + theme->custom_stylesheets = g_slist_remove (theme->custom_stylesheets, stylesheet); + + g_signal_emit (theme, signals[STYLESHEETS_CHANGED], 0); + + /* We need to remove the entry from the hashtable after emitting the signal + * since we might still access the files_by_stylesheet hashtable in + * _st_theme_resolve_url() during the signal emission. + */ + g_hash_table_remove (theme->stylesheets_by_file, file); + g_hash_table_remove (theme->files_by_stylesheet, stylesheet); + cr_stylesheet_unref (stylesheet); +} + +/** + * st_theme_get_custom_stylesheets: + * @theme: an #StTheme + * + * Get a list of the stylesheet files loaded with st_theme_load_stylesheet(). + * + * Returns: (transfer full) (element-type GFile): the list of stylesheet files + * that were loaded with st_theme_load_stylesheet() + */ +GSList* +st_theme_get_custom_stylesheets (StTheme *theme) +{ + GSList *result = NULL; + GSList *iter; + + for (iter = theme->custom_stylesheets; iter; iter = iter->next) + { + CRStyleSheet *stylesheet = iter->data; + GFile *file = g_hash_table_lookup (theme->files_by_stylesheet, stylesheet); + + result = g_slist_prepend (result, g_object_ref (file)); + } + + return result; +} + +static void +st_theme_constructed (GObject *object) +{ + StTheme *theme = ST_THEME (object); + CRStyleSheet *application_stylesheet; + CRStyleSheet *theme_stylesheet; + CRStyleSheet *default_stylesheet; + + G_OBJECT_CLASS (st_theme_parent_class)->constructed (object); + + application_stylesheet = parse_stylesheet_nofail (theme->application_stylesheet); + theme_stylesheet = parse_stylesheet_nofail (theme->theme_stylesheet); + default_stylesheet = parse_stylesheet_nofail (theme->default_stylesheet); + + theme->cascade = cr_cascade_new (application_stylesheet, + theme_stylesheet, + default_stylesheet); + + if (theme->cascade == NULL) + g_error ("Out of memory when creating cascade object"); + + insert_stylesheet (theme, theme->application_stylesheet, application_stylesheet); + insert_stylesheet (theme, theme->theme_stylesheet, theme_stylesheet); + insert_stylesheet (theme, theme->default_stylesheet, default_stylesheet); +} + +static void +st_theme_finalize (GObject * object) +{ + StTheme *theme = ST_THEME (object); + + g_slist_foreach (theme->custom_stylesheets, (GFunc) cr_stylesheet_unref, NULL); + g_slist_free (theme->custom_stylesheets); + theme->custom_stylesheets = NULL; + + g_hash_table_destroy (theme->stylesheets_by_file); + g_hash_table_destroy (theme->files_by_stylesheet); + + g_clear_object (&theme->application_stylesheet); + g_clear_object (&theme->theme_stylesheet); + g_clear_object (&theme->default_stylesheet); + + if (theme->cascade) + { + cr_cascade_unref (theme->cascade); + theme->cascade = NULL; + } + + G_OBJECT_CLASS (st_theme_parent_class)->finalize (object); +} + +static void +st_theme_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + StTheme *theme = ST_THEME (object); + + switch (prop_id) + { + case PROP_APPLICATION_STYLESHEET: + { + GFile *file = g_value_get_object (value); + + if (!file_equal0 (file, theme->application_stylesheet)) + { + g_clear_object (&theme->application_stylesheet); + if (file != NULL) + theme->application_stylesheet = g_object_ref (file); + } + + break; + } + case PROP_THEME_STYLESHEET: + { + GFile *file = g_value_get_object (value); + + if (!file_equal0 (file, theme->theme_stylesheet)) + { + g_clear_object (&theme->theme_stylesheet); + if (file != NULL) + theme->theme_stylesheet = g_object_ref (file); + } + + break; + } + case PROP_DEFAULT_STYLESHEET: + { + GFile *file = g_value_get_object (value); + + if (!file_equal0 (file, theme->default_stylesheet)) + { + g_clear_object (&theme->default_stylesheet); + if (file != NULL) + theme->default_stylesheet = g_object_ref (file); + } + + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +st_theme_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + StTheme *theme = ST_THEME (object); + + switch (prop_id) + { + case PROP_APPLICATION_STYLESHEET: + g_value_set_object (value, theme->application_stylesheet); + break; + case PROP_THEME_STYLESHEET: + g_value_set_object (value, theme->theme_stylesheet); + break; + case PROP_DEFAULT_STYLESHEET: + g_value_set_object (value, theme->default_stylesheet); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/** + * st_theme_new: + * @application_stylesheet: The highest priority stylesheet, representing application-specific + * styling; this is associated with the CSS "author" stylesheet, may be %NULL + * @theme_stylesheet: The second priority stylesheet, representing theme-specific styling ; + * this is associated with the CSS "user" stylesheet, may be %NULL + * @default_stylesheet: The lowest priority stylesheet, representing global default styling; + * this is associated with the CSS "user agent" stylesheet, may be %NULL + * + * Returns: the newly created theme object + **/ +StTheme * +st_theme_new (GFile *application_stylesheet, + GFile *theme_stylesheet, + GFile *default_stylesheet) +{ + StTheme *theme = g_object_new (ST_TYPE_THEME, + "application-stylesheet", application_stylesheet, + "theme-stylesheet", theme_stylesheet, + "default-stylesheet", default_stylesheet, + NULL); + + return theme; +} + +static gboolean +string_in_list (GString *stryng, + GStrv list) +{ + gchar **it; + + if (list == NULL) + return FALSE; + + for (it = list; *it != NULL; it++) + { + if (!strqcmp (*it, stryng->str, stryng->len)) + return TRUE; + } + + return FALSE; +} + +static gboolean +pseudo_class_add_sel_matches_style (StTheme *a_this, + CRAdditionalSel *a_add_sel, + StThemeNode *a_node) +{ + GStrv node_pseudo_classes; + + g_return_val_if_fail (a_this + && a_add_sel + && a_add_sel->content.pseudo + && a_add_sel->content.pseudo->name + && a_add_sel->content.pseudo->name->stryng + && a_add_sel->content.pseudo->name->stryng->str + && a_node, FALSE); + + node_pseudo_classes = st_theme_node_get_pseudo_classes (a_node); + + return string_in_list (a_add_sel->content.pseudo->name->stryng, + node_pseudo_classes); +} + +/** + * class_add_sel_matches_style: + * @a_add_sel: The class additional selector to consider. + * @a_node: The style node to consider. + * + * Returns: %TRUE if the class additional selector matches + * the style node given in argument, %FALSE otherwise. + */ +static gboolean +class_add_sel_matches_style (CRAdditionalSel *a_add_sel, + StThemeNode *a_node) +{ + GStrv element_classes; + + g_return_val_if_fail (a_add_sel + && a_add_sel->type == CLASS_ADD_SELECTOR + && a_add_sel->content.class_name + && a_add_sel->content.class_name->stryng + && a_add_sel->content.class_name->stryng->str + && a_node, FALSE); + + element_classes = st_theme_node_get_element_classes (a_node); + + return string_in_list (a_add_sel->content.class_name->stryng, + element_classes); +} + +/* + *@return TRUE if the additional attribute selector matches + *the current style node given in argument, FALSE otherwise. + *@param a_add_sel the additional attribute selector to consider. + *@param a_node the style node to consider. + */ +static gboolean +id_add_sel_matches_style (CRAdditionalSel *a_add_sel, + StThemeNode *a_node) +{ + gboolean result = FALSE; + const char *id; + + g_return_val_if_fail (a_add_sel + && a_add_sel->type == ID_ADD_SELECTOR + && a_add_sel->content.id_name + && a_add_sel->content.id_name->stryng + && a_add_sel->content.id_name->stryng->str + && a_node, FALSE); + g_return_val_if_fail (a_add_sel + && a_add_sel->type == ID_ADD_SELECTOR + && a_node, FALSE); + + id = st_theme_node_get_element_id (a_node); + + if (id != NULL) + { + if (!strqcmp (id, a_add_sel->content.id_name->stryng->str, + a_add_sel->content.id_name->stryng->len)) + { + result = TRUE; + } + } + + return result; +} + +/** + *additional_selector_matches_style: + *Evaluates if a given additional selector matches an style node. + *@param a_add_sel the additional selector to consider. + *@param a_node the style node to consider. + *@return TRUE is a_add_sel matches a_node, FALSE otherwise. + */ +static gboolean +additional_selector_matches_style (StTheme *a_this, + CRAdditionalSel *a_add_sel, + StThemeNode *a_node) +{ + CRAdditionalSel *cur_add_sel = NULL; + + g_return_val_if_fail (a_add_sel, FALSE); + + for (cur_add_sel = a_add_sel; cur_add_sel; cur_add_sel = cur_add_sel->next) + { + switch (cur_add_sel->type) + { + case NO_ADD_SELECTOR: + return FALSE; + case CLASS_ADD_SELECTOR: + if (!class_add_sel_matches_style (cur_add_sel, a_node)) + return FALSE; + break; + case ID_ADD_SELECTOR: + if (!id_add_sel_matches_style (cur_add_sel, a_node)) + return FALSE; + break; + case ATTRIBUTE_ADD_SELECTOR: + g_warning ("Attribute selectors not supported"); + return FALSE; + case PSEUDO_CLASS_ADD_SELECTOR: + if (!pseudo_class_add_sel_matches_style (a_this, cur_add_sel, a_node)) + return FALSE; + break; + default: + g_warning ("Unhandled selector type %d", cur_add_sel->type); + return FALSE; + } + } + + return TRUE; +} + +static gboolean +element_name_matches_type (const char *element_name, + GType element_type) +{ + if (element_type == G_TYPE_NONE) + { + return strcmp (element_name, "stage") == 0; + } + else + { + GType match_type = g_type_from_name (element_name); + if (match_type == G_TYPE_INVALID) + return FALSE; + + return g_type_is_a (element_type, match_type); + } +} + +/* + *Evaluate a selector (a simple selectors list) and says + *if it matches the style node given in parameter. + *The algorithm used here is the following: + *Walk the combinator separated list of simple selectors backward, starting + *from the end of the list. For each simple selector, looks if + *if matches the current style. + * + *@param a_this the selection engine. + *@param a_sel the simple selection list. + *@param a_node the style node. + *@param a_result out parameter. Set to true if the + *selector matches the style node, FALSE otherwise. + *@param a_recurse if set to TRUE, the function will walk to + *the next simple selector (after the evaluation of the current one) + *and recursively evaluate it. Must be usually set to TRUE unless you + *know what you are doing. + */ +static enum CRStatus +sel_matches_style_real (StTheme *a_this, + CRSimpleSel *a_sel, + StThemeNode *a_node, + gboolean *a_result, + gboolean a_eval_sel_list_from_end, + gboolean a_recurse) +{ + CRSimpleSel *cur_sel = NULL; + StThemeNode *cur_node = NULL; + GType cur_type; + + *a_result = FALSE; + + if (a_eval_sel_list_from_end) + { + /*go and get the last simple selector of the list */ + for (cur_sel = a_sel; cur_sel && cur_sel->next; cur_sel = cur_sel->next) + ; + } + else + { + cur_sel = a_sel; + } + + cur_node = a_node; + cur_type = st_theme_node_get_element_type (cur_node); + + while (cur_sel) + { + if (((cur_sel->type_mask & TYPE_SELECTOR) + && (cur_sel->name + && cur_sel->name->stryng + && cur_sel->name->stryng->str) + && + (element_name_matches_type (cur_sel->name->stryng->str, cur_type))) + || (cur_sel->type_mask & UNIVERSAL_SELECTOR)) + { + /* + *this simple selector + *matches the current style node + *Let's see if the preceding + *simple selectors also match + *their style node counterpart. + */ + if (cur_sel->add_sel) + { + if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node)) + goto walk_a_step_in_expr; + else + goto done; + } + else + goto walk_a_step_in_expr; + } + if (!(cur_sel->type_mask & TYPE_SELECTOR) + && !(cur_sel->type_mask & UNIVERSAL_SELECTOR)) + { + if (!cur_sel->add_sel) + goto done; + if (additional_selector_matches_style (a_this, cur_sel->add_sel, cur_node)) + goto walk_a_step_in_expr; + else + goto done; + } + else + { + goto done; + } + + walk_a_step_in_expr: + if (a_recurse == FALSE) + { + *a_result = TRUE; + goto done; + } + + /* + *here, depending on the combinator of cur_sel + *choose the axis of the element tree traversal + *and walk one step in the element tree. + */ + if (!cur_sel->prev) + break; + + switch (cur_sel->combinator) + { + case NO_COMBINATOR: + break; + + case COMB_WS: /*descendant selector */ + { + StThemeNode *n = NULL; + + /* + *walk the element tree upward looking for a parent + *style that matches the preceding selector. + */ + for (n = st_theme_node_get_parent (a_node); n; n = st_theme_node_get_parent (n)) + { + enum CRStatus status; + gboolean matches = FALSE; + + status = sel_matches_style_real (a_this, cur_sel->prev, n, &matches, FALSE, TRUE); + + if (status != CR_OK) + goto done; + + if (matches) + { + cur_node = n; + cur_type = st_theme_node_get_element_type (cur_node); + break; + } + } + + if (!n) + { + /* + *didn't find any ancestor that matches + *the previous simple selector. + */ + goto done; + } + /* + *in this case, the preceding simple sel + *will have been interpreted twice, which + *is a cpu and mem waste ... I need to find + *another way to do this. Anyway, this is + *my first attempt to write this function and + *I am a bit clueless. + */ + break; + } + + case COMB_PLUS: + g_warning ("+ combinators are not supported"); + goto done; + + case COMB_GT: + cur_node = st_theme_node_get_parent (cur_node); + if (!cur_node) + goto done; + cur_type = st_theme_node_get_element_type (cur_node); + break; + + default: + goto done; + } + + cur_sel = cur_sel->prev; + } + + /* + *if we reached this point, it means the selector matches + *the style node. + */ + *a_result = TRUE; + +done: + return CR_OK; +} + +static void +add_matched_properties (StTheme *a_this, + CRStyleSheet *a_nodesheet, + StThemeNode *a_node, + GPtrArray *props) +{ + CRStatement *cur_stmt = NULL; + CRSelector *sel_list = NULL; + CRSelector *cur_sel = NULL; + gboolean matches = FALSE; + enum CRStatus status = CR_OK; + + /* + *walk through the list of statements and, + *get the selectors list inside the statements that + *contain some, and try to match our style node in these + *selectors lists. + */ + for (cur_stmt = a_nodesheet->statements; cur_stmt; cur_stmt = cur_stmt->next) + { + /* + *initialize the selector list in which we will + *really perform the search. + */ + sel_list = NULL; + + /* + *get the the damn selector list in + *which we have to look + */ + switch (cur_stmt->type) + { + case RULESET_STMT: + if (cur_stmt->kind.ruleset && cur_stmt->kind.ruleset->sel_list) + { + sel_list = cur_stmt->kind.ruleset->sel_list; + } + break; + + case AT_IMPORT_RULE_STMT: + { + CRAtImportRule *import_rule = cur_stmt->kind.import_rule; + + if (import_rule->sheet == NULL) + { + GFile *file = NULL; + + if (import_rule->url->stryng && import_rule->url->stryng->str) + { + file = _st_theme_resolve_url (a_this, + a_nodesheet, + import_rule->url->stryng->str); + import_rule->sheet = parse_stylesheet (file, NULL); + } + + if (import_rule->sheet) + { + insert_stylesheet (a_this, file, import_rule->sheet); + /* refcount of stylesheets starts off at zero, so we don't need to unref! */ + } + else + { + /* Set a marker to avoid repeatedly trying to parse a non-existent or + * broken stylesheet + */ + import_rule->sheet = (CRStyleSheet *) - 1; + } + + if (file) + g_object_unref (file); + } + + if (import_rule->sheet != (CRStyleSheet *) - 1) + { + add_matched_properties (a_this, import_rule->sheet, + a_node, props); + } + } + break; + case AT_MEDIA_RULE_STMT: + case AT_RULE_STMT: + case AT_PAGE_RULE_STMT: + case AT_CHARSET_RULE_STMT: + case AT_FONT_FACE_RULE_STMT: + default: + break; + } + + if (!sel_list) + continue; + + /* + *now, we have a comma separated selector list to look in. + *let's walk it and try to match the style node + *on each item of the list. + */ + for (cur_sel = sel_list; cur_sel; cur_sel = cur_sel->next) + { + if (!cur_sel->simple_sel) + continue; + + status = sel_matches_style_real (a_this, cur_sel->simple_sel, a_node, &matches, TRUE, TRUE); + + if (status == CR_OK && matches) + { + CRDeclaration *cur_decl = NULL; + + /* In order to sort the matching properties, we need to compute the + * specificity of the selector that actually matched this + * element. In a non-thread-safe fashion, we store it in the + * ruleset. (Fixing this would mean cut-and-pasting + * cr_simple_sel_compute_specificity(), and have no need for + * thread-safety anyways.) + * + * Once we've sorted the properties, the specificity no longer + * matters and it can be safely overridden. + */ + cr_simple_sel_compute_specificity (cur_sel->simple_sel); + + cur_stmt->specificity = cur_sel->simple_sel->specificity; + + for (cur_decl = cur_stmt->kind.ruleset->decl_list; cur_decl; cur_decl = cur_decl->next) + g_ptr_array_add (props, cur_decl); + } + } + } +} + +#define ORIGIN_OFFSET_IMPORTANT (NB_ORIGINS) +#define ORIGIN_OFFSET_EXTENSION (NB_ORIGINS * 2) + +static inline int +get_origin (const CRDeclaration * decl) +{ + enum CRStyleOrigin origin = decl->parent_statement->parent_sheet->origin; + gboolean is_extension_sheet = GPOINTER_TO_UINT (decl->parent_statement->parent_sheet->app_data); + + if (decl->important) + origin += ORIGIN_OFFSET_IMPORTANT; + + if (is_extension_sheet) + origin += ORIGIN_OFFSET_EXTENSION; + + return origin; +} + +/* Order of comparison is so that higher priority statements compare after + * lower priority statements */ +static int +compare_declarations (gconstpointer a, + gconstpointer b) +{ + /* g_ptr_array_sort() is broooken */ + CRDeclaration *decl_a = *(CRDeclaration **) a; + CRDeclaration *decl_b = *(CRDeclaration **) b; + + int origin_a = get_origin (decl_a); + int origin_b = get_origin (decl_b); + + if (origin_a != origin_b) + return origin_a - origin_b; + + if (decl_a->parent_statement->specificity != decl_b->parent_statement->specificity) + return decl_a->parent_statement->specificity - decl_b->parent_statement->specificity; + + return 0; +} + +GPtrArray * +_st_theme_get_matched_properties (StTheme *theme, + StThemeNode *node) +{ + enum CRStyleOrigin origin = 0; + CRStyleSheet *sheet = NULL; + GPtrArray *props = g_ptr_array_new (); + GSList *iter; + + g_return_val_if_fail (ST_IS_THEME (theme), NULL); + g_return_val_if_fail (ST_IS_THEME_NODE (node), NULL); + + for (origin = ORIGIN_UA; origin < NB_ORIGINS; origin++) + { + sheet = cr_cascade_get_sheet (theme->cascade, origin); + if (!sheet) + continue; + + add_matched_properties (theme, sheet, node, props); + } + + for (iter = theme->custom_stylesheets; iter; iter = iter->next) + add_matched_properties (theme, iter->data, node, props); + + /* We count on a stable sort here so that later declarations come + * after earlier declarations */ + g_ptr_array_sort (props, compare_declarations); + + return props; +} + +/* Resolve an url from an url() reference in a stylesheet into a GFile, + * if possible. The resolution here is distinctly lame and + * will fail on many examples. + */ +GFile * +_st_theme_resolve_url (StTheme *theme, + CRStyleSheet *base_stylesheet, + const char *url) +{ + char *scheme; + GFile *resource; + + if ((scheme = g_uri_parse_scheme (url))) + { + g_free (scheme); + resource = g_file_new_for_uri (url); + } + else if (base_stylesheet != NULL) + { + GFile *base_file = NULL, *parent; + + base_file = g_hash_table_lookup (theme->files_by_stylesheet, base_stylesheet); + + /* This is an internal function, if we get here with + a bad @base_stylesheet we have a problem. */ + g_assert (base_file); + + parent = g_file_get_parent (base_file); + resource = g_file_resolve_relative_path (parent, url); + + g_object_unref (parent); + } + else + { + resource = g_file_new_for_path (url); + } + + return resource; +} |