1 /*  Copyright 2015 Red Hat, Inc.
2  *
3  * GTK+ is free software; you can redistribute it and/or modify it
4  * under the terms of the GNU Lesser General Public License as
5  * published by the Free Software Foundation; either version 2 of the
6  * License, or (at your option) any later version.
7  *
8  * GLib is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with GTK+; see the file COPYING.  If not,
15  * see <http://www.gnu.org/licenses/>.
16  *
17  * Author: Matthias Clasen
18  */
19 
20 #include <stdlib.h>
21 #include <string.h>
22 #include <errno.h>
23 
24 #include <glib/gi18n.h>
25 #include <glib/gprintf.h>
26 #include <glib/gstdio.h>
27 #include <gtk/gtk.h>
28 #include "gtkbuilderprivate.h"
29 
30 
31 typedef struct {
32   GtkBuilder *builder;
33   GList *classes;
34   gboolean packing;
35   gboolean packing_started;
36   gboolean cell_packing;
37   gboolean cell_packing_started;
38   gint in_child;
39   gint child_started;
40   gchar **attribute_names;
41   gchar **attribute_values;
42   GString *value;
43   gboolean unclosed_starttag;
44   gint indent;
45   char *input_filename;
46   char *output_filename;
47   FILE *output;
48 } MyParserData;
49 
50 static void
canonicalize_key(gchar * key)51 canonicalize_key (gchar *key)
52 {
53   gchar *p;
54 
55   for (p = key; *p != 0; p++)
56     {
57       gchar c = *p;
58 
59       /* We may meet something like AtkObject::accessible-name */
60       if (c == ':' && ((p > key && p[-1] == ':') || p[1] == ':'))
61         continue;
62 
63       if (c != '-' &&
64           (c < '0' || c > '9') &&
65           (c < 'A' || c > 'Z') &&
66           (c < 'a' || c > 'z'))
67         *p = '-';
68     }
69 }
70 
71 static GParamSpec *
get_property_pspec(MyParserData * data,const gchar * class_name,const gchar * property_name)72 get_property_pspec (MyParserData *data,
73                     const gchar  *class_name,
74                     const gchar  *property_name)
75 {
76   GType type;
77   GObjectClass *class;
78   GParamSpec *pspec;
79   gchar *canonical_name;
80 
81   type = g_type_from_name (class_name);
82   if (type == G_TYPE_INVALID)
83     return NULL;
84 
85   class = g_type_class_ref (type);
86   canonical_name = g_strdup (property_name);
87   canonicalize_key (canonical_name);
88   if (data->packing)
89     pspec = gtk_container_class_find_child_property (class, canonical_name);
90   else if (data->cell_packing)
91     {
92       GObjectClass *cell_class;
93 
94       /* We're just assuming that the cell layout is using a GtkCellAreaBox. */
95       cell_class = g_type_class_ref (GTK_TYPE_CELL_AREA_BOX);
96       pspec = gtk_cell_area_class_find_cell_property (GTK_CELL_AREA_CLASS (cell_class), canonical_name);
97       g_type_class_unref (cell_class);
98     }
99   else
100     pspec = g_object_class_find_property (class, canonical_name);
101   g_free (canonical_name);
102   g_type_class_unref (class);
103 
104   return pspec;
105 }
106 
107 
108 static gboolean
value_is_default(MyParserData * data,const gchar * class_name,const gchar * property_name,const gchar * value_string)109 value_is_default (MyParserData *data,
110                   const gchar  *class_name,
111                   const gchar  *property_name,
112                   const gchar  *value_string)
113 {
114   GValue value = { 0, };
115   gboolean ret;
116   GError *error = NULL;
117   GParamSpec *pspec;
118 
119   pspec = get_property_pspec (data, class_name, property_name);
120 
121   if (pspec == NULL)
122     {
123       if (data->packing)
124         g_printerr (_("Packing property %s::%s not found\n"), class_name, property_name);
125       else if (data->cell_packing)
126         g_printerr (_("Cell property %s::%s not found\n"), class_name, property_name);
127       else
128         g_printerr (_("Property %s::%s not found\n"), class_name, property_name);
129       return FALSE;
130     }
131   else if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (pspec), G_TYPE_OBJECT))
132     return FALSE;
133 
134   if (!gtk_builder_value_from_string (data->builder, pspec, value_string, &value, &error))
135     {
136       g_printerr (_("Couldn't parse value for %s::%s: %s\n"), class_name, property_name, error->message);
137       g_error_free (error);
138       ret = FALSE;
139     }
140   else
141     ret = g_param_value_defaults (pspec, &value);
142 
143   g_value_reset (&value);
144 
145   return ret;
146 }
147 
148 static gboolean
property_is_boolean(MyParserData * data,const gchar * class_name,const gchar * property_name)149 property_is_boolean (MyParserData *data,
150                      const gchar  *class_name,
151                      const gchar  *property_name)
152 {
153   GParamSpec *pspec;
154 
155   pspec = get_property_pspec (data, class_name, property_name);
156   if (pspec)
157     return G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN;
158 
159   return FALSE;
160 }
161 
162 static const gchar *
canonical_boolean_value(MyParserData * data,const gchar * string)163 canonical_boolean_value (MyParserData *data,
164                          const gchar  *string)
165 {
166   GValue value = G_VALUE_INIT;
167   gboolean b = FALSE;
168 
169   if (gtk_builder_value_from_string_type (data->builder, G_TYPE_BOOLEAN, string, &value, NULL))
170     b = g_value_get_boolean (&value);
171 
172   return b ? "1" : "0";
173 }
174 
175 /* A number of properties unfortunately can't be omitted even
176  * if they are nominally set to their default value. In many
177  * cases, this is due to subclasses not overriding the default
178  * value from the superclass.
179  */
180 static gboolean
needs_explicit_setting(MyParserData * data,const gchar * class_name,const gchar * property_name)181 needs_explicit_setting (MyParserData *data,
182                         const gchar  *class_name,
183                         const gchar  *property_name)
184 {
185   struct _Prop {
186     const char *class;
187     const char *property;
188     gboolean packing;
189   } props[] = {
190     { "GtkAboutDialog", "program-name", 0 },
191     { "GtkCalendar", "year", 0 },
192     { "GtkCalendar", "month", 0 },
193     { "GtkCalendar", "day", 0 },
194     { "GtkDialog", "border-width", 0 },
195     { "GtkPlacesSidebar", "show-desktop", 0 },
196     { "GtkRadioButton", "draw-indicator", 0 },
197     { "GtkGrid", "left-attach", 1 },
198     { "GtkGrid", "top-attach", 1 },
199     { "GtkWidget", "hexpand", 0 },
200     { "GtkWidget", "vexpand", 0 },
201     { "GtkContainer", "border-width", 0 },
202     { "GtkVBox", "expand", 1 },
203     { "GtkHBox", "expand", 1 },
204     { NULL, NULL, 0 }
205   };
206   gchar *canonical_name;
207   gboolean found;
208   gint k;
209 
210   canonical_name = g_strdup (property_name);
211   g_strdelimit (canonical_name, "_", '-');
212 
213   found = FALSE;
214   for (k = 0; props[k].class; k++)
215     {
216       if (strcmp (class_name, props[k].class) == 0 &&
217           strcmp (canonical_name, props[k].property) == 0 &&
218           data->packing == props[k].packing)
219         {
220           found = TRUE;
221           break;
222         }
223     }
224 
225   g_free (canonical_name);
226 
227   return found;
228 }
229 
230 static void
maybe_start_packing(MyParserData * data)231 maybe_start_packing (MyParserData *data)
232 {
233   if (data->packing)
234     {
235       if (!data->packing_started)
236         {
237           g_fprintf (data->output, "%*s<packing>\n", data->indent, "");
238           data->indent += 2;
239           data->packing_started = TRUE;
240         }
241     }
242 }
243 
244 static void
maybe_start_cell_packing(MyParserData * data)245 maybe_start_cell_packing (MyParserData *data)
246 {
247   if (data->cell_packing)
248     {
249       if (!data->cell_packing_started)
250         {
251           g_fprintf (data->output, "%*s<cell-packing>\n", data->indent, "");
252           data->indent += 2;
253           data->cell_packing_started = TRUE;
254         }
255     }
256 }
257 
258 static void
maybe_start_child(MyParserData * data)259 maybe_start_child (MyParserData *data)
260 {
261   if (data->in_child > 0)
262     {
263       if (data->child_started < data->in_child)
264         {
265           g_fprintf (data->output, "%*s<child>\n", data->indent, "");
266           data->indent += 2;
267           data->child_started += 1;
268         }
269     }
270 }
271 
272 static void
maybe_emit_property(MyParserData * data)273 maybe_emit_property (MyParserData *data)
274 {
275   gint i;
276   gboolean bound;
277   gboolean translatable;
278   gchar *escaped;
279   const gchar *class_name;
280   const gchar *property_name;
281   const gchar *value_string;
282 
283   class_name = (const gchar *)data->classes->data;
284   property_name = "";
285   value_string = (const gchar *)data->value->str;
286 
287   bound = FALSE;
288   translatable = FALSE;
289   for (i = 0; data->attribute_names[i]; i++)
290     {
291       if (strcmp (data->attribute_names[i], "bind-source") == 0 ||
292           strcmp (data->attribute_names[i], "bind_source") == 0)
293         bound = TRUE;
294       else if (strcmp (data->attribute_names[i], "translatable") == 0)
295         translatable = TRUE;
296       else if (strcmp (data->attribute_names[i], "name") == 0)
297         property_name = (const gchar *)data->attribute_values[i];
298     }
299 
300   if (!translatable &&
301       !bound &&
302       !needs_explicit_setting (data, class_name, property_name))
303     {
304       for (i = 0; data->attribute_names[i]; i++)
305         {
306           if (strcmp (data->attribute_names[i], "name") == 0)
307             {
308               if (data->classes == NULL)
309                 break;
310 
311               if (value_is_default (data, class_name, property_name, value_string))
312                 return;
313             }
314         }
315     }
316 
317   maybe_start_packing (data);
318   maybe_start_cell_packing (data);
319 
320   g_fprintf (data->output, "%*s<property", data->indent, "");
321   for (i = 0; data->attribute_names[i]; i++)
322     {
323       if (!translatable &&
324           (strcmp (data->attribute_names[i], "comments") == 0 ||
325            strcmp (data->attribute_names[i], "context") == 0))
326         continue;
327 
328       escaped = g_markup_escape_text (data->attribute_values[i], -1);
329 
330       if (strcmp (data->attribute_names[i], "name") == 0)
331         canonicalize_key (escaped);
332 
333       g_fprintf (data->output, " %s=\"%s\"", data->attribute_names[i], escaped);
334       g_free (escaped);
335     }
336 
337   if (bound)
338     {
339       g_fprintf (data->output, "/>\n");
340     }
341   else
342     {
343       g_fprintf (data->output, ">");
344       if (property_is_boolean (data, class_name, property_name))
345         {
346           g_fprintf (data->output, "%s", canonical_boolean_value (data, value_string));
347         }
348       else
349         {
350           escaped = g_markup_escape_text (value_string, -1);
351           g_fprintf (data->output, "%s", escaped);
352           g_free (escaped);
353         }
354       g_fprintf (data->output, "</property>\n");
355     }
356 }
357 
358 static void
maybe_close_starttag(MyParserData * data)359 maybe_close_starttag (MyParserData *data)
360 {
361   if (data->unclosed_starttag)
362     {
363       g_fprintf (data->output, ">\n");
364       data->unclosed_starttag = FALSE;
365     }
366 }
367 
368 static gboolean
stack_is(GMarkupParseContext * context,...)369 stack_is (GMarkupParseContext *context,
370           ...)
371 {
372   va_list args;
373   gchar *s, *p;
374   const GSList *stack;
375 
376   stack = g_markup_parse_context_get_element_stack (context);
377 
378   va_start (args, context);
379   s = va_arg (args, gchar *);
380   while (s)
381     {
382       if (stack == NULL)
383         {
384           va_end (args);
385           return FALSE;
386         }
387 
388       p = (gchar *)stack->data;
389       if (strcmp (s, p) != 0)
390         {
391           va_end (args);
392           return FALSE;
393         }
394 
395       s = va_arg (args, gchar *);
396       stack = stack->next;
397     }
398 
399   va_end (args);
400   return TRUE;
401 }
402 
403 static void
start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)404 start_element (GMarkupParseContext  *context,
405                const gchar          *element_name,
406                const gchar         **attribute_names,
407                const gchar         **attribute_values,
408                gpointer              user_data,
409                GError              **error)
410 {
411   gint i;
412   gchar *escaped;
413   MyParserData *data = user_data;
414 
415   maybe_close_starttag (data);
416 
417   if (strcmp (element_name, "property") == 0)
418     {
419       g_assert (data->attribute_names == NULL);
420       g_assert (data->attribute_values == NULL);
421       g_assert (data->value == NULL);
422 
423       data->attribute_names = g_strdupv ((gchar **)attribute_names);
424       data->attribute_values = g_strdupv ((gchar **)attribute_values);
425       data->value = g_string_new ("");
426 
427       return;
428     }
429   else if (strcmp (element_name, "packing") == 0)
430     {
431       data->packing = TRUE;
432       data->packing_started = FALSE;
433 
434       return;
435     }
436   else if (strcmp (element_name, "cell-packing") == 0)
437     {
438       data->cell_packing = TRUE;
439       data->cell_packing_started = FALSE;
440 
441       return;
442     }
443   else if (strcmp (element_name, "child") == 0)
444     {
445       data->in_child += 1;
446 
447       if (attribute_names[0] == NULL)
448         return;
449 
450       data->child_started += 1;
451     }
452   else if (strcmp (element_name, "attribute") == 0)
453     {
454       /* attribute in label has no content */
455       if (data->classes == NULL ||
456           strcmp ((gchar *)data->classes->data, "GtkLabel") != 0)
457         data->value = g_string_new ("");
458     }
459   else if (stack_is (context, "item", "items", NULL) ||
460            stack_is (context, "action-widget", "action-widgets", NULL) ||
461            stack_is (context, "mime-type", "mime-types", NULL) ||
462            stack_is (context, "pattern", "patterns", NULL) ||
463            stack_is (context, "application", "applications", NULL) ||
464            stack_is (context, "col", "row", "data", NULL) ||
465            stack_is (context, "mark", "marks", NULL) ||
466            stack_is (context, "action", "accessibility", NULL))
467     {
468       data->value = g_string_new ("");
469     }
470   else if (strcmp (element_name, "placeholder") == 0)
471     {
472       return;
473     }
474   else if (strcmp (element_name, "object") == 0 ||
475            strcmp (element_name, "template") == 0)
476     {
477       maybe_start_child (data);
478 
479       for (i = 0; attribute_names[i]; i++)
480         {
481           if (strcmp (attribute_names[i], "class") == 0)
482             {
483               data->classes = g_list_prepend (data->classes,
484                                               g_strdup (attribute_values[i]));
485               break;
486             }
487         }
488     }
489 
490   g_fprintf (data->output, "%*s<%s", data->indent, "", element_name);
491   for (i = 0; attribute_names[i]; i++)
492     {
493       escaped = g_markup_escape_text (attribute_values[i], -1);
494       g_fprintf (data->output, " %s=\"%s\"", attribute_names[i], escaped);
495       g_free (escaped);
496     }
497   data->unclosed_starttag = TRUE;
498   data->indent += 2;
499 }
500 
501 static void
end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)502 end_element (GMarkupParseContext  *context,
503              const gchar          *element_name,
504              gpointer              user_data,
505              GError              **error)
506 {
507   MyParserData *data = user_data;
508 
509   if (strcmp (element_name, "property") == 0)
510     {
511       maybe_emit_property (data);
512 
513       g_clear_pointer (&data->attribute_names, g_strfreev);
514       g_clear_pointer (&data->attribute_values, g_strfreev);
515       g_string_free (data->value, TRUE);
516       data->value = NULL;
517       return;
518     }
519   else if (strcmp (element_name, "packing") == 0)
520     {
521       data->packing = FALSE;
522       if (!data->packing_started)
523         return;
524     }
525   else if (strcmp (element_name, "cell-packing") == 0)
526     {
527       data->cell_packing = FALSE;
528       if (!data->cell_packing_started)
529         return;
530     }
531   else if (strcmp (element_name, "child") == 0)
532     {
533       data->in_child -= 1;
534       if (data->child_started == data->in_child)
535         return;
536       data->child_started -= 1;
537     }
538   else if (strcmp (element_name, "placeholder") == 0)
539     {
540       return;
541     }
542   else if (strcmp (element_name, "object") == 0 ||
543            strcmp (element_name, "template") == 0)
544     {
545       g_free (data->classes->data);
546       data->classes = g_list_delete_link (data->classes, data->classes);
547     }
548 
549   if (data->value != NULL)
550     {
551       gchar *escaped;
552 
553       if (data->unclosed_starttag)
554         g_fprintf (data->output, ">");
555 
556       escaped = g_markup_escape_text (data->value->str, -1);
557       g_fprintf (data->output, "%s</%s>\n", escaped, element_name);
558       g_free (escaped);
559 
560       g_string_free (data->value, TRUE);
561       data->value = NULL;
562     }
563   else
564     {
565       if (data->unclosed_starttag)
566         g_fprintf (data->output, "/>\n");
567       else
568         g_fprintf (data->output, "%*s</%s>\n", data->indent - 2, "", element_name);
569     }
570 
571   data->indent -= 2;
572   data->unclosed_starttag = FALSE;
573 }
574 
575 static void
text(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)576 text (GMarkupParseContext  *context,
577       const gchar          *text,
578       gsize                 text_len,
579       gpointer              user_data,
580       GError              **error)
581 {
582   MyParserData *data = user_data;
583 
584   if (data->value)
585     {
586       g_string_append_len (data->value, text, text_len);
587       return;
588     }
589 }
590 
591 static void
passthrough(GMarkupParseContext * context,const gchar * text,gsize text_len,gpointer user_data,GError ** error)592 passthrough (GMarkupParseContext  *context,
593              const gchar          *text,
594              gsize                 text_len,
595              gpointer              user_data,
596              GError              **error)
597 {
598   MyParserData *data = user_data;
599 
600   maybe_close_starttag (data);
601 
602   g_fprintf (data->output, "%*s%s\n", data->indent, "", text);
603 }
604 
605 GMarkupParser parser = {
606   start_element,
607   end_element,
608   text,
609   passthrough,
610   NULL
611 };
612 
613 static void
do_simplify(int * argc,const char *** argv)614 do_simplify (int          *argc,
615              const char ***argv)
616 {
617   GMarkupParseContext *context;
618   gchar *buffer;
619   MyParserData data;
620   gboolean replace = FALSE;
621   char **filenames = NULL;
622   GOptionContext *ctx;
623   const GOptionEntry entries[] = {
624     { "replace", 0, 0, G_OPTION_ARG_NONE, &replace, NULL, NULL },
625     { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, NULL },
626     { NULL, }
627   };
628   GError *error = NULL;
629 
630   ctx = g_option_context_new (NULL);
631   g_option_context_set_help_enabled (ctx, FALSE);
632   g_option_context_add_main_entries (ctx, entries, NULL);
633 
634   if (!g_option_context_parse (ctx, argc, (char ***)argv, &error))
635     {
636       g_printerr ("%s\n", error->message);
637       g_error_free (error);
638       exit (1);
639     }
640 
641   g_option_context_free (ctx);
642 
643   if (filenames == NULL)
644     {
645       g_printerr ("No .ui file specified\n");
646       exit (1);
647     }
648 
649   if (g_strv_length (filenames) > 1)
650     {
651       g_printerr ("Can only simplify a single .ui file\n");
652       exit (1);
653     }
654 
655   data.input_filename = filenames[0];
656   data.output_filename = NULL;
657 
658   if (replace)
659     {
660       int fd;
661       fd = g_file_open_tmp ("gtk-builder-tool-XXXXXX", &data.output_filename, NULL);
662       data.output = fdopen (fd, "w");
663     }
664   else
665     {
666       data.output = stdout;
667     }
668 
669   if (!g_file_get_contents (filenames[0], &buffer, NULL, &error))
670     {
671       g_printerr (_("Can't load file: %s\n"), error->message);
672       exit (1);
673     }
674 
675   data.builder = gtk_builder_new ();
676   data.classes = NULL;
677   data.attribute_names = NULL;
678   data.attribute_values = NULL;
679   data.value = NULL;
680   data.packing = FALSE;
681   data.packing_started = FALSE;
682   data.cell_packing = FALSE;
683   data.cell_packing_started = FALSE;
684   data.in_child = 0;
685   data.child_started = 0;
686   data.unclosed_starttag = FALSE;
687   data.indent = 0;
688 
689   context = g_markup_parse_context_new (&parser, G_MARKUP_TREAT_CDATA_AS_TEXT, &data, NULL);
690   if (!g_markup_parse_context_parse (context, buffer, -1, &error))
691     {
692       g_printerr (_("Can't parse file: %s\n"), error->message);
693       exit (1);
694     }
695 
696   fclose (data.output);
697 
698   if (data.output_filename)
699     {
700       char *content;
701       gsize length;
702 
703       if (!g_file_get_contents (data.output_filename, &content, &length, &error))
704         {
705           g_printerr ("Failed to read %s: %s\n", data.output_filename, error->message);
706           exit (1);
707         }
708 
709       if (!g_file_set_contents (data.input_filename, content, length, &error))
710         {
711           g_printerr ("Failed to write %s: %s\n", data.input_filename, error->message);
712           exit (1);
713         }
714     }
715 }
716 
717 static GType
make_fake_type(const gchar * type_name,const gchar * parent_name)718 make_fake_type (const gchar *type_name,
719                 const gchar *parent_name)
720 {
721   GType parent_type;
722   GTypeQuery query;
723 
724   parent_type = g_type_from_name (parent_name);
725   if (parent_type == G_TYPE_INVALID)
726     {
727       g_printerr ("Failed to lookup template parent type %s\n", parent_name);
728       exit (1);
729     }
730 
731   g_type_query (parent_type, &query);
732   return g_type_register_static_simple (parent_type,
733                                         type_name,
734                                         query.class_size,
735                                         NULL,
736                                         query.instance_size,
737                                         NULL,
738                                         0);
739 }
740 
741 static void
do_validate_template(const gchar * filename,const gchar * type_name,const gchar * parent_name)742 do_validate_template (const gchar *filename,
743                       const gchar *type_name,
744                       const gchar *parent_name)
745 {
746   GType template_type;
747   GtkWidget *widget;
748   GtkBuilder *builder;
749   GError *error = NULL;
750   gint ret;
751 
752   /* Only make a fake type if it doesn't exist yet.
753    * This lets us e.g. validate the GtkFileChooserWidget template.
754    */
755   template_type = g_type_from_name (type_name);
756   if (template_type == G_TYPE_INVALID)
757     template_type = make_fake_type (type_name, parent_name);
758 
759   widget = g_object_new (template_type, NULL);
760   if (!widget)
761     {
762       g_printerr ("Failed to create an instance of the template type %s\n", type_name);
763       exit (1);
764     }
765 
766   builder = gtk_builder_new ();
767   ret = gtk_builder_extend_with_template (builder, widget, template_type, " ", 1, &error);
768   if (ret)
769     ret = gtk_builder_add_from_file (builder, filename, &error);
770   g_object_unref (builder);
771 
772   if (ret == 0)
773     {
774       g_printerr ("%s\n", error->message);
775       exit (1);
776     }
777 }
778 
779 static gboolean
parse_template_error(const gchar * message,gchar ** class_name,gchar ** parent_name)780 parse_template_error (const gchar  *message,
781                       gchar       **class_name,
782                       gchar       **parent_name)
783 {
784   gchar *p;
785 
786   if (!strstr (message, "Not expecting to handle a template"))
787     return FALSE;
788 
789   p = strstr (message, "(class '");
790   if (p)
791     {
792       *class_name = g_strdup (p + strlen ("(class '"));
793       p = strstr (*class_name, "'");
794       if (p)
795         *p = '\0';
796     }
797   p = strstr (message, ", parent '");
798   if (p)
799     {
800       *parent_name = g_strdup (p + strlen (", parent '"));
801       p = strstr (*parent_name, "'");
802       if (p)
803         *p = '\0';
804     }
805 
806   return TRUE;
807 }
808 
809 static void
do_validate(const gchar * filename)810 do_validate (const gchar *filename)
811 {
812   GtkBuilder *builder;
813   GError *error = NULL;
814   gint ret;
815   gchar *class_name = NULL;
816   gchar *parent_name = NULL;
817 
818   builder = gtk_builder_new ();
819   ret = gtk_builder_add_from_file (builder, filename, &error);
820   g_object_unref (builder);
821 
822   if (ret == 0)
823     {
824       if (g_error_matches (error, GTK_BUILDER_ERROR, GTK_BUILDER_ERROR_UNHANDLED_TAG)  &&
825           parse_template_error (error->message, &class_name, &parent_name))
826         {
827           do_validate_template (filename, class_name, parent_name);
828         }
829       else
830         {
831           g_printerr ("%s\n", error->message);
832           exit (1);
833         }
834     }
835 }
836 
837 static const gchar *
object_get_name(GObject * object)838 object_get_name (GObject *object)
839 {
840   if (GTK_IS_BUILDABLE (object))
841     return gtk_buildable_get_name (GTK_BUILDABLE (object));
842   else
843     return g_object_get_data (object, "gtk-builder-name");
844 }
845 
846 static void
do_enumerate(const gchar * filename)847 do_enumerate (const gchar *filename)
848 {
849   GtkBuilder *builder;
850   GError *error = NULL;
851   gint ret;
852   GSList *list, *l;
853   GObject *object;
854   const gchar *name;
855 
856   builder = gtk_builder_new ();
857   ret = gtk_builder_add_from_file (builder, filename, &error);
858 
859   if (ret == 0)
860     {
861       g_printerr ("%s\n", error->message);
862       exit (1);
863     }
864 
865   list = gtk_builder_get_objects (builder);
866   for (l = list; l; l = l->next)
867     {
868       object = l->data;
869       name = object_get_name (object);
870       if (g_str_has_prefix (name, "___") && g_str_has_suffix (name, "___"))
871         continue;
872 
873       g_printf ("%s (%s)\n", name, g_type_name_from_instance ((GTypeInstance*)object));
874     }
875   g_slist_free (list);
876 
877   g_object_unref (builder);
878 }
879 
880 static void
set_window_title(GtkWindow * window,const char * filename,const char * id)881 set_window_title (GtkWindow  *window,
882                   const char *filename,
883                   const char *id)
884 {
885   gchar *name;
886   gchar *title;
887 
888   name = g_path_get_basename (filename);
889 
890   if (id)
891     title = g_strdup_printf ("%s in %s", id, name);
892   else
893     title = g_strdup (name);
894 
895   gtk_window_set_title (window, title);
896 
897   g_free (title);
898   g_free (name);
899 }
900 
901 static void
preview_file(const char * filename,const char * id,const char * cssfile)902 preview_file (const char *filename,
903               const char *id,
904               const char *cssfile)
905 {
906   GtkBuilder *builder;
907   GError *error = NULL;
908   GObject *object;
909   GtkWidget *window;
910 
911   if (cssfile)
912     {
913       GtkCssProvider *provider;
914 
915       provider = gtk_css_provider_new ();
916       if (!gtk_css_provider_load_from_path (provider, cssfile, &error))
917         {
918           g_printerr ("%s\n", error->message);
919           exit (1);
920         }
921 
922       gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
923                                                  GTK_STYLE_PROVIDER (provider),
924                                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
925     }
926 
927   builder = gtk_builder_new ();
928   if (!gtk_builder_add_from_file (builder, filename, &error))
929     {
930       g_printerr ("%s\n", error->message);
931       exit (1);
932     }
933 
934   object = NULL;
935 
936   if (id)
937     {
938       object = gtk_builder_get_object (builder, id);
939     }
940   else
941     {
942       GSList *objects, *l;
943 
944       objects = gtk_builder_get_objects (builder);
945       for (l = objects; l; l = l->next)
946         {
947           GObject *obj = l->data;
948 
949           if (GTK_IS_WINDOW (obj))
950             {
951               object = obj;
952               break;
953             }
954           else if (GTK_IS_WIDGET (obj))
955             {
956               if (object == NULL)
957                 object = obj;
958             }
959         }
960       g_slist_free (objects);
961     }
962 
963   if (object == NULL)
964     {
965       if (id)
966         g_printerr ("No object with ID '%s' found\n", id);
967       else
968         g_printerr ("No previewable object found\n");
969       exit (1);
970     }
971 
972   if (!GTK_IS_WIDGET (object))
973     {
974       g_printerr ("Objects of type %s can't be previewed\n", G_OBJECT_TYPE_NAME (object));
975       exit (1);
976     }
977 
978   if (GTK_IS_WINDOW (object))
979     window = GTK_WIDGET (object);
980   else
981     {
982       GtkWidget *widget = GTK_WIDGET (object);
983 
984       window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
985 
986       if (GTK_IS_BUILDABLE (object))
987         id = gtk_buildable_get_name (GTK_BUILDABLE (object));
988 
989       set_window_title (GTK_WINDOW (window), filename, id);
990 
991       g_object_ref (widget);
992       if (gtk_widget_get_parent (widget) != NULL)
993         gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (widget)), widget);
994       gtk_container_add (GTK_CONTAINER (window), widget);
995       g_object_unref (widget);
996     }
997 
998   G_GNUC_BEGIN_IGNORE_DEPRECATIONS
999   gtk_window_present (GTK_WINDOW (window));
1000   G_GNUC_END_IGNORE_DEPRECATIONS
1001 
1002   gtk_main ();
1003 
1004   g_object_unref (builder);
1005 }
1006 
1007 static void
do_preview(int * argc,const char *** argv)1008 do_preview (int          *argc,
1009             const char ***argv)
1010 {
1011   GOptionContext *context;
1012   char *id = NULL;
1013   char *css = NULL;
1014   char **filenames = NULL;
1015   const GOptionEntry entries[] = {
1016     { "id", 0, 0, G_OPTION_ARG_STRING, &id, NULL, NULL },
1017     { "css", 0, 0, G_OPTION_ARG_FILENAME, &css, NULL, NULL },
1018     { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, NULL },
1019     { NULL, }
1020   };
1021   GError *error = NULL;
1022 
1023   context = g_option_context_new (NULL);
1024   g_option_context_set_help_enabled (context, FALSE);
1025   g_option_context_add_main_entries (context, entries, NULL);
1026 
1027   if (!g_option_context_parse (context, argc, (char ***)argv, &error))
1028     {
1029       g_printerr ("%s\n", error->message);
1030       g_error_free (error);
1031       exit (1);
1032     }
1033 
1034   g_option_context_free (context);
1035 
1036   if (filenames == NULL)
1037     {
1038       g_printerr ("No .ui file specified\n");
1039       exit (1);
1040     }
1041 
1042   if (g_strv_length (filenames) > 1)
1043     {
1044       g_printerr ("Can only preview a single .ui file\n");
1045       exit (1);
1046     }
1047 
1048   preview_file (filenames[0], id, css);
1049 
1050   g_strfreev (filenames);
1051   g_free (id);
1052   g_free (css);
1053 }
1054 
1055 static void
usage(void)1056 usage (void)
1057 {
1058   g_print (_("Usage:\n"
1059              "  gtk-builder-tool [COMMAND] FILE\n"
1060              "\n"
1061              "Commands:\n"
1062              "  validate           Validate the file\n"
1063              "  simplify [OPTIONS] Simplify the file\n"
1064              "  enumerate          List all named objects\n"
1065              "  preview [OPTIONS]  Preview the file\n"
1066              "\n"
1067              "Simplify Options:\n"
1068              "  --replace          Replace the file\n"
1069              "\n"
1070              "Preview Options:\n"
1071              "  --id=ID            Preview only the named object\n"
1072              "  --css=FILE         Use style from CSS file\n"
1073              "\n"
1074              "Perform various tasks on GtkBuilder .ui files.\n"));
1075   exit (1);
1076 }
1077 
1078 int
main(int argc,const char * argv[])1079 main (int argc, const char *argv[])
1080 {
1081   g_set_prgname ("gtk-builder-tool");
1082 
1083   gtk_init (NULL, NULL);
1084 
1085   gtk_test_register_all_types ();
1086 
1087   if (argc < 3)
1088     usage ();
1089 
1090   if (strcmp (argv[2], "--help") == 0)
1091     usage ();
1092 
1093   argv++;
1094   argc--;
1095 
1096   if (strcmp (argv[0], "validate") == 0)
1097     do_validate (argv[1]);
1098   else if (strcmp (argv[0], "simplify") == 0)
1099     do_simplify (&argc, &argv);
1100   else if (strcmp (argv[0], "enumerate") == 0)
1101     do_enumerate (argv[1]);
1102   else if (strcmp (argv[0], "preview") == 0)
1103     do_preview (&argc, &argv);
1104   else
1105     usage ();
1106 
1107   return 0;
1108 }
1109