]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssstringfuncs.c
f36e9421516f7ef547e8cbe4a363c9ae471a0570
[~andy/gtk] / gtk / gtkcssstringfuncs.c
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, write to the
16  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #include "config.h"
21
22 #include "gtkcssstringfuncsprivate.h"
23
24 #include <errno.h>
25 #include <stdlib.h>
26 #include <string.h>
27
28 #include <gdk-pixbuf/gdk-pixbuf.h>
29 #include <cairo-gobject.h>
30
31 #include "gtkcssprovider.h"
32
33 /* the actual parsers we have */
34 #include "gtkanimationdescription.h"
35 #include "gtk9slice.h"
36 #include "gtkgradient.h"
37 #include "gtkthemingengine.h"
38
39 typedef gboolean (* FromStringFunc)   (const char    *str,
40                                        GFile         *base,
41                                        GValue        *value,
42                                        GError       **error);
43 typedef char *   (* ToStringFunc)     (const GValue  *value);
44
45 static GHashTable *from_string_funcs = NULL;
46 static GHashTable *to_string_funcs = NULL;
47
48 static void
49 register_conversion_function (GType          type,
50                               FromStringFunc from_string,
51                               ToStringFunc   to_string)
52 {
53   if (from_string)
54     g_hash_table_insert (from_string_funcs, GSIZE_TO_POINTER (type), from_string);
55   if (to_string)
56     g_hash_table_insert (to_string_funcs, GSIZE_TO_POINTER (type), to_string);
57 }
58
59 static gboolean
60 set_default_error (GError **error,
61                    GType    type)
62 {
63   g_set_error (error,
64                GTK_CSS_PROVIDER_ERROR,
65                GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
66                "Could not convert property value to type '%s'",
67                g_type_name (type));
68   return FALSE;
69 }
70
71 /*** IMPLEMENTATIONS ***/
72
73 #define SKIP_SPACES(s) while (g_ascii_isspace (*(s))) (s)++
74 #define SKIP_SPACES_BACK(s) while (g_ascii_isspace (*(s))) (s)--
75
76 static GtkSymbolicColor *
77 symbolic_color_parse_str (const gchar  *string,
78                           gchar       **end_ptr)
79 {
80   GtkSymbolicColor *symbolic_color = NULL;
81   gchar *str;
82
83   str = (gchar *) string;
84   *end_ptr = str;
85
86   if (str[0] == '@')
87     {
88       const gchar *end;
89       gchar *name;
90
91       str++;
92       end = str;
93
94       while (*end == '-' || *end == '_' || g_ascii_isalnum (*end))
95         end++;
96
97       name = g_strndup (str, end - str);
98       symbolic_color = gtk_symbolic_color_new_name (name);
99       g_free (name);
100
101       *end_ptr = (gchar *) end;
102     }
103   else if (g_str_has_prefix (str, "lighter") ||
104            g_str_has_prefix (str, "darker"))
105     {
106       GtkSymbolicColor *param_color;
107       gboolean is_lighter = FALSE;
108
109       is_lighter = g_str_has_prefix (str, "lighter");
110
111       if (is_lighter)
112         str += strlen ("lighter");
113       else
114         str += strlen ("darker");
115
116       SKIP_SPACES (str);
117
118       if (*str != '(')
119         {
120           *end_ptr = (gchar *) str;
121           return NULL;
122         }
123
124       str++;
125       SKIP_SPACES (str);
126       param_color = symbolic_color_parse_str (str, end_ptr);
127
128       if (!param_color)
129         return NULL;
130
131       str = *end_ptr;
132       SKIP_SPACES (str);
133       *end_ptr = (gchar *) str;
134
135       if (*str != ')')
136         {
137           gtk_symbolic_color_unref (param_color);
138           return NULL;
139         }
140
141       if (is_lighter)
142         symbolic_color = gtk_symbolic_color_new_shade (param_color, 1.3);
143       else
144         symbolic_color = gtk_symbolic_color_new_shade (param_color, 0.7);
145
146       gtk_symbolic_color_unref (param_color);
147       (*end_ptr)++;
148     }
149   else if (g_str_has_prefix (str, "shade") ||
150            g_str_has_prefix (str, "alpha"))
151     {
152       GtkSymbolicColor *param_color;
153       gboolean is_shade = FALSE;
154       gdouble factor;
155
156       is_shade = g_str_has_prefix (str, "shade");
157
158       if (is_shade)
159         str += strlen ("shade");
160       else
161         str += strlen ("alpha");
162
163       SKIP_SPACES (str);
164
165       if (*str != '(')
166         {
167           *end_ptr = (gchar *) str;
168           return NULL;
169         }
170
171       str++;
172       SKIP_SPACES (str);
173       param_color = symbolic_color_parse_str (str, end_ptr);
174
175       if (!param_color)
176         return NULL;
177
178       str = *end_ptr;
179       SKIP_SPACES (str);
180
181       if (str[0] != ',')
182         {
183           gtk_symbolic_color_unref (param_color);
184           *end_ptr = (gchar *) str;
185           return NULL;
186         }
187
188       str++;
189       SKIP_SPACES (str);
190       factor = g_ascii_strtod (str, end_ptr);
191
192       str = *end_ptr;
193       SKIP_SPACES (str);
194       *end_ptr = (gchar *) str;
195
196       if (str[0] != ')')
197         {
198           gtk_symbolic_color_unref (param_color);
199           return NULL;
200         }
201
202       if (is_shade)
203         symbolic_color = gtk_symbolic_color_new_shade (param_color, factor);
204       else
205         symbolic_color = gtk_symbolic_color_new_alpha (param_color, factor);
206
207       gtk_symbolic_color_unref (param_color);
208       (*end_ptr)++;
209     }
210   else if (g_str_has_prefix (str, "mix"))
211     {
212       GtkSymbolicColor *color1, *color2;
213       gdouble factor;
214
215       str += strlen ("mix");
216       SKIP_SPACES (str);
217
218       if (*str != '(')
219         {
220           *end_ptr = (gchar *) str;
221           return NULL;
222         }
223
224       str++;
225       SKIP_SPACES (str);
226       color1 = symbolic_color_parse_str (str, end_ptr);
227
228       if (!color1)
229         return NULL;
230
231       str = *end_ptr;
232       SKIP_SPACES (str);
233
234       if (str[0] != ',')
235         {
236           gtk_symbolic_color_unref (color1);
237           *end_ptr = (gchar *) str;
238           return NULL;
239         }
240
241       str++;
242       SKIP_SPACES (str);
243       color2 = symbolic_color_parse_str (str, end_ptr);
244
245       if (!color2 || *end_ptr[0] != ',')
246         {
247           gtk_symbolic_color_unref (color1);
248           return NULL;
249         }
250
251       str = *end_ptr;
252       SKIP_SPACES (str);
253
254       if (str[0] != ',')
255         {
256           gtk_symbolic_color_unref (color1);
257           gtk_symbolic_color_unref (color2);
258           *end_ptr = (gchar *) str;
259           return NULL;
260         }
261
262       str++;
263       SKIP_SPACES (str);
264       factor = g_ascii_strtod (str, end_ptr);
265
266       str = *end_ptr;
267       SKIP_SPACES (str);
268       *end_ptr = (gchar *) str;
269
270       if (str[0] != ')')
271         {
272           gtk_symbolic_color_unref (color1);
273           gtk_symbolic_color_unref (color2);
274           return NULL;
275         }
276
277       symbolic_color = gtk_symbolic_color_new_mix (color1, color2, factor);
278       gtk_symbolic_color_unref (color1);
279       gtk_symbolic_color_unref (color2);
280       (*end_ptr)++;
281     }
282   else
283     {
284       GdkRGBA color;
285       gchar *color_str;
286       const gchar *end;
287
288       end = str + 1;
289
290       if (str[0] == '#')
291         {
292           /* Color in hex format */
293           while (g_ascii_isxdigit (*end))
294             end++;
295         }
296       else if (g_str_has_prefix (str, "rgb"))
297         {
298           /* color in rgb/rgba format */
299           while (*end != ')' && *end != '\0')
300             end++;
301
302           if (*end == ')')
303             end++;
304         }
305       else
306         {
307           /* Color name */
308           while (*end != '\0' &&
309                  (g_ascii_isalnum (*end) || *end == ' '))
310             end++;
311         }
312
313       color_str = g_strndup (str, end - str);
314       *end_ptr = (gchar *) end;
315
316       if (!gdk_rgba_parse (&color, color_str))
317         {
318           g_free (color_str);
319           return NULL;
320         }
321
322       symbolic_color = gtk_symbolic_color_new_literal (&color);
323       g_free (color_str);
324     }
325
326   return symbolic_color;
327 }
328
329 static gboolean 
330 rgba_value_from_string (const char  *str,
331                         GFile       *base,
332                         GValue      *value,
333                         GError     **error)
334 {
335   GtkSymbolicColor *symbolic;
336   GdkRGBA rgba;
337
338   if (gdk_rgba_parse (&rgba, str))
339     {
340       g_value_set_boxed (value, &rgba);
341       return TRUE;
342     }
343
344   symbolic = _gtk_css_parse_symbolic_color (str, error);
345   if (symbolic == NULL)
346     return FALSE;
347
348   g_value_unset (value);
349   g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR);
350   g_value_take_boxed (value, symbolic);
351   return TRUE;
352 }
353
354 static char *
355 rgba_value_to_string (const GValue *value)
356 {
357   const GdkRGBA *rgba = g_value_get_boxed (value);
358
359   if (rgba == NULL)
360     return g_strdup ("none");
361
362   return gdk_rgba_to_string (rgba);
363 }
364
365 static gboolean 
366 color_value_from_string (const char  *str,
367                          GFile       *base,
368                          GValue      *value,
369                          GError     **error)
370 {
371   GtkSymbolicColor *symbolic;
372   GdkColor color;
373
374   if (gdk_color_parse (str, &color))
375     {
376       g_value_set_boxed (value, &color);
377       return TRUE;
378     }
379
380   symbolic = _gtk_css_parse_symbolic_color (str, error);
381   if (symbolic == NULL)
382     return FALSE;
383
384   g_value_unset (value);
385   g_value_init (value, GTK_TYPE_SYMBOLIC_COLOR);
386   g_value_take_boxed (value, symbolic);
387   return TRUE;
388 }
389
390 static char *
391 color_value_to_string (const GValue *value)
392 {
393   const GdkColor *color = g_value_get_boxed (value);
394
395   if (color == NULL)
396     return g_strdup ("none");
397
398   return gdk_color_to_string (color);
399 }
400
401 static gboolean 
402 symbolic_color_value_from_string (const char  *str,
403                                   GFile       *base,
404                                   GValue      *value,
405                                   GError     **error)
406 {
407   GtkSymbolicColor *symbolic;
408
409   symbolic = _gtk_css_parse_symbolic_color (str, error);
410   if (symbolic == NULL)
411     return FALSE;
412
413   g_value_take_boxed (value, symbolic);
414   return TRUE;
415 }
416
417 static char *
418 symbolic_color_value_to_string (const GValue *value)
419 {
420   GtkSymbolicColor *symbolic = g_value_get_boxed (value);
421
422   if (symbolic == NULL)
423     return g_strdup ("none");
424
425   return gtk_symbolic_color_to_string (symbolic);
426 }
427
428 static gboolean 
429 font_description_value_from_string (const char  *str,
430                                     GFile       *base,
431                                     GValue      *value,
432                                     GError     **error)
433 {
434   PangoFontDescription *font_desc;
435
436   font_desc = pango_font_description_from_string (str);
437   g_value_take_boxed (value, font_desc);
438   return TRUE;
439 }
440
441 static char *
442 font_description_value_to_string (const GValue *value)
443 {
444   const PangoFontDescription *desc = g_value_get_boxed (value);
445
446   if (desc == NULL)
447     return g_strdup ("none");
448
449   return pango_font_description_to_string (desc);
450 }
451
452 static gboolean 
453 boolean_value_from_string (const char  *str,
454                            GFile       *base,
455                            GValue      *value,
456                            GError     **error)
457 {
458   if (g_ascii_strcasecmp (str, "true") == 0 ||
459       g_ascii_strcasecmp (str, "1") == 0)
460     {
461       g_value_set_boolean (value, TRUE);
462       return TRUE;
463     }
464   else if (g_ascii_strcasecmp (str, "false") == 0 ||
465            g_ascii_strcasecmp (str, "0") == 0)
466     {
467       g_value_set_boolean (value, FALSE);
468       return TRUE;
469     }
470
471   return set_default_error (error, G_VALUE_TYPE (value));
472 }
473
474 static char *
475 boolean_value_to_string (const GValue *value)
476 {
477   if (g_value_get_boolean (value))
478     return g_strdup ("true");
479   else
480     return g_strdup ("false");
481 }
482
483 static gboolean 
484 int_value_from_string (const char  *str,
485                        GFile       *base,
486                        GValue      *value,
487                        GError     **error)
488 {
489   gint64 i;
490   char *end;
491
492   i = g_ascii_strtoll (str, &end, 10);
493
494   if (*end != '\0')
495     return set_default_error (error, G_VALUE_TYPE (value));
496
497   if (i > G_MAXINT || i < G_MININT)
498     {
499       g_set_error_literal (error,
500                            GTK_CSS_PROVIDER_ERROR,
501                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
502                            "Number too big");
503       return FALSE;
504     }
505
506   g_value_set_int (value, i);
507   return TRUE;
508 }
509
510 static char *
511 int_value_to_string (const GValue *value)
512 {
513   return g_strdup_printf ("%d", g_value_get_int (value));
514 }
515
516 static gboolean 
517 uint_value_from_string (const char  *str,
518                         GFile       *base,
519                         GValue      *value,
520                         GError     **error)
521 {
522   guint64 u;
523   char *end;
524
525   u = g_ascii_strtoull (str, &end, 10);
526
527   if (*end != '\0')
528     return set_default_error (error, G_VALUE_TYPE (value));
529
530   if (u > G_MAXUINT)
531     {
532       g_set_error_literal (error,
533                            GTK_CSS_PROVIDER_ERROR,
534                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
535                            "Number too big");
536       return FALSE;
537     }
538
539   g_value_set_uint (value, u);
540   return TRUE;
541 }
542
543 static char *
544 uint_value_to_string (const GValue *value)
545 {
546   return g_strdup_printf ("%u", g_value_get_uint (value));
547 }
548
549 static gboolean 
550 double_value_from_string (const char  *str,
551                           GFile       *base,
552                           GValue      *value,
553                           GError     **error)
554 {
555   double d;
556   char *end;
557
558   d = g_ascii_strtod (str, &end);
559
560   if (*end != '\0')
561     return set_default_error (error, G_VALUE_TYPE (value));
562
563   if (errno == ERANGE)
564     {
565       g_set_error_literal (error,
566                            GTK_CSS_PROVIDER_ERROR,
567                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
568                            "Number not representable");
569       return FALSE;
570     }
571
572   g_value_set_double (value, d);
573   return TRUE;
574 }
575
576 static char *
577 double_value_to_string (const GValue *value)
578 {
579   char buf[G_ASCII_DTOSTR_BUF_SIZE];
580
581   g_ascii_dtostr (buf, sizeof (buf), g_value_get_double (value));
582
583   return g_strdup (buf);
584 }
585
586 static gboolean 
587 float_value_from_string (const char  *str,
588                          GFile       *base,
589                          GValue      *value,
590                          GError     **error)
591 {
592   double d;
593   char *end;
594
595   d = g_ascii_strtod (str, &end);
596
597   if (*end != '\0')
598     return set_default_error (error, G_VALUE_TYPE (value));
599
600   if (errno == ERANGE)
601     {
602       g_set_error_literal (error,
603                            GTK_CSS_PROVIDER_ERROR,
604                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
605                            "Number not representable");
606       return FALSE;
607     }
608
609   g_value_set_float (value, d);
610   return TRUE;
611 }
612
613 static char *
614 float_value_to_string (const GValue *value)
615 {
616   char buf[G_ASCII_DTOSTR_BUF_SIZE];
617
618   g_ascii_dtostr (buf, sizeof (buf), g_value_get_float (value));
619
620   return g_strdup (buf);
621 }
622
623 static char *
624 gtk_css_string_unescape (const char  *string,
625                          GError     **error)
626 {
627   GString *str;
628   char quote;
629   gsize len;
630
631   quote = string[0];
632   string++;
633   if (quote != '\'' && quote != '"')
634     {
635       g_set_error_literal (error,
636                            GTK_CSS_PROVIDER_ERROR,
637                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
638                            "String value not properly quoted.");
639       return NULL;
640     }
641
642   str = g_string_new (NULL);
643
644   while (TRUE)
645     {
646       len = strcspn (string, "\\'\"\n\r\f");
647
648       g_string_append_len (str, string, len);
649
650       string += len;
651
652       switch (string[0])
653         {
654         case '\\':
655           string++;
656           if (string[0] >= '0' && string[0] <= '9' &&
657               string[0] >= 'a' && string[0] <= 'f' &&
658               string[0] >= 'A' && string[0] <= 'F')
659             {
660               g_set_error_literal (error,
661                                    GTK_CSS_PROVIDER_ERROR,
662                                    GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
663                                    "FIXME: Implement unicode escape sequences.");
664               g_string_free (str, TRUE);
665               return NULL;
666             }
667           else if (string[0] == '\r' && string[1] == '\n')
668             string++;
669           else if (string[0] != '\r' && string[0] != '\n' && string[0] != '\f')
670             g_string_append_c (str, string[0]);
671           break;
672         case '"':
673         case '\'':
674           if (string[0] != quote)
675             {
676               g_string_append_c (str, string[0]);
677             }
678           else
679             {
680               if (string[1] == 0)
681                 {
682                   return g_string_free (str, FALSE);
683                 }
684               else
685                 {
686                   g_set_error_literal (error,
687                                        GTK_CSS_PROVIDER_ERROR,
688                                        GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
689                                        "Junk after end of string.");
690                   g_string_free (str, TRUE);
691                   return NULL;
692                 }
693             }
694           break;
695         case '\0':
696           g_set_error_literal (error,
697                                GTK_CSS_PROVIDER_ERROR,
698                                GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
699                                "Missing end quote in string.");
700           g_string_free (str, TRUE);
701           return NULL;
702         default:
703           g_set_error_literal (error,
704                                GTK_CSS_PROVIDER_ERROR,
705                                GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
706                                "Invalid character in string. Must be escaped.");
707           g_string_free (str, TRUE);
708           return NULL;
709         }
710
711       string++;
712     }
713
714   g_assert_not_reached ();
715   return NULL;
716 }
717
718 static gboolean 
719 string_value_from_string (const char  *str,
720                           GFile       *base,
721                           GValue      *value,
722                           GError     **error)
723 {
724   char *unescaped = gtk_css_string_unescape (str, error);
725
726   if (unescaped == NULL)
727     return FALSE;
728
729   g_value_take_string (value, unescaped);
730   return TRUE;
731 }
732
733 static char *
734 string_value_to_string (const GValue *value)
735 {
736   const char *string;
737   gsize len;
738   GString *str;
739
740   string = g_value_get_string (value);
741   str = g_string_new ("\"");
742
743   do {
744     len = strcspn (string, "\"\n\r\f");
745     g_string_append (str, string);
746     string += len;
747     switch (*string)
748       {
749       case '\0':
750         break;
751       case '\n':
752         g_string_append (str, "\\A ");
753         break;
754       case '\r':
755         g_string_append (str, "\\D ");
756         break;
757       case '\f':
758         g_string_append (str, "\\C ");
759         break;
760       case '\"':
761         g_string_append (str, "\\\"");
762         break;
763       default:
764         g_assert_not_reached ();
765         break;
766       }
767   } while (*string);
768
769   g_string_append_c (str, '"');
770   return g_string_free (str, FALSE);
771 }
772
773 static gboolean 
774 theming_engine_value_from_string (const char  *str,
775                                   GFile       *base,
776                                   GValue      *value,
777                                   GError     **error)
778 {
779   GtkThemingEngine *engine;
780
781   engine = gtk_theming_engine_load (str);
782   if (engine == NULL)
783     {
784       g_set_error (error,
785                    GTK_CSS_PROVIDER_ERROR,
786                    GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
787                    "Themeing engine '%s' not found", str);
788       return FALSE;
789     }
790
791   g_value_set_object (value, engine);
792   return TRUE;
793 }
794
795 static char *
796 theming_engine_value_to_string (const GValue *value)
797 {
798   GtkThemingEngine *engine;
799   char *name;
800
801   engine = g_value_get_object (value);
802   if (engine == NULL)
803     return g_strdup ("none");
804
805   /* XXX: gtk_theming_engine_get_name()? */
806   g_object_get (engine, "name", &name, NULL);
807
808   return name;
809 }
810
811 static gboolean 
812 animation_description_value_from_string (const char  *str,
813                                          GFile       *base,
814                                          GValue      *value,
815                                          GError     **error)
816 {
817   GtkAnimationDescription *desc;
818
819   desc = _gtk_animation_description_from_string (str);
820
821   if (desc == NULL)
822     return set_default_error (error, G_VALUE_TYPE (value));
823   
824   g_value_take_boxed (value, desc);
825   return TRUE;
826 }
827
828 static char *
829 animation_description_value_to_string (const GValue *value)
830 {
831   GtkAnimationDescription *desc = g_value_get_boxed (value);
832
833   if (desc == NULL)
834     return g_strdup ("none");
835
836   return _gtk_animation_description_to_string (desc);
837 }
838
839 static gboolean
840 parse_border_value (const char  *str,
841                     gint16      *value,
842                     const char **end,
843                     GError     **error)
844 {
845   gint64 d;
846
847   d = g_ascii_strtoll (str, (char **) end, 10);
848
849   if (d > G_MAXINT16 || d < 0)
850     {
851       g_set_error_literal (error,
852                            GTK_CSS_PROVIDER_ERROR,
853                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
854                            "Number out of range for border");
855       return FALSE;
856     }
857
858   if (str == *end)
859     {
860       g_set_error_literal (error,
861                            GTK_CSS_PROVIDER_ERROR,
862                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
863                            "No number given for border value");
864       return FALSE;
865     }
866
867   /* Skip optional unit type.
868    * We only handle pixels at the moment.
869    */
870   if (strncmp (*end, "px", 2) == 0)
871     *end += 2;
872
873   if (**end != '\0' &&
874       !g_ascii_isspace (**end))
875     {
876       g_set_error_literal (error,
877                            GTK_CSS_PROVIDER_ERROR,
878                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
879                            "Junk at end of border value");
880       return FALSE;
881     }
882
883   SKIP_SPACES (*end);
884
885   *value = d;
886   return TRUE;
887 }
888
889 static gboolean 
890 border_value_from_string (const char  *str,
891                           GFile       *base,
892                           GValue      *value,
893                           GError     **error)
894 {
895   GtkBorder *border;
896
897   border = gtk_border_new ();
898
899   if (!parse_border_value (str, &border->top, &str, error))
900     return FALSE;
901
902   if (*str == '\0')
903     border->right = border->top;
904   else
905     if (!parse_border_value (str, &border->right, &str, error))
906       return FALSE;
907
908   if (*str == '\0')
909     border->bottom = border->top;
910   else
911     if (!parse_border_value (str, &border->bottom, &str, error))
912       return FALSE;
913
914   if (*str == '\0')
915     border->left = border->right;
916   else
917     if (!parse_border_value (str, &border->left, &str, error))
918       return FALSE;
919
920   if (*str != '\0')
921     {
922       g_set_error_literal (error,
923                            GTK_CSS_PROVIDER_ERROR,
924                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
925                            "Junk at end of border value");
926       return FALSE;
927     }
928
929   g_value_take_boxed (value, border);
930   return TRUE;
931 }
932
933 static char *
934 border_value_to_string (const GValue *value)
935 {
936   const GtkBorder *border = g_value_get_boxed (value);
937
938   if (border == NULL)
939     return g_strdup ("none");
940   else if (border->left != border->right)
941     return g_strdup_printf ("%d %d %d %d", border->top, border->right, border->bottom, border->left);
942   else if (border->top != border->bottom)
943     return g_strdup_printf ("%d %d %d", border->top, border->right, border->bottom);
944   else if (border->top != border->left)
945     return g_strdup_printf ("%d %d", border->top, border->right);
946   else
947     return g_strdup_printf ("%d", border->top);
948 }
949
950 static gboolean 
951 gradient_value_from_string (const char  *str,
952                             GFile       *base,
953                             GValue      *value,
954                             GError     **error)
955 {
956   GtkGradient *gradient;
957   cairo_pattern_type_t type;
958   gdouble coords[6];
959   gchar *end;
960   guint i;
961
962   str += strlen ("-gtk-gradient");
963   SKIP_SPACES (str);
964
965   if (*str != '(')
966     {
967       g_set_error_literal (error,
968                            GTK_CSS_PROVIDER_ERROR,
969                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
970                            "Expected '(' after '-gtk-gradient'");
971       return FALSE;
972     }
973
974   str++;
975   SKIP_SPACES (str);
976
977   /* Parse gradient type */
978   if (g_str_has_prefix (str, "linear"))
979     {
980       type = CAIRO_PATTERN_TYPE_LINEAR;
981       str += strlen ("linear");
982     }
983   else if (g_str_has_prefix (str, "radial"))
984     {
985       type = CAIRO_PATTERN_TYPE_RADIAL;
986       str += strlen ("radial");
987     }
988   else
989     {
990       g_set_error_literal (error,
991                            GTK_CSS_PROVIDER_ERROR,
992                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
993                            "Gradient type must be 'radial' or 'linear'");
994       return FALSE;
995     }
996
997   SKIP_SPACES (str);
998
999   /* Parse start/stop position parameters */
1000   for (i = 0; i < 2; i++)
1001     {
1002       if (*str != ',')
1003         {
1004           g_set_error_literal (error,
1005                                GTK_CSS_PROVIDER_ERROR,
1006                                GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
1007                                "Expected ','");
1008           return FALSE;
1009         }
1010
1011       str++;
1012       SKIP_SPACES (str);
1013
1014       if (strncmp (str, "left", 4) == 0)
1015         {
1016           coords[i * 3] = 0;
1017           str += strlen ("left");
1018         }
1019       else if (strncmp (str, "right", 5) == 0)
1020         {
1021           coords[i * 3] = 1;
1022           str += strlen ("right");
1023         }
1024       else if (strncmp (str, "center", 6) == 0)
1025         {
1026           coords[i * 3] = 0.5;
1027           str += strlen ("center");
1028         }
1029       else
1030         {
1031           coords[i * 3] = g_ascii_strtod (str, &end);
1032
1033           if (str == end)
1034             return set_default_error (error, G_VALUE_TYPE (value));
1035
1036           str = end;
1037         }
1038
1039       SKIP_SPACES (str);
1040
1041       if (strncmp (str, "top", 3) == 0)
1042         {
1043           coords[(i * 3) + 1] = 0;
1044           str += strlen ("top");
1045         }
1046       else if (strncmp (str, "bottom", 6) == 0)
1047         {
1048           coords[(i * 3) + 1] = 1;
1049           str += strlen ("bottom");
1050         }
1051       else if (strncmp (str, "center", 6) == 0)
1052         {
1053           coords[(i * 3) + 1] = 0.5;
1054           str += strlen ("center");
1055         }
1056       else
1057         {
1058           coords[(i * 3) + 1] = g_ascii_strtod (str, &end);
1059
1060           if (str == end)
1061             return set_default_error (error, G_VALUE_TYPE (value));
1062
1063           str = end;
1064         }
1065
1066       SKIP_SPACES (str);
1067
1068       if (type == CAIRO_PATTERN_TYPE_RADIAL)
1069         {
1070           /* Parse radius */
1071           if (*str != ',')
1072             {
1073               g_set_error_literal (error,
1074                                    GTK_CSS_PROVIDER_ERROR,
1075                                    GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
1076                                    "Expected ','");
1077               return FALSE;
1078             }
1079
1080           str++;
1081           SKIP_SPACES (str);
1082
1083           coords[(i * 3) + 2] = g_ascii_strtod (str, &end);
1084           str = end;
1085
1086           SKIP_SPACES (str);
1087         }
1088     }
1089
1090   if (type == CAIRO_PATTERN_TYPE_LINEAR)
1091     gradient = gtk_gradient_new_linear (coords[0], coords[1], coords[3], coords[4]);
1092   else
1093     gradient = gtk_gradient_new_radial (coords[0], coords[1], coords[2],
1094                                         coords[3], coords[4], coords[5]);
1095
1096   while (*str == ',')
1097     {
1098       GtkSymbolicColor *color;
1099       gdouble position;
1100
1101       str++;
1102       SKIP_SPACES (str);
1103
1104       if (g_str_has_prefix (str, "from"))
1105         {
1106           position = 0;
1107           str += strlen ("from");
1108           SKIP_SPACES (str);
1109
1110           if (*str != '(')
1111             {
1112               g_object_unref (gradient);
1113               return set_default_error (error, G_VALUE_TYPE (value));
1114             }
1115         }
1116       else if (g_str_has_prefix (str, "to"))
1117         {
1118           position = 1;
1119           str += strlen ("to");
1120           SKIP_SPACES (str);
1121
1122           if (*str != '(')
1123             {
1124               g_object_unref (gradient);
1125               return set_default_error (error, G_VALUE_TYPE (value));
1126             }
1127         }
1128       else if (g_str_has_prefix (str, "color-stop"))
1129         {
1130           str += strlen ("color-stop");
1131           SKIP_SPACES (str);
1132
1133           if (*str != '(')
1134             {
1135               g_object_unref (gradient);
1136               return set_default_error (error, G_VALUE_TYPE (value));
1137             }
1138
1139           str++;
1140           SKIP_SPACES (str);
1141
1142           position = g_ascii_strtod (str, &end);
1143
1144           str = end;
1145           SKIP_SPACES (str);
1146
1147           if (*str != ',')
1148             {
1149               g_object_unref (gradient);
1150               return set_default_error (error, G_VALUE_TYPE (value));
1151             }
1152         }
1153       else
1154         {
1155           g_object_unref (gradient);
1156           return set_default_error (error, G_VALUE_TYPE (value));
1157         }
1158
1159       str++;
1160       SKIP_SPACES (str);
1161
1162       color = symbolic_color_parse_str (str, &end);
1163
1164       str = end;
1165       SKIP_SPACES (str);
1166
1167       if (*str != ')')
1168         {
1169           if (color)
1170             gtk_symbolic_color_unref (color);
1171           g_object_unref (gradient);
1172           return set_default_error (error, G_VALUE_TYPE (value));
1173         }
1174
1175       str++;
1176       SKIP_SPACES (str);
1177
1178       if (color)
1179         {
1180           gtk_gradient_add_color_stop (gradient, position, color);
1181           gtk_symbolic_color_unref (color);
1182         }
1183     }
1184
1185   if (*str != ')')
1186     {
1187       g_object_unref (gradient);
1188       return set_default_error (error, G_VALUE_TYPE (value));
1189     }
1190
1191   g_value_take_boxed (value, gradient);
1192   return TRUE;
1193 }
1194
1195 static char *
1196 gradient_value_to_string (const GValue *value)
1197 {
1198   GtkGradient *gradient = g_value_get_boxed (value);
1199
1200   if (gradient == NULL)
1201     return g_strdup ("none");
1202
1203   return gtk_gradient_to_string (gradient);
1204 }
1205
1206 static gboolean 
1207 pattern_value_from_string (const char  *str,
1208                            GFile       *base,
1209                            GValue      *value,
1210                            GError     **error)
1211 {
1212   if (g_str_has_prefix (str, "-gtk-gradient"))
1213     {
1214       g_value_unset (value);
1215       g_value_init (value, GTK_TYPE_GRADIENT);
1216       return gradient_value_from_string (str, base, value, error);
1217     }
1218   else
1219     {
1220       gchar *path;
1221       GdkPixbuf *pixbuf;
1222       GFile *file;
1223
1224       file = _gtk_css_parse_url (base, str, NULL, error);
1225       if (file == NULL)
1226         return FALSE;
1227
1228       path = g_file_get_path (file);
1229       g_object_unref (file);
1230
1231       pixbuf = gdk_pixbuf_new_from_file (path, error);
1232       g_free (path);
1233       if (pixbuf == NULL)
1234         return FALSE;
1235
1236       cairo_surface_t *surface;
1237       cairo_pattern_t *pattern;
1238       cairo_t *cr;
1239       cairo_matrix_t matrix;
1240
1241       surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
1242                                             gdk_pixbuf_get_width (pixbuf),
1243                                             gdk_pixbuf_get_height (pixbuf));
1244       cr = cairo_create (surface);
1245       gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
1246       cairo_paint (cr);
1247       pattern = cairo_pattern_create_for_surface (surface);
1248
1249       cairo_matrix_init_scale (&matrix,
1250                                gdk_pixbuf_get_width (pixbuf),
1251                                gdk_pixbuf_get_height (pixbuf));
1252       cairo_pattern_set_matrix (pattern, &matrix);
1253
1254       cairo_surface_destroy (surface);
1255       cairo_destroy (cr);
1256       g_object_unref (pixbuf);
1257
1258       g_value_take_boxed (value, pattern);
1259     }
1260   
1261   return TRUE;
1262 }
1263
1264 static gboolean 
1265 slice_value_from_string (const char  *str,
1266                          GFile       *base,
1267                          GValue      *value,
1268                          GError     **error)
1269 {
1270   gdouble distance_top, distance_bottom;
1271   gdouble distance_left, distance_right;
1272   GtkSliceSideModifier mods[2];
1273   GdkPixbuf *pixbuf;
1274   Gtk9Slice *slice;
1275   GFile *file;
1276   gint i = 0;
1277   char *path;
1278
1279   SKIP_SPACES (str);
1280
1281   /* Parse image url */
1282   file = _gtk_css_parse_url (base, str, (char **) &str, error);
1283   if (!file)
1284       return FALSE;
1285
1286   SKIP_SPACES (str);
1287
1288   /* Parse top/left/bottom/right distances */
1289   distance_top = g_ascii_strtod (str, (char **) &str);
1290
1291   SKIP_SPACES (str);
1292
1293   distance_right = g_ascii_strtod (str, (char **) &str);
1294
1295   SKIP_SPACES (str);
1296
1297   distance_bottom = g_ascii_strtod (str, (char **) &str);
1298
1299   SKIP_SPACES (str);
1300
1301   distance_left = g_ascii_strtod (str, (char **) &str);
1302
1303   SKIP_SPACES (str);
1304
1305   while (*str && i < 2)
1306     {
1307       if (g_str_has_prefix (str, "stretch"))
1308         {
1309           str += strlen ("stretch");
1310           mods[i] = GTK_SLICE_STRETCH;
1311         }
1312       else if (g_str_has_prefix (str, "repeat"))
1313         {
1314           str += strlen ("repeat");
1315           mods[i] = GTK_SLICE_REPEAT;
1316         }
1317       else
1318         {
1319           g_object_unref (file);
1320           return set_default_error (error, G_VALUE_TYPE (value));
1321         }
1322
1323       SKIP_SPACES (str);
1324       i++;
1325     }
1326
1327   if (*str != '\0')
1328     {
1329       g_object_unref (file);
1330       return set_default_error (error, G_VALUE_TYPE (value));
1331     }
1332
1333   if (i != 2)
1334     {
1335       /* Fill in second modifier, same as the first */
1336       mods[1] = mods[0];
1337     }
1338
1339   path = g_file_get_path (file);
1340   pixbuf = gdk_pixbuf_new_from_file (path, error);
1341   g_free (path);
1342   if (!pixbuf)
1343     return FALSE;
1344
1345   slice = _gtk_9slice_new (pixbuf,
1346                            distance_top, distance_bottom,
1347                            distance_left, distance_right,
1348                            mods[0], mods[1]);
1349   g_object_unref (pixbuf);
1350
1351   g_value_take_boxed (value, slice);
1352   return TRUE;
1353 }
1354
1355 static gboolean 
1356 enum_value_from_string (const char  *str,
1357                         GFile       *base,
1358                         GValue      *value,
1359                         GError     **error)
1360 {
1361   GEnumClass *enum_class;
1362   GEnumValue *enum_value;
1363
1364   enum_class = g_type_class_ref (G_VALUE_TYPE (value));
1365   enum_value = g_enum_get_value_by_nick (enum_class, str);
1366
1367   if (!enum_value)
1368     {
1369       g_set_error (error,
1370                    GTK_CSS_PROVIDER_ERROR,
1371                    GTK_CSS_PROVIDER_ERROR_FAILED,
1372                    "Unknown value '%s' for enum type '%s'",
1373                    str, g_type_name (G_VALUE_TYPE (value)));
1374       g_type_class_unref (enum_class);
1375       return FALSE;
1376     }
1377   
1378   g_value_set_enum (value, enum_value->value);
1379   g_type_class_unref (enum_class);
1380   return TRUE;
1381 }
1382
1383 static char *
1384 enum_value_to_string (const GValue *value)
1385 {
1386   GEnumClass *enum_class;
1387   GEnumValue *enum_value;
1388   char *s;
1389
1390   enum_class = g_type_class_ref (G_VALUE_TYPE (value));
1391   enum_value = g_enum_get_value (enum_class, g_value_get_enum (value));
1392
1393   s = g_strdup (enum_value->value_nick);
1394
1395   g_type_class_unref (enum_class);
1396
1397   return s;
1398 }
1399
1400 static gboolean 
1401 flags_value_from_string (const char  *str,
1402                          GFile       *base,
1403                          GValue      *value,
1404                          GError     **error)
1405 {
1406   GFlagsClass *flags_class;
1407   GFlagsValue *flag_value;
1408   guint flags = 0;
1409   char **strv;
1410   guint i;
1411
1412   strv = g_strsplit (str, ",", -1);
1413
1414   flags_class = g_type_class_ref (G_VALUE_TYPE (value));
1415
1416   for (i = 0; strv[i]; i++)
1417     {
1418       strv[i] = g_strstrip (strv[i]);
1419
1420       flag_value = g_flags_get_value_by_nick (flags_class, strv[i]);
1421       if (!flag_value)
1422         {
1423           g_set_error (error,
1424                        GTK_CSS_PROVIDER_ERROR,
1425                        GTK_CSS_PROVIDER_ERROR_PROPERTY_NAME,
1426                        "Unknown flag value '%s' for type '%s'",
1427                        strv[i], g_type_name (G_VALUE_TYPE (value)));
1428           g_type_class_unref (flags_class);
1429           return FALSE;
1430         }
1431       
1432       flags |= flag_value->value;
1433     }
1434
1435   g_strfreev (strv);
1436   g_type_class_unref (flags_class);
1437
1438   g_value_set_enum (value, flags);
1439
1440   return TRUE;
1441 }
1442
1443 static char *
1444 flags_value_to_string (const GValue *value)
1445 {
1446   GFlagsClass *flags_class;
1447   GString *string;
1448   guint i, flags;
1449
1450   flags_class = g_type_class_ref (G_VALUE_TYPE (value));
1451   flags = g_value_get_flags (value);
1452   string = g_string_new (NULL);
1453
1454   for (i = 0; i < flags_class->n_values; i++)
1455     {
1456       GFlagsValue *flags_value = &flags_class->values[i];
1457
1458       if (flags & flags_value->value)
1459         {
1460           if (string->len != 0)
1461             g_string_append (string, ", ");
1462
1463           g_string_append (string, flags_value->value_nick);
1464         }
1465     }
1466
1467   g_type_class_unref (flags_class);
1468
1469   return g_string_free (string, FALSE);
1470 }
1471
1472 /*** API ***/
1473
1474 static void
1475 css_string_funcs_init (void)
1476 {
1477   if (G_LIKELY (from_string_funcs != NULL))
1478     return;
1479
1480   from_string_funcs = g_hash_table_new (NULL, NULL);
1481   to_string_funcs = g_hash_table_new (NULL, NULL);
1482
1483   register_conversion_function (GDK_TYPE_RGBA,
1484                                 rgba_value_from_string,
1485                                 rgba_value_to_string);
1486   register_conversion_function (GDK_TYPE_COLOR,
1487                                 color_value_from_string,
1488                                 color_value_to_string);
1489   register_conversion_function (GTK_TYPE_SYMBOLIC_COLOR,
1490                                 symbolic_color_value_from_string,
1491                                 symbolic_color_value_to_string);
1492   register_conversion_function (PANGO_TYPE_FONT_DESCRIPTION,
1493                                 font_description_value_from_string,
1494                                 font_description_value_to_string);
1495   register_conversion_function (G_TYPE_BOOLEAN,
1496                                 boolean_value_from_string,
1497                                 boolean_value_to_string);
1498   register_conversion_function (G_TYPE_INT,
1499                                 int_value_from_string,
1500                                 int_value_to_string);
1501   register_conversion_function (G_TYPE_UINT,
1502                                 uint_value_from_string,
1503                                 uint_value_to_string);
1504   register_conversion_function (G_TYPE_DOUBLE,
1505                                 double_value_from_string,
1506                                 double_value_to_string);
1507   register_conversion_function (G_TYPE_FLOAT,
1508                                 float_value_from_string,
1509                                 float_value_to_string);
1510   register_conversion_function (G_TYPE_STRING,
1511                                 string_value_from_string,
1512                                 string_value_to_string);
1513   register_conversion_function (GTK_TYPE_THEMING_ENGINE,
1514                                 theming_engine_value_from_string,
1515                                 theming_engine_value_to_string);
1516   register_conversion_function (GTK_TYPE_ANIMATION_DESCRIPTION,
1517                                 animation_description_value_from_string,
1518                                 animation_description_value_to_string);
1519   register_conversion_function (GTK_TYPE_BORDER,
1520                                 border_value_from_string,
1521                                 border_value_to_string);
1522   register_conversion_function (GTK_TYPE_GRADIENT,
1523                                 gradient_value_from_string,
1524                                 gradient_value_to_string);
1525   register_conversion_function (CAIRO_GOBJECT_TYPE_PATTERN,
1526                                 pattern_value_from_string,
1527                                 NULL);
1528   register_conversion_function (GTK_TYPE_9SLICE,
1529                                 slice_value_from_string,
1530                                 NULL);
1531   register_conversion_function (G_TYPE_ENUM,
1532                                 enum_value_from_string,
1533                                 enum_value_to_string);
1534   register_conversion_function (G_TYPE_FLAGS,
1535                                 flags_value_from_string,
1536                                 flags_value_to_string);
1537 }
1538
1539 gboolean
1540 _gtk_css_value_from_string (GValue        *value,
1541                             GFile         *base,
1542                             const char    *string,
1543                             GError       **error)
1544 {
1545   FromStringFunc func;
1546
1547   g_return_val_if_fail (string != NULL, FALSE);
1548   g_return_val_if_fail (string[0] != 0, FALSE);
1549
1550   css_string_funcs_init ();
1551
1552   func = g_hash_table_lookup (from_string_funcs,
1553                               GSIZE_TO_POINTER (G_VALUE_TYPE (value)));
1554   if (func == NULL)
1555     func = g_hash_table_lookup (from_string_funcs,
1556                                 GSIZE_TO_POINTER (g_type_fundamental (G_VALUE_TYPE (value))));
1557
1558   if (func == NULL)
1559     {
1560       g_set_error (error,
1561                    GTK_CSS_PROVIDER_ERROR,
1562                    GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
1563                    "Cannot convert to type '%s'",
1564                    g_type_name (G_VALUE_TYPE (value)));
1565       return FALSE;
1566     }
1567
1568   return (*func) (string, base, value, error);
1569 }
1570
1571 char *
1572 _gtk_css_value_to_string (const GValue *value)
1573 {
1574   ToStringFunc func;
1575
1576   css_string_funcs_init ();
1577
1578   func = g_hash_table_lookup (to_string_funcs,
1579                               GSIZE_TO_POINTER (G_VALUE_TYPE (value)));
1580   if (func == NULL)
1581     func = g_hash_table_lookup (to_string_funcs,
1582                                 GSIZE_TO_POINTER (g_type_fundamental (G_VALUE_TYPE (value))));
1583
1584   if (func)
1585     return func (value);
1586
1587   return g_strdup_value_contents (value);
1588 }
1589
1590 GtkSymbolicColor *
1591 _gtk_css_parse_symbolic_color (const char    *str,
1592                                GError       **error)
1593 {
1594   GtkSymbolicColor *color;
1595   gchar *end;
1596
1597   color = symbolic_color_parse_str (str, &end);
1598
1599   if (*end != '\0')
1600     {
1601       if (color)
1602         {
1603           gtk_symbolic_color_unref (color);
1604           color = NULL;
1605         }
1606
1607       g_set_error_literal (error,
1608                            GTK_CSS_PROVIDER_ERROR,
1609                            GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
1610                            "Failed to parse symbolic color");
1611     }
1612
1613   return color;
1614 }
1615
1616 GFile *
1617 _gtk_css_parse_url (GFile       *base,
1618                     const char  *str,
1619                     char       **end,
1620                     GError     **error)
1621 {
1622   gchar *path, *chr;
1623   GFile *file;
1624
1625   if (g_str_has_prefix (str, "url"))
1626     {
1627       str += strlen ("url");
1628       SKIP_SPACES (str);
1629
1630       if (*str != '(')
1631         {
1632           g_set_error_literal (error,
1633                                GTK_CSS_PROVIDER_ERROR,
1634                                GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
1635                                "Expected '(' after 'url'");
1636           return NULL;
1637         }
1638
1639       chr = strchr (str, ')');
1640       if (!chr)
1641         {
1642           g_set_error_literal (error,
1643                                GTK_CSS_PROVIDER_ERROR,
1644                                GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
1645                                "No closing ')' found for 'url'");
1646           return NULL;
1647         }
1648
1649       if (end)
1650         *end = chr + 1;
1651
1652       str++;
1653       SKIP_SPACES (str);
1654
1655       if (*str == '"' || *str == '\'')
1656         {
1657           const gchar *p;
1658           p = str;
1659
1660           str++;
1661           chr--;
1662           SKIP_SPACES_BACK (chr);
1663
1664           if (*chr != *p || chr == p)
1665             {
1666               g_set_error_literal (error,
1667                                    GTK_CSS_PROVIDER_ERROR,
1668                                    GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
1669                                    "Did not find closing quote for url");
1670               return NULL;
1671             }
1672         }
1673       else
1674         {
1675           g_set_error_literal (error,
1676                                GTK_CSS_PROVIDER_ERROR,
1677                                GTK_CSS_PROVIDER_ERROR_PROPERTY_VALUE,
1678                                "url not properly escaped");
1679           return NULL;
1680         }
1681
1682       path = g_strndup (str, chr - str);
1683       g_strstrip (path);
1684     }
1685   else
1686     {
1687       path = g_strdup (str);
1688       if (end)
1689         *end = (gchar *) str + strlen (str);
1690     }
1691
1692   file = g_file_resolve_relative_path (base, path);
1693   g_free (path);
1694
1695   return file;
1696 }