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