1 /* GTK - The GIMP Toolkit
2 * Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "config.h"
19
20 #include <string.h>
21 #include <stdlib.h>
22
23 #include <gdk-pixbuf/gdk-pixbuf.h>
24 #include <cairo-gobject.h>
25
26 #include "gtkcssproviderprivate.h"
27
28 #include "gtkbitmaskprivate.h"
29 #include "gtkcssarrayvalueprivate.h"
30 #include "gtkcsscolorvalueprivate.h"
31 #include "gtkcsskeyframesprivate.h"
32 #include "gtkcssparserprivate.h"
33 #include "gtkcsssectionprivate.h"
34 #include "gtkcssselectorprivate.h"
35 #include "gtkcssshorthandpropertyprivate.h"
36 #include "gtkcssstylefuncsprivate.h"
37 #include "gtksettingsprivate.h"
38 #include "gtkstyleprovider.h"
39 #include "gtkstylecontextprivate.h"
40 #include "gtkstylepropertyprivate.h"
41 #include "gtkstyleproviderprivate.h"
42 #include "gtkwidgetpath.h"
43 #include "gtkbindings.h"
44 #include "gtkmarshalers.h"
45 #include "gtkprivate.h"
46 #include "gtkintl.h"
47 #include "gtkutilsprivate.h"
48 #include "gtkversion.h"
49
50 /**
51 * SECTION:gtkcssprovider
52 * @Short_description: CSS-like styling for widgets
53 * @Title: GtkCssProvider
54 * @See_also: #GtkStyleContext, #GtkStyleProvider
55 *
56 * GtkCssProvider is an object implementing the #GtkStyleProvider interface.
57 * It is able to parse [CSS-like][css-overview] input in order to style widgets.
58 *
59 * An application can make GTK+ parse a specific CSS style sheet by calling
60 * gtk_css_provider_load_from_file() or gtk_css_provider_load_from_resource()
61 * and adding the provider with gtk_style_context_add_provider() or
62 * gtk_style_context_add_provider_for_screen().
63
64 * In addition, certain files will be read when GTK+ is initialized. First, the
65 * file `$XDG_CONFIG_HOME/gtk-3.0/gtk.css` is loaded if it exists. Then, GTK+
66 * loads the first existing file among
67 * `XDG_DATA_HOME/themes/THEME/gtk-VERSION/gtk.css`,
68 * `$HOME/.themes/THEME/gtk-VERSION/gtk.css`,
69 * `$XDG_DATA_DIRS/themes/THEME/gtk-VERSION/gtk.css` and
70 * `DATADIR/share/themes/THEME/gtk-VERSION/gtk.css`, where `THEME` is the name of
71 * the current theme (see the #GtkSettings:gtk-theme-name setting), `DATADIR`
72 * is the prefix configured when GTK+ was compiled (unless overridden by the
73 * `GTK_DATA_PREFIX` environment variable), and `VERSION` is the GTK+ version number.
74 * If no file is found for the current version, GTK+ tries older versions all the
75 * way back to 3.0.
76 *
77 * In the same way, GTK+ tries to load a gtk-keys.css file for the current
78 * key theme, as defined by #GtkSettings:gtk-key-theme-name.
79 */
80
81
82 typedef struct GtkCssRuleset GtkCssRuleset;
83 typedef struct _GtkCssScanner GtkCssScanner;
84 typedef struct _PropertyValue PropertyValue;
85 typedef struct _WidgetPropertyValue WidgetPropertyValue;
86 typedef enum ParserScope ParserScope;
87 typedef enum ParserSymbol ParserSymbol;
88
89 struct _PropertyValue {
90 GtkCssStyleProperty *property;
91 GtkCssValue *value;
92 GtkCssSection *section;
93 };
94
95 struct _WidgetPropertyValue {
96 WidgetPropertyValue *next;
97 char *name;
98 char *value;
99
100 GtkCssSection *section;
101 };
102
103 struct GtkCssRuleset
104 {
105 GtkCssSelector *selector;
106 GtkCssSelectorTree *selector_match;
107 WidgetPropertyValue *widget_style;
108 PropertyValue *styles;
109 GtkBitmask *set_styles;
110 guint n_styles;
111 guint owns_styles : 1;
112 guint owns_widget_style : 1;
113 };
114
115 struct _GtkCssScanner
116 {
117 GtkCssProvider *provider;
118 GtkCssParser *parser;
119 GtkCssSection *section;
120 GtkCssScanner *parent;
121 GSList *state;
122 };
123
124 struct _GtkCssProviderPrivate
125 {
126 GScanner *scanner;
127
128 GHashTable *symbolic_colors;
129 GHashTable *keyframes;
130
131 GArray *rulesets;
132 GtkCssSelectorTree *tree;
133 GResource *resource;
134 gchar *path;
135 };
136
137 enum {
138 PARSING_ERROR,
139 LAST_SIGNAL
140 };
141
142 static gboolean gtk_keep_css_sections = FALSE;
143
144 static guint css_provider_signals[LAST_SIGNAL] = { 0 };
145
146 static void gtk_css_provider_finalize (GObject *object);
147 static void gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface);
148 static void gtk_css_style_provider_private_iface_init (GtkStyleProviderPrivateInterface *iface);
149 static void widget_property_value_list_free (WidgetPropertyValue *head);
150 static void gtk_css_style_provider_emit_error (GtkStyleProviderPrivate *provider,
151 GtkCssSection *section,
152 const GError *error);
153
154 static gboolean
155 gtk_css_provider_load_internal (GtkCssProvider *css_provider,
156 GtkCssScanner *scanner,
157 GFile *file,
158 const char *data,
159 GError **error);
160
161 GQuark
gtk_css_provider_error_quark(void)162 gtk_css_provider_error_quark (void)
163 {
164 return g_quark_from_static_string ("gtk-css-provider-error-quark");
165 }
166
167 G_DEFINE_TYPE_EXTENDED (GtkCssProvider, gtk_css_provider, G_TYPE_OBJECT, 0,
168 G_ADD_PRIVATE (GtkCssProvider)
169 G_IMPLEMENT_INTERFACE (GTK_TYPE_STYLE_PROVIDER,
170 gtk_css_style_provider_iface_init)
171 G_IMPLEMENT_INTERFACE (GTK_TYPE_STYLE_PROVIDER_PRIVATE,
172 gtk_css_style_provider_private_iface_init));
173
174 static void
gtk_css_provider_parsing_error(GtkCssProvider * provider,GtkCssSection * section,const GError * error)175 gtk_css_provider_parsing_error (GtkCssProvider *provider,
176 GtkCssSection *section,
177 const GError *error)
178 {
179 /* Only emit a warning when we have no error handlers. This is our
180 * default handlers. And in this case erroneous CSS files are a bug
181 * and should be fixed.
182 * Note that these warnings can also be triggered by a broken theme
183 * that people installed from some weird location on the internets.
184 */
185 if (!g_signal_has_handler_pending (provider,
186 css_provider_signals[PARSING_ERROR],
187 0,
188 TRUE))
189 {
190 char *s = _gtk_css_section_to_string (section);
191
192 g_warning ("Theme parsing error: %s: %s",
193 s,
194 error->message);
195
196 g_free (s);
197 }
198 }
199
200 /* This is exported privately for use in GtkInspector.
201 * It is the callers responsibility to reparse the current theme.
202 */
203 void
gtk_css_provider_set_keep_css_sections(void)204 gtk_css_provider_set_keep_css_sections (void)
205 {
206 gtk_keep_css_sections = TRUE;
207 }
208
209 static void
gtk_css_provider_class_init(GtkCssProviderClass * klass)210 gtk_css_provider_class_init (GtkCssProviderClass *klass)
211 {
212 GObjectClass *object_class = G_OBJECT_CLASS (klass);
213
214 if (g_getenv ("GTK_CSS_DEBUG"))
215 gtk_css_provider_set_keep_css_sections ();
216
217 /**
218 * GtkCssProvider::parsing-error:
219 * @provider: the provider that had a parsing error
220 * @section: section the error happened in
221 * @error: The parsing error
222 *
223 * Signals that a parsing error occurred. the @path, @line and @position
224 * describe the actual location of the error as accurately as possible.
225 *
226 * Parsing errors are never fatal, so the parsing will resume after
227 * the error. Errors may however cause parts of the given
228 * data or even all of it to not be parsed at all. So it is a useful idea
229 * to check that the parsing succeeds by connecting to this signal.
230 *
231 * Note that this signal may be emitted at any time as the css provider
232 * may opt to defer parsing parts or all of the input to a later time
233 * than when a loading function was called.
234 */
235 css_provider_signals[PARSING_ERROR] =
236 g_signal_new (I_("parsing-error"),
237 G_TYPE_FROM_CLASS (object_class),
238 G_SIGNAL_RUN_LAST,
239 G_STRUCT_OFFSET (GtkCssProviderClass, parsing_error),
240 NULL, NULL,
241 _gtk_marshal_VOID__BOXED_BOXED,
242 G_TYPE_NONE, 2, GTK_TYPE_CSS_SECTION, G_TYPE_ERROR);
243
244 object_class->finalize = gtk_css_provider_finalize;
245
246 klass->parsing_error = gtk_css_provider_parsing_error;
247 }
248
249 static void
gtk_css_ruleset_init_copy(GtkCssRuleset * new,GtkCssRuleset * ruleset,GtkCssSelector * selector)250 gtk_css_ruleset_init_copy (GtkCssRuleset *new,
251 GtkCssRuleset *ruleset,
252 GtkCssSelector *selector)
253 {
254 memcpy (new, ruleset, sizeof (GtkCssRuleset));
255
256 new->selector = selector;
257 /* First copy takes over ownership */
258 if (ruleset->owns_styles)
259 ruleset->owns_styles = FALSE;
260 if (ruleset->owns_widget_style)
261 ruleset->owns_widget_style = FALSE;
262 if (new->set_styles)
263 new->set_styles = _gtk_bitmask_copy (new->set_styles);
264 }
265
266 static void
gtk_css_ruleset_clear(GtkCssRuleset * ruleset)267 gtk_css_ruleset_clear (GtkCssRuleset *ruleset)
268 {
269 if (ruleset->owns_styles)
270 {
271 guint i;
272
273 for (i = 0; i < ruleset->n_styles; i++)
274 {
275 _gtk_css_value_unref (ruleset->styles[i].value);
276 ruleset->styles[i].value = NULL;
277 if (ruleset->styles[i].section)
278 gtk_css_section_unref (ruleset->styles[i].section);
279 }
280 g_free (ruleset->styles);
281 }
282 if (ruleset->set_styles)
283 _gtk_bitmask_free (ruleset->set_styles);
284 if (ruleset->owns_widget_style)
285 widget_property_value_list_free (ruleset->widget_style);
286 if (ruleset->selector)
287 _gtk_css_selector_free (ruleset->selector);
288
289 memset (ruleset, 0, sizeof (GtkCssRuleset));
290 }
291
292 static WidgetPropertyValue *
widget_property_value_new(char * name,GtkCssSection * section)293 widget_property_value_new (char *name, GtkCssSection *section)
294 {
295 WidgetPropertyValue *value;
296
297 value = g_slice_new0 (WidgetPropertyValue);
298
299 value->name = name;
300 if (gtk_keep_css_sections)
301 value->section = gtk_css_section_ref (section);
302
303 return value;
304 }
305
306 static void
widget_property_value_free(WidgetPropertyValue * value)307 widget_property_value_free (WidgetPropertyValue *value)
308 {
309 g_free (value->value);
310 g_free (value->name);
311 if (value->section)
312 gtk_css_section_unref (value->section);
313
314 g_slice_free (WidgetPropertyValue, value);
315 }
316
317 static void
widget_property_value_list_free(WidgetPropertyValue * head)318 widget_property_value_list_free (WidgetPropertyValue *head)
319 {
320 WidgetPropertyValue *l, *next;
321 for (l = head; l != NULL; l = next)
322 {
323 next = l->next;
324 widget_property_value_free (l);
325 }
326 }
327
328 static WidgetPropertyValue *
widget_property_value_list_remove_name(WidgetPropertyValue * head,const char * name)329 widget_property_value_list_remove_name (WidgetPropertyValue *head, const char *name)
330 {
331 WidgetPropertyValue *l, **last;
332
333 last = &head;
334
335 for (l = head; l != NULL; l = l->next)
336 {
337 if (strcmp (l->name, name) == 0)
338 {
339 *last = l->next;
340 widget_property_value_free (l);
341 break;
342 }
343
344 last = &l->next;
345 }
346
347 return head;
348 }
349
350 static void
gtk_css_ruleset_add_style(GtkCssRuleset * ruleset,char * name,WidgetPropertyValue * value)351 gtk_css_ruleset_add_style (GtkCssRuleset *ruleset,
352 char *name,
353 WidgetPropertyValue *value)
354 {
355 value->next = widget_property_value_list_remove_name (ruleset->widget_style, name);
356 ruleset->widget_style = value;
357 ruleset->owns_widget_style = TRUE;
358 }
359
360 static void
gtk_css_ruleset_add(GtkCssRuleset * ruleset,GtkCssStyleProperty * property,GtkCssValue * value,GtkCssSection * section)361 gtk_css_ruleset_add (GtkCssRuleset *ruleset,
362 GtkCssStyleProperty *property,
363 GtkCssValue *value,
364 GtkCssSection *section)
365 {
366 guint i;
367
368 g_return_if_fail (ruleset->owns_styles || ruleset->n_styles == 0);
369
370 if (ruleset->set_styles == NULL)
371 ruleset->set_styles = _gtk_bitmask_new ();
372
373 ruleset->set_styles = _gtk_bitmask_set (ruleset->set_styles,
374 _gtk_css_style_property_get_id (property),
375 TRUE);
376
377 ruleset->owns_styles = TRUE;
378
379 for (i = 0; i < ruleset->n_styles; i++)
380 {
381 if (ruleset->styles[i].property == property)
382 {
383 _gtk_css_value_unref (ruleset->styles[i].value);
384 ruleset->styles[i].value = NULL;
385 if (ruleset->styles[i].section)
386 gtk_css_section_unref (ruleset->styles[i].section);
387 break;
388 }
389 }
390 if (i == ruleset->n_styles)
391 {
392 ruleset->n_styles++;
393 ruleset->styles = g_realloc (ruleset->styles, ruleset->n_styles * sizeof (PropertyValue));
394 ruleset->styles[i].value = NULL;
395 ruleset->styles[i].property = property;
396 }
397
398 ruleset->styles[i].value = value;
399 if (gtk_keep_css_sections)
400 ruleset->styles[i].section = gtk_css_section_ref (section);
401 else
402 ruleset->styles[i].section = NULL;
403 }
404
405 static void
gtk_css_scanner_destroy(GtkCssScanner * scanner)406 gtk_css_scanner_destroy (GtkCssScanner *scanner)
407 {
408 if (scanner->section)
409 gtk_css_section_unref (scanner->section);
410 g_object_unref (scanner->provider);
411 _gtk_css_parser_free (scanner->parser);
412
413 g_slice_free (GtkCssScanner, scanner);
414 }
415
416 static void
gtk_css_style_provider_emit_error(GtkStyleProviderPrivate * provider,GtkCssSection * section,const GError * error)417 gtk_css_style_provider_emit_error (GtkStyleProviderPrivate *provider,
418 GtkCssSection *section,
419 const GError *error)
420 {
421 g_signal_emit (provider, css_provider_signals[PARSING_ERROR], 0, section, error);
422 }
423
424 static void
gtk_css_provider_emit_error(GtkCssProvider * provider,GtkCssScanner * scanner,const GError * error)425 gtk_css_provider_emit_error (GtkCssProvider *provider,
426 GtkCssScanner *scanner,
427 const GError *error)
428 {
429 gtk_css_style_provider_emit_error (GTK_STYLE_PROVIDER_PRIVATE (provider),
430 scanner ? scanner->section : NULL,
431 error);
432 }
433
434 static void
gtk_css_scanner_parser_error(GtkCssParser * parser,const GError * error,gpointer user_data)435 gtk_css_scanner_parser_error (GtkCssParser *parser,
436 const GError *error,
437 gpointer user_data)
438 {
439 GtkCssScanner *scanner = user_data;
440
441 gtk_css_provider_emit_error (scanner->provider, scanner, error);
442 }
443
444 static GtkCssScanner *
gtk_css_scanner_new(GtkCssProvider * provider,GtkCssScanner * parent,GtkCssSection * section,GFile * file,const gchar * text)445 gtk_css_scanner_new (GtkCssProvider *provider,
446 GtkCssScanner *parent,
447 GtkCssSection *section,
448 GFile *file,
449 const gchar *text)
450 {
451 GtkCssScanner *scanner;
452
453 scanner = g_slice_new0 (GtkCssScanner);
454
455 g_object_ref (provider);
456 scanner->provider = provider;
457 scanner->parent = parent;
458 if (section)
459 scanner->section = gtk_css_section_ref (section);
460
461 scanner->parser = _gtk_css_parser_new (text,
462 file,
463 gtk_css_scanner_parser_error,
464 scanner);
465
466 return scanner;
467 }
468
469 static gboolean
gtk_css_scanner_would_recurse(GtkCssScanner * scanner,GFile * file)470 gtk_css_scanner_would_recurse (GtkCssScanner *scanner,
471 GFile *file)
472 {
473 while (scanner)
474 {
475 GFile *parser_file = _gtk_css_parser_get_file (scanner->parser);
476 if (parser_file && g_file_equal (parser_file, file))
477 return TRUE;
478
479 scanner = scanner->parent;
480 }
481
482 return FALSE;
483 }
484
485 static void
gtk_css_scanner_push_section(GtkCssScanner * scanner,GtkCssSectionType section_type)486 gtk_css_scanner_push_section (GtkCssScanner *scanner,
487 GtkCssSectionType section_type)
488 {
489 GtkCssSection *section;
490
491 section = _gtk_css_section_new (scanner->section,
492 section_type,
493 scanner->parser);
494
495 if (scanner->section)
496 gtk_css_section_unref (scanner->section);
497 scanner->section = section;
498 }
499
500 static void
gtk_css_scanner_pop_section(GtkCssScanner * scanner,GtkCssSectionType check_type)501 gtk_css_scanner_pop_section (GtkCssScanner *scanner,
502 GtkCssSectionType check_type)
503 {
504 GtkCssSection *parent;
505
506 g_assert (gtk_css_section_get_section_type (scanner->section) == check_type);
507
508 parent = gtk_css_section_get_parent (scanner->section);
509 if (parent)
510 gtk_css_section_ref (parent);
511
512 _gtk_css_section_end (scanner->section);
513 gtk_css_section_unref (scanner->section);
514
515 scanner->section = parent;
516 }
517
518 static void
gtk_css_provider_init(GtkCssProvider * css_provider)519 gtk_css_provider_init (GtkCssProvider *css_provider)
520 {
521 GtkCssProviderPrivate *priv;
522
523 priv = css_provider->priv = gtk_css_provider_get_instance_private (css_provider);
524
525 priv->rulesets = g_array_new (FALSE, FALSE, sizeof (GtkCssRuleset));
526
527 priv->symbolic_colors = g_hash_table_new_full (g_str_hash, g_str_equal,
528 (GDestroyNotify) g_free,
529 (GDestroyNotify) _gtk_css_value_unref);
530 priv->keyframes = g_hash_table_new_full (g_str_hash, g_str_equal,
531 (GDestroyNotify) g_free,
532 (GDestroyNotify) _gtk_css_keyframes_unref);
533 }
534
535 static void
verify_tree_match_results(GtkCssProvider * provider,const GtkCssMatcher * matcher,GPtrArray * tree_rules)536 verify_tree_match_results (GtkCssProvider *provider,
537 const GtkCssMatcher *matcher,
538 GPtrArray *tree_rules)
539 {
540 #ifdef VERIFY_TREE
541 GtkCssProviderPrivate *priv = provider->priv;
542 GtkCssRuleset *ruleset;
543 gboolean should_match;
544 int i, j;
545
546 for (i = 0; i < priv->rulesets->len; i++)
547 {
548 gboolean found = FALSE;
549
550 ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i);
551
552 for (j = 0; j < tree_rules->len; j++)
553 {
554 if (ruleset == tree_rules->pdata[j])
555 {
556 found = TRUE;
557 break;
558 }
559 }
560 should_match = _gtk_css_selector_matches (ruleset->selector, matcher);
561 if (found != !!should_match)
562 {
563 g_error ("expected rule '%s' to %s, but it %s",
564 _gtk_css_selector_to_string (ruleset->selector),
565 should_match ? "match" : "not match",
566 found ? "matched" : "didn't match");
567 }
568 }
569 #endif
570 }
571
572 static void
verify_tree_get_change_results(GtkCssProvider * provider,const GtkCssMatcher * matcher,GtkCssChange change)573 verify_tree_get_change_results (GtkCssProvider *provider,
574 const GtkCssMatcher *matcher,
575 GtkCssChange change)
576 {
577 #ifdef VERIFY_TREE
578 {
579 GtkCssChange verify_change = 0;
580 GPtrArray *tree_rules;
581 int i;
582
583 tree_rules = _gtk_css_selector_tree_match_all (provider->priv->tree, matcher);
584 if (tree_rules)
585 {
586 verify_tree_match_results (provider, matcher, tree_rules);
587
588 for (i = tree_rules->len - 1; i >= 0; i--)
589 {
590 GtkCssRuleset *ruleset;
591
592 ruleset = tree_rules->pdata[i];
593
594 verify_change |= _gtk_css_selector_get_change (ruleset->selector);
595 }
596
597 g_ptr_array_free (tree_rules, TRUE);
598 }
599
600 if (change != verify_change)
601 {
602 GString *s;
603
604 s = g_string_new ("");
605 g_string_append (s, "expected change ");
606 gtk_css_change_print (verify_change, s);
607 g_string_append (s, ", but it was ");
608 gtk_css_change_print (change, s);
609 if ((change & ~verify_change) != 0)
610 {
611 g_string_append (s, ", unexpectedly set: ");
612 gtk_css_change_print (change & ~verify_change, s);
613 }
614 if ((~change & verify_change) != 0)
615 {
616 g_string_append_printf (s, ", unexpectedly not set: ");
617 gtk_css_change_print (~change & verify_change, s);
618 }
619 g_warning (s->str);
620 g_string_free (s, TRUE);
621 }
622 }
623 #endif
624 }
625
626
627 static gboolean
gtk_css_provider_get_style_property(GtkStyleProvider * provider,GtkWidgetPath * path,GtkStateFlags state,GParamSpec * pspec,GValue * value)628 gtk_css_provider_get_style_property (GtkStyleProvider *provider,
629 GtkWidgetPath *path,
630 GtkStateFlags state,
631 GParamSpec *pspec,
632 GValue *value)
633 {
634 GtkCssProvider *css_provider = GTK_CSS_PROVIDER (provider);
635 GtkCssProviderPrivate *priv = css_provider->priv;
636 WidgetPropertyValue *val;
637 GPtrArray *tree_rules;
638 GtkCssMatcher matcher;
639 gboolean found = FALSE;
640 gchar *prop_name;
641 gint i;
642
643 if (state == gtk_widget_path_iter_get_state (path, -1))
644 {
645 gtk_widget_path_ref (path);
646 }
647 else
648 {
649 path = gtk_widget_path_copy (path);
650 gtk_widget_path_iter_set_state (path, -1, state);
651 }
652
653 if (!_gtk_css_matcher_init (&matcher, path, NULL))
654 {
655 gtk_widget_path_unref (path);
656 return FALSE;
657 }
658
659 tree_rules = _gtk_css_selector_tree_match_all (priv->tree, &matcher);
660 if (tree_rules)
661 {
662 verify_tree_match_results (css_provider, &matcher, tree_rules);
663
664 prop_name = g_strdup_printf ("-%s-%s",
665 g_type_name (pspec->owner_type),
666 pspec->name);
667
668 for (i = tree_rules->len - 1; i >= 0; i--)
669 {
670 GtkCssRuleset *ruleset = tree_rules->pdata[i];
671
672 if (ruleset->widget_style == NULL)
673 continue;
674
675 for (val = ruleset->widget_style; val != NULL; val = val->next)
676 {
677 if (strcmp (val->name, prop_name) == 0)
678 {
679 GtkCssScanner *scanner;
680
681 scanner = gtk_css_scanner_new (css_provider,
682 NULL,
683 val->section,
684 val->section != NULL ? gtk_css_section_get_file (val->section) : NULL,
685 val->value);
686 if (!val->section)
687 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_VALUE);
688 found = _gtk_css_style_funcs_parse_value (value, scanner->parser);
689 if (!val->section)
690 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE);
691 gtk_css_scanner_destroy (scanner);
692 break;
693 }
694 }
695
696 if (found)
697 break;
698 }
699
700 g_free (prop_name);
701 g_ptr_array_free (tree_rules, TRUE);
702 }
703
704 gtk_widget_path_unref (path);
705
706 return found;
707 }
708
709 static void
gtk_css_style_provider_iface_init(GtkStyleProviderIface * iface)710 gtk_css_style_provider_iface_init (GtkStyleProviderIface *iface)
711 {
712 iface->get_style_property = gtk_css_provider_get_style_property;
713 }
714
715 static GtkCssValue *
gtk_css_style_provider_get_color(GtkStyleProviderPrivate * provider,const char * name)716 gtk_css_style_provider_get_color (GtkStyleProviderPrivate *provider,
717 const char *name)
718 {
719 GtkCssProvider *css_provider = GTK_CSS_PROVIDER (provider);
720
721 return g_hash_table_lookup (css_provider->priv->symbolic_colors, name);
722 }
723
724 static GtkCssKeyframes *
gtk_css_style_provider_get_keyframes(GtkStyleProviderPrivate * provider,const char * name)725 gtk_css_style_provider_get_keyframes (GtkStyleProviderPrivate *provider,
726 const char *name)
727 {
728 GtkCssProvider *css_provider = GTK_CSS_PROVIDER (provider);
729
730 return g_hash_table_lookup (css_provider->priv->keyframes, name);
731 }
732
733 static void
gtk_css_style_provider_lookup(GtkStyleProviderPrivate * provider,const GtkCssMatcher * matcher,GtkCssLookup * lookup,GtkCssChange * change)734 gtk_css_style_provider_lookup (GtkStyleProviderPrivate *provider,
735 const GtkCssMatcher *matcher,
736 GtkCssLookup *lookup,
737 GtkCssChange *change)
738 {
739 GtkCssProvider *css_provider;
740 GtkCssProviderPrivate *priv;
741 GtkCssRuleset *ruleset;
742 guint j;
743 int i;
744 GPtrArray *tree_rules;
745
746 css_provider = GTK_CSS_PROVIDER (provider);
747 priv = css_provider->priv;
748
749 tree_rules = _gtk_css_selector_tree_match_all (priv->tree, matcher);
750 if (tree_rules)
751 {
752 verify_tree_match_results (css_provider, matcher, tree_rules);
753
754 for (i = tree_rules->len - 1; i >= 0; i--)
755 {
756 ruleset = tree_rules->pdata[i];
757
758 if (ruleset->styles == NULL)
759 continue;
760
761 if (!_gtk_bitmask_intersects (_gtk_css_lookup_get_missing (lookup),
762 ruleset->set_styles))
763 continue;
764
765 for (j = 0; j < ruleset->n_styles; j++)
766 {
767 GtkCssStyleProperty *prop = ruleset->styles[j].property;
768 guint id = _gtk_css_style_property_get_id (prop);
769
770 if (!_gtk_css_lookup_is_missing (lookup, id))
771 continue;
772
773 _gtk_css_lookup_set (lookup,
774 id,
775 ruleset->styles[j].section,
776 ruleset->styles[j].value);
777 }
778
779 if (_gtk_bitmask_is_empty (_gtk_css_lookup_get_missing (lookup)))
780 break;
781 }
782
783 g_ptr_array_free (tree_rules, TRUE);
784 }
785
786 if (change)
787 {
788 GtkCssMatcher change_matcher;
789
790 _gtk_css_matcher_superset_init (&change_matcher, matcher, GTK_CSS_CHANGE_NAME | GTK_CSS_CHANGE_CLASS);
791
792 *change = _gtk_css_selector_tree_get_change_all (priv->tree, &change_matcher);
793 verify_tree_get_change_results (css_provider, &change_matcher, *change);
794 }
795 }
796
797 static void
gtk_css_style_provider_private_iface_init(GtkStyleProviderPrivateInterface * iface)798 gtk_css_style_provider_private_iface_init (GtkStyleProviderPrivateInterface *iface)
799 {
800 iface->get_color = gtk_css_style_provider_get_color;
801 iface->get_keyframes = gtk_css_style_provider_get_keyframes;
802 iface->lookup = gtk_css_style_provider_lookup;
803 iface->emit_error = gtk_css_style_provider_emit_error;
804 }
805
806 static void
gtk_css_provider_finalize(GObject * object)807 gtk_css_provider_finalize (GObject *object)
808 {
809 GtkCssProvider *css_provider;
810 GtkCssProviderPrivate *priv;
811 guint i;
812
813 css_provider = GTK_CSS_PROVIDER (object);
814 priv = css_provider->priv;
815
816 for (i = 0; i < priv->rulesets->len; i++)
817 gtk_css_ruleset_clear (&g_array_index (priv->rulesets, GtkCssRuleset, i));
818
819 g_array_free (priv->rulesets, TRUE);
820 _gtk_css_selector_tree_free (priv->tree);
821
822 g_hash_table_destroy (priv->symbolic_colors);
823 g_hash_table_destroy (priv->keyframes);
824
825 if (priv->resource)
826 {
827 g_resources_unregister (priv->resource);
828 g_resource_unref (priv->resource);
829 priv->resource = NULL;
830 }
831
832 g_free (priv->path);
833
834 G_OBJECT_CLASS (gtk_css_provider_parent_class)->finalize (object);
835 }
836
837 /**
838 * gtk_css_provider_new:
839 *
840 * Returns a newly created #GtkCssProvider.
841 *
842 * Returns: A new #GtkCssProvider
843 **/
844 GtkCssProvider *
gtk_css_provider_new(void)845 gtk_css_provider_new (void)
846 {
847 return g_object_new (GTK_TYPE_CSS_PROVIDER, NULL);
848 }
849
850 static void
gtk_css_provider_take_error(GtkCssProvider * provider,GtkCssScanner * scanner,GError * error)851 gtk_css_provider_take_error (GtkCssProvider *provider,
852 GtkCssScanner *scanner,
853 GError *error)
854 {
855 gtk_css_provider_emit_error (provider, scanner, error);
856 g_error_free (error);
857 }
858
859 static void
gtk_css_provider_error_literal(GtkCssProvider * provider,GtkCssScanner * scanner,GQuark domain,gint code,const char * message)860 gtk_css_provider_error_literal (GtkCssProvider *provider,
861 GtkCssScanner *scanner,
862 GQuark domain,
863 gint code,
864 const char *message)
865 {
866 gtk_css_provider_take_error (provider,
867 scanner,
868 g_error_new_literal (domain, code, message));
869 }
870
871 static void
872 gtk_css_provider_error (GtkCssProvider *provider,
873 GtkCssScanner *scanner,
874 GQuark domain,
875 gint code,
876 const char *format,
877 ...) G_GNUC_PRINTF (5, 6);
878 static void
gtk_css_provider_error(GtkCssProvider * provider,GtkCssScanner * scanner,GQuark domain,gint code,const char * format,...)879 gtk_css_provider_error (GtkCssProvider *provider,
880 GtkCssScanner *scanner,
881 GQuark domain,
882 gint code,
883 const char *format,
884 ...)
885 {
886 GError *error;
887 va_list args;
888
889 gtk_internal_return_if_fail (GTK_IS_CSS_PROVIDER (provider));
890 gtk_internal_return_if_fail (scanner != NULL);
891
892 va_start (args, format);
893 error = g_error_new_valist (domain, code, format, args);
894 va_end (args);
895
896 gtk_css_provider_take_error (provider, scanner, error);
897 }
898
899 static void
gtk_css_provider_invalid_token(GtkCssProvider * provider,GtkCssScanner * scanner,const char * expected)900 gtk_css_provider_invalid_token (GtkCssProvider *provider,
901 GtkCssScanner *scanner,
902 const char *expected)
903 {
904 gtk_css_provider_error (provider,
905 scanner,
906 GTK_CSS_PROVIDER_ERROR,
907 GTK_CSS_PROVIDER_ERROR_SYNTAX,
908 "expected %s", expected);
909 }
910
911 static void
css_provider_commit(GtkCssProvider * css_provider,GSList * selectors,GtkCssRuleset * ruleset)912 css_provider_commit (GtkCssProvider *css_provider,
913 GSList *selectors,
914 GtkCssRuleset *ruleset)
915 {
916 GtkCssProviderPrivate *priv;
917 GSList *l;
918
919 priv = css_provider->priv;
920
921 if (ruleset->styles == NULL && ruleset->widget_style == NULL)
922 {
923 g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
924 return;
925 }
926
927 for (l = selectors; l; l = l->next)
928 {
929 GtkCssRuleset new;
930
931 gtk_css_ruleset_init_copy (&new, ruleset, l->data);
932
933 g_array_append_val (priv->rulesets, new);
934 }
935
936 g_slist_free (selectors);
937 }
938
939 static void
gtk_css_provider_reset(GtkCssProvider * css_provider)940 gtk_css_provider_reset (GtkCssProvider *css_provider)
941 {
942 GtkCssProviderPrivate *priv;
943 guint i;
944
945 priv = css_provider->priv;
946
947 if (priv->resource)
948 {
949 g_resources_unregister (priv->resource);
950 g_resource_unref (priv->resource);
951 priv->resource = NULL;
952 }
953
954 if (priv->path)
955 {
956 g_free (priv->path);
957 priv->path = NULL;
958 }
959
960 g_hash_table_remove_all (priv->symbolic_colors);
961 g_hash_table_remove_all (priv->keyframes);
962
963 for (i = 0; i < priv->rulesets->len; i++)
964 gtk_css_ruleset_clear (&g_array_index (priv->rulesets, GtkCssRuleset, i));
965 g_array_set_size (priv->rulesets, 0);
966 _gtk_css_selector_tree_free (priv->tree);
967 priv->tree = NULL;
968
969 }
970
971 static void
gtk_css_provider_propagate_error(GtkCssProvider * provider,GtkCssSection * section,const GError * error,GError ** propagate_to)972 gtk_css_provider_propagate_error (GtkCssProvider *provider,
973 GtkCssSection *section,
974 const GError *error,
975 GError **propagate_to)
976 {
977
978 char *s;
979
980 /* don't fail for deprecations */
981 if (g_error_matches (error, GTK_CSS_PROVIDER_ERROR, GTK_CSS_PROVIDER_ERROR_DEPRECATED))
982 {
983 s = _gtk_css_section_to_string (section);
984 g_warning ("Theme parsing error: %s: %s", s, error->message);
985 g_free (s);
986 return;
987 }
988
989 /* we already set an error. And we'd like to keep the first one */
990 if (*propagate_to)
991 return;
992
993 *propagate_to = g_error_copy (error);
994 if (section)
995 {
996 s = _gtk_css_section_to_string (section);
997 g_prefix_error (propagate_to, "%s", s);
998 g_free (s);
999 }
1000 }
1001
1002 static gboolean
parse_import(GtkCssScanner * scanner)1003 parse_import (GtkCssScanner *scanner)
1004 {
1005 GFile *file;
1006
1007 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_IMPORT);
1008
1009 if (!_gtk_css_parser_try (scanner->parser, "@import", TRUE))
1010 {
1011 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_IMPORT);
1012 return FALSE;
1013 }
1014
1015 if (_gtk_css_parser_is_string (scanner->parser))
1016 {
1017 char *uri;
1018
1019 uri = _gtk_css_parser_read_string (scanner->parser);
1020 file = _gtk_css_parser_get_file_for_path (scanner->parser, uri);
1021 g_free (uri);
1022 }
1023 else
1024 {
1025 file = _gtk_css_parser_read_url (scanner->parser);
1026 }
1027
1028 if (file == NULL)
1029 {
1030 _gtk_css_parser_resync (scanner->parser, TRUE, 0);
1031 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_IMPORT);
1032 return TRUE;
1033 }
1034
1035 if (!_gtk_css_parser_try (scanner->parser, ";", FALSE))
1036 {
1037 gtk_css_provider_invalid_token (scanner->provider, scanner, "semicolon");
1038 _gtk_css_parser_resync (scanner->parser, TRUE, 0);
1039 }
1040 else if (gtk_css_scanner_would_recurse (scanner, file))
1041 {
1042 char *path = g_file_get_path (file);
1043 gtk_css_provider_error (scanner->provider,
1044 scanner,
1045 GTK_CSS_PROVIDER_ERROR,
1046 GTK_CSS_PROVIDER_ERROR_IMPORT,
1047 "Loading '%s' would recurse",
1048 path);
1049 g_free (path);
1050 }
1051 else
1052 {
1053 gtk_css_provider_load_internal (scanner->provider,
1054 scanner,
1055 file,
1056 NULL,
1057 NULL);
1058 }
1059
1060 g_object_unref (file);
1061
1062 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_IMPORT);
1063 _gtk_css_parser_skip_whitespace (scanner->parser);
1064
1065 return TRUE;
1066 }
1067
1068 static gboolean
parse_color_definition(GtkCssScanner * scanner)1069 parse_color_definition (GtkCssScanner *scanner)
1070 {
1071 GtkCssValue *color;
1072 char *name;
1073
1074 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION);
1075
1076 if (!_gtk_css_parser_try (scanner->parser, "@define-color", TRUE))
1077 {
1078 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION);
1079 return FALSE;
1080 }
1081
1082 name = _gtk_css_parser_try_name (scanner->parser, TRUE);
1083 if (name == NULL)
1084 {
1085 gtk_css_provider_error_literal (scanner->provider,
1086 scanner,
1087 GTK_CSS_PROVIDER_ERROR,
1088 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1089 "Not a valid color name");
1090 _gtk_css_parser_resync (scanner->parser, TRUE, 0);
1091 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION);
1092 return TRUE;
1093 }
1094
1095 color = _gtk_css_color_value_parse (scanner->parser);
1096 if (color == NULL)
1097 {
1098 g_free (name);
1099 _gtk_css_parser_resync (scanner->parser, TRUE, 0);
1100 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION);
1101 return TRUE;
1102 }
1103
1104 if (!_gtk_css_parser_try (scanner->parser, ";", TRUE))
1105 {
1106 g_free (name);
1107 _gtk_css_value_unref (color);
1108 gtk_css_provider_error_literal (scanner->provider,
1109 scanner,
1110 GTK_CSS_PROVIDER_ERROR,
1111 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1112 "Missing semicolon at end of color definition");
1113 _gtk_css_parser_resync (scanner->parser, TRUE, 0);
1114
1115 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION);
1116 return TRUE;
1117 }
1118
1119 g_hash_table_insert (scanner->provider->priv->symbolic_colors, name, color);
1120
1121 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_COLOR_DEFINITION);
1122 return TRUE;
1123 }
1124
1125 static gboolean
parse_binding_set(GtkCssScanner * scanner)1126 parse_binding_set (GtkCssScanner *scanner)
1127 {
1128 GtkBindingSet *binding_set;
1129 char *name;
1130
1131 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_BINDING_SET);
1132
1133 if (!_gtk_css_parser_try (scanner->parser, "@binding-set", TRUE))
1134 {
1135 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_BINDING_SET);
1136 return FALSE;
1137 }
1138
1139 name = _gtk_css_parser_try_ident (scanner->parser, TRUE);
1140 if (name == NULL)
1141 {
1142 gtk_css_provider_error_literal (scanner->provider,
1143 scanner,
1144 GTK_CSS_PROVIDER_ERROR,
1145 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1146 "Expected name for binding set");
1147 _gtk_css_parser_resync (scanner->parser, TRUE, 0);
1148 goto skip_semicolon;
1149 }
1150
1151 binding_set = gtk_binding_set_find (name);
1152 if (!binding_set)
1153 {
1154 binding_set = gtk_binding_set_new (name);
1155 binding_set->parsed = TRUE;
1156 }
1157 g_free (name);
1158
1159 if (!_gtk_css_parser_try (scanner->parser, "{", TRUE))
1160 {
1161 gtk_css_provider_error_literal (scanner->provider,
1162 scanner,
1163 GTK_CSS_PROVIDER_ERROR,
1164 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1165 "Expected '{' for binding set");
1166 _gtk_css_parser_resync (scanner->parser, TRUE, 0);
1167 goto skip_semicolon;
1168 }
1169
1170 while (!_gtk_css_parser_is_eof (scanner->parser) &&
1171 !_gtk_css_parser_begins_with (scanner->parser, '}'))
1172 {
1173 name = _gtk_css_parser_read_value (scanner->parser);
1174 if (name == NULL)
1175 {
1176 _gtk_css_parser_resync (scanner->parser, TRUE, '}');
1177 continue;
1178 }
1179
1180 if (gtk_binding_entry_add_signal_from_string (binding_set, name) != G_TOKEN_NONE)
1181 {
1182 gtk_css_provider_error_literal (scanner->provider,
1183 scanner,
1184 GTK_CSS_PROVIDER_ERROR,
1185 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1186 "Failed to parse binding set.");
1187 }
1188
1189 g_free (name);
1190
1191 if (!_gtk_css_parser_try (scanner->parser, ";", TRUE))
1192 {
1193 if (!_gtk_css_parser_begins_with (scanner->parser, '}') &&
1194 !_gtk_css_parser_is_eof (scanner->parser))
1195 {
1196 gtk_css_provider_error_literal (scanner->provider,
1197 scanner,
1198 GTK_CSS_PROVIDER_ERROR,
1199 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1200 "Expected semicolon");
1201 _gtk_css_parser_resync (scanner->parser, TRUE, '}');
1202 }
1203 }
1204 }
1205
1206 if (!_gtk_css_parser_try (scanner->parser, "}", TRUE))
1207 {
1208 gtk_css_provider_error_literal (scanner->provider,
1209 scanner,
1210 GTK_CSS_PROVIDER_ERROR,
1211 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1212 "expected '}' after declarations");
1213 if (!_gtk_css_parser_is_eof (scanner->parser))
1214 _gtk_css_parser_resync (scanner->parser, FALSE, 0);
1215 }
1216
1217 skip_semicolon:
1218 if (_gtk_css_parser_begins_with (scanner->parser, ';'))
1219 {
1220 gtk_css_provider_error_literal (scanner->provider,
1221 scanner,
1222 GTK_CSS_PROVIDER_ERROR,
1223 GTK_CSS_PROVIDER_ERROR_DEPRECATED,
1224 "Nonstandard semicolon at end of binding set");
1225 _gtk_css_parser_try (scanner->parser, ";", TRUE);
1226 }
1227
1228 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_BINDING_SET);
1229
1230 return TRUE;
1231 }
1232
1233 static gboolean
parse_keyframes(GtkCssScanner * scanner)1234 parse_keyframes (GtkCssScanner *scanner)
1235 {
1236 GtkCssKeyframes *keyframes;
1237 char *name;
1238
1239 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_KEYFRAMES);
1240
1241 if (!_gtk_css_parser_try (scanner->parser, "@keyframes", TRUE))
1242 {
1243 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_KEYFRAMES);
1244 return FALSE;
1245 }
1246
1247 name = _gtk_css_parser_try_ident (scanner->parser, TRUE);
1248 if (name == NULL)
1249 {
1250 gtk_css_provider_error_literal (scanner->provider,
1251 scanner,
1252 GTK_CSS_PROVIDER_ERROR,
1253 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1254 "Expected name for keyframes");
1255 _gtk_css_parser_resync (scanner->parser, TRUE, 0);
1256 goto exit;
1257 }
1258
1259 if (!_gtk_css_parser_try (scanner->parser, "{", TRUE))
1260 {
1261 gtk_css_provider_error_literal (scanner->provider,
1262 scanner,
1263 GTK_CSS_PROVIDER_ERROR,
1264 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1265 "Expected '{' for keyframes");
1266 _gtk_css_parser_resync (scanner->parser, TRUE, 0);
1267 g_free (name);
1268 goto exit;
1269 }
1270
1271 keyframes = _gtk_css_keyframes_parse (scanner->parser);
1272 if (keyframes == NULL)
1273 {
1274 _gtk_css_parser_resync (scanner->parser, TRUE, '}');
1275 g_free (name);
1276 goto exit;
1277 }
1278
1279 g_hash_table_insert (scanner->provider->priv->keyframes, name, keyframes);
1280
1281 if (!_gtk_css_parser_try (scanner->parser, "}", TRUE))
1282 {
1283 gtk_css_provider_error_literal (scanner->provider,
1284 scanner,
1285 GTK_CSS_PROVIDER_ERROR,
1286 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1287 "expected '}' after declarations");
1288 if (!_gtk_css_parser_is_eof (scanner->parser))
1289 _gtk_css_parser_resync (scanner->parser, FALSE, 0);
1290 }
1291
1292 exit:
1293 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_KEYFRAMES);
1294
1295 return TRUE;
1296 }
1297
1298 static void
parse_at_keyword(GtkCssScanner * scanner)1299 parse_at_keyword (GtkCssScanner *scanner)
1300 {
1301 if (parse_import (scanner))
1302 return;
1303 if (parse_color_definition (scanner))
1304 return;
1305 if (parse_binding_set (scanner))
1306 return;
1307 if (parse_keyframes (scanner))
1308 return;
1309
1310 else
1311 {
1312 gtk_css_provider_error_literal (scanner->provider,
1313 scanner,
1314 GTK_CSS_PROVIDER_ERROR,
1315 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1316 "unknown @ rule");
1317 _gtk_css_parser_resync (scanner->parser, TRUE, 0);
1318 }
1319 }
1320
1321 static GSList *
parse_selector_list(GtkCssScanner * scanner)1322 parse_selector_list (GtkCssScanner *scanner)
1323 {
1324 GSList *selectors = NULL;
1325
1326 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_SELECTOR);
1327
1328 do {
1329 GtkCssSelector *select = _gtk_css_selector_parse (scanner->parser);
1330
1331 if (select == NULL)
1332 {
1333 g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
1334 _gtk_css_parser_resync (scanner->parser, FALSE, 0);
1335 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_SELECTOR);
1336 return NULL;
1337 }
1338
1339 selectors = g_slist_prepend (selectors, select);
1340 }
1341 while (_gtk_css_parser_try (scanner->parser, ",", TRUE));
1342
1343 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_SELECTOR);
1344
1345 return selectors;
1346 }
1347
1348 static gboolean
name_is_style_property(const char * name)1349 name_is_style_property (const char *name)
1350 {
1351 if (name[0] != '-')
1352 return FALSE;
1353
1354 if (g_str_has_prefix (name, "-gtk-"))
1355 return FALSE;
1356
1357 return TRUE;
1358 }
1359
1360 static void
warn_if_deprecated(GtkCssScanner * scanner,const gchar * name)1361 warn_if_deprecated (GtkCssScanner *scanner,
1362 const gchar *name)
1363 {
1364 gchar *n = NULL;
1365 gchar *p;
1366 const gchar *type_name;
1367 const gchar *property_name;
1368 GType type;
1369 GTypeClass *class = NULL;
1370 GParamSpec *pspec;
1371
1372 n = g_strdup (name);
1373
1374 /* skip initial - */
1375 type_name = n + 1;
1376
1377 p = strchr (type_name, '-');
1378 if (!p)
1379 goto out;
1380
1381 p[0] = '\0';
1382 property_name = p + 1;
1383
1384 type = g_type_from_name (type_name);
1385 if (type == G_TYPE_INVALID ||
1386 !g_type_is_a (type, GTK_TYPE_WIDGET))
1387 goto out;
1388
1389 class = g_type_class_ref (type);
1390 pspec = gtk_widget_class_find_style_property (GTK_WIDGET_CLASS (class), property_name);
1391 if (!pspec)
1392 goto out;
1393
1394 if (!(pspec->flags & G_PARAM_DEPRECATED))
1395 goto out;
1396
1397 _gtk_css_parser_error_full (scanner->parser,
1398 GTK_CSS_PROVIDER_ERROR_DEPRECATED,
1399 "The style property %s:%s is deprecated and shouldn't be "
1400 "used anymore. It will be removed in a future version",
1401 g_type_name (pspec->owner_type), pspec->name);
1402
1403 out:
1404 g_free (n);
1405 if (class)
1406 g_type_class_unref (class);
1407 }
1408
1409 static void
parse_declaration(GtkCssScanner * scanner,GtkCssRuleset * ruleset)1410 parse_declaration (GtkCssScanner *scanner,
1411 GtkCssRuleset *ruleset)
1412 {
1413 GtkStyleProperty *property;
1414 char *name;
1415
1416 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_DECLARATION);
1417
1418 name = _gtk_css_parser_try_ident (scanner->parser, TRUE);
1419 if (name == NULL)
1420 goto check_for_semicolon;
1421
1422 property = _gtk_style_property_lookup (name);
1423 if (property == NULL && !name_is_style_property (name))
1424 {
1425 gtk_css_provider_error (scanner->provider,
1426 scanner,
1427 GTK_CSS_PROVIDER_ERROR,
1428 GTK_CSS_PROVIDER_ERROR_NAME,
1429 "'%s' is not a valid property name",
1430 name);
1431 _gtk_css_parser_resync (scanner->parser, TRUE, '}');
1432 g_free (name);
1433 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
1434 return;
1435 }
1436
1437 if (property != NULL && strcmp (name, property->name) != 0)
1438 {
1439 gtk_css_provider_error (scanner->provider,
1440 scanner,
1441 GTK_CSS_PROVIDER_ERROR,
1442 GTK_CSS_PROVIDER_ERROR_DEPRECATED,
1443 "The '%s' property has been renamed to '%s'",
1444 name, property->name);
1445 }
1446 else if (strcmp (name, "engine") == 0)
1447 {
1448 gtk_css_provider_error (scanner->provider,
1449 scanner,
1450 GTK_CSS_PROVIDER_ERROR,
1451 GTK_CSS_PROVIDER_ERROR_DEPRECATED,
1452 "The '%s' property is ignored",
1453 name);
1454 }
1455
1456 if (!_gtk_css_parser_try (scanner->parser, ":", TRUE))
1457 {
1458 gtk_css_provider_invalid_token (scanner->provider, scanner, "':'");
1459 _gtk_css_parser_resync (scanner->parser, TRUE, '}');
1460 g_free (name);
1461 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
1462 return;
1463 }
1464
1465 if (property)
1466 {
1467 GtkCssValue *value;
1468
1469 g_free (name);
1470
1471 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_VALUE);
1472
1473 value = _gtk_style_property_parse_value (property,
1474 scanner->parser);
1475
1476 if (value == NULL)
1477 {
1478 _gtk_css_parser_resync (scanner->parser, TRUE, '}');
1479 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE);
1480 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
1481 return;
1482 }
1483
1484 if (!_gtk_css_parser_begins_with (scanner->parser, ';') &&
1485 !_gtk_css_parser_begins_with (scanner->parser, '}') &&
1486 !_gtk_css_parser_is_eof (scanner->parser))
1487 {
1488 gtk_css_provider_error (scanner->provider,
1489 scanner,
1490 GTK_CSS_PROVIDER_ERROR,
1491 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1492 "Junk at end of value for %s", property->name);
1493 _gtk_css_parser_resync (scanner->parser, TRUE, '}');
1494 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE);
1495 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
1496 return;
1497 }
1498
1499 if (GTK_IS_CSS_SHORTHAND_PROPERTY (property))
1500 {
1501 GtkCssShorthandProperty *shorthand = GTK_CSS_SHORTHAND_PROPERTY (property);
1502 guint i;
1503
1504 for (i = 0; i < _gtk_css_shorthand_property_get_n_subproperties (shorthand); i++)
1505 {
1506 GtkCssStyleProperty *child = _gtk_css_shorthand_property_get_subproperty (shorthand, i);
1507 GtkCssValue *sub = _gtk_css_array_value_get_nth (value, i);
1508
1509 gtk_css_ruleset_add (ruleset, child, _gtk_css_value_ref (sub), scanner->section);
1510 }
1511
1512 _gtk_css_value_unref (value);
1513 }
1514 else if (GTK_IS_CSS_STYLE_PROPERTY (property))
1515 {
1516 gtk_css_ruleset_add (ruleset, GTK_CSS_STYLE_PROPERTY (property), value, scanner->section);
1517 }
1518 else
1519 {
1520 g_assert_not_reached ();
1521 _gtk_css_value_unref (value);
1522 }
1523
1524
1525 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE);
1526 }
1527 else if (name_is_style_property (name))
1528 {
1529 char *value_str;
1530
1531 warn_if_deprecated (scanner, name);
1532
1533 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_VALUE);
1534
1535 value_str = _gtk_css_parser_read_value (scanner->parser);
1536 if (value_str)
1537 {
1538 WidgetPropertyValue *val;
1539
1540 val = widget_property_value_new (name, scanner->section);
1541 val->value = value_str;
1542
1543 gtk_css_ruleset_add_style (ruleset, name, val);
1544 }
1545 else
1546 {
1547 _gtk_css_parser_resync (scanner->parser, TRUE, '}');
1548 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE);
1549 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
1550 return;
1551 }
1552
1553 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_VALUE);
1554 }
1555 else
1556 g_free (name);
1557
1558 check_for_semicolon:
1559 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DECLARATION);
1560
1561 if (!_gtk_css_parser_try (scanner->parser, ";", TRUE))
1562 {
1563 if (!_gtk_css_parser_begins_with (scanner->parser, '}') &&
1564 !_gtk_css_parser_is_eof (scanner->parser))
1565 {
1566 gtk_css_provider_error_literal (scanner->provider,
1567 scanner,
1568 GTK_CSS_PROVIDER_ERROR,
1569 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1570 "Expected semicolon");
1571 _gtk_css_parser_resync (scanner->parser, TRUE, '}');
1572 }
1573 }
1574 }
1575
1576 static void
parse_declarations(GtkCssScanner * scanner,GtkCssRuleset * ruleset)1577 parse_declarations (GtkCssScanner *scanner,
1578 GtkCssRuleset *ruleset)
1579 {
1580 while (!_gtk_css_parser_is_eof (scanner->parser) &&
1581 !_gtk_css_parser_begins_with (scanner->parser, '}'))
1582 {
1583 parse_declaration (scanner, ruleset);
1584 }
1585 }
1586
1587 static void
parse_ruleset(GtkCssScanner * scanner)1588 parse_ruleset (GtkCssScanner *scanner)
1589 {
1590 GSList *selectors;
1591 GtkCssRuleset ruleset = { 0, };
1592
1593 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_RULESET);
1594
1595 selectors = parse_selector_list (scanner);
1596 if (selectors == NULL)
1597 {
1598 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_RULESET);
1599 return;
1600 }
1601
1602 if (!_gtk_css_parser_try (scanner->parser, "{", TRUE))
1603 {
1604 gtk_css_provider_error_literal (scanner->provider,
1605 scanner,
1606 GTK_CSS_PROVIDER_ERROR,
1607 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1608 "expected '{' after selectors");
1609 _gtk_css_parser_resync (scanner->parser, FALSE, 0);
1610 g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
1611 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_RULESET);
1612 return;
1613 }
1614
1615 parse_declarations (scanner, &ruleset);
1616
1617 if (!_gtk_css_parser_try (scanner->parser, "}", TRUE))
1618 {
1619 gtk_css_provider_error_literal (scanner->provider,
1620 scanner,
1621 GTK_CSS_PROVIDER_ERROR,
1622 GTK_CSS_PROVIDER_ERROR_SYNTAX,
1623 "expected '}' after declarations");
1624 if (!_gtk_css_parser_is_eof (scanner->parser))
1625 {
1626 _gtk_css_parser_resync (scanner->parser, FALSE, 0);
1627 g_slist_free_full (selectors, (GDestroyNotify) _gtk_css_selector_free);
1628 gtk_css_ruleset_clear (&ruleset);
1629 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_RULESET);
1630 }
1631 }
1632
1633 css_provider_commit (scanner->provider, selectors, &ruleset);
1634 gtk_css_ruleset_clear (&ruleset);
1635 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_RULESET);
1636 }
1637
1638 static void
parse_statement(GtkCssScanner * scanner)1639 parse_statement (GtkCssScanner *scanner)
1640 {
1641 if (_gtk_css_parser_begins_with (scanner->parser, '@'))
1642 parse_at_keyword (scanner);
1643 else
1644 parse_ruleset (scanner);
1645 }
1646
1647 static void
parse_stylesheet(GtkCssScanner * scanner)1648 parse_stylesheet (GtkCssScanner *scanner)
1649 {
1650 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_DOCUMENT);
1651
1652 _gtk_css_parser_skip_whitespace (scanner->parser);
1653
1654 while (!_gtk_css_parser_is_eof (scanner->parser))
1655 {
1656 if (_gtk_css_parser_try (scanner->parser, "<!--", TRUE) ||
1657 _gtk_css_parser_try (scanner->parser, "-->", TRUE))
1658 continue;
1659
1660 parse_statement (scanner);
1661 }
1662
1663 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DOCUMENT);
1664 }
1665
1666 static int
gtk_css_provider_compare_rule(gconstpointer a_,gconstpointer b_)1667 gtk_css_provider_compare_rule (gconstpointer a_,
1668 gconstpointer b_)
1669 {
1670 const GtkCssRuleset *a = (const GtkCssRuleset *) a_;
1671 const GtkCssRuleset *b = (const GtkCssRuleset *) b_;
1672 int compare;
1673
1674 compare = _gtk_css_selector_compare (a->selector, b->selector);
1675 if (compare != 0)
1676 return compare;
1677
1678 return 0;
1679 }
1680
1681 static void
gtk_css_provider_postprocess(GtkCssProvider * css_provider)1682 gtk_css_provider_postprocess (GtkCssProvider *css_provider)
1683 {
1684 GtkCssProviderPrivate *priv = css_provider->priv;
1685 GtkCssSelectorTreeBuilder *builder;
1686 guint i;
1687
1688 g_array_sort (priv->rulesets, gtk_css_provider_compare_rule);
1689
1690 builder = _gtk_css_selector_tree_builder_new ();
1691 for (i = 0; i < priv->rulesets->len; i++)
1692 {
1693 GtkCssRuleset *ruleset;
1694
1695 ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i);
1696
1697 _gtk_css_selector_tree_builder_add (builder,
1698 ruleset->selector,
1699 &ruleset->selector_match,
1700 ruleset);
1701 }
1702
1703 priv->tree = _gtk_css_selector_tree_builder_build (builder);
1704 _gtk_css_selector_tree_builder_free (builder);
1705
1706 #ifndef VERIFY_TREE
1707 for (i = 0; i < priv->rulesets->len; i++)
1708 {
1709 GtkCssRuleset *ruleset;
1710
1711 ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i);
1712
1713 _gtk_css_selector_free (ruleset->selector);
1714 ruleset->selector = NULL;
1715 }
1716 #endif
1717 }
1718
1719 static gboolean
gtk_css_provider_load_internal(GtkCssProvider * css_provider,GtkCssScanner * parent,GFile * file,const char * text,GError ** error)1720 gtk_css_provider_load_internal (GtkCssProvider *css_provider,
1721 GtkCssScanner *parent,
1722 GFile *file,
1723 const char *text,
1724 GError **error)
1725 {
1726 GBytes *free_bytes = NULL;
1727 GtkCssScanner *scanner;
1728 gulong error_handler;
1729
1730 if (error)
1731 error_handler = g_signal_connect (css_provider,
1732 "parsing-error",
1733 G_CALLBACK (gtk_css_provider_propagate_error),
1734 error);
1735 else
1736 error_handler = 0; /* silence gcc */
1737
1738 if (text == NULL)
1739 {
1740 GError *load_error = NULL;
1741
1742 free_bytes = gtk_file_load_bytes (file, NULL, &load_error);
1743
1744 if (free_bytes != NULL)
1745 {
1746 text = g_bytes_get_data (free_bytes, NULL);
1747 }
1748 else
1749 {
1750 if (parent == NULL)
1751 {
1752 scanner = gtk_css_scanner_new (css_provider,
1753 NULL,
1754 NULL,
1755 file,
1756 "");
1757
1758 gtk_css_scanner_push_section (scanner, GTK_CSS_SECTION_DOCUMENT);
1759 }
1760 else
1761 scanner = parent;
1762
1763 gtk_css_provider_error (css_provider,
1764 scanner,
1765 GTK_CSS_PROVIDER_ERROR,
1766 GTK_CSS_PROVIDER_ERROR_IMPORT,
1767 "Failed to import: %s",
1768 load_error->message);
1769
1770 if (parent == NULL)
1771 {
1772 gtk_css_scanner_pop_section (scanner, GTK_CSS_SECTION_DOCUMENT);
1773
1774 gtk_css_scanner_destroy (scanner);
1775 }
1776 }
1777 }
1778
1779 if (text)
1780 {
1781 scanner = gtk_css_scanner_new (css_provider,
1782 parent,
1783 parent ? parent->section : NULL,
1784 file,
1785 text);
1786
1787 parse_stylesheet (scanner);
1788
1789 gtk_css_scanner_destroy (scanner);
1790
1791 if (parent == NULL)
1792 gtk_css_provider_postprocess (css_provider);
1793 }
1794
1795 if (free_bytes)
1796 g_bytes_unref (free_bytes);
1797
1798 if (error)
1799 {
1800 g_signal_handler_disconnect (css_provider, error_handler);
1801
1802 if (*error)
1803 {
1804 /* We clear all contents from the provider for backwards compat reasons */
1805 gtk_css_provider_reset (css_provider);
1806 return FALSE;
1807 }
1808 }
1809
1810 return TRUE;
1811 }
1812
1813 /**
1814 * gtk_css_provider_load_from_data:
1815 * @css_provider: a #GtkCssProvider
1816 * @data: (array length=length) (element-type guint8): CSS data loaded in memory
1817 * @length: the length of @data in bytes, or -1 for NUL terminated strings. If
1818 * @length is not -1, the code will assume it is not NUL terminated and will
1819 * potentially do a copy.
1820 * @error: (out) (allow-none): return location for a #GError, or %NULL
1821 *
1822 * Loads @data into @css_provider, and by doing so clears any previously loaded
1823 * information.
1824 *
1825 * Returns: %TRUE. The return value is deprecated and %FALSE will only be
1826 * returned for backwards compatibility reasons if an @error is not
1827 * %NULL and a loading error occurred. To track errors while loading
1828 * CSS, connect to the #GtkCssProvider::parsing-error signal.
1829 **/
1830 gboolean
gtk_css_provider_load_from_data(GtkCssProvider * css_provider,const gchar * data,gssize length,GError ** error)1831 gtk_css_provider_load_from_data (GtkCssProvider *css_provider,
1832 const gchar *data,
1833 gssize length,
1834 GError **error)
1835 {
1836 char *free_data;
1837 gboolean ret;
1838
1839 g_return_val_if_fail (GTK_IS_CSS_PROVIDER (css_provider), FALSE);
1840 g_return_val_if_fail (data != NULL, FALSE);
1841
1842 if (length < 0)
1843 {
1844 length = strlen (data);
1845 free_data = NULL;
1846 }
1847 else
1848 {
1849 free_data = g_strndup (data, length);
1850 data = free_data;
1851 }
1852
1853 gtk_css_provider_reset (css_provider);
1854
1855 ret = gtk_css_provider_load_internal (css_provider, NULL, NULL, data, error);
1856
1857 g_free (free_data);
1858
1859 _gtk_style_provider_private_changed (GTK_STYLE_PROVIDER_PRIVATE (css_provider));
1860
1861 return ret;
1862 }
1863
1864 /**
1865 * gtk_css_provider_load_from_file:
1866 * @css_provider: a #GtkCssProvider
1867 * @file: #GFile pointing to a file to load
1868 * @error: (out) (allow-none): return location for a #GError, or %NULL
1869 *
1870 * Loads the data contained in @file into @css_provider, making it
1871 * clear any previously loaded information.
1872 *
1873 * Returns: %TRUE. The return value is deprecated and %FALSE will only be
1874 * returned for backwards compatibility reasons if an @error is not
1875 * %NULL and a loading error occurred. To track errors while loading
1876 * CSS, connect to the #GtkCssProvider::parsing-error signal.
1877 **/
1878 gboolean
gtk_css_provider_load_from_file(GtkCssProvider * css_provider,GFile * file,GError ** error)1879 gtk_css_provider_load_from_file (GtkCssProvider *css_provider,
1880 GFile *file,
1881 GError **error)
1882 {
1883 gboolean success;
1884
1885 g_return_val_if_fail (GTK_IS_CSS_PROVIDER (css_provider), FALSE);
1886 g_return_val_if_fail (G_IS_FILE (file), FALSE);
1887
1888 gtk_css_provider_reset (css_provider);
1889
1890 success = gtk_css_provider_load_internal (css_provider, NULL, file, NULL, error);
1891
1892 _gtk_style_provider_private_changed (GTK_STYLE_PROVIDER_PRIVATE (css_provider));
1893
1894 return success;
1895 }
1896
1897 /**
1898 * gtk_css_provider_load_from_path:
1899 * @css_provider: a #GtkCssProvider
1900 * @path: the path of a filename to load, in the GLib filename encoding
1901 * @error: (out) (allow-none): return location for a #GError, or %NULL
1902 *
1903 * Loads the data contained in @path into @css_provider, making it clear
1904 * any previously loaded information.
1905 *
1906 * Returns: %TRUE. The return value is deprecated and %FALSE will only be
1907 * returned for backwards compatibility reasons if an @error is not
1908 * %NULL and a loading error occurred. To track errors while loading
1909 * CSS, connect to the #GtkCssProvider::parsing-error signal.
1910 **/
1911 gboolean
gtk_css_provider_load_from_path(GtkCssProvider * css_provider,const gchar * path,GError ** error)1912 gtk_css_provider_load_from_path (GtkCssProvider *css_provider,
1913 const gchar *path,
1914 GError **error)
1915 {
1916 GFile *file;
1917 gboolean result;
1918
1919 g_return_val_if_fail (GTK_IS_CSS_PROVIDER (css_provider), FALSE);
1920 g_return_val_if_fail (path != NULL, FALSE);
1921
1922 file = g_file_new_for_path (path);
1923
1924 result = gtk_css_provider_load_from_file (css_provider, file, error);
1925
1926 g_object_unref (file);
1927
1928 return result;
1929 }
1930
1931 /**
1932 * gtk_css_provider_load_from_resource:
1933 * @css_provider: a #GtkCssProvider
1934 * @resource_path: a #GResource resource path
1935 *
1936 * Loads the data contained in the resource at @resource_path into
1937 * the #GtkCssProvider, clearing any previously loaded information.
1938 *
1939 * To track errors while loading CSS, connect to the
1940 * #GtkCssProvider::parsing-error signal.
1941 *
1942 * Since: 3.16
1943 */
1944 void
gtk_css_provider_load_from_resource(GtkCssProvider * css_provider,const gchar * resource_path)1945 gtk_css_provider_load_from_resource (GtkCssProvider *css_provider,
1946 const gchar *resource_path)
1947 {
1948 GFile *file;
1949 gchar *uri, *escaped;
1950
1951 g_return_if_fail (GTK_IS_CSS_PROVIDER (css_provider));
1952 g_return_if_fail (resource_path != NULL);
1953
1954 escaped = g_uri_escape_string (resource_path,
1955 G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
1956 uri = g_strconcat ("resource://", escaped, NULL);
1957 g_free (escaped);
1958
1959 file = g_file_new_for_uri (uri);
1960 g_free (uri);
1961
1962 gtk_css_provider_load_from_file (css_provider, file, NULL);
1963
1964 g_object_unref (file);
1965 }
1966
1967 /**
1968 * gtk_css_provider_get_default:
1969 *
1970 * Returns the provider containing the style settings used as a
1971 * fallback for all widgets.
1972 *
1973 * Returns: (transfer none): The provider used for fallback styling.
1974 * This memory is owned by GTK+, and you must not free it.
1975 *
1976 * Deprecated: 3.24: Use gtk_css_provider_new() instead.
1977 **/
1978 GtkCssProvider *
gtk_css_provider_get_default(void)1979 gtk_css_provider_get_default (void)
1980 {
1981 static GtkCssProvider *provider;
1982
1983 if (G_UNLIKELY (!provider))
1984 {
1985 provider = gtk_css_provider_new ();
1986 }
1987
1988 return provider;
1989 }
1990
1991 gchar *
_gtk_get_theme_dir(void)1992 _gtk_get_theme_dir (void)
1993 {
1994 const gchar *var;
1995
1996 var = g_getenv ("GTK_DATA_PREFIX");
1997 if (var == NULL)
1998 var = _gtk_get_data_prefix ();
1999 return g_build_filename (var, "share", "themes", NULL);
2000 }
2001
2002 /* Return the path that this providers gtk.css was loaded from,
2003 * if it is part of a theme, otherwise NULL.
2004 */
2005 const gchar *
_gtk_css_provider_get_theme_dir(GtkCssProvider * provider)2006 _gtk_css_provider_get_theme_dir (GtkCssProvider *provider)
2007 {
2008 return provider->priv->path;
2009 }
2010
2011 #if (GTK_MINOR_VERSION % 2)
2012 #define MINOR (GTK_MINOR_VERSION + 1)
2013 #else
2014 #define MINOR GTK_MINOR_VERSION
2015 #endif
2016
2017 /*
2018 * Look for
2019 * $dir/$subdir/gtk-3.16/gtk-$variant.css
2020 * $dir/$subdir/gtk-3.14/gtk-$variant.css
2021 * ...
2022 * $dir/$subdir/gtk-3.0/gtk-$variant.css
2023 * and return the first found file.
2024 * We don't check versions before 3.14,
2025 * since those GTK+ versions didn't have
2026 * the versioned loading mechanism.
2027 */
2028 static gchar *
_gtk_css_find_theme_dir(const gchar * dir,const gchar * subdir,const gchar * name,const gchar * variant)2029 _gtk_css_find_theme_dir (const gchar *dir,
2030 const gchar *subdir,
2031 const gchar *name,
2032 const gchar *variant)
2033 {
2034 gchar *file;
2035 gchar *base;
2036 gchar *subsubdir;
2037 gint i;
2038 gchar *path;
2039
2040 if (variant)
2041 file = g_strconcat ("gtk-", variant, ".css", NULL);
2042 else
2043 file = g_strdup ("gtk.css");
2044
2045 if (subdir)
2046 base = g_build_filename (dir, subdir, name, NULL);
2047 else
2048 base = g_build_filename (dir, name, NULL);
2049
2050 for (i = MINOR; i >= 0; i = i - 2)
2051 {
2052 if (i < 14)
2053 i = 0;
2054
2055 subsubdir = g_strdup_printf ("gtk-3.%d", i);
2056 path = g_build_filename (base, subsubdir, file, NULL);
2057 g_free (subsubdir);
2058
2059 if (g_file_test (path, G_FILE_TEST_EXISTS))
2060 break;
2061
2062 g_free (path);
2063 path = NULL;
2064 }
2065
2066 g_free (file);
2067 g_free (base);
2068
2069 return path;
2070 }
2071
2072 #undef MINOR
2073
2074 static gchar *
_gtk_css_find_theme(const gchar * name,const gchar * variant)2075 _gtk_css_find_theme (const gchar *name,
2076 const gchar *variant)
2077 {
2078 gchar *path;
2079 const char *const *dirs;
2080 int i;
2081 char *dir;
2082
2083 /* First look in the user's data directory */
2084 path = _gtk_css_find_theme_dir (g_get_user_data_dir (), "themes", name, variant);
2085 if (path)
2086 return path;
2087
2088 /* Next look in the user's home directory */
2089 path = _gtk_css_find_theme_dir (g_get_home_dir (), ".themes", name, variant);
2090 if (path)
2091 return path;
2092
2093 /* Look in system data directories */
2094 dirs = g_get_system_data_dirs ();
2095 for (i = 0; dirs[i]; i++)
2096 {
2097 path = _gtk_css_find_theme_dir (dirs[i], "themes", name, variant);
2098 if (path)
2099 return path;
2100 }
2101
2102 /* Finally, try in the default theme directory */
2103 dir = _gtk_get_theme_dir ();
2104 path = _gtk_css_find_theme_dir (dir, NULL, name, variant);
2105 g_free (dir);
2106
2107 return path;
2108 }
2109
2110 /**
2111 * _gtk_css_provider_load_named:
2112 * @provider: a #GtkCssProvider
2113 * @name: A theme name
2114 * @variant: (allow-none): variant to load, for example, "dark", or
2115 * %NULL for the default
2116 *
2117 * Loads a theme from the usual theme paths. The actual process of
2118 * finding the theme might change between releases, but it is
2119 * guaranteed that this function uses the same mechanism to load the
2120 * theme than GTK uses for loading its own theme.
2121 **/
2122 void
_gtk_css_provider_load_named(GtkCssProvider * provider,const gchar * name,const gchar * variant)2123 _gtk_css_provider_load_named (GtkCssProvider *provider,
2124 const gchar *name,
2125 const gchar *variant)
2126 {
2127 gchar *path;
2128 gchar *resource_path;
2129
2130 g_return_if_fail (GTK_IS_CSS_PROVIDER (provider));
2131 g_return_if_fail (name != NULL);
2132
2133 gtk_css_provider_reset (provider);
2134
2135 /* try loading the resource for the theme. This is mostly meant for built-in
2136 * themes.
2137 */
2138 if (variant)
2139 resource_path = g_strdup_printf ("/org/gtk/libgtk/theme/%s/gtk-%s.css", name, variant);
2140 else
2141 resource_path = g_strdup_printf ("/org/gtk/libgtk/theme/%s/gtk.css", name);
2142
2143 if (g_resources_get_info (resource_path, 0, NULL, NULL, NULL))
2144 {
2145 gtk_css_provider_load_from_resource (provider, resource_path);
2146 g_free (resource_path);
2147 return;
2148 }
2149 g_free (resource_path);
2150
2151 /* Next try looking for files in the various theme directories. */
2152 path = _gtk_css_find_theme (name, variant);
2153 if (path)
2154 {
2155 char *dir, *resource_file;
2156 GResource *resource;
2157
2158 dir = g_path_get_dirname (path);
2159 resource_file = g_build_filename (dir, "gtk.gresource", NULL);
2160 resource = g_resource_load (resource_file, NULL);
2161 g_free (resource_file);
2162
2163 if (resource != NULL)
2164 g_resources_register (resource);
2165
2166 gtk_css_provider_load_from_path (provider, path, NULL);
2167
2168 /* Only set this after load, as load_from_path will clear it */
2169 provider->priv->resource = resource;
2170 provider->priv->path = dir;
2171
2172 g_free (path);
2173 }
2174 else
2175 {
2176 /* Things failed! Fall back! Fall back! */
2177
2178 if (variant)
2179 {
2180 /* If there was a variant, try without */
2181 _gtk_css_provider_load_named (provider, name, NULL);
2182 }
2183 else
2184 {
2185 /* Worst case, fall back to the default */
2186 g_return_if_fail (!g_str_equal (name, DEFAULT_THEME_NAME)); /* infloop protection */
2187 _gtk_css_provider_load_named (provider, DEFAULT_THEME_NAME, NULL);
2188 }
2189 }
2190 }
2191
2192 /**
2193 * gtk_css_provider_get_named:
2194 * @name: A theme name
2195 * @variant: (allow-none): variant to load, for example, "dark", or
2196 * %NULL for the default
2197 *
2198 * Loads a theme from the usual theme paths
2199 *
2200 * Returns: (transfer none): a #GtkCssProvider with the theme loaded.
2201 * This memory is owned by GTK+, and you must not free it.
2202 */
2203 GtkCssProvider *
gtk_css_provider_get_named(const gchar * name,const gchar * variant)2204 gtk_css_provider_get_named (const gchar *name,
2205 const gchar *variant)
2206 {
2207 static GHashTable *themes = NULL;
2208 GtkCssProvider *provider;
2209 gchar *key;
2210
2211 if (variant == NULL)
2212 key = g_strdup (name);
2213 else
2214 key = g_strconcat (name, "-", variant, NULL);
2215 if (G_UNLIKELY (!themes))
2216 themes = g_hash_table_new (g_str_hash, g_str_equal);
2217
2218 provider = g_hash_table_lookup (themes, key);
2219
2220 if (!provider)
2221 {
2222 provider = gtk_css_provider_new ();
2223 _gtk_css_provider_load_named (provider, name, variant);
2224 g_hash_table_insert (themes, g_strdup (key), provider);
2225 }
2226
2227 g_free (key);
2228
2229 return provider;
2230 }
2231
2232 static int
compare_properties(gconstpointer a,gconstpointer b,gpointer style)2233 compare_properties (gconstpointer a, gconstpointer b, gpointer style)
2234 {
2235 const guint *ua = a;
2236 const guint *ub = b;
2237 PropertyValue *styles = style;
2238
2239 return strcmp (_gtk_style_property_get_name (GTK_STYLE_PROPERTY (styles[*ua].property)),
2240 _gtk_style_property_get_name (GTK_STYLE_PROPERTY (styles[*ub].property)));
2241 }
2242
2243 static int
compare_names(gconstpointer a,gconstpointer b)2244 compare_names (gconstpointer a, gconstpointer b)
2245 {
2246 const WidgetPropertyValue *aa = a;
2247 const WidgetPropertyValue *bb = b;
2248 return strcmp (aa->name, bb->name);
2249 }
2250
2251 static void
gtk_css_ruleset_print(const GtkCssRuleset * ruleset,GString * str)2252 gtk_css_ruleset_print (const GtkCssRuleset *ruleset,
2253 GString *str)
2254 {
2255 GList *values, *walk;
2256 WidgetPropertyValue *widget_value;
2257 guint i;
2258
2259 _gtk_css_selector_tree_match_print (ruleset->selector_match, str);
2260
2261 g_string_append (str, " {\n");
2262
2263 if (ruleset->styles)
2264 {
2265 guint *sorted = g_new (guint, ruleset->n_styles);
2266
2267 for (i = 0; i < ruleset->n_styles; i++)
2268 sorted[i] = i;
2269
2270 /* so the output is identical for identical selector styles */
2271 g_qsort_with_data (sorted, ruleset->n_styles, sizeof (guint), compare_properties, ruleset->styles);
2272
2273 for (i = 0; i < ruleset->n_styles; i++)
2274 {
2275 PropertyValue *prop = &ruleset->styles[sorted[i]];
2276 g_string_append (str, " ");
2277 g_string_append (str, _gtk_style_property_get_name (GTK_STYLE_PROPERTY (prop->property)));
2278 g_string_append (str, ": ");
2279 _gtk_css_value_print (prop->value, str);
2280 g_string_append (str, ";\n");
2281 }
2282
2283 g_free (sorted);
2284 }
2285
2286 if (ruleset->widget_style)
2287 {
2288 values = NULL;
2289 for (widget_value = ruleset->widget_style; widget_value != NULL; widget_value = widget_value->next)
2290 values = g_list_prepend (values, widget_value);
2291
2292 /* so the output is identical for identical selector styles */
2293 values = g_list_sort (values, compare_names);
2294
2295 for (walk = values; walk; walk = walk->next)
2296 {
2297 widget_value = walk->data;
2298
2299 g_string_append (str, " ");
2300 g_string_append (str, widget_value->name);
2301 g_string_append (str, ": ");
2302 g_string_append (str, widget_value->value);
2303 g_string_append (str, ";\n");
2304 }
2305
2306 g_list_free (values);
2307 }
2308
2309 g_string_append (str, "}\n");
2310 }
2311
2312 static void
gtk_css_provider_print_colors(GHashTable * colors,GString * str)2313 gtk_css_provider_print_colors (GHashTable *colors,
2314 GString *str)
2315 {
2316 GList *keys, *walk;
2317
2318 keys = g_hash_table_get_keys (colors);
2319 /* so the output is identical for identical styles */
2320 keys = g_list_sort (keys, (GCompareFunc) strcmp);
2321
2322 for (walk = keys; walk; walk = walk->next)
2323 {
2324 const char *name = walk->data;
2325 GtkCssValue *color = g_hash_table_lookup (colors, (gpointer) name);
2326
2327 g_string_append (str, "@define-color ");
2328 g_string_append (str, name);
2329 g_string_append (str, " ");
2330 _gtk_css_value_print (color, str);
2331 g_string_append (str, ";\n");
2332 }
2333
2334 g_list_free (keys);
2335 }
2336
2337 static void
gtk_css_provider_print_keyframes(GHashTable * keyframes,GString * str)2338 gtk_css_provider_print_keyframes (GHashTable *keyframes,
2339 GString *str)
2340 {
2341 GList *keys, *walk;
2342
2343 keys = g_hash_table_get_keys (keyframes);
2344 /* so the output is identical for identical styles */
2345 keys = g_list_sort (keys, (GCompareFunc) strcmp);
2346
2347 for (walk = keys; walk; walk = walk->next)
2348 {
2349 const char *name = walk->data;
2350 GtkCssKeyframes *keyframe = g_hash_table_lookup (keyframes, (gpointer) name);
2351
2352 if (str->len > 0)
2353 g_string_append (str, "\n");
2354 g_string_append (str, "@keyframes ");
2355 g_string_append (str, name);
2356 g_string_append (str, " {\n");
2357 _gtk_css_keyframes_print (keyframe, str);
2358 g_string_append (str, "}\n");
2359 }
2360
2361 g_list_free (keys);
2362 }
2363
2364 /**
2365 * gtk_css_provider_to_string:
2366 * @provider: the provider to write to a string
2367 *
2368 * Converts the @provider into a string representation in CSS
2369 * format.
2370 *
2371 * Using gtk_css_provider_load_from_data() with the return value
2372 * from this function on a new provider created with
2373 * gtk_css_provider_new() will basically create a duplicate of
2374 * this @provider.
2375 *
2376 * Returns: a new string representing the @provider.
2377 *
2378 * Since: 3.2
2379 **/
2380 char *
gtk_css_provider_to_string(GtkCssProvider * provider)2381 gtk_css_provider_to_string (GtkCssProvider *provider)
2382 {
2383 GtkCssProviderPrivate *priv;
2384 GString *str;
2385 guint i;
2386
2387 g_return_val_if_fail (GTK_IS_CSS_PROVIDER (provider), NULL);
2388
2389 priv = provider->priv;
2390
2391 str = g_string_new ("");
2392
2393 gtk_css_provider_print_colors (priv->symbolic_colors, str);
2394 gtk_css_provider_print_keyframes (priv->keyframes, str);
2395
2396 for (i = 0; i < priv->rulesets->len; i++)
2397 {
2398 if (str->len != 0)
2399 g_string_append (str, "\n");
2400 gtk_css_ruleset_print (&g_array_index (priv->rulesets, GtkCssRuleset, i), str);
2401 }
2402
2403 return g_string_free (str, FALSE);
2404 }
2405
2406