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