X-Git-Url: http://pileus.org/git/?a=blobdiff_plain;f=gtk%2Fgtkcssprovider.c;h=751266847464ff2e9d495af992c5b203d60c7e8a;hb=feb64f40b0f50735104da0a7fdafbe480763c180;hp=4f09c5e9390c31cc2f7761a4eaa2b7deea69e604;hpb=73c39f5b1681f2efe5ddf3fb3c7fa6e071ac6039;p=~andy%2Fgtk diff --git a/gtk/gtkcssprovider.c b/gtk/gtkcssprovider.c index 4f09c5e93..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" @@ -27,13 +25,20 @@ #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 "gtksymboliccolor.h" +#include "gtkcssshorthandpropertyprivate.h" +#include "gtkcssstylefuncsprivate.h" #include "gtkstyleprovider.h" #include "gtkstylecontextprivate.h" #include "gtkstylepropertiesprivate.h" #include "gtkstylepropertyprivate.h" +#include "gtkstyleproviderprivate.h" #include "gtkbindings.h" #include "gtkmarshalers.h" #include "gtkprivate.h" @@ -141,12 +146,12 @@ * * /* Theme any widget within a GtkBin */ * GtkBin * { - * font-name: Sans 20 + * font: Sans 20 * } * * /* Theme a label named title-label */ * GtkLabel#title-label { - * font-name: Sans 15 + * font: Sans 15 * } * * /* Theme any widget named main-entry */ @@ -428,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) + * * * * @@ -527,21 +540,85 @@ * which is always rendered on top of the shadow layer. * * - * + * + * Box shadow + * + * Themes can apply shadows on framed elements using the CSS3 box-shadow syntax, + * as defined in + * the CSS3 specification. + * + * + * A box shadow is specified using the syntax + * box-shadow: [ @inset ] @horizontal_offset @vertical_offset [ @blur_radius ] [ @spread ] @color + * A positive offset will draw a shadow that is offset to the right (down) of the box, + * a negative offset to the left (top). The optional spread parameter defines an additional + * distance to expand the shadow shape in all directions, by the specified radius. + * The optional blur radius parameter is parsed, but it is currently not rendered by + * the GTK+ theming engine. + * The inset parameter defines whether the drop shadow should be rendered inside or outside + * the box canvas. Only inset box-shadows are currently supported by the GTK+ theming engine, + * non-inset elements are currently ignored. + * + * + * To set multiple box-shadows on an element, you can specify a comma-separated list + * of shadow elements in the box-shadow property. Shadows are always rendered + * front-back, i.e. the first shadow specified is on top of the others, so they may + * overlap other boxes or other shadows. + * + * + * * Border images * - * Images can be used in 'slices' for the purpose of creating scalable - * borders. + * Images and gradients can also be used in slices for the purpose of creating + * scalable borders. + * For more information, see the CSS3 documentation for the border-image property, + * which can be found here. * * * - * The syntax for specifying border images of this kind is: - * url(@path) @top @right @bottom @left [repeat|stretch]? [repeat|stretch]? - * The sizes of the 'cut off' portions are specified - * with the @top, @right, @bottom and @left parameters. - * The 'middle' sections can be repeated or stretched to create - * the desired effect, by adding the 'repeat' or 'stretch' options after - * the dimensions. If two options are specified, the first one affects + * The parameters of the slicing process are controlled by + * four separate properties. Note that you can use the + * border-image shorthand property + * to set values for the three properties at the same time. + * + * + * border-image-source: url(@path) + * (or border-image-source: -gtk-gradient(...)): + * Specifies the source of the border image, and it can either + * be an URL or a gradient (see above). + * + * + * border-image-slice: @top @right @bottom @left + * The sizes specified by the @top, @right, @bottom and @left parameters + * are the offsets, in pixels, from the relevant edge where the image + * should be "cut off" to build the slices used for the rendering + * of the border. + * + * + * border-image-width: @top @right @bottom @left + * The sizes specified by the @top, @right, @bottom and @left parameters + * are inward distances from the border box edge, used to specify the + * rendered size of each slice determined by border-image-slice. + * If this property is not specified, the values of border-width will + * be used as a fallback. + * + * + * border-image-repeat: [stretch|repeat|round|space] ? + * [stretch|repeat|round|space] + * Specifies how the image slices should be rendered in the area + * outlined by border-width. + * The default (stretch) is to resize the slice to fill in the whole + * allocated area. + * If the value of this property is 'repeat', the image slice + * will be tiled to fill the area. + * If the value of this property is 'round', the image slice will + * be tiled to fill the area, and scaled to fit it exactly + * a whole number of times. + * If the value of this property is 'space', the image slice will + * be tiled to fill the area, and if it doesn't fit it exactly a whole + * number of times, the extra space is distributed as padding around + * the slices. + * If two options are specified, the first one affects * the horizontal behaviour and the second one the vertical behaviour. * If only one option is specified, it affects both. * @@ -603,13 +680,6 @@ * map intuitively in a widget based environment). * * - * There is also a difference in shorthand properties, for - * example in common CSS it is fine to define a font through - * the different @font-family, @font-style, @font-size - * properties, meanwhile in GTK+'s CSS only the canonical - * @font property is supported. - * - * * The currently supported properties are: * * @@ -633,8 +703,8 @@ * * background-color * color (see above) - * #GdkRGBA - * background-color: #fff; + * #GdkRGBA + * background-color: #fff; * color: &color1; * background-color: shade (&color1, 0.5); * color: mix (&color1, #f0f, 0.8); @@ -644,7 +714,51 @@ * color * * + * border-top-color + * transparent|color (see above) + * + * + * border-right-color + * + * + * border-bottom-color + * + * + * border-left-color + * + * * border-color + * [transparent|color]{1,4} + * + * + * font-family + * @family [, @family]* + * #gchararray + * font-family: Sans, Arial; + * + * + * font-style + * [normal|oblique|italic] + * #PANGO_TYPE_STYLE + * font-style: italic; + * + * + * font-variant + * [normal|small-caps] + * #PANGO_TYPE_VARIANT + * font-variant: normal; + * + * + * font-weight + * [normal|bold|bolder|lighter|100|200|300|400|500|600|700|800|900] + * #PANGO_TYPE_WEIGHT + * font-weight: bold; + * + * + * font-size + * Font size in point + * #gint + * font-size: 13; * * * font @@ -653,6 +767,30 @@ * font: Sans 15; * * + * margin-top + * integer + * #gint + * margin-top: 0; + * + * + * margin-left + * integer + * #gint + * margin-left: 1; + * + * + * margin-bottom + * integer + * #gint + * margin-bottom: 2; + * + * + * margin-right + * integer + * #gint + * margin-right: 4; + * + * * margin * @width * @vertical_width @horizontal_width @@ -667,6 +805,30 @@ * * * + * padding-top + * integer + * #gint + * padding-top: 5; + * + * + * padding-left + * integer + * #gint + * padding-left: 5; + * + * + * padding-bottom + * integer + * #gint + * padding-bottom: 5; + * + * + * padding-right + * integer + * #gint + * padding-right: 5; + * + * * padding * * @@ -690,10 +852,47 @@ * * * - * border-width + * background-repeat + * [repeat|no-repeat] + * internal + * background-repeat: no-repeat; + * If not specified, the style doesn't respect the CSS3 + * specification, since the background will be + * stretched to fill the area. + * + * + * + * border-top-width + * integer + * #gint + * border-top-width: 5; + * + * + * border-left-width * integer * #gint - * border-width: 5; + * border-left-width: 5; + * + * + * border-bottom-width + * integer + * #gint + * border-bottom-width: 5; + * + * + * border-right-width + * integer + * #gint + * border-right-width: 5; + * + * + * border-width + * #GtkBorder + * border-width: 1; + * border-width: 1 2; + * border-width: 1 2 3; + * border-width: 1 2 3 5; + * * * * border-radius @@ -718,7 +917,7 @@ * * text-shadow * shadow list (see above) - * #GtkTextShadow + * internal use only * text-shadow: 1 1 0 blue, -4 -4 red; * * @@ -765,28 +964,44 @@ 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; - - guint has_inherit :1; + 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 { GtkCssProvider *provider; GtkCssParser *parser; + GtkCssSection *section; GtkCssScanner *parent; - GFile *file; - GFile *base; GSList *state; - GSList *cur_selectors; - GHashTable *cur_properties; }; struct _GtkCssProviderPrivate @@ -794,34 +1009,11 @@ struct _GtkCssProviderPrivate GScanner *scanner; GHashTable *symbolic_colors; + GHashTable *keyframes; GArray *rulesets; -}; - -enum ParserScope { - SCOPE_SELECTOR, - SCOPE_PSEUDO_CLASS, - SCOPE_NTH_CHILD, - SCOPE_DECLARATION, - SCOPE_VALUE, - SCOPE_BINDING_SET -}; - -/* Extend GtkStateType, since these - * values are also used as symbols - */ -enum ParserSymbol { - /* Scope: pseudo-class */ - SYMBOL_NTH_CHILD = GTK_STATE_FOCUSED + 1, - SYMBOL_FIRST_CHILD, - SYMBOL_LAST_CHILD, - SYMBOL_SORTED_CHILD, - - /* Scope: nth-child */ - SYMBOL_NTH_CHILD_EVEN, - SYMBOL_NTH_CHILD_ODD, - SYMBOL_NTH_CHILD_FIRST, - SYMBOL_NTH_CHILD_LAST + GtkCssSelectorTree *tree; + GResource *resource; }; enum { @@ -829,17 +1021,20 @@ 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, GtkCssScanner *scanner, GFile *file, const char *data, - gsize length, GError **error); GQuark @@ -850,14 +1045,14 @@ gtk_css_provider_error_quark (void) G_DEFINE_TYPE_EXTENDED (GtkCssProvider, gtk_css_provider, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE (GTK_TYPE_STYLE_PROVIDER, - gtk_css_style_provider_iface_init)); + gtk_css_style_provider_iface_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_STYLE_PROVIDER_PRIVATE, + gtk_css_style_provider_private_iface_init)); static void gtk_css_provider_parsing_error (GtkCssProvider *provider, - const gchar *path, - guint line, - guint position, - const GError * error) + GtkCssSection *section, + const GError *error) { /* Only emit a warning when we have no error handlers. This is our * default handlers. And in this case erroneous CSS files are a bug @@ -870,7 +1065,13 @@ gtk_css_provider_parsing_error (GtkCssProvider *provider, 0, TRUE)) { - g_warning ("Theme parsing error: %s:%u:%u: %s", path ? path : "", line, position, error->message); + char *s = _gtk_css_section_to_string (section); + + g_warning ("Theme parsing error: %s: %s", + s, + error->message); + + g_free (s); } } @@ -879,14 +1080,13 @@ 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 - * @path: path to the parsed file or %NULL if the file cannot be - * identified or the data was not loaded from a file - * @line: line in the file or data or 0 if unknown - * @position: offset into the current line or 0 if unknown or the - * whole line is affected + * @section: section the error happened in * @error: The parsing error * * Signals that a parsing error occured. the @path, @line and @position @@ -907,9 +1107,8 @@ gtk_css_provider_class_init (GtkCssProviderClass *klass) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GtkCssProviderClass, parsing_error), NULL, NULL, - _gtk_marshal_VOID__STRING_UINT_UINT_BOXED, - G_TYPE_NONE, 4, - G_TYPE_STRING, G_TYPE_UINT, G_TYPE_UINT, G_TYPE_ERROR); + _gtk_marshal_VOID__BOXED_BOXED, + G_TYPE_NONE, 2, GTK_TYPE_CSS_SECTION, G_TYPE_ERROR); object_class->finalize = gtk_css_provider_finalize; @@ -918,133 +1117,182 @@ gtk_css_provider_class_init (GtkCssProviderClass *klass) g_type_class_add_private (object_class, sizeof (GtkCssProviderPrivate)); } -static void -gtk_css_provider_take_error_full (GtkCssProvider *provider, - GFile *file, - guint line, - guint position, - GError *error) -{ - char *filename; - - if (file) - filename = g_file_get_path (file); - else - filename = NULL; - - g_signal_emit (provider, css_provider_signals[PARSING_ERROR], 0, - filename, line, position, error); - - g_free (filename); - g_error_free (error); -} - 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); } static void gtk_css_ruleset_clear (GtkCssRuleset *ruleset) { - if (ruleset->style) - g_hash_table_unref (ruleset->style); - if (ruleset->widget_style) - g_hash_table_unref (ruleset->widget_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->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)); } -static void -property_value_free (GValue *value) +static WidgetPropertyValue * +widget_property_value_new (char *name, GtkCssSection *section) { - if (G_IS_VALUE (value)) - g_value_unset (value); + WidgetPropertyValue *value; + + value = g_slice_new0 (WidgetPropertyValue); - g_slice_free (GValue, value); + value->name = name; + if (gtk_keep_css_sections) + value->section = gtk_css_section_ref (section); + + return value; } static void -gtk_css_ruleset_add_style (GtkCssRuleset *ruleset, - char *name, - GValue *value) +widget_property_value_free (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_free (value->value); + g_free (value->name); + if (value->section) + gtk_css_section_unref (value->section); - g_hash_table_insert (ruleset->widget_style, name, value); + g_slice_free (WidgetPropertyValue, value); } static void -gtk_css_ruleset_add (GtkCssRuleset *ruleset, - const GtkStyleProperty *prop, - GValue *value) +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 (ruleset->style == NULL) - ruleset->style = g_hash_table_new_full (g_direct_hash, - g_direct_equal, - NULL, - (GDestroyNotify) property_value_free); + 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; + } + + last = &l->next; + } - ruleset->has_inherit |= gtk_style_param_get_inherit (prop->pspec); - g_hash_table_insert (ruleset->style, (gpointer) prop, value); + return head; } -static gboolean -gtk_css_ruleset_matches (GtkCssRuleset *ruleset, - GtkWidgetPath *path, - guint length) +static void +gtk_css_ruleset_add_style (GtkCssRuleset *ruleset, + char *name, + WidgetPropertyValue *value) { - return _gtk_css_selector_matches (ruleset->selector, path, length); + 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_scanner_reset (GtkCssScanner *scanner) +gtk_css_ruleset_add (GtkCssRuleset *ruleset, + GtkCssStyleProperty *property, + GtkCssValue *value, + GtkCssSection *section) { - g_slist_free (scanner->state); - scanner->state = NULL; + guint i; + + g_return_if_fail (ruleset->owns_styles || ruleset->n_styles == 0); - g_slist_free_full (scanner->cur_selectors, (GDestroyNotify) _gtk_css_selector_free); - scanner->cur_selectors = NULL; + if (ruleset->set_styles == NULL) + ruleset->set_styles = _gtk_bitmask_new (); - if (scanner->cur_properties) - g_hash_table_unref (scanner->cur_properties); + ruleset->set_styles = _gtk_bitmask_set (ruleset->set_styles, + _gtk_css_style_property_get_id (property), + TRUE); + + ruleset->owns_styles = TRUE; + + for (i = 0; i < ruleset->n_styles; i++) + { + 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; + } + } + if (i == ruleset->n_styles) + { + ruleset->n_styles++; + ruleset->styles = g_realloc (ruleset->styles, ruleset->n_styles * sizeof (PropertyValue)); + ruleset->styles[i].value = NULL; + ruleset->styles[i].property = property; + } - scanner ->cur_properties = g_hash_table_new_full (g_str_hash, - g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) property_value_free); + 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 gtk_css_scanner_destroy (GtkCssScanner *scanner) { - gtk_css_scanner_reset (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); - g_hash_table_destroy (scanner->cur_properties); _gtk_css_parser_free (scanner->parser); g_slice_free (GtkCssScanner, scanner); } +static void +gtk_css_provider_emit_error (GtkCssProvider *provider, + GtkCssScanner *scanner, + const GError *error) +{ + g_signal_emit (provider, css_provider_signals[PARSING_ERROR], 0, + scanner != NULL ? scanner->section : NULL, error); +} + static void gtk_css_scanner_parser_error (GtkCssParser *parser, const GError *error, @@ -1052,67 +1300,44 @@ gtk_css_scanner_parser_error (GtkCssParser *parser, { GtkCssScanner *scanner = user_data; - gtk_css_provider_take_error_full (scanner->provider, - scanner->file, - _gtk_css_parser_get_line (scanner->parser), - _gtk_css_parser_get_position (scanner->parser), - g_error_copy (error)); + gtk_css_provider_emit_error (scanner->provider, + scanner, + error); } static GtkCssScanner * gtk_css_scanner_new (GtkCssProvider *provider, GtkCssScanner *parent, + GtkCssSection *section, GFile *file, - const gchar *data, - gsize length) + const gchar *text) { GtkCssScanner *scanner; - g_assert (data[length] == 0); - scanner = g_slice_new0 (GtkCssScanner); g_object_ref (provider); scanner->provider = provider; scanner->parent = parent; + 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->cur_properties = g_hash_table_new_full (g_str_hash, - g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) property_value_free); - - scanner->parser = _gtk_css_parser_new (data, + 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; @@ -1122,113 +1347,137 @@ gtk_css_scanner_would_recurse (GtkCssScanner *scanner, } static void -gtk_css_provider_init (GtkCssProvider *css_provider) +gtk_css_scanner_push_section (GtkCssScanner *scanner, + GtkCssSectionType section_type) { - GtkCssProviderPrivate *priv; - - priv = css_provider->priv = G_TYPE_INSTANCE_GET_PRIVATE (css_provider, - GTK_TYPE_CSS_PROVIDER, - GtkCssProviderPrivate); + GtkCssSection *section; - priv->rulesets = g_array_new (FALSE, FALSE, sizeof (GtkCssRuleset)); + section = _gtk_css_section_new (scanner->section, + section_type, + scanner->parser); - priv->symbolic_colors = g_hash_table_new_full (g_str_hash, g_str_equal, - (GDestroyNotify) g_free, - (GDestroyNotify) gtk_symbolic_color_unref); + if (scanner->section) + gtk_css_section_unref (scanner->section); + scanner->section = section; } static void -css_provider_dump_symbolic_colors (GtkCssProvider *css_provider, - GtkStyleProperties *props) +gtk_css_scanner_pop_section (GtkCssScanner *scanner, + GtkCssSectionType check_type) { - GtkCssProviderPrivate *priv; - GHashTableIter iter; - gpointer key, value; - - priv = css_provider->priv; - g_hash_table_iter_init (&iter, priv->symbolic_colors); + GtkCssSection *parent; + + g_assert (gtk_css_section_get_section_type (scanner->section) == check_type); - while (g_hash_table_iter_next (&iter, &key, &value)) - { - const gchar *name; - GtkSymbolicColor *color; + parent = gtk_css_section_get_parent (scanner->section); + if (parent) + gtk_css_section_ref (parent); - name = key; - color = value; + _gtk_css_section_end (scanner->section); + gtk_css_section_unref (scanner->section); - gtk_style_properties_map_color (props, name, color); - } + scanner->section = parent; } -static GtkStyleProperties * -gtk_css_provider_get_style (GtkStyleProvider *provider, - GtkWidgetPath *path) +static void +gtk_css_provider_init (GtkCssProvider *css_provider) { - GtkCssProvider *css_provider; GtkCssProviderPrivate *priv; - GtkStyleProperties *props; - guint i, l, length; - - css_provider = GTK_CSS_PROVIDER (provider); - priv = css_provider->priv; - length = gtk_widget_path_length (path); - props = gtk_style_properties_new (); - - css_provider_dump_symbolic_colors (css_provider, props); - - for (l = 1; l <= length; l++) - { - for (i = 0; i < priv->rulesets->len; i++) - { - GtkCssRuleset *ruleset; - GHashTableIter iter; - gpointer key, value; - ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i); - - if (ruleset->style == NULL) - continue; + priv = css_provider->priv = G_TYPE_INSTANCE_GET_PRIVATE (css_provider, + GTK_TYPE_CSS_PROVIDER, + GtkCssProviderPrivate); - if (l < length && (!ruleset->has_inherit || _gtk_css_selector_get_state_flags (ruleset->selector))) - continue; + priv->rulesets = g_array_new (FALSE, FALSE, sizeof (GtkCssRuleset)); - if (!gtk_css_ruleset_matches (ruleset, path, l)) - continue; + priv->symbolic_colors = g_hash_table_new_full (g_str_hash, g_str_equal, + (GDestroyNotify) g_free, + (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); +} - g_hash_table_iter_init (&iter, ruleset->style); +static void +verify_tree_match_results (GtkCssProvider *provider, + const GtkCssMatcher *matcher, + GPtrArray *tree_rules) +{ +#ifdef VERIFY_TREE + GtkCssProviderPrivate *priv = provider->priv; + GtkCssRuleset *ruleset; + gboolean should_match; + int i, j; - while (g_hash_table_iter_next (&iter, &key, &value)) - { - GtkStyleProperty *prop = key; + for (i = 0; i < priv->rulesets->len; i++) + { + gboolean found = FALSE; - if (l != length && !gtk_style_param_get_inherit (prop->pspec)) - continue; + ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i); - _gtk_style_properties_set_property_by_property (props, - prop, - _gtk_css_selector_get_state_flags (ruleset->selector), - 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"); + } } - - return props; +#endif } static void -gtk_css_provider_parser_error (GtkCssParser *parser, - const GError *error, - gpointer user_data) +verify_tree_get_change_results (GtkCssProvider *provider, + const GtkCssMatcher *matcher, + GtkCssChange change) { - GtkCssProvider *provider = user_data; - - gtk_css_provider_take_error_full (provider, - NULL, - _gtk_css_parser_get_line (parser), - _gtk_css_parser_get_position (parser), - g_error_copy (error)); +#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, @@ -1238,55 +1487,57 @@ gtk_css_provider_get_style_property (GtkStyleProvider *provider, { GtkCssProvider *css_provider = GTK_CSS_PROVIDER (provider); GtkCssProviderPrivate *priv = css_provider->priv; - const GValue *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; - GtkStateFlags selector_state; - - 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, gtk_widget_path_length (path))) - continue; - - selector_state = _gtk_css_selector_get_state_flags (ruleset->selector); - 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 && - (selector_state == 0 || - selector_state == state || - ((selector_state & state) != 0 && - (selector_state & ~(state)) == 0))) - { - GtkCssParser *parser; + scanner = gtk_css_scanner_new (css_provider, + NULL, + val->section, + val->section != NULL ? gtk_css_section_get_file (val->section) : NULL, + val->value); - parser = _gtk_css_parser_new (g_value_get_string (val), - gtk_css_provider_parser_error, - provider); + found = _gtk_css_style_parse_value (value, + scanner->parser); - found = _gtk_css_value_parse (value, - parser, - NULL); + gtk_css_scanner_destroy (scanner); - _gtk_css_parser_free (parser); + break; + } + } - if (found) - break; - } + if (found) + break; } g_free (prop_name); + g_ptr_array_free (tree_rules, TRUE); return found; } @@ -1294,32 +1545,138 @@ 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 GtkCssValue * +gtk_css_style_provider_get_color (GtkStyleProviderPrivate *provider, + const char *name) +{ + GtkCssProvider *css_provider = GTK_CSS_PROVIDER (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_provider_finalize (GObject *object) +gtk_css_style_provider_lookup (GtkStyleProviderPrivate *provider, + const GtkCssMatcher *matcher, + GtkCssLookup *lookup) { GtkCssProvider *css_provider; GtkCssProviderPrivate *priv; + GtkCssRuleset *ruleset; + guint j; + int i; + GPtrArray *tree_rules; - css_provider = GTK_CSS_PROVIDER (object); + css_provider = GTK_CSS_PROVIDER (provider); priv = css_provider->priv; - g_array_free (priv->rulesets, TRUE); + tree_rules = _gtk_css_selector_tree_match_all (priv->tree, matcher); + verify_tree_match_results (css_provider, matcher, tree_rules); - if (priv->symbolic_colors) - g_hash_table_destroy (priv->symbolic_colors); + for (i = tree_rules->len - 1; i >= 0; i--) + { + ruleset = tree_rules->pdata[i]; - G_OBJECT_CLASS (gtk_css_provider_parent_class)->finalize (object); -} + if (ruleset->styles == NULL) + continue; -/** - * gtk_css_provider_new: - * - * Returns a newly created #GtkCssProvider. - * + if (!_gtk_bitmask_intersects (_gtk_css_lookup_get_missing (lookup), + ruleset->set_styles)) + continue; + + for (j = 0; j < ruleset->n_styles; j++) + { + 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, + 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 +gtk_css_provider_finalize (GObject *object) +{ + GtkCssProvider *css_provider; + GtkCssProviderPrivate *priv; + guint i; + + css_provider = GTK_CSS_PROVIDER (object); + priv = css_provider->priv; + + for (i = 0; i < priv->rulesets->len; i++) + 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->resource) + { + g_resources_unregister (priv->resource); + g_resource_unref (priv->resource); + priv->resource = NULL; + } + + G_OBJECT_CLASS (gtk_css_provider_parent_class)->finalize (object); +} + +/** + * gtk_css_provider_new: + * + * Returns a newly created #GtkCssProvider. + * * Returns: A new #GtkCssProvider **/ GtkCssProvider * @@ -1333,11 +1690,11 @@ gtk_css_provider_take_error (GtkCssProvider *provider, GtkCssScanner *scanner, GError *error) { - gtk_css_provider_take_error_full (provider, - scanner->file, - _gtk_css_parser_get_line (scanner->parser), - _gtk_css_parser_get_position (scanner->parser), - error); + gtk_css_provider_emit_error (provider, + scanner, + error); + + g_error_free (error); } static void @@ -1399,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; @@ -1425,23 +1782,39 @@ 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 gtk_css_provider_propagate_error (GtkCssProvider *provider, - const gchar *path, - guint line, - guint position, + GtkCssSection *section, const GError *error, GError **propagate_to) { + + 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 ? path : "", line, position, error->message); + s = _gtk_css_section_to_string (section); + g_warning ("Theme parsing error: %s: %s", s, error->message); + g_free (s); return; } @@ -1450,26 +1823,53 @@ gtk_css_provider_propagate_error (GtkCssProvider *provider, return; *propagate_to = g_error_copy (error); - g_prefix_error (propagate_to, "%s:%u:%u: ", path ? path : "", line, position); + if (section) + { + s = _gtk_css_section_to_string (section); + g_prefix_error (propagate_to, "%s", s); + g_free (s); + } } -static void +static gboolean parse_import (GtkCssScanner *scanner) { GFile *file; - char *uri; - uri = _gtk_css_parser_read_uri (scanner->parser); - if (uri == NULL) + gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_IMPORT); + + if (!_gtk_css_parser_try (scanner->parser, "@import", TRUE)) { - _gtk_css_parser_resync (scanner->parser, TRUE, 0); - return; + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_IMPORT); + return FALSE; } - file = g_file_resolve_relative_path (gtk_css_scanner_get_base_url (scanner), uri); - g_free (uri); + if (_gtk_css_parser_is_string (scanner->parser)) + { + char *uri; - if (gtk_css_scanner_would_recurse (scanner, file)) + uri = _gtk_css_parser_read_string (scanner->parser); + file = _gtk_css_parser_get_file_for_path (scanner->parser, uri); + g_free (uri); + } + else + { + file = _gtk_css_parser_read_url (scanner->parser); + } + + if (file == NULL) + { + _gtk_css_parser_resync (scanner->parser, TRUE, 0); + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_IMPORT); + return TRUE; + } + + 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, @@ -1485,27 +1885,32 @@ parse_import (GtkCssScanner *scanner) gtk_css_provider_load_internal (scanner->provider, scanner, file, - NULL, 0, + NULL, NULL); } - if (!_gtk_css_parser_try (scanner->parser, ";", TRUE)) - { - g_object_unref (file); - gtk_css_provider_invalid_token (scanner->provider, scanner, "semicolon"); - _gtk_css_parser_resync (scanner->parser, TRUE, 0); - return; - } - g_object_unref (file); + + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_IMPORT); + _gtk_css_parser_skip_whitespace (scanner->parser); + + return TRUE; } -static void +static gboolean parse_color_definition (GtkCssScanner *scanner) { - GtkSymbolicColor *symbolic; + GtkCssValue *color; char *name; + gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION); + + if (!_gtk_css_parser_try (scanner->parser, "@define-color", TRUE)) + { + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION); + return FALSE; + } + name = _gtk_css_parser_try_name (scanner->parser, TRUE); if (name == NULL) { @@ -1515,39 +1920,54 @@ parse_color_definition (GtkCssScanner *scanner) GTK_CSS_PROVIDER_ERROR_SYNTAX, "Not a valid color name"); _gtk_css_parser_resync (scanner->parser, TRUE, 0); - return; + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION); + 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); - return; + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION); + return TRUE; } 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, GTK_CSS_PROVIDER_ERROR_SYNTAX, "Missing semicolon at end of color definition"); _gtk_css_parser_resync (scanner->parser, TRUE, 0); - return; + + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION); + 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; } -static void +static gboolean parse_binding_set (GtkCssScanner *scanner) { GtkBindingSet *binding_set; char *name; + gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_BINDING_SET); + + if (!_gtk_css_parser_try (scanner->parser, "@binding-set", TRUE)) + { + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_BINDING_SET); + return FALSE; + } + name = _gtk_css_parser_try_ident (scanner->parser, TRUE); if (name == NULL) { @@ -1589,7 +2009,15 @@ parse_binding_set (GtkCssScanner *scanner) continue; } - gtk_binding_entry_add_signal_from_string (binding_set, name); + if (gtk_binding_entry_add_signal_from_string (binding_set, name) != G_TOKEN_NONE) + { + gtk_css_provider_error_literal (scanner->provider, + scanner, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_SYNTAX, + "Failed to parse binding set."); + } + g_free (name); if (!_gtk_css_parser_try (scanner->parser, ";", TRUE)) @@ -1619,307 +2047,107 @@ parse_binding_set (GtkCssScanner *scanner) } skip_semicolon: - if (_gtk_css_parser_try (scanner->parser, ";", TRUE)) - gtk_css_provider_error_literal (scanner->provider, - scanner, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_DEPRECATED, - "Nonstandard semicolon at end of binding set"); -} - -static void -parse_at_keyword (GtkCssScanner *scanner) -{ - if (_gtk_css_parser_try (scanner->parser, "@import", TRUE)) - parse_import (scanner); - else if (_gtk_css_parser_try (scanner->parser, "@define-color", TRUE)) - parse_color_definition (scanner); - else if (_gtk_css_parser_try (scanner->parser, "@binding-set", TRUE)) - parse_binding_set (scanner); - else + if (_gtk_css_parser_begins_with (scanner->parser, ';')) { gtk_css_provider_error_literal (scanner->provider, scanner, GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_SYNTAX, - "unknown @ rule"); - _gtk_css_parser_resync (scanner->parser, TRUE, 0); + GTK_CSS_PROVIDER_ERROR_DEPRECATED, + "Nonstandard semicolon at end of binding set"); + _gtk_css_parser_try (scanner->parser, ";", TRUE); } + + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_BINDING_SET); + + return TRUE; } static gboolean -parse_selector_class (GtkCssScanner *scanner, GArray *classes) +parse_keyframes (GtkCssScanner *scanner) { - GQuark qname; + GtkCssKeyframes *keyframes; char *name; - - name = _gtk_css_parser_try_name (scanner->parser, FALSE); - if (name == NULL) + gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_KEYFRAMES); + + if (!_gtk_css_parser_try (scanner->parser, "@keyframes", TRUE)) { - gtk_css_provider_error_literal (scanner->provider, - scanner, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_SYNTAX, - "Expected a valid name for class"); + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_KEYFRAMES); return FALSE; } - qname = g_quark_from_string (name); - g_array_append_val (classes, qname); - g_free (name); - return TRUE; -} - -static gboolean -parse_selector_name (GtkCssScanner *scanner, GArray *names) -{ - GQuark qname; - char *name; - - name = _gtk_css_parser_try_name (scanner->parser, 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, - "Expected a valid name for id"); - return FALSE; + "Expected name for keyframes"); + _gtk_css_parser_resync (scanner->parser, TRUE, 0); + goto exit; } - qname = g_quark_from_string (name); - g_array_append_val (names, qname); - g_free (name); - 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", GTK_REGION_FIRST, 0 }, - { "last", GTK_REGION_LAST, 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 }, - { 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; - - name = _gtk_css_parser_try_ident (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, - "Missing name of pseudo-class"); - return FALSE; + "Expected '{' for keyframes"); + _gtk_css_parser_resync (scanner->parser, TRUE, 0); + g_free (name); + goto exit; } - if (_gtk_css_parser_try (scanner->parser, "(", TRUE)) + keyframes = _gtk_css_keyframes_parse (scanner->parser); + if (keyframes == NULL) { - 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; + _gtk_css_parser_resync (scanner->parser, TRUE, '}'); + g_free (name); + goto exit; } - 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_hash_table_insert (scanner->provider->priv->keyframes, name, keyframes); - g_free (name); - return TRUE; - } + 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 '}' after declarations"); + if (!_gtk_css_parser_is_eof (scanner->parser)) + _gtk_css_parser_resync (scanner->parser, FALSE, 0); } - 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; -} +exit: + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_KEYFRAMES); -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) +static void +parse_at_keyword (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; - } + if (parse_import (scanner)) + return; + if (parse_color_definition (scanner)) + return; + if (parse_binding_set (scanner)) + return; + if (parse_keyframes (scanner)) + return; - 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); + else + { + gtk_css_provider_error_literal (scanner->provider, + scanner, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_SYNTAX, + "unknown @ rule"); + _gtk_css_parser_resync (scanner->parser, TRUE, 0); } - 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 * @@ -1927,13 +2155,16 @@ 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) { g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free); _gtk_css_parser_resync (scanner->parser, FALSE, 0); + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_SELECTOR); return NULL; } @@ -1941,6 +2172,8 @@ parse_selector_list (GtkCssScanner *scanner) } while (_gtk_css_parser_try (scanner->parser, ",", TRUE)); + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_SELECTOR); + return selectors; } @@ -1948,9 +2181,11 @@ static void parse_declaration (GtkCssScanner *scanner, GtkCssRuleset *ruleset) { - const GtkStyleProperty *property; + GtkStyleProperty *property; char *name; + gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_DECLARATION); + name = _gtk_css_parser_try_ident (scanner->parser, TRUE); if (name == NULL) goto check_for_semicolon; @@ -1966,6 +2201,7 @@ parse_declaration (GtkCssScanner *scanner, name); _gtk_css_parser_resync (scanner->parser, TRUE, '}'); g_free (name); + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION); return; } @@ -1974,104 +2210,104 @@ parse_declaration (GtkCssScanner *scanner, gtk_css_provider_invalid_token (scanner->provider, scanner, "':'"); _gtk_css_parser_resync (scanner->parser, TRUE, '}'); g_free (name); + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION); return; } if (property) { - GValue *val; + GtkCssValue *value; g_free (name); - val = g_slice_new0 (GValue); - g_value_init (val, property->pspec->value_type); + gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_VALUE); - if (_gtk_css_parser_try (scanner->parser, "none", TRUE)) + value = _gtk_style_property_parse_value (property, + scanner->parser); + + if (value == NULL) { - /* Insert the default value, so it has an opportunity - * to override other style providers when merged - */ - g_param_value_set_default (property->pspec, val); - gtk_css_ruleset_add (ruleset, property, val); + _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 (property->parse_func) + + if (!_gtk_css_parser_begins_with (scanner->parser, ';') && + !_gtk_css_parser_begins_with (scanner->parser, '}') && + !_gtk_css_parser_is_eof (scanner->parser)) { - GError *error = NULL; - char *value_str; - - value_str = _gtk_css_parser_read_value (scanner->parser); - if (value_str == NULL) + 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++) { - _gtk_css_parser_resync (scanner->parser, TRUE, '}'); - return; + 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); } - if ((*property->parse_func) (value_str, val, &error)) - gtk_css_ruleset_add (ruleset, property, val); - else - gtk_css_provider_take_error (scanner->provider, scanner, error); - - g_free (value_str); + _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 { - if (_gtk_css_value_parse (val, - scanner->parser, - gtk_css_scanner_get_base_url (scanner))) - { - 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, '}'); - g_value_unset (val); - g_slice_free (GValue, val); - return; - } - } - else - { - g_value_unset (val); - g_slice_free (GValue, val); - _gtk_css_parser_resync (scanner->parser, TRUE, '}'); - return; - } + g_assert_not_reached (); + _gtk_css_value_unref (value); } + + + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE); } else if (name[0] == '-') { char *value_str; + gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_VALUE); + value_str = _gtk_css_parser_read_value (scanner->parser); if (value_str) { - GValue *val; + WidgetPropertyValue *val; - val = g_slice_new0 (GValue); - g_value_init (val, G_TYPE_STRING); - g_value_take_string (val, value_str); + val = widget_property_value_new (name, scanner->section); + val->value = value_str; gtk_css_ruleset_add_style (ruleset, name, val); } else { _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; } + + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE); } else g_free (name); check_for_semicolon: + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION); + if (!_gtk_css_parser_try (scanner->parser, ";", TRUE)) { if (!_gtk_css_parser_begins_with (scanner->parser, '}') && @@ -2104,9 +2340,14 @@ parse_ruleset (GtkCssScanner *scanner) GSList *selectors; GtkCssRuleset ruleset = { 0, }; + gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_RULESET); + selectors = parse_selector_list (scanner); if (selectors == NULL) - return; + { + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_RULESET); + return; + } if (!_gtk_css_parser_try (scanner->parser, "{", TRUE)) { @@ -2117,6 +2358,7 @@ parse_ruleset (GtkCssScanner *scanner) "expected '{' after selectors"); _gtk_css_parser_resync (scanner->parser, FALSE, 0); g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free); + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_RULESET); return; } @@ -2134,12 +2376,13 @@ parse_ruleset (GtkCssScanner *scanner) _gtk_css_parser_resync (scanner->parser, FALSE, 0); g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free); gtk_css_ruleset_clear (&ruleset); - return; + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_RULESET); } } css_provider_commit (scanner->provider, selectors, &ruleset); gtk_css_ruleset_clear (&ruleset); + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_RULESET); } static void @@ -2154,6 +2397,8 @@ parse_statement (GtkCssScanner *scanner) static void parse_stylesheet (GtkCssScanner *scanner) { + gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_DOCUMENT); + _gtk_css_parser_skip_whitespace (scanner->parser); while (!_gtk_css_parser_is_eof (scanner->parser)) @@ -2164,6 +2409,8 @@ parse_stylesheet (GtkCssScanner *scanner) parse_statement (scanner); } + + gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DOCUMENT); } static int @@ -2178,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; } @@ -2192,21 +2432,50 @@ 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 gtk_css_provider_load_internal (GtkCssProvider *css_provider, GtkCssScanner *parent, GFile *file, - const char *data, - gsize length, + const char *text, GError **error) { GtkCssScanner *scanner; gulong error_handler; - char *free_data; + char *free_data = NULL; if (error) error_handler = g_signal_connect (css_provider, @@ -2216,43 +2485,43 @@ gtk_css_provider_load_internal (GtkCssProvider *css_provider, else error_handler = 0; /* silence gcc */ - if (data == NULL) + if (text == NULL) { GError *load_error = NULL; if (g_file_load_contents (file, NULL, - &free_data, &length, + &free_data, NULL, NULL, &load_error)) { - data = free_data; + text = free_data; } else { + GtkCssSection *section; + if (parent) - { - gtk_css_provider_error (css_provider, - parent, - GTK_CSS_PROVIDER_ERROR, - GTK_CSS_PROVIDER_ERROR_IMPORT, - "Failed to import: %s", - load_error->message); - g_error_free (load_error); - } + section = gtk_css_section_ref (parent->section); else - { - gtk_css_provider_take_error_full (css_provider, - file, - 0, 0, - load_error); - } + section = _gtk_css_section_new_for_file (GTK_CSS_SECTION_DOCUMENT, file); + + gtk_css_provider_error (css_provider, + parent, + GTK_CSS_PROVIDER_ERROR, + GTK_CSS_PROVIDER_ERROR_IMPORT, + "Failed to import: %s", + load_error->message); + + gtk_css_section_unref (section); } } - else - free_data = NULL; - if (data) + if (text) { - scanner = gtk_css_scanner_new (css_provider, parent, file, data, length); + scanner = gtk_css_scanner_new (css_provider, + parent, + parent ? parent->section : NULL, + file, + text); parse_stylesheet (scanner); @@ -2262,10 +2531,12 @@ gtk_css_provider_load_internal (GtkCssProvider *css_provider, gtk_css_provider_postprocess (css_provider); } + g_free (free_data); + if (error) { g_signal_handler_disconnect (css_provider, error_handler); - + if (*error) { /* We clear all contents from the provider for backwards compat reasons */ @@ -2280,14 +2551,19 @@ gtk_css_provider_load_internal (GtkCssProvider *css_provider, /** * gtk_css_provider_load_from_data: * @css_provider: a #GtkCssProvider - * @data: CSS data loaded in memory - * @length: the length of @data in bytes, or -1 for NUL terminated strings + * @data: (array length=length) (element-type guint8): CSS data loaded in memory + * @length: the length of @data in bytes, or -1 for NUL terminated strings. If + * @length is not -1, the code will assume it is not NUL terminated and will + * potentially do a copy. * @error: (out) (allow-none): return location for a #GError, or %NULL * * 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, @@ -2295,15 +2571,32 @@ gtk_css_provider_load_from_data (GtkCssProvider *css_provider, gssize length, GError **error) { + char *free_data; + gboolean ret; + g_return_val_if_fail (GTK_IS_CSS_PROVIDER (css_provider), FALSE); g_return_val_if_fail (data != NULL, FALSE); if (length < 0) - length = strlen (data); + { + length = strlen (data); + free_data = NULL; + } + else + { + free_data = g_strndup (data, length); + data = free_data; + } gtk_css_provider_reset (css_provider); - return gtk_css_provider_load_internal (css_provider, NULL, NULL, data, length, error); + ret = gtk_css_provider_load_internal (css_provider, NULL, NULL, data, error); + + g_free (free_data); + + _gtk_style_provider_private_changed (GTK_STYLE_PROVIDER_PRIVATE (css_provider)); + + return ret; } /** @@ -2315,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, 0, 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; } /** @@ -2339,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, @@ -2361,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: * @@ -2377,375 +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" - " 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" - ".menu.check, .menu.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" - ".menu.check:hover,\n" - ".menu.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; @@ -2762,68 +2722,76 @@ _gtk_css_provider_get_theme_dir (void) if (var) path = g_build_filename (var, "share", "themes", NULL); else - path = g_build_filename (GTK_DATA_PREFIX, "share", "themes", NULL); + path = g_build_filename (_gtk_get_data_prefix (), "share", "themes", NULL); return path; } /** - * 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); - if (variant == NULL) - key = (gchar *)name; - else - key = g_strconcat (name, "-", variant, NULL); + gtk_css_provider_reset (provider); - provider = g_hash_table_lookup (themes, key); + /* 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 + 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); @@ -2834,93 +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); } } +} - if (key != name) - g_free (key); +/** + * 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 (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) { - return strcmp (((const GtkStyleProperty *) a)->pspec->name, - ((const GtkStyleProperty *) b)->pspec->name); + 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) +{ + 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; - char *s; + 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++) { - GtkStyleProperty *prop = walk->data; - const GValue *value = g_hash_table_lookup (ruleset->style, prop); - + PropertyValue *prop = &ruleset->styles[sorted[i]]; g_string_append (str, " "); - g_string_append (str, prop->pspec->name); + g_string_append (str, _gtk_style_property_get_name (GTK_STYLE_PROPERTY (prop->property))); g_string_append (str, ": "); - s = _gtk_css_value_to_string (value); - g_string_append (str, s); - g_free (s); + _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 GValue *value = g_hash_table_lookup (ruleset->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, ": "); - s = _gtk_css_value_to_string (value); - g_string_append (str, s); - g_free (s); + 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"); @@ -2931,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 */ @@ -2940,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 @@ -2967,6 +3030,8 @@ gtk_css_provider_print_colors (GHashTable *colors, * this @provider. * * Returns: a new string representing the @provider. + * + * Since: 3.2 **/ char * gtk_css_provider_to_string (GtkCssProvider *provider) @@ -2982,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); }