X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkcssprovider.c;h=751266847464ff2e9d495af992c5b203d60c7e8a;hb=ce0675f1fb2582717793ec29b1787039dfbf8437;hp=102708697f82afeb59bd66c5c3e98dd27a56bb7f;hpb=30eb26087c8b8ac3562c9370a07d3a389cd7a8e1;p=~andy%2Fgtk diff --git a/gtk/gtkcssprovider.c b/gtk/gtkcssprovider.c index 102708697..751266847 100644 --- a/gtk/gtkcssprovider.c +++ b/gtk/gtkcssprovider.c @@ -12,9 +12,7 @@ * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. + * License along with this library. If not, see . */ #include "config.h" @@ -28,11 +26,14 @@ #include "gtkcssproviderprivate.h" #include "gtkbitmaskprivate.h" +#include "gtkcssarrayvalueprivate.h" +#include "gtkcsscolorvalueprivate.h" +#include "gtkcsskeyframesprivate.h" #include "gtkcssparserprivate.h" #include "gtkcsssectionprivate.h" #include "gtkcssselectorprivate.h" #include "gtkcssshorthandpropertyprivate.h" -#include "gtksymboliccolor.h" +#include "gtkcssstylefuncsprivate.h" #include "gtkstyleprovider.h" #include "gtkstylecontextprivate.h" #include "gtkstylepropertiesprivate.h" @@ -432,6 +433,14 @@ * darker(@color) * A darker variant of @color * + * + * alpha(@color, @f) + * Modifies passed color's alpha by a factor @f. @f is a + * floating point number. @f < 1.0 results in a more transparent + * color while @f > 1.0 results in a more opaque color. + * + * alhpa(blue, 0.5) + * * * * @@ -908,7 +917,7 @@ * * text-shadow * shadow list (see above) - * #GtkTextShadow + * internal use only * text-shadow: 1 1 0 blue, -4 -4 red; * * @@ -955,15 +964,35 @@ typedef struct GtkCssRuleset GtkCssRuleset; typedef struct _GtkCssScanner GtkCssScanner; +typedef struct _PropertyValue PropertyValue; +typedef struct _WidgetPropertyValue WidgetPropertyValue; typedef enum ParserScope ParserScope; typedef enum ParserSymbol ParserSymbol; +struct _PropertyValue { + GtkCssStyleProperty *property; + GtkCssValue *value; + GtkCssSection *section; +}; + +struct _WidgetPropertyValue { + WidgetPropertyValue *next; + char *name; + char *value; + + GtkCssSection *section; +}; + struct GtkCssRuleset { GtkCssSelector *selector; - GHashTable *widget_style; - GHashTable *style; + GtkCssSelectorTree *selector_match; + WidgetPropertyValue *widget_style; + PropertyValue *styles; GtkBitmask *set_styles; + guint n_styles; + guint owns_styles : 1; + guint owns_widget_style : 1; }; struct _GtkCssScanner @@ -972,8 +1001,6 @@ struct _GtkCssScanner GtkCssParser *parser; GtkCssSection *section; GtkCssScanner *parent; - GFile *file; - GFile *base; GSList *state; }; @@ -982,8 +1009,11 @@ struct _GtkCssProviderPrivate GScanner *scanner; GHashTable *symbolic_colors; + GHashTable *keyframes; GArray *rulesets; + GtkCssSelectorTree *tree; + GResource *resource; }; enum { @@ -991,11 +1021,14 @@ enum { LAST_SIGNAL }; +static gboolean gtk_keep_css_sections = FALSE; + static guint css_provider_signals[LAST_SIGNAL] = { 0 }; static void gtk_css_provider_finalize (GObject *object); static void gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface); static void gtk_css_style_provider_private_iface_init (GtkStyleProviderPrivateInterface *iface); +static void widget_property_value_list_free (WidgetPropertyValue *head); static gboolean gtk_css_provider_load_internal (GtkCssProvider *css_provider, @@ -1032,34 +1065,13 @@ gtk_css_provider_parsing_error (GtkCssProvider *provider, 0, TRUE)) { - GFileInfo *info; - GFile *file; - const char *path; - - file = gtk_css_section_get_file (section); - if (file) - { - info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, 0, NULL, NULL); - - if (info) - path = g_file_info_get_display_name (info); - else - path = ""; - } - else - { - info = NULL; - path = ""; - } + char *s = _gtk_css_section_to_string (section); - g_warning ("Theme parsing error: %s:%u:%u: %s", - path, - gtk_css_section_get_end_line (section) + 1, - gtk_css_section_get_end_position (section), + g_warning ("Theme parsing error: %s: %s", + s, error->message); - if (info) - g_object_unref (info); + g_free (s); } } @@ -1068,6 +1080,9 @@ gtk_css_provider_class_init (GtkCssProviderClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); + if (g_getenv ("GTK_CSS_DEBUG")) + gtk_keep_css_sections = TRUE; + /** * GtkCssProvider::parsing-error: * @provider: the provider that had a parsing error @@ -1104,16 +1119,17 @@ gtk_css_provider_class_init (GtkCssProviderClass *klass) static void gtk_css_ruleset_init_copy (GtkCssRuleset *new, - const GtkCssRuleset *ruleset, + GtkCssRuleset *ruleset, GtkCssSelector *selector) { memcpy (new, ruleset, sizeof (GtkCssRuleset)); new->selector = selector; - if (new->widget_style) - g_hash_table_ref (new->widget_style); - if (new->style) - g_hash_table_ref (new->style); + /* First copy takes over ownership */ + if (ruleset->owns_styles) + ruleset->owns_styles = FALSE; + if (ruleset->owns_widget_style) + ruleset->owns_widget_style = FALSE; if (new->set_styles) new->set_styles = _gtk_bitmask_copy (new->set_styles); } @@ -1121,115 +1137,140 @@ gtk_css_ruleset_init_copy (GtkCssRuleset *new, static void gtk_css_ruleset_clear (GtkCssRuleset *ruleset) { - if (ruleset->style) - g_hash_table_unref (ruleset->style); + if (ruleset->owns_styles) + { + guint i; + + for (i = 0; i < ruleset->n_styles; i++) + { + _gtk_css_value_unref (ruleset->styles[i].value); + ruleset->styles[i].value = NULL; + if (ruleset->styles[i].section) + gtk_css_section_unref (ruleset->styles[i].section); + } + g_free (ruleset->styles); + } if (ruleset->set_styles) _gtk_bitmask_free (ruleset->set_styles); - if (ruleset->widget_style) - g_hash_table_unref (ruleset->widget_style); + if (ruleset->owns_widget_style) + widget_property_value_list_free (ruleset->widget_style); if (ruleset->selector) _gtk_css_selector_free (ruleset->selector); memset (ruleset, 0, sizeof (GtkCssRuleset)); } -typedef struct _PropertyValue PropertyValue; -struct _PropertyValue { - GtkCssSection *section; - GValue value; -}; - -static PropertyValue * -property_value_new (GtkCssSection *section) +static WidgetPropertyValue * +widget_property_value_new (char *name, GtkCssSection *section) { - PropertyValue *value; + WidgetPropertyValue *value; - value = g_slice_new0 (PropertyValue); + value = g_slice_new0 (WidgetPropertyValue); - value->section = gtk_css_section_ref (section); + value->name = name; + if (gtk_keep_css_sections) + value->section = gtk_css_section_ref (section); return value; } static void -property_value_free (PropertyValue *value) +widget_property_value_free (WidgetPropertyValue *value) +{ + g_free (value->value); + g_free (value->name); + if (value->section) + gtk_css_section_unref (value->section); + + g_slice_free (WidgetPropertyValue, value); +} + +static void +widget_property_value_list_free (WidgetPropertyValue *head) +{ + WidgetPropertyValue *l, *next; + for (l = head; l != NULL; l = next) + { + next = l->next; + widget_property_value_free (l); + } +} + +static WidgetPropertyValue * +widget_property_value_list_remove_name (WidgetPropertyValue *head, const char *name) { - if (G_IS_VALUE (&value->value)) - g_value_unset (&value->value); + WidgetPropertyValue *l, **last; + + last = &head; + + for (l = head; l != NULL; l = l->next) + { + if (strcmp (l->name, name) == 0) + { + *last = l->next; + widget_property_value_free (l); + break; + } - gtk_css_section_unref (value->section); + last = &l->next; + } - g_slice_free (PropertyValue, value); + return head; } static void gtk_css_ruleset_add_style (GtkCssRuleset *ruleset, char *name, - PropertyValue *value) + WidgetPropertyValue *value) { - if (ruleset->widget_style == NULL) - ruleset->widget_style = g_hash_table_new_full (g_str_hash, - g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) property_value_free); - - g_hash_table_insert (ruleset->widget_style, name, value); + value->next = widget_property_value_list_remove_name (ruleset->widget_style, name); + ruleset->widget_style = value; + ruleset->owns_widget_style = TRUE; } static void -gtk_css_ruleset_add (GtkCssRuleset *ruleset, - GtkStyleProperty *prop, - PropertyValue *value) +gtk_css_ruleset_add (GtkCssRuleset *ruleset, + GtkCssStyleProperty *property, + GtkCssValue *value, + GtkCssSection *section) { - if (ruleset->style == NULL) - { - ruleset->style = g_hash_table_new_full (g_direct_hash, - g_direct_equal, - NULL, - (GDestroyNotify) property_value_free); - ruleset->set_styles = _gtk_bitmask_new (); - } + guint i; - if (GTK_IS_CSS_SHORTHAND_PROPERTY (prop)) - { - GParameter *parameters; - guint i, n_parameters; + g_return_if_fail (ruleset->owns_styles || ruleset->n_styles == 0); - parameters = _gtk_style_property_unpack (prop, &value->value, &n_parameters); + if (ruleset->set_styles == NULL) + ruleset->set_styles = _gtk_bitmask_new (); - for (i = 0; i < n_parameters; i++) - { - GtkStyleProperty *child; - PropertyValue *val; + ruleset->set_styles = _gtk_bitmask_set (ruleset->set_styles, + _gtk_css_style_property_get_id (property), + TRUE); - child = _gtk_style_property_lookup (parameters[i].name); - val = property_value_new (value->section); - memcpy (&val->value, ¶meters[i].value, sizeof (GValue)); - gtk_css_ruleset_add (ruleset, child, val); - } - g_free (parameters); - property_value_free (value); - return; - } - else if (GTK_IS_CSS_STYLE_PROPERTY (prop)) + ruleset->owns_styles = TRUE; + + for (i = 0; i < ruleset->n_styles; i++) { - _gtk_bitmask_set (ruleset->set_styles, - _gtk_css_style_property_get_id (GTK_CSS_STYLE_PROPERTY (prop)), - TRUE); - g_hash_table_insert (ruleset->style, prop, value); + if (ruleset->styles[i].property == property) + { + _gtk_css_value_unref (ruleset->styles[i].value); + ruleset->styles[i].value = NULL; + if (ruleset->styles[i].section) + gtk_css_section_unref (ruleset->styles[i].section); + break; + } } - else + if (i == ruleset->n_styles) { - g_assert_not_reached (); + ruleset->n_styles++; + ruleset->styles = g_realloc (ruleset->styles, ruleset->n_styles * sizeof (PropertyValue)); + ruleset->styles[i].value = NULL; + ruleset->styles[i].property = property; } -} -static gboolean -gtk_css_ruleset_matches (GtkCssRuleset *ruleset, - GtkWidgetPath *path, - GtkStateFlags state) -{ - return _gtk_css_selector_matches (ruleset->selector, path, state); + ruleset->styles[i].value = value; + if (gtk_keep_css_sections) + ruleset->styles[i].section = gtk_css_section_ref (section); + else + ruleset->styles[i].section = NULL; } static void @@ -1238,9 +1279,6 @@ gtk_css_scanner_destroy (GtkCssScanner *scanner) if (scanner->section) gtk_css_section_unref (scanner->section); g_object_unref (scanner->provider); - if (scanner->file) - g_object_unref (scanner->file); - g_object_unref (scanner->base); _gtk_css_parser_free (scanner->parser); g_slice_free (GtkCssScanner, scanner); @@ -1284,38 +1322,22 @@ gtk_css_scanner_new (GtkCssProvider *provider, if (section) scanner->section = gtk_css_section_ref (section); - if (file) - { - scanner->file = g_object_ref (file); - scanner->base = g_file_get_parent (file); - } - else - { - char *dir = g_get_current_dir (); - scanner->base = g_file_new_for_path (dir); - g_free (dir); - } - scanner->parser = _gtk_css_parser_new (text, + file, gtk_css_scanner_parser_error, scanner); return scanner; } -static GFile * -gtk_css_scanner_get_base_url (GtkCssScanner *scanner) -{ - return scanner->base; -} - static gboolean gtk_css_scanner_would_recurse (GtkCssScanner *scanner, GFile *file) { while (scanner) { - if (scanner->file && g_file_equal (scanner->file, file)) + GFile *parser_file = _gtk_css_parser_get_file (scanner->parser); + if (parser_file && g_file_equal (parser_file, file)) return TRUE; scanner = scanner->parent; @@ -1332,8 +1354,7 @@ gtk_css_scanner_push_section (GtkCssScanner *scanner, section = _gtk_css_section_new (scanner->section, section_type, - scanner->parser, - scanner->file); + scanner->parser); if (scanner->section) gtk_css_section_unref (scanner->section); @@ -1371,78 +1392,92 @@ gtk_css_provider_init (GtkCssProvider *css_provider) priv->symbolic_colors = g_hash_table_new_full (g_str_hash, g_str_equal, (GDestroyNotify) g_free, - (GDestroyNotify) gtk_symbolic_color_unref); + (GDestroyNotify) _gtk_css_value_unref); + priv->keyframes = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) _gtk_css_value_unref); } static void -css_provider_dump_symbolic_colors (GtkCssProvider *css_provider, - GtkStyleProperties *props) -{ - GtkCssProviderPrivate *priv; - GHashTableIter iter; - gpointer key, value; - - priv = css_provider->priv; - g_hash_table_iter_init (&iter, priv->symbolic_colors); - - while (g_hash_table_iter_next (&iter, &key, &value)) - { - const gchar *name; - GtkSymbolicColor *color; - - name = key; - color = value; - - gtk_style_properties_map_color (props, name, color); - } -} - -static GtkStyleProperties * -gtk_css_provider_get_style (GtkStyleProvider *provider, - GtkWidgetPath *path) +verify_tree_match_results (GtkCssProvider *provider, + const GtkCssMatcher *matcher, + GPtrArray *tree_rules) { - GtkCssProvider *css_provider; - GtkCssProviderPrivate *priv; - GtkStyleProperties *props; - guint i; - - css_provider = GTK_CSS_PROVIDER (provider); - priv = css_provider->priv; - props = gtk_style_properties_new (); - - css_provider_dump_symbolic_colors (css_provider, props); +#ifdef VERIFY_TREE + GtkCssProviderPrivate *priv = provider->priv; + GtkCssRuleset *ruleset; + gboolean should_match; + int i, j; for (i = 0; i < priv->rulesets->len; i++) { - GtkCssRuleset *ruleset; - GHashTableIter iter; - gpointer key, val; + gboolean found = FALSE; ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i); - if (ruleset->style == NULL) - continue; - - if (!gtk_css_ruleset_matches (ruleset, path, 0)) - continue; - - g_hash_table_iter_init (&iter, ruleset->style); - - while (g_hash_table_iter_next (&iter, &key, &val)) - { - GtkCssStyleProperty *prop = key; - PropertyValue *value = val; - - _gtk_style_properties_set_property_by_property (props, - prop, - _gtk_css_selector_get_state_flags (ruleset->selector), - &value->value); - } - } + for (j = 0; j < tree_rules->len; j++) + { + if (ruleset == tree_rules->pdata[j]) + { + found = TRUE; + break; + } + } + should_match = _gtk_css_selector_matches (ruleset->selector, matcher); + if (found != !!should_match) + { + g_error ("expected rule '%s' to %s, but it %s\n", + _gtk_css_selector_to_string (ruleset->selector), + should_match ? "match" : "not match", + found ? "matched" : "didn't match"); + } + } +#endif +} - return props; +static void +verify_tree_get_change_results (GtkCssProvider *provider, + const GtkCssMatcher *matcher, + GtkCssChange change) +{ +#ifdef VERIFY_TREE + { + GtkCssChange verify_change = 0; + GPtrArray *tree_rules; + int i; + + tree_rules = _gtk_css_selector_tree_match_all (provider->priv->tree, matcher); + verify_tree_match_results (provider, matcher, tree_rules); + + for (i = tree_rules->len - 1; i >= 0; i--) + { + GtkCssRuleset *ruleset; + + ruleset = tree_rules->pdata[i]; + + verify_change |= _gtk_css_selector_tree_match_get_change (ruleset->selector_match); + } + + if (change != verify_change) + { + GString *s; + + s = g_string_new (""); + g_string_append_printf (s, "expected change 0x%x, but it was 0x%x", verify_change, change); + if ((change & ~verify_change) != 0) + g_string_append_printf (s, ", unexpectedly set: 0x%x", change & ~verify_change); + if ((~change & verify_change) != 0) + g_string_append_printf (s, ", unexpectedly no set: 0x%x", ~change & verify_change); + g_warning (s->str); + g_string_free (s, TRUE); + } + + g_ptr_array_free (tree_rules, TRUE); + } +#endif } + static gboolean gtk_css_provider_get_style_property (GtkStyleProvider *provider, GtkWidgetPath *path, @@ -1452,52 +1487,57 @@ gtk_css_provider_get_style_property (GtkStyleProvider *provider, { GtkCssProvider *css_provider = GTK_CSS_PROVIDER (provider); GtkCssProviderPrivate *priv = css_provider->priv; - PropertyValue *val; + WidgetPropertyValue *val; + GPtrArray *tree_rules; + GtkCssMatcher matcher; gboolean found = FALSE; gchar *prop_name; gint i; + if (!_gtk_css_matcher_init (&matcher, path, state)) + return FALSE; + + tree_rules = _gtk_css_selector_tree_match_all (priv->tree, &matcher); + verify_tree_match_results (css_provider, &matcher, tree_rules); + prop_name = g_strdup_printf ("-%s-%s", g_type_name (pspec->owner_type), pspec->name); - for (i = priv->rulesets->len - 1; i >= 0; i--) + for (i = tree_rules->len - 1; i >= 0; i--) { - GtkCssRuleset *ruleset; - - ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i); + GtkCssRuleset *ruleset = tree_rules->pdata[i]; if (ruleset->widget_style == NULL) continue; - if (!gtk_css_ruleset_matches (ruleset, path, state)) - continue; - - val = g_hash_table_lookup (ruleset->widget_style, prop_name); + for (val = ruleset->widget_style; val != NULL; val = val->next) + { + if (strcmp (val->name, prop_name) == 0) + { + GtkCssScanner *scanner; - if (val) - { - GtkCssScanner *scanner; + scanner = gtk_css_scanner_new (css_provider, + NULL, + val->section, + val->section != NULL ? gtk_css_section_get_file (val->section) : NULL, + val->value); - scanner = gtk_css_scanner_new (css_provider, - NULL, - val->section, - gtk_css_section_get_file (val->section), - g_value_get_string (&val->value)); + found = _gtk_css_style_parse_value (value, + scanner->parser); - found = _gtk_style_property_parse_value (NULL, - value, - scanner->parser, - NULL); + gtk_css_scanner_destroy (scanner); - gtk_css_scanner_destroy (scanner); + break; + } + } - if (found) - break; - } + if (found) + break; } g_free (prop_name); + g_ptr_array_free (tree_rules, TRUE); return found; } @@ -1505,11 +1545,10 @@ gtk_css_provider_get_style_property (GtkStyleProvider *provider, static void gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface) { - iface->get_style = gtk_css_provider_get_style; iface->get_style_property = gtk_css_provider_get_style_property; } -static GtkSymbolicColor * +static GtkCssValue * gtk_css_style_provider_get_color (GtkStyleProviderPrivate *provider, const char *name) { @@ -1518,58 +1557,90 @@ gtk_css_style_provider_get_color (GtkStyleProviderPrivate *provider, return g_hash_table_lookup (css_provider->priv->symbolic_colors, name); } +static GtkCssKeyframes * +gtk_css_style_provider_get_keyframes (GtkStyleProviderPrivate *provider, + const char *name) +{ + GtkCssProvider *css_provider = GTK_CSS_PROVIDER (provider); + + return g_hash_table_lookup (css_provider->priv->keyframes, name); +} + static void gtk_css_style_provider_lookup (GtkStyleProviderPrivate *provider, - GtkWidgetPath *path, - GtkStateFlags state, + const GtkCssMatcher *matcher, GtkCssLookup *lookup) { GtkCssProvider *css_provider; GtkCssProviderPrivate *priv; + GtkCssRuleset *ruleset; + guint j; int i; + GPtrArray *tree_rules; css_provider = GTK_CSS_PROVIDER (provider); priv = css_provider->priv; - for (i = priv->rulesets->len - 1; i >= 0; i--) - { - GtkCssRuleset *ruleset; - GHashTableIter iter; - gpointer key, val; + tree_rules = _gtk_css_selector_tree_match_all (priv->tree, matcher); + verify_tree_match_results (css_provider, matcher, tree_rules); - ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i); + for (i = tree_rules->len - 1; i >= 0; i--) + { + ruleset = tree_rules->pdata[i]; - if (ruleset->style == NULL) + if (ruleset->styles == NULL) continue; if (!_gtk_bitmask_intersects (_gtk_css_lookup_get_missing (lookup), ruleset->set_styles)) continue; - if (!gtk_css_ruleset_matches (ruleset, path, state)) - continue; - - g_hash_table_iter_init (&iter, ruleset->style); - - while (g_hash_table_iter_next (&iter, &key, &val)) + for (j = 0; j < ruleset->n_styles; j++) { - GtkCssStyleProperty *prop = key; - PropertyValue *value = val; + GtkCssStyleProperty *prop = ruleset->styles[j].property; guint id = _gtk_css_style_property_get_id (prop); if (!_gtk_css_lookup_is_missing (lookup, id)) continue; - _gtk_css_lookup_set (lookup, id, value->section, &value->value); + _gtk_css_lookup_set (lookup, + id, + ruleset->styles[j].section, + ruleset->styles[j].value); } + + if (_gtk_bitmask_is_empty (_gtk_css_lookup_get_missing (lookup))) + break; } + + g_ptr_array_free (tree_rules, TRUE); +} + +static GtkCssChange +gtk_css_style_provider_get_change (GtkStyleProviderPrivate *provider, + const GtkCssMatcher *matcher) +{ + GtkCssProvider *css_provider; + GtkCssProviderPrivate *priv; + GtkCssChange change; + + css_provider = GTK_CSS_PROVIDER (provider); + priv = css_provider->priv; + + change = _gtk_css_selector_tree_get_change_all (priv->tree, matcher); + + verify_tree_get_change_results (css_provider, matcher, change); + + return change; } static void gtk_css_style_provider_private_iface_init (GtkStyleProviderPrivateInterface *iface) { iface->get_color = gtk_css_style_provider_get_color; + iface->get_keyframes = gtk_css_style_provider_get_keyframes; iface->lookup = gtk_css_style_provider_lookup; + iface->get_change = gtk_css_style_provider_get_change; } static void @@ -1586,9 +1657,17 @@ gtk_css_provider_finalize (GObject *object) gtk_css_ruleset_clear (&g_array_index (priv->rulesets, GtkCssRuleset, i)); g_array_free (priv->rulesets, TRUE); + _gtk_css_selector_tree_free (priv->tree); + + g_hash_table_destroy (priv->symbolic_colors); + g_hash_table_destroy (priv->keyframes); - if (priv->symbolic_colors) - g_hash_table_destroy (priv->symbolic_colors); + if (priv->resource) + { + g_resources_unregister (priv->resource); + g_resource_unref (priv->resource); + priv->resource = NULL; + } G_OBJECT_CLASS (gtk_css_provider_parent_class)->finalize (object); } @@ -1677,7 +1756,7 @@ css_provider_commit (GtkCssProvider *css_provider, priv = css_provider->priv; - if (ruleset->style == NULL && ruleset->widget_style == NULL) + if (ruleset->styles == NULL && ruleset->widget_style == NULL) { g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free); return; @@ -1703,11 +1782,22 @@ gtk_css_provider_reset (GtkCssProvider *css_provider) priv = css_provider->priv; + if (priv->resource) + { + g_resources_unregister (priv->resource); + g_resource_unref (priv->resource); + priv->resource = NULL; + } + g_hash_table_remove_all (priv->symbolic_colors); + g_hash_table_remove_all (priv->keyframes); for (i = 0; i < priv->rulesets->len; i++) gtk_css_ruleset_clear (&g_array_index (priv->rulesets, GtkCssRuleset, i)); g_array_set_size (priv->rulesets, 0); + _gtk_css_selector_tree_free (priv->tree); + priv->tree = NULL; + } static void @@ -1717,32 +1807,14 @@ gtk_css_provider_propagate_error (GtkCssProvider *provider, GError **propagate_to) { - GFileInfo *info; - GFile *file; - const char *path; - - file = gtk_css_section_get_file (section); - if (file) - { - info = g_file_query_info (file,G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, 0, NULL, NULL); - - if (info) - path = g_file_info_get_display_name (info); - else - path = ""; - } - else - { - info = NULL; - path = ""; - } + char *s; /* don't fail for deprecations */ if (g_error_matches (error, GTK_CSS_PROVIDER_ERROR, GTK_CSS_PROVIDER_ERROR_DEPRECATED)) { - g_warning ("Theme parsing error: %s:%u:%u: %s", path, - gtk_css_section_get_end_line (section) + 1, - gtk_css_section_get_end_position (section), error->message); + s = _gtk_css_section_to_string (section); + g_warning ("Theme parsing error: %s: %s", s, error->message); + g_free (s); return; } @@ -1751,19 +1823,18 @@ gtk_css_provider_propagate_error (GtkCssProvider *provider, return; *propagate_to = g_error_copy (error); - g_prefix_error (propagate_to, "%s:%u:%u: ", path, - gtk_css_section_get_end_line (section) + 1, - gtk_css_section_get_end_position (section)); - - if (info) - g_object_unref (info); + if (section) + { + s = _gtk_css_section_to_string (section); + g_prefix_error (propagate_to, "%s", s); + g_free (s); + } } static gboolean parse_import (GtkCssScanner *scanner) { GFile *file; - char *uri; gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_IMPORT); @@ -1774,21 +1845,31 @@ parse_import (GtkCssScanner *scanner) } if (_gtk_css_parser_is_string (scanner->parser)) - uri = _gtk_css_parser_read_string (scanner->parser); + { + char *uri; + + uri = _gtk_css_parser_read_string (scanner->parser); + file = _gtk_css_parser_get_file_for_path (scanner->parser, uri); + g_free (uri); + } else - uri = _gtk_css_parser_read_uri (scanner->parser); + { + file = _gtk_css_parser_read_url (scanner->parser); + } - if (uri == NULL) + if (file == NULL) { _gtk_css_parser_resync (scanner->parser, TRUE, 0); gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_IMPORT); return TRUE; } - file = g_file_resolve_relative_path (gtk_css_scanner_get_base_url (scanner), uri); - g_free (uri); - - if (gtk_css_scanner_would_recurse (scanner, file)) + if (!_gtk_css_parser_try (scanner->parser, ";", FALSE)) + { + gtk_css_provider_invalid_token (scanner->provider, scanner, "semicolon"); + _gtk_css_parser_resync (scanner->parser, TRUE, 0); + } + else if (gtk_css_scanner_would_recurse (scanner, file)) { char *path = g_file_get_path (file); gtk_css_provider_error (scanner->provider, @@ -1808,22 +1889,18 @@ parse_import (GtkCssScanner *scanner) NULL); } - if (!_gtk_css_parser_try (scanner->parser, ";", TRUE)) - { - gtk_css_provider_invalid_token (scanner->provider, scanner, "semicolon"); - _gtk_css_parser_resync (scanner->parser, TRUE, 0); - } - g_object_unref (file); gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_IMPORT); + _gtk_css_parser_skip_whitespace (scanner->parser); + return TRUE; } static gboolean parse_color_definition (GtkCssScanner *scanner) { - GtkSymbolicColor *symbolic; + GtkCssValue *color; char *name; gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION); @@ -1847,8 +1924,8 @@ parse_color_definition (GtkCssScanner *scanner) return TRUE; } - symbolic = _gtk_css_parser_read_symbolic_color (scanner->parser); - if (symbolic == NULL) + color = _gtk_css_color_value_parse (scanner->parser); + if (color == NULL) { g_free (name); _gtk_css_parser_resync (scanner->parser, TRUE, 0); @@ -1859,7 +1936,7 @@ parse_color_definition (GtkCssScanner *scanner) if (!_gtk_css_parser_try (scanner->parser, ";", TRUE)) { g_free (name); - gtk_symbolic_color_unref (symbolic); + _gtk_css_value_unref (color); gtk_css_provider_error_literal (scanner->provider, scanner, GTK_CSS_PROVIDER_ERROR, @@ -1871,7 +1948,7 @@ parse_color_definition (GtkCssScanner *scanner) return TRUE; } - g_hash_table_insert (scanner->provider->priv->symbolic_colors, name, symbolic); + g_hash_table_insert (scanner->provider->priv->symbolic_colors, name, color); gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION); return TRUE; @@ -1985,313 +2062,103 @@ skip_semicolon: return TRUE; } -static void -parse_at_keyword (GtkCssScanner *scanner) +static gboolean +parse_keyframes (GtkCssScanner *scanner) { - if (parse_import (scanner)) - return; - if (parse_color_definition (scanner)) - return; - if (parse_binding_set (scanner)) - return; + GtkCssKeyframes *keyframes; + char *name; - else + gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_KEYFRAMES); + + if (!_gtk_css_parser_try (scanner->parser, "@keyframes", TRUE)) + { + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_KEYFRAMES); + return FALSE; + } + + name = _gtk_css_parser_try_ident (scanner->parser, TRUE); + if (name == NULL) { gtk_css_provider_error_literal (scanner->provider, scanner, GTK_CSS_PROVIDER_ERROR, GTK_CSS_PROVIDER_ERROR_SYNTAX, - "unknown @ rule"); + "Expected name for keyframes"); _gtk_css_parser_resync (scanner->parser, TRUE, 0); + goto exit; } -} - -static gboolean -parse_selector_class (GtkCssScanner *scanner, GArray *classes) -{ - GQuark qname; - char *name; - - name = _gtk_css_parser_try_name (scanner->parser, FALSE); - if (name == NULL) + if (!_gtk_css_parser_try (scanner->parser, "{", TRUE)) { gtk_css_provider_error_literal (scanner->provider, scanner, GTK_CSS_PROVIDER_ERROR, GTK_CSS_PROVIDER_ERROR_SYNTAX, - "Expected a valid name for class"); - return FALSE; + "Expected '{' for keyframes"); + _gtk_css_parser_resync (scanner->parser, TRUE, 0); + g_free (name); + goto exit; } - qname = g_quark_from_string (name); - g_array_append_val (classes, qname); - g_free (name); - return TRUE; -} + keyframes = _gtk_css_keyframes_parse (scanner->parser); + if (keyframes == NULL) + { + _gtk_css_parser_resync (scanner->parser, TRUE, '}'); + g_free (name); + goto exit; + } -static gboolean -parse_selector_name (GtkCssScanner *scanner, GArray *names) -{ - GQuark qname; - char *name; - - name = _gtk_css_parser_try_name (scanner->parser, FALSE); + g_hash_table_insert (scanner->provider->priv->keyframes, name, keyframes); - if (name == NULL) + if (!_gtk_css_parser_try (scanner->parser, "}", TRUE)) { gtk_css_provider_error_literal (scanner->provider, scanner, GTK_CSS_PROVIDER_ERROR, GTK_CSS_PROVIDER_ERROR_SYNTAX, - "Expected a valid name for id"); - return FALSE; + "expected '}' after declarations"); + if (!_gtk_css_parser_is_eof (scanner->parser)) + _gtk_css_parser_resync (scanner->parser, FALSE, 0); } - qname = g_quark_from_string (name); - g_array_append_val (names, qname); - g_free (name); +exit: + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_KEYFRAMES); + return TRUE; } -static gboolean -parse_selector_pseudo_class (GtkCssScanner *scanner, - GtkRegionFlags *region_to_modify, - GtkStateFlags *state_to_modify) -{ - struct { - const char *name; - GtkRegionFlags region_flag; - GtkStateFlags state_flag; - } pseudo_classes[] = { - { "first-child", GTK_REGION_FIRST, 0 }, - { "last-child", GTK_REGION_LAST, 0 }, - { "only-child", GTK_REGION_ONLY, 0 }, - { "sorted", GTK_REGION_SORTED, 0 }, - { "active", 0, GTK_STATE_FLAG_ACTIVE }, - { "prelight", 0, GTK_STATE_FLAG_PRELIGHT }, - { "hover", 0, GTK_STATE_FLAG_PRELIGHT }, - { "selected", 0, GTK_STATE_FLAG_SELECTED }, - { "insensitive", 0, GTK_STATE_FLAG_INSENSITIVE }, - { "inconsistent", 0, GTK_STATE_FLAG_INCONSISTENT }, - { "focused", 0, GTK_STATE_FLAG_FOCUSED }, - { "focus", 0, GTK_STATE_FLAG_FOCUSED }, - { "window-unfocused", 0, GTK_STATE_FLAG_WINDOW_UNFOCUSED }, - { NULL, } - }, nth_child_classes[] = { - { "first", GTK_REGION_FIRST, 0 }, - { "last", GTK_REGION_LAST, 0 }, - { "even", GTK_REGION_EVEN, 0 }, - { "odd", GTK_REGION_ODD, 0 }, - { NULL, } - }, *classes; - guint i; - char *name; +static void +parse_at_keyword (GtkCssScanner *scanner) +{ + if (parse_import (scanner)) + return; + if (parse_color_definition (scanner)) + return; + if (parse_binding_set (scanner)) + return; + if (parse_keyframes (scanner)) + return; - name = _gtk_css_parser_try_ident (scanner->parser, FALSE); - if (name == NULL) + else { gtk_css_provider_error_literal (scanner->provider, scanner, GTK_CSS_PROVIDER_ERROR, GTK_CSS_PROVIDER_ERROR_SYNTAX, - "Missing name of pseudo-class"); - return FALSE; + "unknown @ rule"); + _gtk_css_parser_resync (scanner->parser, TRUE, 0); } +} - if (_gtk_css_parser_try (scanner->parser, "(", TRUE)) - { - char *function = name; - - name = _gtk_css_parser_try_ident (scanner->parser, TRUE); - if (!_gtk_css_parser_try (scanner->parser, ")", FALSE)) - { - gtk_css_provider_error_literal (scanner->provider, - scanner, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_SYNTAX, - "Missing closing bracket for pseudo-class"); - return FALSE; - } - - if (g_ascii_strcasecmp (function, "nth-child") != 0) - { - gtk_css_provider_error (scanner->provider, - scanner, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE, - "Unknown pseudo-class '%s(%s)'", function, name ? name : ""); - g_free (function); - g_free (name); - return FALSE; - } - - g_free (function); - - if (name == NULL) - { - gtk_css_provider_error (scanner->provider, - scanner, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE, - "nth-child() requires an argument"); - return FALSE; - } - - classes = nth_child_classes; - } - else - classes = pseudo_classes; - - for (i = 0; classes[i].name != NULL; i++) - { - if (g_ascii_strcasecmp (name, classes[i].name) == 0) - { - if ((*region_to_modify & classes[i].region_flag) || - (*state_to_modify & classes[i].state_flag)) - { - if (classes == nth_child_classes) - gtk_css_provider_error (scanner->provider, - scanner, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_SYNTAX, - "Duplicate pseudo-class 'nth-child(%s)'", name); - else - gtk_css_provider_error (scanner->provider, - scanner, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_SYNTAX, - "Duplicate pseudo-class '%s'", name); - } - *region_to_modify |= classes[i].region_flag; - *state_to_modify |= classes[i].state_flag; - - g_free (name); - return TRUE; - } - } - - if (classes == nth_child_classes) - gtk_css_provider_error (scanner->provider, - scanner, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE, - "Unknown pseudo-class 'nth-child(%s)'", name); - else - gtk_css_provider_error (scanner->provider, - scanner, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_UNKNOWN_VALUE, - "Unknown pseudo-class '%s'", name); - g_free (name); - return FALSE; -} - -static gboolean -parse_simple_selector (GtkCssScanner *scanner, - char **name, - GArray *ids, - GArray *classes, - GtkRegionFlags *pseudo_classes, - GtkStateFlags *state) -{ - gboolean parsed_something; - - *name = _gtk_css_parser_try_ident (scanner->parser, FALSE); - if (*name) - parsed_something = TRUE; - else - parsed_something = _gtk_css_parser_try (scanner->parser, "*", FALSE); - - do { - if (_gtk_css_parser_try (scanner->parser, "#", FALSE)) - { - if (!parse_selector_name (scanner, ids)) - return FALSE; - } - else if (_gtk_css_parser_try (scanner->parser, ".", FALSE)) - { - if (!parse_selector_class (scanner, classes)) - return FALSE; - } - else if (_gtk_css_parser_try (scanner->parser, ":", FALSE)) - { - if (!parse_selector_pseudo_class (scanner, pseudo_classes, state)) - return FALSE; - } - else if (!parsed_something) - { - gtk_css_provider_error_literal (scanner->provider, - scanner, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_SYNTAX, - "Expected a valid selector"); - return FALSE; - } - else - break; - - parsed_something = TRUE; - } - while (!_gtk_css_parser_is_eof (scanner->parser)); - - _gtk_css_parser_skip_whitespace (scanner->parser); - return TRUE; -} - -static GtkCssSelector * -parse_selector (GtkCssScanner *scanner) -{ - GtkCssSelector *selector = NULL; - - do { - char *name = NULL; - GArray *ids = g_array_new (TRUE, FALSE, sizeof (GQuark)); - GArray *classes = g_array_new (TRUE, FALSE, sizeof (GQuark)); - GtkRegionFlags pseudo_classes = 0; - GtkStateFlags state = 0; - GtkCssCombinator combine = GTK_CSS_COMBINE_DESCANDANT; - - if (selector) - { - if (_gtk_css_parser_try (scanner->parser, ">", TRUE)) - combine = GTK_CSS_COMBINE_CHILD; - } - - if (!parse_simple_selector (scanner, &name, ids, classes, &pseudo_classes, &state)) - { - g_array_free (ids, TRUE); - g_array_free (classes, TRUE); - if (selector) - _gtk_css_selector_free (selector); - return NULL; - } - - selector = _gtk_css_selector_new (selector, - combine, - name, - (GQuark *) g_array_free (ids, ids->len == 0), - (GQuark *) g_array_free (classes, classes->len == 0), - pseudo_classes, - state); - g_free (name); - } - while (!_gtk_css_parser_is_eof (scanner->parser) && - !_gtk_css_parser_begins_with (scanner->parser, ',') && - !_gtk_css_parser_begins_with (scanner->parser, '{')); - - return selector; -} - -static GSList * -parse_selector_list (GtkCssScanner *scanner) -{ - GSList *selectors = NULL; +static GSList * +parse_selector_list (GtkCssScanner *scanner) +{ + GSList *selectors = NULL; gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_SELECTOR); do { - GtkCssSelector *select = parse_selector (scanner); + GtkCssSelector *select = _gtk_css_selector_parse (scanner->parser); if (select == NULL) { @@ -2349,48 +2216,64 @@ parse_declaration (GtkCssScanner *scanner, if (property) { - PropertyValue *val; + GtkCssValue *value; g_free (name); gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_VALUE); - val = property_value_new (scanner->section); - g_value_init (&val->value, property->pspec->value_type); + value = _gtk_style_property_parse_value (property, + scanner->parser); - if (_gtk_style_property_parse_value (property, - &val->value, - scanner->parser, - gtk_css_scanner_get_base_url (scanner))) + if (value == NULL) { - if (_gtk_css_parser_begins_with (scanner->parser, ';') || - _gtk_css_parser_begins_with (scanner->parser, '}') || - _gtk_css_parser_is_eof (scanner->parser)) - { - gtk_css_ruleset_add (ruleset, property, val); - } - else - { - gtk_css_provider_error_literal (scanner->provider, - scanner, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_SYNTAX, - "Junk at end of value"); - _gtk_css_parser_resync (scanner->parser, TRUE, '}'); - property_value_free (val); - gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE); - gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION); - return; - } + _gtk_css_parser_resync (scanner->parser, TRUE, '}'); + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE); + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION); + return; } - else + + if (!_gtk_css_parser_begins_with (scanner->parser, ';') && + !_gtk_css_parser_begins_with (scanner->parser, '}') && + !_gtk_css_parser_is_eof (scanner->parser)) { - property_value_free (val); + gtk_css_provider_error_literal (scanner->provider, + scanner, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_SYNTAX, + "Junk at end of value"); _gtk_css_parser_resync (scanner->parser, TRUE, '}'); gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE); gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION); return; } + + if (GTK_IS_CSS_SHORTHAND_PROPERTY (property)) + { + GtkCssShorthandProperty *shorthand = GTK_CSS_SHORTHAND_PROPERTY (property); + guint i; + + for (i = 0; i < _gtk_css_shorthand_property_get_n_subproperties (shorthand); i++) + { + GtkCssStyleProperty *child = _gtk_css_shorthand_property_get_subproperty (shorthand, i); + GtkCssValue *sub = _gtk_css_array_value_get_nth (value, i); + + gtk_css_ruleset_add (ruleset, child, _gtk_css_value_ref (sub), scanner->section); + } + + _gtk_css_value_unref (value); + } + else if (GTK_IS_CSS_STYLE_PROPERTY (property)) + { + gtk_css_ruleset_add (ruleset, GTK_CSS_STYLE_PROPERTY (property), value, scanner->section); + } + else + { + g_assert_not_reached (); + _gtk_css_value_unref (value); + } + + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE); } else if (name[0] == '-') @@ -2402,11 +2285,10 @@ parse_declaration (GtkCssScanner *scanner, value_str = _gtk_css_parser_read_value (scanner->parser); if (value_str) { - PropertyValue *val; + WidgetPropertyValue *val; - val = property_value_new (scanner->section); - g_value_init (&val->value, G_TYPE_STRING); - g_value_take_string (&val->value, value_str); + val = widget_property_value_new (name, scanner->section); + val->value = value_str; gtk_css_ruleset_add_style (ruleset, name, val); } @@ -2543,13 +2425,6 @@ gtk_css_provider_compare_rule (gconstpointer a_, if (compare != 0) return compare; - /* compare pointers in array to ensure a stable sort */ - if (a_ < b_) - return -1; - - if (a_ > b_) - return 1; - return 0; } @@ -2557,8 +2432,38 @@ static void gtk_css_provider_postprocess (GtkCssProvider *css_provider) { GtkCssProviderPrivate *priv = css_provider->priv; + GtkCssSelectorTreeBuilder *builder; + guint i; g_array_sort (priv->rulesets, gtk_css_provider_compare_rule); + + builder = _gtk_css_selector_tree_builder_new (); + for (i = 0; i < priv->rulesets->len; i++) + { + GtkCssRuleset *ruleset; + + ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i); + + _gtk_css_selector_tree_builder_add (builder, + ruleset->selector, + &ruleset->selector_match, + ruleset); + } + + priv->tree = _gtk_css_selector_tree_builder_build (builder); + _gtk_css_selector_tree_builder_free (builder); + +#ifndef VERIFY_TREE + for (i = 0; i < priv->rulesets->len; i++) + { + GtkCssRuleset *ruleset; + + ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i); + + _gtk_css_selector_free (ruleset->selector); + ruleset->selector = NULL; + } +#endif } static gboolean @@ -2655,7 +2560,10 @@ gtk_css_provider_load_internal (GtkCssProvider *css_provider, * Loads @data into @css_provider, making it clear any previously loaded * information. * - * Returns: %TRUE if the data could be loaded. + * Returns: %TRUE. The return value is deprecated and %FALSE will only be + * returned for backwards compatibility reasons if an @error is not + * %NULL and a loading error occured. To track errors while loading + * CSS, connect to the GtkCssProvider::parsing-error signal. **/ gboolean gtk_css_provider_load_from_data (GtkCssProvider *css_provider, @@ -2686,6 +2594,8 @@ gtk_css_provider_load_from_data (GtkCssProvider *css_provider, g_free (free_data); + _gtk_style_provider_private_changed (GTK_STYLE_PROVIDER_PRIVATE (css_provider)); + return ret; } @@ -2698,19 +2608,28 @@ gtk_css_provider_load_from_data (GtkCssProvider *css_provider, * Loads the data contained in @file into @css_provider, making it * clear any previously loaded information. * - * Returns: %TRUE if the data could be loaded. + * Returns: %TRUE. The return value is deprecated and %FALSE will only be + * returned for backwards compatibility reasons if an @error is not + * %NULL and a loading error occured. To track errors while loading + * CSS, connect to the GtkCssProvider::parsing-error signal. **/ gboolean gtk_css_provider_load_from_file (GtkCssProvider *css_provider, GFile *file, GError **error) { + gboolean success; + g_return_val_if_fail (GTK_IS_CSS_PROVIDER (css_provider), FALSE); g_return_val_if_fail (G_IS_FILE (file), FALSE); gtk_css_provider_reset (css_provider); - return gtk_css_provider_load_internal (css_provider, NULL, file, NULL, error); + success = gtk_css_provider_load_internal (css_provider, NULL, file, NULL, error); + + _gtk_style_provider_private_changed (GTK_STYLE_PROVIDER_PRIVATE (css_provider)); + + return success; } /** @@ -2722,7 +2641,10 @@ gtk_css_provider_load_from_file (GtkCssProvider *css_provider, * Loads the data contained in @path into @css_provider, making it clear * any previously loaded information. * - * Returns: %TRUE if the data could be loaded. + * Returns: %TRUE. The return value is deprecated and %FALSE will only be + * returned for backwards compatibility reasons if an @error is not + * %NULL and a loading error occured. To track errors while loading + * CSS, connect to the GtkCssProvider::parsing-error signal. **/ gboolean gtk_css_provider_load_from_path (GtkCssProvider *css_provider, @@ -2744,6 +2666,29 @@ gtk_css_provider_load_from_path (GtkCssProvider *css_provider, return result; } +static void +gtk_css_provider_load_from_resource (GtkCssProvider *css_provider, + const gchar *resource_path) +{ + GFile *file; + char *uri, *escaped; + + g_return_if_fail (GTK_IS_CSS_PROVIDER (css_provider)); + g_return_if_fail (resource_path != NULL); + + escaped = g_uri_escape_string (resource_path, + G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE); + uri = g_strconcat ("resource://", escaped, NULL); + g_free (escaped); + + file = g_file_new_for_uri (uri); + g_free (uri); + + gtk_css_provider_load_from_file (css_provider, file, NULL); + + g_object_unref (file); +} + /** * gtk_css_provider_get_default: * @@ -2760,377 +2705,7 @@ gtk_css_provider_get_default (void) if (G_UNLIKELY (!provider)) { - const gchar *str = - "@define-color fg_color #000; \n" - "@define-color bg_color #dcdad5; \n" - "@define-color text_color #000; \n" - "@define-color base_color #fff; \n" - "@define-color selected_bg_color #4b6983; \n" - "@define-color selected_fg_color #fff; \n" - "@define-color tooltip_bg_color #eee1b3; \n" - "@define-color tooltip_fg_color #000; \n" - "@define-color placeholder_text_color #808080; \n" - "\n" - "@define-color info_fg_color rgb (181, 171, 156);\n" - "@define-color info_bg_color rgb (252, 252, 189);\n" - "@define-color warning_fg_color rgb (173, 120, 41);\n" - "@define-color warning_bg_color rgb (250, 173, 61);\n" - "@define-color question_fg_color rgb (97, 122, 214);\n" - "@define-color question_bg_color rgb (138, 173, 212);\n" - "@define-color error_fg_color rgb (166, 38, 38);\n" - "@define-color error_bg_color rgb (237, 54, 54);\n" - "\n" - "* {\n" - " background-color: @bg_color;\n" - " color: @fg_color;\n" - " border-color: shade (@bg_color, 0.6);\n" - " padding: 2;\n" - " border-width: 0;\n" - "}\n" - "\n" - "*:prelight {\n" - " background-color: shade (@bg_color, 1.05);\n" - " color: shade (@fg_color, 1.3);\n" - "}\n" - "\n" - "*:selected {\n" - " background-color: @selected_bg_color;\n" - " color: @selected_fg_color;\n" - "}\n" - "\n" - ".expander, GtkTreeView.view.expander {\n" - " color: #fff;\n" - "}\n" - "\n" - ".expander:prelight,\n" - "GtkTreeView.view.expander:selected:prelight {\n" - " color: @text_color;\n" - "}\n" - "\n" - ".expander:active {\n" - " transition: 200ms linear;\n" - "}\n" - "\n" - "*:insensitive {\n" - " border-color: shade (@bg_color, 0.7);\n" - " background-color: shade (@bg_color, 0.9);\n" - " color: shade (@bg_color, 0.7);\n" - "}\n" - "\n" - ".view {\n" - " border-width: 0;\n" - " border-radius: 0;\n" - " background-color: @base_color;\n" - " color: @text_color;\n" - "}\n" - ".view:selected {\n" - " background-color: shade (@bg_color, 0.9);\n" - " color: @fg_color;\n" - "}\n" - "\n" - ".view:selected:focused {\n" - " background-color: @selected_bg_color;\n" - " color: @selected_fg_color;\n" - "}\n" - "\n" - ".view column:sorted row,\n" - ".view column:sorted row:prelight {\n" - " background-color: shade (@bg_color, 0.85);\n" - "}\n" - "\n" - ".view column:sorted row:nth-child(odd),\n" - ".view column:sorted row:nth-child(odd):prelight {\n" - " background-color: shade (@bg_color, 0.8);\n" - "}\n" - "\n" - ".view row,\n" - ".view row:prelight {\n" - " background-color: @base_color;\n" - " color: @text_color;\n" - "}\n" - "\n" - ".view row:nth-child(odd),\n" - ".view row:nth-child(odd):prelight {\n" - " background-color: shade (@base_color, 0.93); \n" - "}\n" - "\n" - ".view row:selected:focused {\n" - " background-color: @selected_bg_color;\n" - "}\n" - "\n" - ".view row:selected {\n" - " background-color: darker (@bg_color);\n" - " color: @selected_fg_color;\n" - "}\n" - "\n" - ".view.cell.trough,\n" - ".view.cell.trough:hover,\n" - ".view.cell.trough:selected,\n" - ".view.cell.trough:selected:focused {\n" - " background-color: @bg_color;\n" - " color: @fg_color;\n" - "}\n" - "\n" - ".view.cell.progressbar,\n" - ".view.cell.progressbar:hover,\n" - ".view.cell.progressbar:selected,\n" - ".view.cell.progressbar:selected:focused {\n" - " background-color: @selected_bg_color;\n" - " color: @selected_fg_color;\n" - "}\n" - "\n" - ".rubberband {\n" - " background-color: alpha (@fg_color, 0.25);\n" - " border-color: @fg_color;\n" - " border-style: solid;\n" - " border-width: 1;\n" - "}\n" - "\n" - ".tooltip,\n" - ".tooltip * {\n" - " background-color: @tooltip_bg_color; \n" - " color: @tooltip_fg_color; \n" - " border-color: @tooltip_fg_color; \n" - " border-width: 1;\n" - " border-style: solid;\n" - "}\n" - "\n" - ".button,\n" - ".slider {\n" - " border-style: outset; \n" - " border-width: 2; \n" - "}\n" - "\n" - ".button:active {\n" - " background-color: shade (@bg_color, 0.7);\n" - " border-style: inset; \n" - "}\n" - "\n" - ".button:prelight,\n" - ".slider:prelight {\n" - " background-color: @selected_bg_color;\n" - " color: @selected_fg_color;\n" - " border-color: shade (@selected_bg_color, 0.7);\n" - "}\n" - "\n" - ".trough {\n" - " background-color: darker (@bg_color);\n" - " border-style: inset;\n" - " border-width: 1;\n" - " padding: 0;\n" - "}\n" - "\n" - ".entry {\n" - " border-style: inset;\n" - " border-width: 2;\n" - " background-color: @base_color;\n" - " color: @text_color;\n" - "}\n" - "\n" - ".entry:insensitive {\n" - " background-color: shade (@base_color, 0.9);\n" - " color: shade (@base_color, 0.7);\n" - "}\n" - ".entry:active {\n" - " background-color: #c4c2bd;\n" - " color: #000;\n" - "}\n" - "\n" - ".progressbar,\n" - ".entry.progressbar, \n" - ".cell.progressbar {\n" - " background-color: @selected_bg_color;\n" - " border-color: shade (@selected_bg_color, 0.7);\n" - " color: @selected_fg_color;\n" - " border-style: outset;\n" - " border-width: 1;\n" - "}\n" - "\n" - "GtkCheckButton:hover,\n" - "GtkCheckButton:selected,\n" - "GtkRadioButton:hover,\n" - "GtkRadioButton:selected {\n" - " background-color: shade (@bg_color, 1.05);\n" - "}\n" - "\n" - ".check, .radio," - ".cell.check, .cell.radio,\n" - ".cell.check:hover, .cell.radio:hover {\n" - " border-style: solid;\n" - " border-width: 1;\n" - " background-color: @base_color;\n" - " border-color: @fg_color;\n" - "}\n" - "\n" - ".check:active, .radio:active,\n" - ".check:hover, .radio:hover {\n" - " background-color: @base_color;\n" - " border-color: @fg_color;\n" - " color: @text_color;\n" - "}\n" - "\n" - ".check:selected, .radio:selected {\n" - " background-color: darker (@bg_color);\n" - " color: @selected_fg_color;\n" - " border-color: @selected_fg_color;\n" - "}\n" - "\n" - ".check:selected:focused, .radio:selected:focused {\n" - " background-color: @selected_bg_color;\n" - "}\n" - "\n" - ".menuitem.check, .menuitem.radio {\n" - " color: @fg_color;\n" - " border-style: none;\n" - " border-width: 0;\n" - "}\n" - "\n" - ".popup {\n" - " border-style: outset;\n" - " border-width: 1;\n" - "}\n" - "\n" - ".viewport {\n" - " border-style: inset;\n" - " border-width: 2;\n" - "}\n" - "\n" - ".notebook {\n" - " border-style: outset;\n" - " border-width: 1;\n" - "}\n" - "\n" - ".frame {\n" - " border-style: inset;\n" - " border-width: 1;\n" - "}\n" - "\n" - "GtkScrolledWindow.frame {\n" - " padding: 0;\n" - "}\n" - "\n" - ".menu,\n" - ".menubar,\n" - ".toolbar {\n" - " border-style: outset;\n" - " border-width: 1;\n" - "}\n" - "\n" - ".menu:hover,\n" - ".menubar:hover,\n" - ".menuitem:hover,\n" - ".menuitem.check:hover,\n" - ".menuitem.radio:hover {\n" - " background-color: @selected_bg_color;\n" - " color: @selected_fg_color;\n" - "}\n" - "\n" - "GtkSpinButton.button {\n" - " border-width: 1;\n" - "}\n" - "\n" - ".scale.slider:hover,\n" - "GtkSpinButton.button:hover {\n" - " background-color: shade (@bg_color, 1.05);\n" - " border-color: shade (@bg_color, 0.8);\n" - "}\n" - "\n" - "GtkSwitch.trough:active {\n" - " background-color: @selected_bg_color;\n" - " color: @selected_fg_color;\n" - "}\n" - "\n" - "GtkToggleButton.button:inconsistent {\n" - " border-style: outset;\n" - " border-width: 1px;\n" - " background-color: shade (@bg_color, 0.9);\n" - " border-color: shade (@bg_color, 0.7);\n" - "}\n" - "\n" - "GtkLabel:selected {\n" - " background-color: shade (@bg_color, 0.9);\n" - "}\n" - "\n" - "GtkLabel:selected:focused {\n" - " background-color: @selected_bg_color;\n" - "}\n" - "\n" - ".spinner:active {\n" - " transition: 750ms linear loop;\n" - "}\n" - "\n" - ".info {\n" - " background-color: @info_bg_color;\n" - " color: @info_fg_color;\n" - "}\n" - "\n" - ".warning {\n" - " background-color: @warning_bg_color;\n" - " color: @warning_fg_color;\n" - "}\n" - "\n" - ".question {\n" - " background-color: @question_bg_color;\n" - " color: @question_fg_color;\n" - "}\n" - "\n" - ".error {\n" - " background-color: @error_bg_color;\n" - " color: @error_fg_color;\n" - "}\n" - "\n" - ".highlight {\n" - " background-color: @selected_bg_color;\n" - " color: @selected_fg_color;\n" - "}\n" - "\n" - ".light-area-focus {\n" - " color: #000;\n" - "}\n" - "\n" - ".dark-area-focus {\n" - " color: #fff;\n" - "}\n" - "GtkCalendar.view {\n" - " border-width: 1;\n" - " border-style: inset;\n" - " padding: 1;\n" - "}\n" - "\n" - "GtkCalendar.view:inconsistent {\n" - " color: darker (@bg_color);\n" - "}\n" - "\n" - "GtkCalendar.header {\n" - " background-color: @bg_color;\n" - " border-style: outset;\n" - " border-width: 2;\n" - "}\n" - "\n" - "GtkCalendar.highlight {\n" - " border-width: 0;\n" - "}\n" - "\n" - "GtkCalendar.button {\n" - " background-color: @bg_color;\n" - "}\n" - "\n" - "GtkCalendar.button:hover {\n" - " background-color: lighter (@bg_color);\n" - " color: @fg_color;\n" - "}\n" - "\n" - ".menu * {\n" - " border-width: 0;\n" - " padding: 2;\n" - "}\n" - "\n"; - provider = gtk_css_provider_new (); - if (!gtk_css_provider_load_from_data (provider, str, -1, NULL)) - { - g_error ("Failed to load the internal default CSS."); - } } return provider; @@ -3152,76 +2727,71 @@ _gtk_css_provider_get_theme_dir (void) return path; } -#include "gtkwin32css.h" - /** - * gtk_css_provider_get_named: + * _gtk_css_provider_load_named: + * @provider: a #GtkCssProvider * @name: A theme name * @variant: (allow-none): variant to load, for example, "dark", or * %NULL for the default * - * Loads a theme from the usual theme paths - * - * Returns: (transfer none): a #GtkCssProvider with the theme loaded. - * This memory is owned by GTK+, and you must not free it. - */ -GtkCssProvider * -gtk_css_provider_get_named (const gchar *name, - const gchar *variant) + * Loads a theme from the usual theme paths. The actual process of + * finding the theme might change between releases, but it is + * guaranteed that this function uses the same mechanism to load the + * theme than GTK uses for loading its own theme. + **/ +void +_gtk_css_provider_load_named (GtkCssProvider *provider, + const gchar *name, + const gchar *variant) { - static GHashTable *themes = NULL; - GtkCssProvider *provider; - gchar *key; + gchar *subpath, *path; + gchar *resource_path; - if (G_UNLIKELY (!themes)) - { - themes = g_hash_table_new (g_str_hash, g_str_equal); + g_return_if_fail (GTK_IS_CSS_PROVIDER (provider)); + g_return_if_fail (name != NULL); - provider = gtk_css_provider_new (); - if (!gtk_css_provider_load_from_data (provider, gtk_win32_default_css, -1, NULL)) - { - g_warning ("Failed to load the internal win32 default CSS."); - g_object_unref (provider); - } - else - g_hash_table_insert (themes, "gtk-win32", provider); - } + gtk_css_provider_reset (provider); - if (variant == NULL) - key = (gchar *)name; + /* try loading the resource for the theme. This is mostly meant for built-in + * themes. + */ + if (variant) + resource_path = g_strdup_printf ("/org/gtk/libgtk/%s-%s.css", name, variant); else - key = g_strconcat (name, "-", variant, NULL); - - provider = g_hash_table_lookup (themes, key); + resource_path = g_strdup_printf ("/org/gtk/libgtk/%s.css", name); - if (!provider) + if (g_resources_get_info (resource_path, 0, NULL, NULL, NULL)) { - const gchar *home_dir; - gchar *subpath, *path = NULL; - - if (variant) - subpath = g_strdup_printf ("gtk-3.0" G_DIR_SEPARATOR_S "gtk-%s.css", variant); - else - subpath = g_strdup ("gtk-3.0" G_DIR_SEPARATOR_S "gtk.css"); + gtk_css_provider_load_from_resource (provider, resource_path); + g_free (resource_path); + return; + } + g_free (resource_path); - /* First look in the users home directory - */ - home_dir = g_get_home_dir (); - if (home_dir) - { - path = g_build_filename (home_dir, ".themes", name, subpath, NULL); - if (!g_file_test (path, G_FILE_TEST_EXISTS)) - { - g_free (path); - path = NULL; - } - } + /* Next try looking for files in the various theme directories. + */ + if (variant) + subpath = g_strdup_printf ("gtk-3.0" G_DIR_SEPARATOR_S "gtk-%s.css", variant); + else + subpath = g_strdup ("gtk-3.0" G_DIR_SEPARATOR_S "gtk.css"); - if (!path) + /* First look in the user's config directory + */ + path = g_build_filename (g_get_user_data_dir (), "themes", name, subpath, NULL); + if (!g_file_test (path, G_FILE_TEST_EXISTS)) + { + g_free (path); + /* Next look in the user's home directory + */ + path = g_build_filename (g_get_home_dir (), ".themes", name, subpath, NULL); + if (!g_file_test (path, G_FILE_TEST_EXISTS)) { gchar *theme_dir; + g_free (path); + + /* Finally, try in the default theme directory */ theme_dir = _gtk_css_provider_get_theme_dir (); path = g_build_filename (theme_dir, name, subpath, NULL); g_free (theme_dir); @@ -3232,88 +2802,164 @@ gtk_css_provider_get_named (const gchar *name, path = NULL; } } + } - g_free (subpath); + g_free (subpath); - if (path) - { - provider = gtk_css_provider_new (); + if (path) + { + char *dir, *resource_file; + GResource *resource; - if (!gtk_css_provider_load_from_path (provider, path, NULL)) - { - g_object_unref (provider); - provider = NULL; - } - else - g_hash_table_insert (themes, g_strdup (key), provider); + dir = g_path_get_dirname (path); + resource_file = g_build_filename (dir, "gtk.gresource", NULL); + resource = g_resource_load (resource_file, NULL); + g_free (resource_file); - g_free (path); + if (resource != NULL) + g_resources_register (resource); + + gtk_css_provider_load_from_path (provider, path, NULL); + + /* Only set this after load, as load_from_path will clear it */ + provider->priv->resource = resource; + + g_free (path); + g_free (dir); + } + else + { + /* Things failed! Fall back! Fall back! */ + + if (variant) + { + /* If there was a variant, try without */ + _gtk_css_provider_load_named (provider, name, NULL); + } + else + { + /* Worst case, fall back to Raleigh */ + g_return_if_fail (!g_str_equal (name, "Raleigh")); /* infloop protection */ + _gtk_css_provider_load_named (provider, "Raleigh", NULL); } } +} + +/** + * gtk_css_provider_get_named: + * @name: A theme name + * @variant: (allow-none): variant to load, for example, "dark", or + * %NULL for the default + * + * Loads a theme from the usual theme paths + * + * Returns: (transfer none): a #GtkCssProvider with the theme loaded. + * This memory is owned by GTK+, and you must not free it. + */ +GtkCssProvider * +gtk_css_provider_get_named (const gchar *name, + const gchar *variant) +{ + static GHashTable *themes = NULL; + GtkCssProvider *provider; + gchar *key; - if (key != name) - g_free (key); + if (variant == NULL) + key = g_strdup (name); + else + key = g_strconcat (name, "-", variant, NULL); + if (G_UNLIKELY (!themes)) + themes = g_hash_table_new (g_str_hash, g_str_equal); + + provider = g_hash_table_lookup (themes, key); + + if (!provider) + { + provider = gtk_css_provider_new (); + _gtk_css_provider_load_named (provider, name, variant); + g_hash_table_insert (themes, g_strdup (key), provider); + } + + g_free (key); return provider; } static int -compare_properties (gconstpointer a, gconstpointer b) +compare_properties (gconstpointer a, gconstpointer b, gpointer style) +{ + const guint *ua = a; + const guint *ub = b; + PropertyValue *styles = style; + + return strcmp (_gtk_style_property_get_name (GTK_STYLE_PROPERTY (styles[*ua].property)), + _gtk_style_property_get_name (GTK_STYLE_PROPERTY (styles[*ub].property))); +} + +static int +compare_names (gconstpointer a, gconstpointer b) { - return strcmp (_gtk_style_property_get_name ((GtkStyleProperty *) a), - _gtk_style_property_get_name ((GtkStyleProperty *) b)); + const WidgetPropertyValue *aa = a; + const WidgetPropertyValue *bb = b; + return strcmp (aa->name, bb->name); } static void gtk_css_ruleset_print (const GtkCssRuleset *ruleset, GString *str) { - GList *keys, *walk; + GList *values, *walk; + WidgetPropertyValue *widget_value; + guint i; - _gtk_css_selector_print (ruleset->selector, str); + _gtk_css_selector_tree_match_print (ruleset->selector_match, str); g_string_append (str, " {\n"); - if (ruleset->style) + if (ruleset->styles) { - keys = g_hash_table_get_keys (ruleset->style); + guint *sorted = g_new (guint, ruleset->n_styles); + + for (i = 0; i < ruleset->n_styles; i++) + sorted[i] = i; + /* so the output is identical for identical selector styles */ - keys = g_list_sort (keys, compare_properties); + g_qsort_with_data (sorted, ruleset->n_styles, sizeof (guint), compare_properties, ruleset->styles); - for (walk = keys; walk; walk = walk->next) + for (i = 0; i < ruleset->n_styles; i++) { - GtkCssStyleProperty *prop = walk->data; - const PropertyValue *value = g_hash_table_lookup (ruleset->style, prop); - + PropertyValue *prop = &ruleset->styles[sorted[i]]; g_string_append (str, " "); - g_string_append (str, _gtk_style_property_get_name (GTK_STYLE_PROPERTY (prop))); + g_string_append (str, _gtk_style_property_get_name (GTK_STYLE_PROPERTY (prop->property))); g_string_append (str, ": "); - _gtk_css_style_property_print_value (prop, &value->value, str); + _gtk_css_value_print (prop->value, str); g_string_append (str, ";\n"); } - g_list_free (keys); + g_free (sorted); } if (ruleset->widget_style) { - keys = g_hash_table_get_keys (ruleset->widget_style); + values = NULL; + for (widget_value = ruleset->widget_style; widget_value != NULL; widget_value = widget_value->next) + values = g_list_prepend (values, widget_value); + /* so the output is identical for identical selector styles */ - keys = g_list_sort (keys, (GCompareFunc) strcmp); + values = g_list_sort (values, compare_names); - for (walk = keys; walk; walk = walk->next) + for (walk = values; walk; walk = walk->next) { - const char *name = walk->data; - const PropertyValue *value = g_hash_table_lookup (ruleset->widget_style, (gpointer) name); + widget_value = walk->data; g_string_append (str, " "); - g_string_append (str, name); + g_string_append (str, widget_value->name); g_string_append (str, ": "); - g_string_append (str, g_value_get_string (&value->value)); + g_string_append (str, widget_value->value); g_string_append (str, ";\n"); } - g_list_free (keys); + g_list_free (values); } g_string_append (str, "}\n"); @@ -3324,7 +2970,6 @@ gtk_css_provider_print_colors (GHashTable *colors, GString *str) { GList *keys, *walk; - char *s; keys = g_hash_table_get_keys (colors); /* so the output is identical for identical styles */ @@ -3333,20 +2978,45 @@ gtk_css_provider_print_colors (GHashTable *colors, for (walk = keys; walk; walk = walk->next) { const char *name = walk->data; - GtkSymbolicColor *symbolic = g_hash_table_lookup (colors, (gpointer) name); + GtkCssValue *color = g_hash_table_lookup (colors, (gpointer) name); g_string_append (str, "@define-color "); g_string_append (str, name); g_string_append (str, " "); - s = gtk_symbolic_color_to_string (symbolic); - g_string_append (str, s); - g_free (s); + _gtk_css_value_print (color, str); g_string_append (str, ";\n"); } g_list_free (keys); } +static void +gtk_css_provider_print_keyframes (GHashTable *keyframes, + GString *str) +{ + GList *keys, *walk; + + keys = g_hash_table_get_keys (keyframes); + /* so the output is identical for identical styles */ + keys = g_list_sort (keys, (GCompareFunc) strcmp); + + for (walk = keys; walk; walk = walk->next) + { + const char *name = walk->data; + GtkCssKeyframes *keyframe = g_hash_table_lookup (keyframes, (gpointer) name); + + if (str->len > 0) + g_string_append (str, "\n"); + g_string_append (str, "@keyframes "); + g_string_append (str, name); + g_string_append (str, " {\n"); + _gtk_css_keyframes_print (keyframe, str); + g_string_append (str, "}\n"); + } + + g_list_free (keys); +} + /** * gtk_css_provider_to_string: * @provider: the provider to write to a string @@ -3377,10 +3047,11 @@ gtk_css_provider_to_string (GtkCssProvider *provider) str = g_string_new (""); gtk_css_provider_print_colors (priv->symbolic_colors, str); + gtk_css_provider_print_keyframes (priv->keyframes, str); for (i = 0; i < priv->rulesets->len; i++) { - if (i > 0) + if (str->len != 0) g_string_append (str, "\n"); gtk_css_ruleset_print (&g_array_index (priv->rulesets, GtkCssRuleset, i), str); }