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 "gtkcssproviderprivate.h"
21
22#include <gtk/css/gtkcss.h>
23#include "gtk/css/gtkcsstokenizerprivate.h"
24#include "gtk/css/gtkcssparserprivate.h"
25#include "gtkbitmaskprivate.h"
26#include "gtkcssarrayvalueprivate.h"
27#include "gtkcsscolorvalueprivate.h"
28#include "gtkcsskeyframesprivate.h"
29#include "gtkcssselectorprivate.h"
30#include "gtkcssshorthandpropertyprivate.h"
31#include "gtksettingsprivate.h"
32#include "gtkstyleprovider.h"
33#include "gtkstylepropertyprivate.h"
34#include "gtkstyleproviderprivate.h"
35#include "gtkmarshalers.h"
36#include "gtkprivate.h"
37#include "gtkintl.h"
38#include "gtkversion.h"
39
40#include <string.h>
41#include <stdlib.h>
42
43#include <gdk-pixbuf/gdk-pixbuf.h>
44#include "gdk/gdkprofilerprivate.h"
45#include <cairo-gobject.h>
46
47#define GDK_ARRAY_NAME gtk_css_selectors
48#define GDK_ARRAY_TYPE_NAME GtkCssSelectors
49#define GDK_ARRAY_ELEMENT_TYPE GtkCssSelector *
50#define GDK_ARRAY_PREALLOC 64
51#include "gdk/gdkarrayimpl.c"
52
53/**
54 * GtkCssProvider:
55 *
56 * `GtkCssProvider` is an object implementing the `GtkStyleProvider` interface
57 * for CSS.
58 *
59 * It is able to parse CSS-like input in order to style widgets.
60 *
61 * An application can make GTK parse a specific CSS style sheet by calling
62 * [method@Gtk.CssProvider.load_from_file] or
63 * [method@Gtk.CssProvider.load_from_resource]
64 * and adding the provider with [method@Gtk.StyleContext.add_provider] or
65 * [func@Gtk.StyleContext.add_provider_for_display].
66
67 * In addition, certain files will be read when GTK is initialized.
68 * First, the file `$XDG_CONFIG_HOME/gtk-4.0/gtk.css` is loaded if it
69 * exists. Then, GTK loads the first existing file among
70 * `XDG_DATA_HOME/themes/THEME/gtk-VERSION/gtk-VARIANT.css`,
71 * `$HOME/.themes/THEME/gtk-VERSION/gtk-VARIANT.css`,
72 * `$XDG_DATA_DIRS/themes/THEME/gtk-VERSION/gtk-VARIANT.css` and
73 * `DATADIR/share/themes/THEME/gtk-VERSION/gtk-VARIANT.css`,
74 * where `THEME` is the name of the current theme (see the
75 * [property@Gtk.Settings:gtk-theme-name] setting), `VARIANT` is the
76 * variant to load (see the
77 * [property@Gtk.Settings:gtk-application-prefer-dark-theme] setting),
78 * `DATADIR` is the prefix configured when GTK was compiled (unless
79 * overridden by the `GTK_DATA_PREFIX` environment variable), and
80 * `VERSION` is the GTK version number. If no file is found for the
81 * current version, GTK tries older versions all the way back to 4.0.
82 *
83 * To track errors while loading CSS, connect to the
84 * [signal@Gtk.CssProvider::parsing-error] signal.
85 */
86
87#define MAX_SELECTOR_LIST_LENGTH 64
88
89struct _GtkCssProviderClass
90{
91 GObjectClass parent_class;
92
93 void (* parsing_error) (GtkCssProvider *provider,
94 GtkCssSection *section,
95 const GError * error);
96};
97
98typedef struct GtkCssRuleset GtkCssRuleset;
99typedef struct _GtkCssScanner GtkCssScanner;
100typedef struct _PropertyValue PropertyValue;
101typedef enum ParserScope ParserScope;
102typedef enum ParserSymbol ParserSymbol;
103
104struct _PropertyValue {
105 GtkCssStyleProperty *property;
106 GtkCssValue *value;
107 GtkCssSection *section;
108};
109
110struct GtkCssRuleset
111{
112 GtkCssSelector *selector;
113 GtkCssSelectorTree *selector_match;
114 PropertyValue *styles;
115 guint n_styles;
116 guint owns_styles : 1;
117};
118
119struct _GtkCssScanner
120{
121 GtkCssProvider *provider;
122 GtkCssParser *parser;
123 GtkCssScanner *parent;
124};
125
126struct _GtkCssProviderPrivate
127{
128 GScanner *scanner;
129
130 GHashTable *symbolic_colors;
131 GHashTable *keyframes;
132
133 GArray *rulesets;
134 GtkCssSelectorTree *tree;
135 GResource *resource;
136 char *path;
137};
138
139enum {
140 PARSING_ERROR,
141 LAST_SIGNAL
142};
143
144static gboolean gtk_keep_css_sections = FALSE;
145
146static guint css_provider_signals[LAST_SIGNAL] = { 0 };
147
148static void gtk_css_provider_finalize (GObject *object);
149static void gtk_css_style_provider_iface_init (GtkStyleProviderInterface *iface);
150static void gtk_css_style_provider_emit_error (GtkStyleProvider *provider,
151 GtkCssSection *section,
152 const GError *error);
153
154static void
155gtk_css_provider_load_internal (GtkCssProvider *css_provider,
156 GtkCssScanner *scanner,
157 GFile *file,
158 GBytes *bytes);
159
160G_DEFINE_TYPE_EXTENDED (GtkCssProvider, gtk_css_provider, G_TYPE_OBJECT, 0,
161 G_ADD_PRIVATE (GtkCssProvider)
162 G_IMPLEMENT_INTERFACE (GTK_TYPE_STYLE_PROVIDER,
163 gtk_css_style_provider_iface_init));
164
165static void
166gtk_css_provider_parsing_error (GtkCssProvider *provider,
167 GtkCssSection *section,
168 const GError *error)
169{
170 /* Only emit a warning when we have no error handlers. This is our
171 * default handlers. And in this case erroneous CSS files are a bug
172 * and should be fixed.
173 * Note that these warnings can also be triggered by a broken theme
174 * that people installed from some weird location on the internets.
175 */
176 if (!g_signal_has_handler_pending (instance: provider,
177 signal_id: css_provider_signals[PARSING_ERROR],
178 detail: 0,
179 TRUE))
180 {
181 char *s = gtk_css_section_to_string (section);
182
183 g_warning ("Theme parser %s: %s: %s",
184 error->domain == GTK_CSS_PARSER_WARNING ? "warning" : "error",
185 s,
186 error->message);
187
188 g_free (mem: s);
189 }
190}
191
192/* This is exported privately for use in GtkInspector.
193 * It is the callers responsibility to reparse the current theme.
194 */
195void
196gtk_css_provider_set_keep_css_sections (void)
197{
198 gtk_keep_css_sections = TRUE;
199}
200
201static void
202gtk_css_provider_class_init (GtkCssProviderClass *klass)
203{
204 GObjectClass *object_class = G_OBJECT_CLASS (klass);
205
206 if (g_getenv (variable: "GTK_CSS_DEBUG"))
207 gtk_css_provider_set_keep_css_sections ();
208
209 /**
210 * GtkCssProvider::parsing-error:
211 * @provider: the provider that had a parsing error
212 * @section: section the error happened in
213 * @error: The parsing error
214 *
215 * Signals that a parsing error occurred.
216 *
217 * The @path, @line and @position describe the actual location of
218 * the error as accurately as possible.
219 *
220 * Parsing errors are never fatal, so the parsing will resume after
221 * the error. Errors may however cause parts of the given data or
222 * even all of it to not be parsed at all. So it is a useful idea
223 * to check that the parsing succeeds by connecting to this signal.
224 *
225 * Note that this signal may be emitted at any time as the css provider
226 * may opt to defer parsing parts or all of the input to a later time
227 * than when a loading function was called.
228 */
229 css_provider_signals[PARSING_ERROR] =
230 g_signal_new (I_("parsing-error"),
231 G_TYPE_FROM_CLASS (object_class),
232 signal_flags: G_SIGNAL_RUN_LAST,
233 G_STRUCT_OFFSET (GtkCssProviderClass, parsing_error),
234 NULL, NULL,
235 c_marshaller: _gtk_marshal_VOID__BOXED_BOXED,
236 G_TYPE_NONE, n_params: 2, GTK_TYPE_CSS_SECTION, G_TYPE_ERROR);
237
238 object_class->finalize = gtk_css_provider_finalize;
239
240 klass->parsing_error = gtk_css_provider_parsing_error;
241}
242
243static void
244gtk_css_ruleset_init_copy (GtkCssRuleset *new,
245 GtkCssRuleset *ruleset,
246 GtkCssSelector *selector)
247{
248 memcpy (dest: new, src: ruleset, n: sizeof (GtkCssRuleset));
249
250 new->selector = selector;
251 /* First copy takes over ownership */
252 if (ruleset->owns_styles)
253 ruleset->owns_styles = FALSE;
254}
255
256static void
257gtk_css_ruleset_clear (GtkCssRuleset *ruleset)
258{
259 if (ruleset->owns_styles)
260 {
261 guint i;
262
263 for (i = 0; i < ruleset->n_styles; i++)
264 {
265 _gtk_css_value_unref (value: ruleset->styles[i].value);
266 ruleset->styles[i].value = NULL;
267 if (ruleset->styles[i].section)
268 gtk_css_section_unref (section: ruleset->styles[i].section);
269 }
270 g_free (mem: ruleset->styles);
271 }
272 if (ruleset->selector)
273 _gtk_css_selector_free (selector: ruleset->selector);
274
275 memset (s: ruleset, c: 0, n: sizeof (GtkCssRuleset));
276}
277
278static void
279gtk_css_ruleset_add (GtkCssRuleset *ruleset,
280 GtkCssStyleProperty *property,
281 GtkCssValue *value,
282 GtkCssSection *section)
283{
284 guint i;
285
286 g_return_if_fail (ruleset->owns_styles || ruleset->n_styles == 0);
287
288 ruleset->owns_styles = TRUE;
289
290 for (i = 0; i < ruleset->n_styles; i++)
291 {
292 if (ruleset->styles[i].property == property)
293 {
294 _gtk_css_value_unref (value: ruleset->styles[i].value);
295 ruleset->styles[i].value = NULL;
296 if (ruleset->styles[i].section)
297 gtk_css_section_unref (section: ruleset->styles[i].section);
298 break;
299 }
300 }
301 if (i == ruleset->n_styles)
302 {
303 ruleset->n_styles++;
304 ruleset->styles = g_realloc (mem: ruleset->styles, n_bytes: ruleset->n_styles * sizeof (PropertyValue));
305 ruleset->styles[i].value = NULL;
306 ruleset->styles[i].property = property;
307 }
308
309 ruleset->styles[i].value = value;
310 if (gtk_keep_css_sections)
311 ruleset->styles[i].section = gtk_css_section_ref (section);
312 else
313 ruleset->styles[i].section = NULL;
314}
315
316static void
317gtk_css_scanner_destroy (GtkCssScanner *scanner)
318{
319 g_object_unref (object: scanner->provider);
320 gtk_css_parser_unref (self: scanner->parser);
321
322 g_slice_free (GtkCssScanner, scanner);
323}
324
325static void
326gtk_css_style_provider_emit_error (GtkStyleProvider *provider,
327 GtkCssSection *section,
328 const GError *error)
329{
330 g_signal_emit (instance: provider, signal_id: css_provider_signals[PARSING_ERROR], detail: 0, section, error);
331}
332
333static void
334gtk_css_scanner_parser_error (GtkCssParser *parser,
335 const GtkCssLocation *start,
336 const GtkCssLocation *end,
337 const GError *error,
338 gpointer user_data)
339{
340 GtkCssScanner *scanner = user_data;
341 GtkCssSection *section;
342
343 section = gtk_css_section_new (file: gtk_css_parser_get_file (self: parser),
344 start,
345 end);
346
347 gtk_css_style_provider_emit_error (GTK_STYLE_PROVIDER (scanner->provider), section, error);
348
349 gtk_css_section_unref (section);
350}
351
352static GtkCssScanner *
353gtk_css_scanner_new (GtkCssProvider *provider,
354 GtkCssScanner *parent,
355 GFile *file,
356 GBytes *bytes)
357{
358 GtkCssScanner *scanner;
359
360 scanner = g_slice_new0 (GtkCssScanner);
361
362 g_object_ref (provider);
363 scanner->provider = provider;
364 scanner->parent = parent;
365
366 scanner->parser = gtk_css_parser_new_for_bytes (bytes,
367 file,
368 error_func: gtk_css_scanner_parser_error,
369 user_data: scanner,
370 NULL);
371
372 return scanner;
373}
374
375static gboolean
376gtk_css_scanner_would_recurse (GtkCssScanner *scanner,
377 GFile *file)
378{
379 while (scanner)
380 {
381 GFile *parser_file = gtk_css_parser_get_file (self: scanner->parser);
382 if (parser_file && g_file_equal (file1: parser_file, file2: file))
383 return TRUE;
384
385 scanner = scanner->parent;
386 }
387
388 return FALSE;
389}
390
391static void
392gtk_css_provider_init (GtkCssProvider *css_provider)
393{
394 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: css_provider);
395
396 priv->rulesets = g_array_new (FALSE, FALSE, element_size: sizeof (GtkCssRuleset));
397
398 priv->symbolic_colors = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal,
399 key_destroy_func: (GDestroyNotify) g_free,
400 value_destroy_func: (GDestroyNotify) _gtk_css_value_unref);
401 priv->keyframes = g_hash_table_new_full (hash_func: g_str_hash, key_equal_func: g_str_equal,
402 key_destroy_func: (GDestroyNotify) g_free,
403 value_destroy_func: (GDestroyNotify) _gtk_css_keyframes_unref);
404}
405
406static void
407verify_tree_match_results (GtkCssProvider *provider,
408 GtkCssNode *node,
409 GtkCssSelectorMatches *tree_rules)
410{
411#ifdef VERIFY_TREE
412 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (provider);
413 GtkCssRuleset *ruleset;
414 gboolean should_match;
415 int i, j;
416
417 for (i = 0; i < priv->rulesets->len; i++)
418 {
419 gboolean found = FALSE;
420
421 ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i);
422
423 for (j = 0; j < gtk_css_selector_matches_get_size (tree_rules); j++)
424 {
425 if (ruleset == gtk_css_selector_matches_get (tree_rules, j))
426 {
427 found = TRUE;
428 break;
429 }
430 }
431 should_match = gtk_css_selector_matches (ruleset->selector, node);
432 if (found != !!should_match)
433 {
434 g_error ("expected rule '%s' to %s, but it %s",
435 _gtk_css_selector_to_string (ruleset->selector),
436 should_match ? "match" : "not match",
437 found ? "matched" : "didn't match");
438 }
439 }
440#endif
441}
442
443static GtkCssValue *
444gtk_css_style_provider_get_color (GtkStyleProvider *provider,
445 const char *name)
446{
447 GtkCssProvider *css_provider = GTK_CSS_PROVIDER (provider);
448 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: css_provider);
449
450 return g_hash_table_lookup (hash_table: priv->symbolic_colors, key: name);
451}
452
453static GtkCssKeyframes *
454gtk_css_style_provider_get_keyframes (GtkStyleProvider *provider,
455 const char *name)
456{
457 GtkCssProvider *css_provider = GTK_CSS_PROVIDER (provider);
458 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: css_provider);
459
460 return g_hash_table_lookup (hash_table: priv->keyframes, key: name);
461}
462
463static void
464gtk_css_style_provider_lookup (GtkStyleProvider *provider,
465 const GtkCountingBloomFilter *filter,
466 GtkCssNode *node,
467 GtkCssLookup *lookup,
468 GtkCssChange *change)
469{
470 GtkCssProvider *css_provider = GTK_CSS_PROVIDER (provider);
471 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: css_provider);
472 GtkCssRuleset *ruleset;
473 guint j;
474 int i;
475 GtkCssSelectorMatches tree_rules;
476
477 if (_gtk_css_selector_tree_is_empty (tree: priv->tree))
478 return;
479
480 gtk_css_selector_matches_init (self: &tree_rules);
481 _gtk_css_selector_tree_match_all (tree: priv->tree, filter, node, out_tree_rules: &tree_rules);
482
483 if (!gtk_css_selector_matches_is_empty (self: &tree_rules))
484 {
485 verify_tree_match_results (provider: css_provider, node, tree_rules: &tree_rules);
486
487 for (i = gtk_css_selector_matches_get_size (self: &tree_rules) - 1; i >= 0; i--)
488 {
489 ruleset = gtk_css_selector_matches_get (self: &tree_rules, pos: i);
490
491 if (ruleset->styles == NULL)
492 continue;
493
494 for (j = 0; j < ruleset->n_styles; j++)
495 {
496 GtkCssStyleProperty *prop = ruleset->styles[j].property;
497 guint id = _gtk_css_style_property_get_id (property: prop);
498
499 if (!_gtk_css_lookup_is_missing (lookup, id))
500 continue;
501
502 _gtk_css_lookup_set (lookup,
503 id,
504 section: ruleset->styles[j].section,
505 value: ruleset->styles[j].value);
506 }
507 }
508 }
509 gtk_css_selector_matches_clear (self: &tree_rules);
510
511 if (change)
512 *change = gtk_css_selector_tree_get_change_all (tree: priv->tree, filter, node);
513}
514
515static void
516gtk_css_style_provider_iface_init (GtkStyleProviderInterface *iface)
517{
518 iface->get_color = gtk_css_style_provider_get_color;
519 iface->get_keyframes = gtk_css_style_provider_get_keyframes;
520 iface->lookup = gtk_css_style_provider_lookup;
521 iface->emit_error = gtk_css_style_provider_emit_error;
522}
523
524static void
525gtk_css_provider_finalize (GObject *object)
526{
527 GtkCssProvider *css_provider = GTK_CSS_PROVIDER (object);
528 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: css_provider);
529 guint i;
530
531 for (i = 0; i < priv->rulesets->len; i++)
532 gtk_css_ruleset_clear (ruleset: &g_array_index (priv->rulesets, GtkCssRuleset, i));
533
534 g_array_free (array: priv->rulesets, TRUE);
535 _gtk_css_selector_tree_free (tree: priv->tree);
536
537 g_hash_table_destroy (hash_table: priv->symbolic_colors);
538 g_hash_table_destroy (hash_table: priv->keyframes);
539
540 if (priv->resource)
541 {
542 g_resources_unregister (resource: priv->resource);
543 g_resource_unref (resource: priv->resource);
544 priv->resource = NULL;
545 }
546
547 g_free (mem: priv->path);
548
549 G_OBJECT_CLASS (gtk_css_provider_parent_class)->finalize (object);
550}
551
552/**
553 * gtk_css_provider_new:
554 *
555 * Returns a newly created `GtkCssProvider`.
556 *
557 * Returns: A new `GtkCssProvider`
558 */
559GtkCssProvider *
560gtk_css_provider_new (void)
561{
562 return g_object_new (GTK_TYPE_CSS_PROVIDER, NULL);
563}
564
565static void
566css_provider_commit (GtkCssProvider *css_provider,
567 GtkCssSelectors *selectors,
568 GtkCssRuleset *ruleset)
569{
570 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: css_provider);
571 guint i;
572
573 if (ruleset->styles == NULL)
574 {
575 for (i = 0; i < gtk_css_selectors_get_size (self: selectors); i++)
576 _gtk_css_selector_free (selector: gtk_css_selectors_get (self: selectors, pos: i));
577 return;
578 }
579
580 for (i = 0; i < gtk_css_selectors_get_size (self: selectors); i++)
581 {
582 GtkCssRuleset *new;
583
584 g_array_set_size (array: priv->rulesets, length: priv->rulesets->len + 1);
585
586 new = &g_array_index (priv->rulesets, GtkCssRuleset, priv->rulesets->len - 1);
587 gtk_css_ruleset_init_copy (new, ruleset, selector: gtk_css_selectors_get (self: selectors, pos: i));
588 }
589}
590
591static void
592gtk_css_provider_reset (GtkCssProvider *css_provider)
593{
594 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: css_provider);
595 guint i;
596
597 if (priv->resource)
598 {
599 g_resources_unregister (resource: priv->resource);
600 g_resource_unref (resource: priv->resource);
601 priv->resource = NULL;
602 }
603
604 if (priv->path)
605 {
606 g_free (mem: priv->path);
607 priv->path = NULL;
608 }
609
610 g_hash_table_remove_all (hash_table: priv->symbolic_colors);
611 g_hash_table_remove_all (hash_table: priv->keyframes);
612
613 for (i = 0; i < priv->rulesets->len; i++)
614 gtk_css_ruleset_clear (ruleset: &g_array_index (priv->rulesets, GtkCssRuleset, i));
615 g_array_set_size (array: priv->rulesets, length: 0);
616 _gtk_css_selector_tree_free (tree: priv->tree);
617 priv->tree = NULL;
618}
619
620static gboolean
621parse_import (GtkCssScanner *scanner)
622{
623 GFile *file;
624
625 if (!gtk_css_parser_try_at_keyword (self: scanner->parser, keyword: "import"))
626 return FALSE;
627
628 if (gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_STRING))
629 {
630 char *url;
631
632 url = gtk_css_parser_consume_string (self: scanner->parser);
633 if (url)
634 {
635 file = gtk_css_parser_resolve_url (self: scanner->parser, url);
636 if (file == NULL)
637 {
638 gtk_css_parser_error_import (self: scanner->parser,
639 format: "Could not resolve \"%s\" to a valid URL",
640 url);
641 }
642 g_free (mem: url);
643 }
644 else
645 file = NULL;
646 }
647 else
648 {
649 char *url = gtk_css_parser_consume_url (self: scanner->parser);
650 if (url)
651 {
652 file = gtk_css_parser_resolve_url (self: scanner->parser, url);
653 g_free (mem: url);
654 }
655 else
656 file = NULL;
657 }
658
659 if (file == NULL)
660 {
661 /* nothing to do */
662 }
663 else if (!gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_EOF))
664 {
665 gtk_css_parser_error_syntax (self: scanner->parser, format: "Expected ';'");
666 }
667 else if (gtk_css_scanner_would_recurse (scanner, file))
668 {
669 char *path = g_file_get_path (file);
670 gtk_css_parser_error (self: scanner->parser,
671 code: GTK_CSS_PARSER_ERROR_IMPORT,
672 start: gtk_css_parser_get_block_location (self: scanner->parser),
673 end: gtk_css_parser_get_end_location (self: scanner->parser),
674 format: "Loading '%s' would recurse",
675 path);
676 g_free (mem: path);
677 }
678 else
679 {
680 gtk_css_provider_load_internal (css_provider: scanner->provider,
681 scanner,
682 file,
683 NULL);
684 }
685
686 g_clear_object (&file);
687
688 return TRUE;
689}
690
691static gboolean
692parse_color_definition (GtkCssScanner *scanner)
693{
694 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: scanner->provider);
695 GtkCssValue *color;
696 char *name;
697
698 if (!gtk_css_parser_try_at_keyword (self: scanner->parser, keyword: "define-color"))
699 return FALSE;
700
701 name = gtk_css_parser_consume_ident (self: scanner->parser);
702 if (name == NULL)
703 return TRUE;
704
705 color = _gtk_css_color_value_parse (parser: scanner->parser);
706 if (color == NULL)
707 {
708 g_free (mem: name);
709 return TRUE;
710 }
711
712 if (!gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_EOF))
713 {
714 g_free (mem: name);
715 _gtk_css_value_unref (value: color);
716 gtk_css_parser_error_syntax (self: scanner->parser,
717 format: "Missing semicolon at end of color definition");
718 return TRUE;
719 }
720
721 g_hash_table_insert (hash_table: priv->symbolic_colors, key: name, value: color);
722
723 return TRUE;
724}
725
726static gboolean
727parse_keyframes (GtkCssScanner *scanner)
728{
729 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: scanner->provider);
730 GtkCssKeyframes *keyframes;
731 char *name;
732
733 if (!gtk_css_parser_try_at_keyword (self: scanner->parser, keyword: "keyframes"))
734 return FALSE;
735
736 name = gtk_css_parser_consume_ident (self: scanner->parser);
737 if (name == NULL)
738 return FALSE;
739
740 if (!gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_EOF))
741 {
742 gtk_css_parser_error_syntax (self: scanner->parser, format: "Expected '{' for keyframes");
743 return FALSE;
744 }
745
746 gtk_css_parser_end_block_prelude (self: scanner->parser);
747
748 keyframes = _gtk_css_keyframes_parse (parser: scanner->parser);
749 if (keyframes != NULL)
750 g_hash_table_insert (hash_table: priv->keyframes, key: name, value: keyframes);
751
752 if (!gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_EOF))
753 gtk_css_parser_error_syntax (self: scanner->parser, format: "Expected '}' after declarations");
754
755 return TRUE;
756}
757
758static void
759parse_at_keyword (GtkCssScanner *scanner)
760{
761 gtk_css_parser_start_semicolon_block (self: scanner->parser, alternative_token: GTK_CSS_TOKEN_OPEN_CURLY);
762
763 if (!parse_import (scanner) &&
764 !parse_color_definition (scanner) &&
765 !parse_keyframes (scanner))
766 {
767 gtk_css_parser_error_syntax (self: scanner->parser, format: "Unknown @ rule");
768 }
769
770 gtk_css_parser_end_block (self: scanner->parser);
771}
772
773static void
774parse_selector_list (GtkCssScanner *scanner,
775 GtkCssSelectors *selectors)
776{
777 do {
778 GtkCssSelector *select = _gtk_css_selector_parse (parser: scanner->parser);
779
780 if (select == NULL)
781 {
782 for (int i = 0; i < gtk_css_selectors_get_size (self: selectors); i++)
783 _gtk_css_selector_free (selector: gtk_css_selectors_get (self: selectors, pos: i));
784 gtk_css_selectors_clear (self: selectors);
785 return;
786 }
787
788 gtk_css_selectors_append (self: selectors, value: select);
789 }
790 while (gtk_css_parser_try_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_COMMA));
791}
792
793static void
794parse_declaration (GtkCssScanner *scanner,
795 GtkCssRuleset *ruleset)
796{
797 GtkStyleProperty *property;
798 char *name;
799
800 /* advance the location over whitespace */
801 gtk_css_parser_get_token (self: scanner->parser);
802 gtk_css_parser_start_semicolon_block (self: scanner->parser, alternative_token: GTK_CSS_TOKEN_EOF);
803
804 if (gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_EOF))
805 {
806 gtk_css_parser_warn_syntax (self: scanner->parser, format: "Empty declaration");
807 gtk_css_parser_end_block (self: scanner->parser);
808 return;
809 }
810
811 name = gtk_css_parser_consume_ident (self: scanner->parser);
812 if (name == NULL)
813 goto out;
814
815 property = _gtk_style_property_lookup (name);
816
817 if (property)
818 {
819 GtkCssSection *section;
820 GtkCssValue *value;
821
822 if (!gtk_css_parser_try_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_COLON))
823 {
824 gtk_css_parser_error_syntax (self: scanner->parser, format: "Expected ':'");
825 goto out;
826 }
827
828 value = _gtk_style_property_parse_value (property, parser: scanner->parser);
829
830 if (value == NULL)
831 goto out;
832
833 if (!gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_EOF))
834 {
835 gtk_css_parser_error_syntax (self: scanner->parser, format: "Junk at end of value for %s", property->name);
836 goto out;
837 }
838
839 if (gtk_keep_css_sections)
840 {
841 section = gtk_css_section_new (file: gtk_css_parser_get_file (self: scanner->parser),
842 start: gtk_css_parser_get_block_location (self: scanner->parser),
843 end: gtk_css_parser_get_end_location (self: scanner->parser));
844 }
845 else
846 section = NULL;
847
848 if (GTK_IS_CSS_SHORTHAND_PROPERTY (property))
849 {
850 GtkCssShorthandProperty *shorthand = GTK_CSS_SHORTHAND_PROPERTY (property);
851 guint i;
852
853 for (i = 0; i < _gtk_css_shorthand_property_get_n_subproperties (shorthand); i++)
854 {
855 GtkCssStyleProperty *child = _gtk_css_shorthand_property_get_subproperty (shorthand, property: i);
856 GtkCssValue *sub = _gtk_css_array_value_get_nth (value, i);
857
858 gtk_css_ruleset_add (ruleset, property: child, _gtk_css_value_ref (value: sub), section);
859 }
860
861 _gtk_css_value_unref (value);
862 }
863 else if (GTK_IS_CSS_STYLE_PROPERTY (property))
864 {
865
866 gtk_css_ruleset_add (ruleset, GTK_CSS_STYLE_PROPERTY (property), value, section);
867 }
868 else
869 {
870 g_assert_not_reached ();
871 _gtk_css_value_unref (value);
872 }
873
874 g_clear_pointer (&section, gtk_css_section_unref);
875 }
876 else
877 {
878 gtk_css_parser_error_value (self: scanner->parser, format: "No property named \"%s\"", name);
879 }
880
881out:
882 g_free (mem: name);
883
884 gtk_css_parser_end_block (self: scanner->parser);
885}
886
887static void
888parse_declarations (GtkCssScanner *scanner,
889 GtkCssRuleset *ruleset)
890{
891 while (!gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_EOF))
892 {
893 parse_declaration (scanner, ruleset);
894 }
895}
896
897static void
898parse_ruleset (GtkCssScanner *scanner)
899{
900 GtkCssSelectors selectors;
901 GtkCssRuleset ruleset = { 0, };
902
903 gtk_css_selectors_init (self: &selectors);
904
905 parse_selector_list (scanner, selectors: &selectors);
906 if (gtk_css_selectors_get_size (self: &selectors) == 0)
907 {
908 gtk_css_parser_skip_until (self: scanner->parser, token_type: GTK_CSS_TOKEN_OPEN_CURLY);
909 gtk_css_parser_skip (self: scanner->parser);
910 goto out;
911 }
912
913 if (!gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_OPEN_CURLY))
914 {
915 guint i;
916 gtk_css_parser_error_syntax (self: scanner->parser, format: "Expected '{' after selectors");
917 for (i = 0; i < gtk_css_selectors_get_size (self: &selectors); i++)
918 _gtk_css_selector_free (selector: gtk_css_selectors_get (self: &selectors, pos: i));
919 gtk_css_parser_skip_until (self: scanner->parser, token_type: GTK_CSS_TOKEN_OPEN_CURLY);
920 gtk_css_parser_skip (self: scanner->parser);
921 goto out;
922 }
923
924 gtk_css_parser_start_block (self: scanner->parser);
925
926 parse_declarations (scanner, ruleset: &ruleset);
927
928 gtk_css_parser_end_block (self: scanner->parser);
929
930 css_provider_commit (css_provider: scanner->provider, selectors: &selectors, ruleset: &ruleset);
931 gtk_css_ruleset_clear (ruleset: &ruleset);
932
933out:
934 gtk_css_selectors_clear (self: &selectors);
935}
936
937static void
938parse_statement (GtkCssScanner *scanner)
939{
940 if (gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_AT_KEYWORD))
941 parse_at_keyword (scanner);
942 else
943 parse_ruleset (scanner);
944}
945
946static void
947parse_stylesheet (GtkCssScanner *scanner)
948{
949 while (!gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_EOF))
950 {
951 if (gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_CDO) ||
952 gtk_css_parser_has_token (self: scanner->parser, token_type: GTK_CSS_TOKEN_CDC))
953 {
954 gtk_css_parser_consume_token (self: scanner->parser);
955 continue;
956 }
957
958 parse_statement (scanner);
959 }
960}
961
962static int
963gtk_css_provider_compare_rule (gconstpointer a_,
964 gconstpointer b_)
965{
966 const GtkCssRuleset *a = (const GtkCssRuleset *) a_;
967 const GtkCssRuleset *b = (const GtkCssRuleset *) b_;
968 int compare;
969
970 compare = _gtk_css_selector_compare (a: a->selector, b: b->selector);
971 if (compare != 0)
972 return compare;
973
974 return 0;
975}
976
977static void
978gtk_css_provider_postprocess (GtkCssProvider *css_provider)
979{
980 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: css_provider);
981 GtkCssSelectorTreeBuilder *builder;
982 guint i;
983 gint64 before G_GNUC_UNUSED;
984
985 before = GDK_PROFILER_CURRENT_TIME;
986
987 g_array_sort (array: priv->rulesets, compare_func: gtk_css_provider_compare_rule);
988
989 builder = _gtk_css_selector_tree_builder_new ();
990 for (i = 0; i < priv->rulesets->len; i++)
991 {
992 GtkCssRuleset *ruleset;
993
994 ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i);
995
996 _gtk_css_selector_tree_builder_add (builder,
997 selectors: ruleset->selector,
998 selector_match: &ruleset->selector_match,
999 match: ruleset);
1000 }
1001
1002 priv->tree = _gtk_css_selector_tree_builder_build (builder);
1003 _gtk_css_selector_tree_builder_free (builder);
1004
1005#ifndef VERIFY_TREE
1006 for (i = 0; i < priv->rulesets->len; i++)
1007 {
1008 GtkCssRuleset *ruleset;
1009
1010 ruleset = &g_array_index (priv->rulesets, GtkCssRuleset, i);
1011
1012 _gtk_css_selector_free (selector: ruleset->selector);
1013 ruleset->selector = NULL;
1014 }
1015#endif
1016
1017 gdk_profiler_end_mark (before, "create selector tree", NULL);
1018}
1019
1020static void
1021gtk_css_provider_load_internal (GtkCssProvider *self,
1022 GtkCssScanner *parent,
1023 GFile *file,
1024 GBytes *bytes)
1025{
1026 gint64 before G_GNUC_UNUSED;
1027
1028 before = GDK_PROFILER_CURRENT_TIME;
1029
1030 if (bytes == NULL)
1031 {
1032 GError *load_error = NULL;
1033
1034 bytes = g_file_load_bytes (file, NULL, NULL, error: &load_error);
1035
1036 if (bytes == NULL)
1037 {
1038 if (parent == NULL)
1039 {
1040 GtkCssLocation empty = { 0, };
1041 GtkCssSection *section = gtk_css_section_new (file, start: &empty, end: &empty);
1042
1043 gtk_css_style_provider_emit_error (GTK_STYLE_PROVIDER (self), section, error: load_error);
1044 gtk_css_section_unref (section);
1045 }
1046 else
1047 {
1048 gtk_css_parser_error (self: parent->parser,
1049 code: GTK_CSS_PARSER_ERROR_IMPORT,
1050 start: gtk_css_parser_get_block_location (self: parent->parser),
1051 end: gtk_css_parser_get_end_location (self: parent->parser),
1052 format: "Failed to import: %s",
1053 load_error->message);
1054 }
1055
1056 g_error_free (error: load_error);
1057 }
1058 }
1059
1060 if (bytes)
1061 {
1062 GtkCssScanner *scanner;
1063
1064 scanner = gtk_css_scanner_new (provider: self,
1065 parent,
1066 file,
1067 bytes);
1068
1069 parse_stylesheet (scanner);
1070
1071 gtk_css_scanner_destroy (scanner);
1072
1073 if (parent == NULL)
1074 gtk_css_provider_postprocess (css_provider: self);
1075
1076 g_bytes_unref (bytes);
1077 }
1078
1079 if (GDK_PROFILER_IS_RUNNING)
1080 {
1081 char *uri = g_file_get_uri (file);
1082 gdk_profiler_end_mark (before, "theme load", uri);
1083 g_free (mem: uri);
1084 }
1085}
1086
1087/**
1088 * gtk_css_provider_load_from_data:
1089 * @css_provider: a `GtkCssProvider`
1090 * @data: (array length=length) (element-type guint8): CSS data loaded in memory
1091 * @length: the length of @data in bytes, or -1 for NUL terminated strings. If
1092 * @length is not -1, the code will assume it is not NUL terminated and will
1093 * potentially do a copy.
1094 *
1095 * Loads @data into @css_provider.
1096 *
1097 * This clears any previously loaded information.
1098 */
1099void
1100gtk_css_provider_load_from_data (GtkCssProvider *css_provider,
1101 const char *data,
1102 gssize length)
1103{
1104 GBytes *bytes;
1105
1106 g_return_if_fail (GTK_IS_CSS_PROVIDER (css_provider));
1107 g_return_if_fail (data != NULL);
1108
1109 if (length < 0)
1110 length = strlen (s: data);
1111
1112 bytes = g_bytes_new_static (data, size: length);
1113
1114 gtk_css_provider_reset (css_provider);
1115
1116 g_bytes_ref (bytes);
1117 gtk_css_provider_load_internal (self: css_provider, NULL, NULL, bytes);
1118 g_bytes_unref (bytes);
1119
1120 gtk_style_provider_changed (GTK_STYLE_PROVIDER (css_provider));
1121}
1122
1123/**
1124 * gtk_css_provider_load_from_file:
1125 * @css_provider: a `GtkCssProvider`
1126 * @file: `GFile` pointing to a file to load
1127 *
1128 * Loads the data contained in @file into @css_provider.
1129 *
1130 * This clears any previously loaded information.
1131 */
1132void
1133gtk_css_provider_load_from_file (GtkCssProvider *css_provider,
1134 GFile *file)
1135{
1136 g_return_if_fail (GTK_IS_CSS_PROVIDER (css_provider));
1137 g_return_if_fail (G_IS_FILE (file));
1138
1139 gtk_css_provider_reset (css_provider);
1140
1141 gtk_css_provider_load_internal (self: css_provider, NULL, file, NULL);
1142
1143 gtk_style_provider_changed (GTK_STYLE_PROVIDER (css_provider));
1144}
1145
1146/**
1147 * gtk_css_provider_load_from_path:
1148 * @css_provider: a `GtkCssProvider`
1149 * @path: (type filename): the path of a filename to load, in the GLib filename encoding
1150 *
1151 * Loads the data contained in @path into @css_provider.
1152 *
1153 * This clears any previously loaded information.
1154 */
1155void
1156gtk_css_provider_load_from_path (GtkCssProvider *css_provider,
1157 const char *path)
1158{
1159 GFile *file;
1160
1161 g_return_if_fail (GTK_IS_CSS_PROVIDER (css_provider));
1162 g_return_if_fail (path != NULL);
1163
1164 file = g_file_new_for_path (path);
1165
1166 gtk_css_provider_load_from_file (css_provider, file);
1167
1168 g_object_unref (object: file);
1169}
1170
1171/**
1172 * gtk_css_provider_load_from_resource:
1173 * @css_provider: a `GtkCssProvider`
1174 * @resource_path: a `GResource` resource path
1175 *
1176 * Loads the data contained in the resource at @resource_path into
1177 * the @css_provider.
1178 *
1179 * This clears any previously loaded information.
1180 */
1181void
1182gtk_css_provider_load_from_resource (GtkCssProvider *css_provider,
1183 const char *resource_path)
1184{
1185 GFile *file;
1186 char *uri, *escaped;
1187
1188 g_return_if_fail (GTK_IS_CSS_PROVIDER (css_provider));
1189 g_return_if_fail (resource_path != NULL);
1190
1191 escaped = g_uri_escape_string (unescaped: resource_path,
1192 G_URI_RESERVED_CHARS_ALLOWED_IN_PATH, FALSE);
1193 uri = g_strconcat (string1: "resource://", escaped, NULL);
1194 g_free (mem: escaped);
1195
1196 file = g_file_new_for_uri (uri);
1197 g_free (mem: uri);
1198
1199 gtk_css_provider_load_from_file (css_provider, file);
1200
1201 g_object_unref (object: file);
1202}
1203
1204char *
1205_gtk_get_theme_dir (void)
1206{
1207 const char *var;
1208
1209 var = g_getenv (variable: "GTK_DATA_PREFIX");
1210 if (var == NULL)
1211 var = _gtk_get_data_prefix ();
1212 return g_build_filename (first_element: var, "share", "themes", NULL);
1213}
1214
1215/* Return the path that this providers gtk.css was loaded from,
1216 * if it is part of a theme, otherwise NULL.
1217 */
1218const char *
1219_gtk_css_provider_get_theme_dir (GtkCssProvider *provider)
1220{
1221 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: provider);
1222
1223 return priv->path;
1224}
1225
1226#if (GTK_MINOR_VERSION % 2)
1227#define MINOR (GTK_MINOR_VERSION + 1)
1228#else
1229#define MINOR GTK_MINOR_VERSION
1230#endif
1231
1232/*
1233 * Look for
1234 * $dir/$subdir/gtk-4.16/gtk-$variant.css
1235 * $dir/$subdir/gtk-4.14/gtk-$variant.css
1236 * ...
1237 * $dir/$subdir/gtk-4.0/gtk-$variant.css
1238 * and return the first found file.
1239 */
1240static char *
1241_gtk_css_find_theme_dir (const char *dir,
1242 const char *subdir,
1243 const char *name,
1244 const char *variant)
1245{
1246 char *file;
1247 char *base;
1248 char *subsubdir;
1249 int i;
1250 char *path;
1251
1252 if (variant)
1253 file = g_strconcat (string1: "gtk-", variant, ".css", NULL);
1254 else
1255 file = g_strdup (str: "gtk.css");
1256
1257 if (subdir)
1258 base = g_build_filename (first_element: dir, subdir, name, NULL);
1259 else
1260 base = g_build_filename (first_element: dir, name, NULL);
1261
1262 for (i = MINOR; i >= 0; i = i - 2)
1263 {
1264 subsubdir = g_strdup_printf (format: "gtk-4.%d", i);
1265 path = g_build_filename (first_element: base, subsubdir, file, NULL);
1266 g_free (mem: subsubdir);
1267
1268 if (g_file_test (filename: path, test: G_FILE_TEST_EXISTS))
1269 break;
1270
1271 g_free (mem: path);
1272 path = NULL;
1273 }
1274
1275 g_free (mem: file);
1276 g_free (mem: base);
1277
1278 return path;
1279}
1280
1281#undef MINOR
1282
1283static char *
1284_gtk_css_find_theme (const char *name,
1285 const char *variant)
1286{
1287 char *path;
1288 const char *const *dirs;
1289 int i;
1290 char *dir;
1291
1292 /* First look in the user's data directory */
1293 path = _gtk_css_find_theme_dir (dir: g_get_user_data_dir (), subdir: "themes", name, variant);
1294 if (path)
1295 return path;
1296
1297 /* Next look in the user's home directory */
1298 path = _gtk_css_find_theme_dir (dir: g_get_home_dir (), subdir: ".themes", name, variant);
1299 if (path)
1300 return path;
1301
1302 /* Look in system data directories */
1303 dirs = g_get_system_data_dirs ();
1304 for (i = 0; dirs[i]; i++)
1305 {
1306 path = _gtk_css_find_theme_dir (dir: dirs[i], subdir: "themes", name, variant);
1307 if (path)
1308 return path;
1309 }
1310
1311 /* Finally, try in the default theme directory */
1312 dir = _gtk_get_theme_dir ();
1313 path = _gtk_css_find_theme_dir (dir, NULL, name, variant);
1314 g_free (mem: dir);
1315
1316 return path;
1317}
1318
1319/**
1320 * gtk_css_provider_load_named:
1321 * @provider: a `GtkCssProvider`
1322 * @name: A theme name
1323 * @variant: (nullable): variant to load, for example, "dark", or
1324 * %NULL for the default
1325 *
1326 * Loads a theme from the usual theme paths.
1327 *
1328 * The actual process of finding the theme might change between
1329 * releases, but it is guaranteed that this function uses the same
1330 * mechanism to load the theme that GTK uses for loading its own theme.
1331 */
1332void
1333gtk_css_provider_load_named (GtkCssProvider *provider,
1334 const char *name,
1335 const char *variant)
1336{
1337 char *path;
1338 char *resource_path;
1339
1340 g_return_if_fail (GTK_IS_CSS_PROVIDER (provider));
1341 g_return_if_fail (name != NULL);
1342
1343 gtk_css_provider_reset (css_provider: provider);
1344
1345 /* try loading the resource for the theme. This is mostly meant for built-in
1346 * themes.
1347 */
1348 if (variant)
1349 resource_path = g_strdup_printf (format: "/org/gtk/libgtk/theme/%s/gtk-%s.css", name, variant);
1350 else
1351 resource_path = g_strdup_printf (format: "/org/gtk/libgtk/theme/%s/gtk.css", name);
1352
1353 if (g_resources_get_info (path: resource_path, lookup_flags: 0, NULL, NULL, NULL))
1354 {
1355 gtk_css_provider_load_from_resource (css_provider: provider, resource_path);
1356 g_free (mem: resource_path);
1357 return;
1358 }
1359 g_free (mem: resource_path);
1360
1361 /* Next try looking for files in the various theme directories. */
1362 path = _gtk_css_find_theme (name, variant);
1363 if (path)
1364 {
1365 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: provider);
1366 char *dir, *resource_file;
1367 GResource *resource;
1368
1369 dir = g_path_get_dirname (file_name: path);
1370 resource_file = g_build_filename (first_element: dir, "gtk.gresource", NULL);
1371 resource = g_resource_load (filename: resource_file, NULL);
1372 g_free (mem: resource_file);
1373
1374 if (resource != NULL)
1375 g_resources_register (resource);
1376
1377 gtk_css_provider_load_from_path (css_provider: provider, path);
1378
1379 /* Only set this after load, as load_from_path will clear it */
1380 priv->resource = resource;
1381 priv->path = dir;
1382
1383 g_free (mem: path);
1384 }
1385 else
1386 {
1387 /* Things failed! Fall back! Fall back!
1388 *
1389 * We accept the names HighContrast, HighContrastInverse,
1390 * Adwaita and Adwaita-dark as aliases for the variants
1391 * of the Default theme.
1392 */
1393 if (strcmp (s1: name, s2: "HighContrast") == 0)
1394 {
1395 if (g_strcmp0 (str1: variant, str2: "dark") == 0)
1396 gtk_css_provider_load_named (provider, DEFAULT_THEME_NAME, variant: "hc-dark");
1397 else
1398 gtk_css_provider_load_named (provider, DEFAULT_THEME_NAME, variant: "hc");
1399 }
1400 else if (strcmp (s1: name, s2: "HighConstrastInverse") == 0)
1401 gtk_css_provider_load_named (provider, DEFAULT_THEME_NAME, variant: "hc-dark");
1402 else if (strcmp (s1: name, s2: "Adwaita-dark") == 0)
1403 gtk_css_provider_load_named (provider, DEFAULT_THEME_NAME, variant: "dark");
1404 else if (strcmp (s1: name, DEFAULT_THEME_NAME) != 0)
1405 gtk_css_provider_load_named (provider, DEFAULT_THEME_NAME, variant);
1406 else
1407 {
1408 g_return_if_fail (variant != NULL);
1409 gtk_css_provider_load_named (provider, DEFAULT_THEME_NAME, NULL);
1410 }
1411 }
1412}
1413
1414static int
1415compare_properties (gconstpointer a, gconstpointer b, gpointer style)
1416{
1417 const guint *ua = a;
1418 const guint *ub = b;
1419 PropertyValue *styles = style;
1420
1421 return strcmp (s1: _gtk_style_property_get_name (GTK_STYLE_PROPERTY (styles[*ua].property)),
1422 s2: _gtk_style_property_get_name (GTK_STYLE_PROPERTY (styles[*ub].property)));
1423}
1424
1425static void
1426gtk_css_ruleset_print (const GtkCssRuleset *ruleset,
1427 GString *str)
1428{
1429 guint i;
1430
1431 _gtk_css_selector_tree_match_print (tree: ruleset->selector_match, str);
1432
1433 g_string_append (string: str, val: " {\n");
1434
1435 if (ruleset->styles)
1436 {
1437 guint *sorted = g_new (guint, ruleset->n_styles);
1438
1439 for (i = 0; i < ruleset->n_styles; i++)
1440 sorted[i] = i;
1441
1442 /* so the output is identical for identical selector styles */
1443 g_qsort_with_data (pbase: sorted, total_elems: ruleset->n_styles, size: sizeof (guint), compare_func: compare_properties, user_data: ruleset->styles);
1444
1445 for (i = 0; i < ruleset->n_styles; i++)
1446 {
1447 PropertyValue *prop = &ruleset->styles[sorted[i]];
1448 g_string_append (string: str, val: " ");
1449 g_string_append (string: str, val: _gtk_style_property_get_name (GTK_STYLE_PROPERTY (prop->property)));
1450 g_string_append (string: str, val: ": ");
1451 _gtk_css_value_print (value: prop->value, string: str);
1452 g_string_append (string: str, val: ";\n");
1453 }
1454
1455 g_free (mem: sorted);
1456 }
1457
1458 g_string_append (string: str, val: "}\n");
1459}
1460
1461static void
1462gtk_css_provider_print_colors (GHashTable *colors,
1463 GString *str)
1464{
1465 GList *keys, *walk;
1466
1467 keys = g_hash_table_get_keys (hash_table: colors);
1468 /* so the output is identical for identical styles */
1469 keys = g_list_sort (list: keys, compare_func: (GCompareFunc) strcmp);
1470
1471 for (walk = keys; walk; walk = walk->next)
1472 {
1473 const char *name = walk->data;
1474 GtkCssValue *color = g_hash_table_lookup (hash_table: colors, key: (gpointer) name);
1475
1476 g_string_append (string: str, val: "@define-color ");
1477 g_string_append (string: str, val: name);
1478 g_string_append (string: str, val: " ");
1479 _gtk_css_value_print (value: color, string: str);
1480 g_string_append (string: str, val: ";\n");
1481 }
1482
1483 g_list_free (list: keys);
1484}
1485
1486static void
1487gtk_css_provider_print_keyframes (GHashTable *keyframes,
1488 GString *str)
1489{
1490 GList *keys, *walk;
1491
1492 keys = g_hash_table_get_keys (hash_table: keyframes);
1493 /* so the output is identical for identical styles */
1494 keys = g_list_sort (list: keys, compare_func: (GCompareFunc) strcmp);
1495
1496 for (walk = keys; walk; walk = walk->next)
1497 {
1498 const char *name = walk->data;
1499 GtkCssKeyframes *keyframe = g_hash_table_lookup (hash_table: keyframes, key: (gpointer) name);
1500
1501 if (str->len > 0)
1502 g_string_append (string: str, val: "\n");
1503 g_string_append (string: str, val: "@keyframes ");
1504 g_string_append (string: str, val: name);
1505 g_string_append (string: str, val: " {\n");
1506 _gtk_css_keyframes_print (keyframes: keyframe, string: str);
1507 g_string_append (string: str, val: "}\n");
1508 }
1509
1510 g_list_free (list: keys);
1511}
1512
1513/**
1514 * gtk_css_provider_to_string:
1515 * @provider: the provider to write to a string
1516 *
1517 * Converts the @provider into a string representation in CSS
1518 * format.
1519 *
1520 * Using [method@Gtk.CssProvider.load_from_data] with the return
1521 * value from this function on a new provider created with
1522 * [ctor@Gtk.CssProvider.new] will basically create a duplicate
1523 * of this @provider.
1524 *
1525 * Returns: a new string representing the @provider.
1526 */
1527char *
1528gtk_css_provider_to_string (GtkCssProvider *provider)
1529{
1530 GtkCssProviderPrivate *priv = gtk_css_provider_get_instance_private (self: provider);
1531 GString *str;
1532 guint i;
1533
1534 g_return_val_if_fail (GTK_IS_CSS_PROVIDER (provider), NULL);
1535
1536 str = g_string_new (init: "");
1537
1538 gtk_css_provider_print_colors (colors: priv->symbolic_colors, str);
1539 gtk_css_provider_print_keyframes (keyframes: priv->keyframes, str);
1540
1541 for (i = 0; i < priv->rulesets->len; i++)
1542 {
1543 if (str->len != 0)
1544 g_string_append (string: str, val: "\n");
1545 gtk_css_ruleset_print (ruleset: &g_array_index (priv->rulesets, GtkCssRuleset, i), str);
1546 }
1547
1548 return g_string_free (string: str, FALSE);
1549}
1550
1551

source code of gtk/gtk/gtkcssprovider.c