[gtk/stackpage: 3/11] tools: Split gtk-builder-tool



commit ad6ab9d9ef719687f563daa0b99530487d30f399
Author: Matthias Clasen <mclasen redhat com>
Date:   Thu Feb 7 04:12:09 2019 -0500

    tools: Split gtk-builder-tool
    
    Put each command into its own file. This is in
    preparation for redoing the simplify command.

 gtk/tools/gtk-builder-tool-enumerate.c |   74 +++
 gtk/tools/gtk-builder-tool-preview.c   |  197 ++++++
 gtk/tools/gtk-builder-tool-simplify.c  |  717 ++++++++++++++++++++++
 gtk/tools/gtk-builder-tool-validate.c  |  152 +++++
 gtk/tools/gtk-builder-tool.c           | 1028 +-------------------------------
 gtk/tools/meson.build                  |    6 +-
 6 files changed, 1151 insertions(+), 1023 deletions(-)
---
diff --git a/gtk/tools/gtk-builder-tool-enumerate.c b/gtk/tools/gtk-builder-tool-enumerate.c
new file mode 100644
index 0000000000..c2428d918c
--- /dev/null
+++ b/gtk/tools/gtk-builder-tool-enumerate.c
@@ -0,0 +1,74 @@
+/*  Copyright 2015 Red Hat, Inc.
+ *
+ * GTK+ is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * GLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GTK+; see the file COPYING.  If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+#include "gtkbuilderprivate.h"
+
+static const gchar *
+object_get_name (GObject *object)
+{
+  if (GTK_IS_BUILDABLE (object))
+    return gtk_buildable_get_name (GTK_BUILDABLE (object));
+  else
+    return g_object_get_data (object, "gtk-builder-name");
+}
+
+void
+do_enumerate (int *argc, char ***argv)
+{
+  GtkBuilder *builder;
+  GError *error = NULL;
+  gint ret;
+  GSList *list, *l;
+  GObject *object;
+  const gchar *name;
+  const gchar *filename;
+
+  filename = (*argv)[1];
+
+  builder = gtk_builder_new ();
+  ret = gtk_builder_add_from_file (builder, filename, &error);
+
+  if (ret == 0)
+    {
+      g_printerr ("%s\n", error->message);
+      exit (1);
+    }
+
+  list = gtk_builder_get_objects (builder);
+  for (l = list; l; l = l->next)
+    {
+      object = l->data;
+      name = object_get_name (object);
+      if (g_str_has_prefix (name, "___") && g_str_has_suffix (name, "___"))
+        continue;
+
+      g_printf ("%s (%s)\n", name, g_type_name_from_instance ((GTypeInstance*)object));
+    }
+  g_slist_free (list);
+
+  g_object_unref (builder);
+}
diff --git a/gtk/tools/gtk-builder-tool-preview.c b/gtk/tools/gtk-builder-tool-preview.c
new file mode 100644
index 0000000000..cc19251235
--- /dev/null
+++ b/gtk/tools/gtk-builder-tool-preview.c
@@ -0,0 +1,197 @@
+/*  Copyright 2015 Red Hat, Inc.
+ *
+ * GTK+ is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * GLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GTK+; see the file COPYING.  If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+#include "gtkbuilderprivate.h"
+
+static void
+set_window_title (GtkWindow  *window,
+                  const char *filename,
+                  const char *id)
+{
+  gchar *name;
+  gchar *title;
+
+  name = g_path_get_basename (filename);
+
+  if (id)
+    title = g_strdup_printf ("%s in %s", id, name);
+  else
+    title = g_strdup (name);
+
+  gtk_window_set_title (window, title);
+
+  g_free (title);
+  g_free (name);
+}
+
+static void
+preview_file (const char *filename,
+              const char *id,
+              const char *cssfile)
+{
+  GtkBuilder *builder;
+  GError *error = NULL;
+  GObject *object;
+  GtkWidget *window;
+
+  if (cssfile)
+    {
+      GtkCssProvider *provider;
+
+      provider = gtk_css_provider_new ();
+      gtk_css_provider_load_from_path (provider, cssfile);
+
+      gtk_style_context_add_provider_for_display (gdk_display_get_default (),
+                                                  GTK_STYLE_PROVIDER (provider),
+                                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+    }
+
+  builder = gtk_builder_new ();
+  if (!gtk_builder_add_from_file (builder, filename, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      exit (1);
+    }
+
+  object = NULL;
+
+  if (id)
+    {
+      object = gtk_builder_get_object (builder, id);
+    }
+  else
+    {
+      GSList *objects, *l;
+
+      objects = gtk_builder_get_objects (builder);
+      for (l = objects; l; l = l->next)
+        {
+          GObject *obj = l->data;
+
+          if (GTK_IS_WINDOW (obj))
+            {
+              object = obj;
+              break;
+            }
+          else if (GTK_IS_WIDGET (obj))
+            {
+              if (object == NULL)
+                object = obj;
+            }
+        }
+      g_slist_free (objects);
+    }
+
+  if (object == NULL)
+    {
+      if (id)
+        g_printerr ("No object with ID '%s' found\n", id);
+      else
+        g_printerr ("No previewable object found\n");
+      exit (1);
+    }
+
+  if (!GTK_IS_WIDGET (object))
+    {
+      g_printerr ("Objects of type %s can't be previewed\n", G_OBJECT_TYPE_NAME (object));
+      exit (1);
+    }
+
+  if (GTK_IS_WINDOW (object))
+    window = GTK_WIDGET (object);
+  else
+    {
+      GtkWidget *widget = GTK_WIDGET (object);
+
+      window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+
+      if (GTK_IS_BUILDABLE (object))
+        id = gtk_buildable_get_name (GTK_BUILDABLE (object));
+
+      set_window_title (GTK_WINDOW (window), filename, id);
+
+      g_object_ref (widget);
+      if (gtk_widget_get_parent (widget) != NULL)
+        gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (widget)), widget);
+      gtk_container_add (GTK_CONTAINER (window), widget);
+      g_object_unref (widget);
+    }
+
+  gtk_window_present (GTK_WINDOW (window));
+
+  gtk_main ();
+
+  g_object_unref (builder);
+}
+
+void
+do_preview (int          *argc,
+            const char ***argv)
+{
+  GOptionContext *context;
+  char *id = NULL;
+  char *css = NULL;
+  char **filenames = NULL;
+  const GOptionEntry entries[] = {
+    { "id", 0, 0, G_OPTION_ARG_STRING, &id, NULL, NULL },
+    { "css", 0, 0, G_OPTION_ARG_FILENAME, &css, NULL, NULL },
+    { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, NULL },
+    { NULL, }
+  };
+  GError *error = NULL;
+
+  context = g_option_context_new (NULL);
+  g_option_context_set_help_enabled (context, FALSE);
+  g_option_context_add_main_entries (context, entries, NULL);
+
+  if (!g_option_context_parse (context, argc, (char ***)argv, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      g_error_free (error);
+      exit (1);
+    }
+
+  g_option_context_free (context);
+
+  if (filenames == NULL)
+    {
+      g_printerr ("No .ui file specified\n");
+      exit (1);
+    }
+
+  if (g_strv_length (filenames) > 1)
+    {
+      g_printerr ("Can only preview a single .ui file\n");
+      exit (1);
+    }
+
+  preview_file (filenames[0], id, css);
+
+  g_strfreev (filenames);
+  g_free (id);
+  g_free (css);
+}
diff --git a/gtk/tools/gtk-builder-tool-simplify.c b/gtk/tools/gtk-builder-tool-simplify.c
new file mode 100644
index 0000000000..ec3cd271ec
--- /dev/null
+++ b/gtk/tools/gtk-builder-tool-simplify.c
@@ -0,0 +1,717 @@
+/*  Copyright 2015 Red Hat, Inc.
+ *
+ * GTK+ is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * GLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GTK+; see the file COPYING.  If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+#include "gtkbuilderprivate.h"
+
+
+typedef struct {
+  GtkBuilder *builder;
+  GList *classes;
+  gboolean packing;
+  gboolean packing_started;
+  gboolean cell_packing;
+  gboolean cell_packing_started;
+  gint in_child;
+  gint child_started;
+  gchar **attribute_names;
+  gchar **attribute_values;
+  GString *value;
+  gboolean unclosed_starttag;
+  gint indent;
+  char *input_filename;
+  char *output_filename;
+  FILE *output;
+} MyParserData;
+
+static void
+canonicalize_key (gchar *key)
+{
+  gchar *p;
+
+  for (p = key; *p != 0; p++)
+    {
+      gchar c = *p;
+
+      /* We may meet something like AtkObject::accessible-name */
+      if (c == ':' && ((p > key && p[-1] == ':') || p[1] == ':'))
+        continue;
+
+      if (c != '-' &&
+          (c < '0' || c > '9') &&
+          (c < 'A' || c > 'Z') &&
+          (c < 'a' || c > 'z'))
+        *p = '-';
+    }
+}
+
+static GParamSpec *
+get_property_pspec (MyParserData *data,
+                    const gchar  *class_name,
+                    const gchar  *property_name)
+{
+  GType type;
+  GObjectClass *class;
+  GParamSpec *pspec;
+  gchar *canonical_name;
+
+  type = g_type_from_name (class_name);
+  if (type == G_TYPE_INVALID)
+    {
+      GtkBuilder *builder = gtk_builder_new ();
+      type = gtk_builder_get_type_from_name (builder, class_name);
+      g_object_unref (builder);
+      if (type == G_TYPE_INVALID)
+        return NULL;
+    }
+
+  class = g_type_class_ref (type);
+  canonical_name = g_strdup (property_name);
+  canonicalize_key (canonical_name);
+  if (data->packing)
+    pspec = gtk_container_class_find_child_property (class, canonical_name);
+  else if (data->cell_packing)
+    {
+      GObjectClass *cell_class;
+
+      /* We're just assuming that the cell layout is using a GtkCellAreaBox. */
+      cell_class = g_type_class_ref (GTK_TYPE_CELL_AREA_BOX);
+      pspec = gtk_cell_area_class_find_cell_property (GTK_CELL_AREA_CLASS (cell_class), canonical_name);
+      g_type_class_unref (cell_class);
+    }
+  else
+    pspec = g_object_class_find_property (class, canonical_name);
+  g_free (canonical_name);
+  g_type_class_unref (class);
+
+  return pspec;
+}
+
+
+static gboolean
+value_is_default (MyParserData *data,
+                  const gchar  *class_name,
+                  const gchar  *property_name,
+                  const gchar  *value_string)
+{
+  GValue value = { 0, };
+  gboolean ret;
+  GError *error = NULL;
+  GParamSpec *pspec;
+
+  pspec = get_property_pspec (data, class_name, property_name);
+
+  if (pspec == NULL)
+    {
+      if (data->packing)
+        g_printerr (_("Packing property %s::%s not found\n"), class_name, property_name);
+      else if (data->cell_packing)
+        g_printerr (_("Cell property %s::%s not found\n"), class_name, property_name);
+      else
+        g_printerr (_("Property %s::%s not found\n"), class_name, property_name);
+      return FALSE;
+    }
+  else if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (pspec), G_TYPE_OBJECT))
+    return FALSE;
+
+  if (!gtk_builder_value_from_string (data->builder, pspec, value_string, &value, &error))
+    {
+      g_printerr (_("Couldn’t parse value for %s::%s: %s\n"), class_name, property_name, error->message);
+      g_error_free (error);
+      ret = FALSE;
+    }
+  else
+    ret = g_param_value_defaults (pspec, &value);
+
+  g_value_reset (&value);
+
+  return ret;
+}
+
+static gboolean
+property_is_boolean (MyParserData *data,
+                     const gchar  *class_name,
+                     const gchar  *property_name)
+{
+  GParamSpec *pspec;
+
+  pspec = get_property_pspec (data, class_name, property_name);
+  if (pspec)
+    return G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN;
+
+  return FALSE;
+}
+
+static const gchar *
+canonical_boolean_value (MyParserData *data,
+                         const gchar  *string)
+{
+  GValue value = G_VALUE_INIT;
+  gboolean b = FALSE;
+
+  if (gtk_builder_value_from_string_type (data->builder, G_TYPE_BOOLEAN, string, &value, NULL))
+    b = g_value_get_boolean (&value);
+
+  return b ? "1" : "0";
+}
+
+/* A number of properties unfortunately can't be omitted even
+ * if they are nominally set to their default value. In many
+ * cases, this is due to subclasses not overriding the default
+ * value from the superclass.
+ */
+static gboolean
+needs_explicit_setting (MyParserData *data,
+                        const gchar  *class_name,
+                        const gchar  *property_name)
+{
+  struct _Prop {
+    const char *class;
+    const char *property;
+    gboolean packing;
+  } props[] = {
+    { "GtkAboutDialog", "program-name", 0 },
+    { "GtkCalendar", "year", 0 },
+    { "GtkCalendar", "month", 0 },
+    { "GtkCalendar", "day", 0 },
+    { "GtkPlacesSidebar", "show-desktop", 0 },
+    { "GtkRadioButton", "draw-indicator", 0 },
+    { "GtkGrid", "left-attach", 1 },
+    { "GtkGrid", "top-attach", 1 },
+    { "GtkWidget", "hexpand", 0 },
+    { "GtkWidget", "vexpand", 0 },
+    { NULL, NULL, 0 }
+  };
+  gchar *canonical_name;
+  gboolean found;
+  gint k;
+
+  canonical_name = g_strdup (property_name);
+  g_strdelimit (canonical_name, "_", '-');
+
+  found = FALSE;
+  for (k = 0; props[k].class; k++)
+    {
+      if (strcmp (class_name, props[k].class) == 0 &&
+          strcmp (canonical_name, props[k].property) == 0 &&
+          data->packing == props[k].packing)
+        {
+          found = TRUE;
+          break;
+        }
+    }
+
+  g_free (canonical_name);
+
+  return found;
+}
+
+static void
+maybe_start_packing (MyParserData *data)
+{
+  if (data->packing)
+    {
+      if (!data->packing_started)
+        {
+          g_fprintf (data->output, "%*s<packing>\n", data->indent, "");
+          data->indent += 2;
+          data->packing_started = TRUE;
+        }
+    }
+}
+
+static void
+maybe_start_cell_packing (MyParserData *data)
+{
+  if (data->cell_packing)
+    {
+      if (!data->cell_packing_started)
+        {
+          g_fprintf (data->output, "%*s<cell-packing>\n", data->indent, "");
+          data->indent += 2;
+          data->cell_packing_started = TRUE;
+        }
+    }
+}
+
+static void
+maybe_start_child (MyParserData *data)
+{
+  if (data->in_child > 0)
+    {
+      if (data->child_started < data->in_child)
+        {
+          g_fprintf (data->output, "%*s<child>\n", data->indent, "");
+          data->indent += 2;
+          data->child_started += 1;
+        }
+    }
+}
+
+static void
+maybe_emit_property (MyParserData *data)
+{
+  gint i;
+  gboolean bound;
+  gboolean translatable;
+  gchar *escaped;
+  const gchar *class_name;
+  const gchar *property_name;
+  const gchar *value_string;
+
+  class_name = (const gchar *)data->classes->data;
+  property_name = "";
+  value_string = (const gchar *)data->value->str;
+
+  bound = FALSE;
+  translatable = FALSE;
+  for (i = 0; data->attribute_names[i]; i++)
+    {
+      if (strcmp (data->attribute_names[i], "bind-source") == 0 ||
+          strcmp (data->attribute_names[i], "bind_source") == 0)
+        bound = TRUE;
+      else if (strcmp (data->attribute_names[i], "translatable") == 0)
+        translatable = TRUE;
+      else if (strcmp (data->attribute_names[i], "name") == 0)
+        property_name = (const gchar *)data->attribute_values[i];
+    }
+
+  if (!translatable &&
+      !bound &&
+      !needs_explicit_setting (data, class_name, property_name))
+    {
+      for (i = 0; data->attribute_names[i]; i++)
+        {
+          if (strcmp (data->attribute_names[i], "name") == 0)
+            {
+              if (data->classes == NULL)
+                break;
+
+              if (value_is_default (data, class_name, property_name, value_string))
+                return;
+            }
+        }
+    }
+
+  maybe_start_packing (data);
+  maybe_start_cell_packing (data);
+
+  g_fprintf (data->output, "%*s<property", data->indent, "");
+  for (i = 0; data->attribute_names[i]; i++)
+    {
+      if (!translatable &&
+          (strcmp (data->attribute_names[i], "comments") == 0 ||
+           strcmp (data->attribute_names[i], "context") == 0))
+        continue;
+
+      escaped = g_markup_escape_text (data->attribute_values[i], -1);
+
+      if (strcmp (data->attribute_names[i], "name") == 0)
+        canonicalize_key (escaped);
+
+      g_fprintf (data->output, " %s=\"%s\"", data->attribute_names[i], escaped);
+      g_free (escaped);
+    }
+
+  if (bound)
+    {
+      g_fprintf (data->output, "/>\n");
+    }
+  else
+    {
+      g_fprintf (data->output, ">");
+      if (property_is_boolean (data, class_name, property_name))
+        {
+          g_fprintf (data->output, "%s", canonical_boolean_value (data, value_string));
+        }
+      else
+        {
+          escaped = g_markup_escape_text (value_string, -1);
+          g_fprintf (data->output, "%s", escaped);
+          g_free (escaped);
+        }
+      g_fprintf (data->output, "</property>\n");
+    }
+}
+
+static void
+maybe_close_starttag (MyParserData *data)
+{
+  if (data->unclosed_starttag)
+    {
+      g_fprintf (data->output, ">\n");
+      data->unclosed_starttag = FALSE;
+    }
+}
+
+static gboolean
+stack_is (GMarkupParseContext *context,
+          ...)
+{
+  va_list args;
+  gchar *s, *p;
+  const GSList *stack;
+
+  stack = g_markup_parse_context_get_element_stack (context);
+
+  va_start (args, context);
+  s = va_arg (args, gchar *);
+  while (s)
+    {
+      if (stack == NULL)
+        {
+          va_end (args);
+          return FALSE;
+        }
+
+      p = (gchar *)stack->data;
+      if (strcmp (s, p) != 0)
+        {
+          va_end (args);
+          return FALSE;
+        }
+
+      s = va_arg (args, gchar *);
+      stack = stack->next;
+    }
+
+  va_end (args);
+  return TRUE;
+}
+
+static void
+start_element (GMarkupParseContext  *context,
+               const gchar          *element_name,
+               const gchar         **attribute_names,
+               const gchar         **attribute_values,
+               gpointer              user_data,
+               GError              **error)
+{
+  gint i;
+  gchar *escaped;
+  MyParserData *data = user_data;
+
+  maybe_close_starttag (data);
+
+  if (strcmp (element_name, "property") == 0)
+    {
+      g_assert (data->attribute_names == NULL);
+      g_assert (data->attribute_values == NULL);
+      g_assert (data->value == NULL);
+
+      data->attribute_names = g_strdupv ((gchar **)attribute_names);
+      data->attribute_values = g_strdupv ((gchar **)attribute_values);
+      data->value = g_string_new ("");
+
+      return;
+    }
+  else if (strcmp (element_name, "packing") == 0)
+    {
+      data->packing = TRUE;
+      data->packing_started = FALSE;
+
+      return;
+    }
+  else if (strcmp (element_name, "cell-packing") == 0)
+    {
+      data->cell_packing = TRUE;
+      data->cell_packing_started = FALSE;
+
+      return;
+    }
+  else if (strcmp (element_name, "child") == 0)
+    {
+      data->in_child += 1;
+
+      if (attribute_names[0] == NULL)
+        return;
+
+      data->child_started += 1;
+    }
+  else if (strcmp (element_name, "attribute") == 0)
+    {
+      /* attribute in label has no content */
+      if (data->classes == NULL ||
+          strcmp ((gchar *)data->classes->data, "GtkLabel") != 0)
+        data->value = g_string_new ("");
+    }
+  else if (stack_is (context, "item", "items", NULL) ||
+           stack_is (context, "action-widget", "action-widgets", NULL) ||
+           stack_is (context, "mime-type", "mime-types", NULL) ||
+           stack_is (context, "pattern", "patterns", NULL) ||
+           stack_is (context, "application", "applications", NULL) ||
+           stack_is (context, "col", "row", "data", NULL) ||
+           stack_is (context, "mark", "marks", NULL) ||
+           stack_is (context, "action", "accessibility", NULL))
+    {
+      data->value = g_string_new ("");
+    }
+  else if (strcmp (element_name, "placeholder") == 0)
+    {
+      return;
+    }
+  else if (strcmp (element_name, "object") == 0 ||
+           strcmp (element_name, "template") == 0)
+    {
+      maybe_start_child (data);
+
+      for (i = 0; attribute_names[i]; i++)
+        {
+          if (strcmp (attribute_names[i], "class") == 0)
+            {
+              data->classes = g_list_prepend (data->classes,
+                                              g_strdup (attribute_values[i]));
+              break;
+            }
+        }
+    }
+
+  g_fprintf (data->output, "%*s<%s", data->indent, "", element_name);
+  for (i = 0; attribute_names[i]; i++)
+    {
+      escaped = g_markup_escape_text (attribute_values[i], -1);
+      g_fprintf (data->output, " %s=\"%s\"", attribute_names[i], escaped);
+      g_free (escaped);
+    }
+  data->unclosed_starttag = TRUE;
+  data->indent += 2;
+}
+
+static void
+end_element (GMarkupParseContext  *context,
+             const gchar          *element_name,
+             gpointer              user_data,
+             GError              **error)
+{
+  MyParserData *data = user_data;
+
+  if (strcmp (element_name, "property") == 0)
+    {
+      maybe_emit_property (data);
+
+      g_clear_pointer (&data->attribute_names, g_strfreev);
+      g_clear_pointer (&data->attribute_values, g_strfreev);
+      g_string_free (data->value, TRUE);
+      data->value = NULL;
+      return;
+    }
+  else if (strcmp (element_name, "packing") == 0)
+    {
+      data->packing = FALSE;
+      if (!data->packing_started)
+        return;
+    }
+  else if (strcmp (element_name, "cell-packing") == 0)
+    {
+      data->cell_packing = FALSE;
+      if (!data->cell_packing_started)
+        return;
+    }
+  else if (strcmp (element_name, "child") == 0)
+    {
+      data->in_child -= 1;
+      if (data->child_started == data->in_child)
+        return;
+      data->child_started -= 1;
+    }
+  else if (strcmp (element_name, "placeholder") == 0)
+    {
+      return;
+    }
+  else if (strcmp (element_name, "object") == 0 ||
+           strcmp (element_name, "template") == 0)
+    {
+      g_free (data->classes->data);
+      data->classes = g_list_delete_link (data->classes, data->classes);
+    }
+
+  if (data->value != NULL)
+    {
+      gchar *escaped;
+
+      if (data->unclosed_starttag)
+        g_fprintf (data->output, ">");
+
+      escaped = g_markup_escape_text (data->value->str, -1);
+      g_fprintf (data->output, "%s</%s>\n", escaped, element_name);
+      g_free (escaped);
+
+      g_string_free (data->value, TRUE);
+      data->value = NULL;
+    }
+  else
+    {
+      if (data->unclosed_starttag)
+        g_fprintf (data->output, "/>\n");
+      else
+        g_fprintf (data->output, "%*s</%s>\n", data->indent - 2, "", element_name);
+    }
+
+  data->indent -= 2;
+  data->unclosed_starttag = FALSE;
+}
+
+static void
+text (GMarkupParseContext  *context,
+      const gchar          *text,
+      gsize                 text_len,
+      gpointer              user_data,
+      GError              **error)
+{
+  MyParserData *data = user_data;
+
+  if (data->value)
+    {
+      g_string_append_len (data->value, text, text_len);
+      return;
+    }
+}
+
+static void
+passthrough (GMarkupParseContext  *context,
+             const gchar          *text,
+             gsize                 text_len,
+             gpointer              user_data,
+             GError              **error)
+{
+  MyParserData *data = user_data;
+
+  maybe_close_starttag (data);
+
+  g_fprintf (data->output, "%*s%s\n", data->indent, "", text);
+}
+
+GMarkupParser parser = {
+  start_element,
+  end_element,
+  text,
+  passthrough,
+  NULL
+};
+
+void
+do_simplify (int          *argc,
+             const char ***argv)
+{
+  GMarkupParseContext *context;
+  gchar *buffer;
+  MyParserData data;
+  gboolean replace = FALSE;
+  char **filenames = NULL;
+  GOptionContext *ctx;
+  const GOptionEntry entries[] = {
+    { "replace", 0, 0, G_OPTION_ARG_NONE, &replace, NULL, NULL },
+    { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, NULL },
+    { NULL, }
+  };
+  GError *error = NULL;
+
+  ctx = g_option_context_new (NULL);
+  g_option_context_set_help_enabled (ctx, FALSE);
+  g_option_context_add_main_entries (ctx, entries, NULL);
+
+  if (!g_option_context_parse (ctx, argc, (char ***)argv, &error))
+    {
+      g_printerr ("%s\n", error->message);
+      g_error_free (error);
+      exit (1);
+    }
+
+  g_option_context_free (ctx);
+
+  if (filenames == NULL)
+    {
+      g_printerr ("No .ui file specified\n");
+      exit (1);
+    }
+
+  if (g_strv_length (filenames) > 1)
+    {
+      g_printerr ("Can only simplify a single .ui file\n");
+      exit (1);
+    }
+
+  data.input_filename = filenames[0];
+  data.output_filename = NULL;
+
+  if (replace)
+    {
+      int fd;
+      fd = g_file_open_tmp ("gtk-builder-tool-XXXXXX", &data.output_filename, NULL);
+      data.output = fdopen (fd, "w");
+    }
+  else
+    {
+      data.output = stdout;
+    }
+
+  if (!g_file_get_contents (filenames[0], &buffer, NULL, &error))
+    {
+      g_printerr (_("Can’t load file: %s\n"), error->message);
+      exit (1);
+    }
+
+  data.builder = gtk_builder_new ();
+  data.classes = NULL;
+  data.attribute_names = NULL;
+  data.attribute_values = NULL;
+  data.value = NULL;
+  data.packing = FALSE;
+  data.packing_started = FALSE;
+  data.cell_packing = FALSE;
+  data.cell_packing_started = FALSE;
+  data.in_child = 0;
+  data.child_started = 0;
+  data.unclosed_starttag = FALSE;
+  data.indent = 0;
+
+  context = g_markup_parse_context_new (&parser, G_MARKUP_TREAT_CDATA_AS_TEXT, &data, NULL);
+  if (!g_markup_parse_context_parse (context, buffer, -1, &error))
+    {
+      g_printerr (_("Can’t parse file: %s\n"), error->message);
+      exit (1);
+    }
+
+  fclose (data.output);
+
+  if (data.output_filename)
+    {
+      char *content;
+      gsize length;
+
+      if (!g_file_get_contents (data.output_filename, &content, &length, &error))
+        {
+          g_printerr ("Failed to read %s: %s\n", data.output_filename, error->message);
+          exit (1);
+        }
+
+      if (!g_file_set_contents (data.input_filename, content, length, &error))
+        {
+          g_printerr ("Failed to write %s: %s\n", data.input_filename, error->message);
+          exit (1);
+        }
+    }
+}
diff --git a/gtk/tools/gtk-builder-tool-validate.c b/gtk/tools/gtk-builder-tool-validate.c
new file mode 100644
index 0000000000..33ee6ff22b
--- /dev/null
+++ b/gtk/tools/gtk-builder-tool-validate.c
@@ -0,0 +1,152 @@
+/*  Copyright 2015 Red Hat, Inc.
+ *
+ * GTK+ is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * GLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GTK+; see the file COPYING.  If not,
+ * see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Matthias Clasen
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <glib/gprintf.h>
+#include <glib/gstdio.h>
+#include <gtk/gtk.h>
+#include "gtkbuilderprivate.h"
+
+static GType
+make_fake_type (const gchar *type_name,
+                const gchar *parent_name)
+{
+  GType parent_type;
+  GTypeQuery query;
+
+  parent_type = g_type_from_name (parent_name);
+  if (parent_type == G_TYPE_INVALID)
+    {
+      g_printerr ("Failed to lookup template parent type %s\n", parent_name);
+      exit (1);
+    }
+
+  g_type_query (parent_type, &query);
+  return g_type_register_static_simple (parent_type,
+                                        type_name,
+                                        query.class_size,
+                                        NULL,
+                                        query.instance_size,
+                                        NULL,
+                                        0);
+}
+
+static void
+do_validate_template (const gchar *filename,
+                      const gchar *type_name,
+                      const gchar *parent_name)
+{
+  GType template_type;
+  GtkWidget *widget;
+  GtkBuilder *builder;
+  GError *error = NULL;
+  gint ret;
+
+  /* Only make a fake type if it doesn't exist yet.
+   * This lets us e.g. validate the GtkFileChooserWidget template.
+   */
+  template_type = g_type_from_name (type_name);
+  if (template_type == G_TYPE_INVALID)
+    template_type = make_fake_type (type_name, parent_name);
+
+  widget = g_object_new (template_type, NULL);
+  if (!widget)
+    {
+      g_printerr ("Failed to create an instance of the template type %s\n", type_name);
+      exit (1);
+    }
+
+  builder = gtk_builder_new ();
+  ret = gtk_builder_extend_with_template (builder, widget, template_type, " ", 1, &error);
+  if (ret)
+    ret = gtk_builder_add_from_file (builder, filename, &error);
+  g_object_unref (builder);
+
+  if (ret == 0)
+    {
+      g_printerr ("%s\n", error->message);
+      exit (1);
+    }
+}
+
+static gboolean
+parse_template_error (const gchar  *message,
+                      gchar       **class_name,
+                      gchar       **parent_name)
+{
+  gchar *p;
+
+  if (!strstr (message, "Not expecting to handle a template"))
+    return FALSE;
+
+  p = strstr (message, "(class '");
+  if (p)
+    {
+      *class_name = g_strdup (p + strlen ("(class '"));
+      p = strstr (*class_name, "'");
+      if (p)
+        *p = '\0';
+    }
+  p = strstr (message, ", parent '");
+  if (p)
+    {
+      *parent_name = g_strdup (p + strlen (", parent '"));
+      p = strstr (*parent_name, "'");
+      if (p)
+        *p = '\0';
+    }
+
+  return TRUE;
+}
+
+void
+do_validate (int *argc, char ***argv)
+{
+  GtkBuilder *builder;
+  GError *error = NULL;
+  gint ret;
+  gchar *class_name = NULL;
+  gchar *parent_name = NULL;
+  const gchar *filename;
+
+  filename = (*argv)[1];
+
+  builder = gtk_builder_new ();
+  ret = gtk_builder_add_from_file (builder, filename, &error);
+  g_object_unref (builder);
+
+  if (ret == 0)
+    {
+      if (g_error_matches (error, GTK_BUILDER_ERROR, GTK_BUILDER_ERROR_UNHANDLED_TAG)  &&
+          parse_template_error (error->message, &class_name, &parent_name))
+        {
+          do_validate_template (filename, class_name, parent_name);
+        }
+      else
+        {
+          g_printerr ("%s\n", error->message);
+          exit (1);
+        }
+    }
+}
+
diff --git a/gtk/tools/gtk-builder-tool.c b/gtk/tools/gtk-builder-tool.c
index ebd965ec12..48b78fb08f 100644
--- a/gtk/tools/gtk-builder-tool.c
+++ b/gtk/tools/gtk-builder-tool.c
@@ -27,1026 +27,10 @@
 #include <gtk/gtk.h>
 #include "gtkbuilderprivate.h"
 
-
-typedef struct {
-  GtkBuilder *builder;
-  GList *classes;
-  gboolean packing;
-  gboolean packing_started;
-  gboolean cell_packing;
-  gboolean cell_packing_started;
-  gint in_child;
-  gint child_started;
-  gchar **attribute_names;
-  gchar **attribute_values;
-  GString *value;
-  gboolean unclosed_starttag;
-  gint indent;
-  char *input_filename;
-  char *output_filename;
-  FILE *output;
-} MyParserData;
-
-static void
-canonicalize_key (gchar *key)
-{
-  gchar *p;
-
-  for (p = key; *p != 0; p++)
-    {
-      gchar c = *p;
-
-      /* We may meet something like AtkObject::accessible-name */
-      if (c == ':' && ((p > key && p[-1] == ':') || p[1] == ':'))
-        continue;
-
-      if (c != '-' &&
-          (c < '0' || c > '9') &&
-          (c < 'A' || c > 'Z') &&
-          (c < 'a' || c > 'z'))
-        *p = '-';
-    }
-}
-
-static GParamSpec *
-get_property_pspec (MyParserData *data,
-                    const gchar  *class_name,
-                    const gchar  *property_name)
-{
-  GType type;
-  GObjectClass *class;
-  GParamSpec *pspec;
-  gchar *canonical_name;
-
-  type = g_type_from_name (class_name);
-  if (type == G_TYPE_INVALID)
-    {
-      GtkBuilder *builder = gtk_builder_new ();
-      type = gtk_builder_get_type_from_name (builder, class_name);
-      g_object_unref (builder);
-      if (type == G_TYPE_INVALID)
-        return NULL;
-    }
-
-  class = g_type_class_ref (type);
-  canonical_name = g_strdup (property_name);
-  canonicalize_key (canonical_name);
-  if (data->packing)
-    pspec = gtk_container_class_find_child_property (class, canonical_name);
-  else if (data->cell_packing)
-    {
-      GObjectClass *cell_class;
-
-      /* We're just assuming that the cell layout is using a GtkCellAreaBox. */
-      cell_class = g_type_class_ref (GTK_TYPE_CELL_AREA_BOX);
-      pspec = gtk_cell_area_class_find_cell_property (GTK_CELL_AREA_CLASS (cell_class), canonical_name);
-      g_type_class_unref (cell_class);
-    }
-  else
-    pspec = g_object_class_find_property (class, canonical_name);
-  g_free (canonical_name);
-  g_type_class_unref (class);
-
-  return pspec;
-}
-
-
-static gboolean
-value_is_default (MyParserData *data,
-                  const gchar  *class_name,
-                  const gchar  *property_name,
-                  const gchar  *value_string)
-{
-  GValue value = { 0, };
-  gboolean ret;
-  GError *error = NULL;
-  GParamSpec *pspec;
-
-  pspec = get_property_pspec (data, class_name, property_name);
-
-  if (pspec == NULL)
-    {
-      if (data->packing)
-        g_printerr (_("Packing property %s::%s not found\n"), class_name, property_name);
-      else if (data->cell_packing)
-        g_printerr (_("Cell property %s::%s not found\n"), class_name, property_name);
-      else
-        g_printerr (_("Property %s::%s not found\n"), class_name, property_name);
-      return FALSE;
-    }
-  else if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (pspec), G_TYPE_OBJECT))
-    return FALSE;
-
-  if (!gtk_builder_value_from_string (data->builder, pspec, value_string, &value, &error))
-    {
-      g_printerr (_("Couldn’t parse value for %s::%s: %s\n"), class_name, property_name, error->message);
-      g_error_free (error);
-      ret = FALSE;
-    }
-  else
-    ret = g_param_value_defaults (pspec, &value);
-
-  g_value_reset (&value);
-
-  return ret;
-}
-
-static gboolean
-property_is_boolean (MyParserData *data,
-                     const gchar  *class_name,
-                     const gchar  *property_name)
-{
-  GParamSpec *pspec;
-
-  pspec = get_property_pspec (data, class_name, property_name);
-  if (pspec)
-    return G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN;
-
-  return FALSE;
-}
-
-static const gchar *
-canonical_boolean_value (MyParserData *data,
-                         const gchar  *string)
-{
-  GValue value = G_VALUE_INIT;
-  gboolean b = FALSE;
-
-  if (gtk_builder_value_from_string_type (data->builder, G_TYPE_BOOLEAN, string, &value, NULL))
-    b = g_value_get_boolean (&value);
-
-  return b ? "1" : "0";
-}
-
-/* A number of properties unfortunately can't be omitted even
- * if they are nominally set to their default value. In many
- * cases, this is due to subclasses not overriding the default
- * value from the superclass.
- */
-static gboolean
-needs_explicit_setting (MyParserData *data,
-                        const gchar  *class_name,
-                        const gchar  *property_name)
-{
-  struct _Prop {
-    const char *class;
-    const char *property;
-    gboolean packing;
-  } props[] = {
-    { "GtkAboutDialog", "program-name", 0 },
-    { "GtkCalendar", "year", 0 },
-    { "GtkCalendar", "month", 0 },
-    { "GtkCalendar", "day", 0 },
-    { "GtkPlacesSidebar", "show-desktop", 0 },
-    { "GtkRadioButton", "draw-indicator", 0 },
-    { "GtkGrid", "left-attach", 1 },
-    { "GtkGrid", "top-attach", 1 },
-    { "GtkWidget", "hexpand", 0 },
-    { "GtkWidget", "vexpand", 0 },
-    { NULL, NULL, 0 }
-  };
-  gchar *canonical_name;
-  gboolean found;
-  gint k;
-
-  canonical_name = g_strdup (property_name);
-  g_strdelimit (canonical_name, "_", '-');
-
-  found = FALSE;
-  for (k = 0; props[k].class; k++)
-    {
-      if (strcmp (class_name, props[k].class) == 0 &&
-          strcmp (canonical_name, props[k].property) == 0 &&
-          data->packing == props[k].packing)
-        {
-          found = TRUE;
-          break;
-        }
-    }
-
-  g_free (canonical_name);
-
-  return found;
-}
-
-static void
-maybe_start_packing (MyParserData *data)
-{
-  if (data->packing)
-    {
-      if (!data->packing_started)
-        {
-          g_fprintf (data->output, "%*s<packing>\n", data->indent, "");
-          data->indent += 2;
-          data->packing_started = TRUE;
-        }
-    }
-}
-
-static void
-maybe_start_cell_packing (MyParserData *data)
-{
-  if (data->cell_packing)
-    {
-      if (!data->cell_packing_started)
-        {
-          g_fprintf (data->output, "%*s<cell-packing>\n", data->indent, "");
-          data->indent += 2;
-          data->cell_packing_started = TRUE;
-        }
-    }
-}
-
-static void
-maybe_start_child (MyParserData *data)
-{
-  if (data->in_child > 0)
-    {
-      if (data->child_started < data->in_child)
-        {
-          g_fprintf (data->output, "%*s<child>\n", data->indent, "");
-          data->indent += 2;
-          data->child_started += 1;
-        }
-    }
-}
-
-static void
-maybe_emit_property (MyParserData *data)
-{
-  gint i;
-  gboolean bound;
-  gboolean translatable;
-  gchar *escaped;
-  const gchar *class_name;
-  const gchar *property_name;
-  const gchar *value_string;
-
-  class_name = (const gchar *)data->classes->data;
-  property_name = "";
-  value_string = (const gchar *)data->value->str;
-
-  bound = FALSE;
-  translatable = FALSE;
-  for (i = 0; data->attribute_names[i]; i++)
-    {
-      if (strcmp (data->attribute_names[i], "bind-source") == 0 ||
-          strcmp (data->attribute_names[i], "bind_source") == 0)
-        bound = TRUE;
-      else if (strcmp (data->attribute_names[i], "translatable") == 0)
-        translatable = TRUE;
-      else if (strcmp (data->attribute_names[i], "name") == 0)
-        property_name = (const gchar *)data->attribute_values[i];
-    }
-
-  if (!translatable &&
-      !bound &&
-      !needs_explicit_setting (data, class_name, property_name))
-    {
-      for (i = 0; data->attribute_names[i]; i++)
-        {
-          if (strcmp (data->attribute_names[i], "name") == 0)
-            {
-              if (data->classes == NULL)
-                break;
-
-              if (value_is_default (data, class_name, property_name, value_string))
-                return;
-            }
-        }
-    }
-
-  maybe_start_packing (data);
-  maybe_start_cell_packing (data);
-
-  g_fprintf (data->output, "%*s<property", data->indent, "");
-  for (i = 0; data->attribute_names[i]; i++)
-    {
-      if (!translatable &&
-          (strcmp (data->attribute_names[i], "comments") == 0 ||
-           strcmp (data->attribute_names[i], "context") == 0))
-        continue;
-
-      escaped = g_markup_escape_text (data->attribute_values[i], -1);
-
-      if (strcmp (data->attribute_names[i], "name") == 0)
-        canonicalize_key (escaped);
-
-      g_fprintf (data->output, " %s=\"%s\"", data->attribute_names[i], escaped);
-      g_free (escaped);
-    }
-
-  if (bound)
-    {
-      g_fprintf (data->output, "/>\n");
-    }
-  else
-    {
-      g_fprintf (data->output, ">");
-      if (property_is_boolean (data, class_name, property_name))
-        {
-          g_fprintf (data->output, "%s", canonical_boolean_value (data, value_string));
-        }
-      else
-        {
-          escaped = g_markup_escape_text (value_string, -1);
-          g_fprintf (data->output, "%s", escaped);
-          g_free (escaped);
-        }
-      g_fprintf (data->output, "</property>\n");
-    }
-}
-
-static void
-maybe_close_starttag (MyParserData *data)
-{
-  if (data->unclosed_starttag)
-    {
-      g_fprintf (data->output, ">\n");
-      data->unclosed_starttag = FALSE;
-    }
-}
-
-static gboolean
-stack_is (GMarkupParseContext *context,
-          ...)
-{
-  va_list args;
-  gchar *s, *p;
-  const GSList *stack;
-
-  stack = g_markup_parse_context_get_element_stack (context);
-
-  va_start (args, context);
-  s = va_arg (args, gchar *);
-  while (s)
-    {
-      if (stack == NULL)
-        {
-          va_end (args);
-          return FALSE;
-        }
-
-      p = (gchar *)stack->data;
-      if (strcmp (s, p) != 0)
-        {
-          va_end (args);
-          return FALSE;
-        }
-
-      s = va_arg (args, gchar *);
-      stack = stack->next;
-    }
-
-  va_end (args);
-  return TRUE;
-}
-
-static void
-start_element (GMarkupParseContext  *context,
-               const gchar          *element_name,
-               const gchar         **attribute_names,
-               const gchar         **attribute_values,
-               gpointer              user_data,
-               GError              **error)
-{
-  gint i;
-  gchar *escaped;
-  MyParserData *data = user_data;
-
-  maybe_close_starttag (data);
-
-  if (strcmp (element_name, "property") == 0)
-    {
-      g_assert (data->attribute_names == NULL);
-      g_assert (data->attribute_values == NULL);
-      g_assert (data->value == NULL);
-
-      data->attribute_names = g_strdupv ((gchar **)attribute_names);
-      data->attribute_values = g_strdupv ((gchar **)attribute_values);
-      data->value = g_string_new ("");
-
-      return;
-    }
-  else if (strcmp (element_name, "packing") == 0)
-    {
-      data->packing = TRUE;
-      data->packing_started = FALSE;
-
-      return;
-    }
-  else if (strcmp (element_name, "cell-packing") == 0)
-    {
-      data->cell_packing = TRUE;
-      data->cell_packing_started = FALSE;
-
-      return;
-    }
-  else if (strcmp (element_name, "child") == 0)
-    {
-      data->in_child += 1;
-
-      if (attribute_names[0] == NULL)
-        return;
-
-      data->child_started += 1;
-    }
-  else if (strcmp (element_name, "attribute") == 0)
-    {
-      /* attribute in label has no content */
-      if (data->classes == NULL ||
-          strcmp ((gchar *)data->classes->data, "GtkLabel") != 0)
-        data->value = g_string_new ("");
-    }
-  else if (stack_is (context, "item", "items", NULL) ||
-           stack_is (context, "action-widget", "action-widgets", NULL) ||
-           stack_is (context, "mime-type", "mime-types", NULL) ||
-           stack_is (context, "pattern", "patterns", NULL) ||
-           stack_is (context, "application", "applications", NULL) ||
-           stack_is (context, "col", "row", "data", NULL) ||
-           stack_is (context, "mark", "marks", NULL) ||
-           stack_is (context, "action", "accessibility", NULL))
-    {
-      data->value = g_string_new ("");
-    }
-  else if (strcmp (element_name, "placeholder") == 0)
-    {
-      return;
-    }
-  else if (strcmp (element_name, "object") == 0 ||
-           strcmp (element_name, "template") == 0)
-    {
-      maybe_start_child (data);
-
-      for (i = 0; attribute_names[i]; i++)
-        {
-          if (strcmp (attribute_names[i], "class") == 0)
-            {
-              data->classes = g_list_prepend (data->classes,
-                                              g_strdup (attribute_values[i]));
-              break;
-            }
-        }
-    }
-
-  g_fprintf (data->output, "%*s<%s", data->indent, "", element_name);
-  for (i = 0; attribute_names[i]; i++)
-    {
-      escaped = g_markup_escape_text (attribute_values[i], -1);
-      g_fprintf (data->output, " %s=\"%s\"", attribute_names[i], escaped);
-      g_free (escaped);
-    }
-  data->unclosed_starttag = TRUE;
-  data->indent += 2;
-}
-
-static void
-end_element (GMarkupParseContext  *context,
-             const gchar          *element_name,
-             gpointer              user_data,
-             GError              **error)
-{
-  MyParserData *data = user_data;
-
-  if (strcmp (element_name, "property") == 0)
-    {
-      maybe_emit_property (data);
-
-      g_clear_pointer (&data->attribute_names, g_strfreev);
-      g_clear_pointer (&data->attribute_values, g_strfreev);
-      g_string_free (data->value, TRUE);
-      data->value = NULL;
-      return;
-    }
-  else if (strcmp (element_name, "packing") == 0)
-    {
-      data->packing = FALSE;
-      if (!data->packing_started)
-        return;
-    }
-  else if (strcmp (element_name, "cell-packing") == 0)
-    {
-      data->cell_packing = FALSE;
-      if (!data->cell_packing_started)
-        return;
-    }
-  else if (strcmp (element_name, "child") == 0)
-    {
-      data->in_child -= 1;
-      if (data->child_started == data->in_child)
-        return;
-      data->child_started -= 1;
-    }
-  else if (strcmp (element_name, "placeholder") == 0)
-    {
-      return;
-    }
-  else if (strcmp (element_name, "object") == 0 ||
-           strcmp (element_name, "template") == 0)
-    {
-      g_free (data->classes->data);
-      data->classes = g_list_delete_link (data->classes, data->classes);
-    }
-
-  if (data->value != NULL)
-    {
-      gchar *escaped;
-
-      if (data->unclosed_starttag)
-        g_fprintf (data->output, ">");
-
-      escaped = g_markup_escape_text (data->value->str, -1);
-      g_fprintf (data->output, "%s</%s>\n", escaped, element_name);
-      g_free (escaped);
-
-      g_string_free (data->value, TRUE);
-      data->value = NULL;
-    }
-  else
-    {
-      if (data->unclosed_starttag)
-        g_fprintf (data->output, "/>\n");
-      else
-        g_fprintf (data->output, "%*s</%s>\n", data->indent - 2, "", element_name);
-    }
-
-  data->indent -= 2;
-  data->unclosed_starttag = FALSE;
-}
-
-static void
-text (GMarkupParseContext  *context,
-      const gchar          *text,
-      gsize                 text_len,
-      gpointer              user_data,
-      GError              **error)
-{
-  MyParserData *data = user_data;
-
-  if (data->value)
-    {
-      g_string_append_len (data->value, text, text_len);
-      return;
-    }
-}
-
-static void
-passthrough (GMarkupParseContext  *context,
-             const gchar          *text,
-             gsize                 text_len,
-             gpointer              user_data,
-             GError              **error)
-{
-  MyParserData *data = user_data;
-
-  maybe_close_starttag (data);
-
-  g_fprintf (data->output, "%*s%s\n", data->indent, "", text);
-}
-
-GMarkupParser parser = {
-  start_element,
-  end_element,
-  text,
-  passthrough,
-  NULL
-};
-
-static void
-do_simplify (int          *argc,
-             const char ***argv)
-{
-  GMarkupParseContext *context;
-  gchar *buffer;
-  MyParserData data;
-  gboolean replace = FALSE;
-  char **filenames = NULL;
-  GOptionContext *ctx;
-  const GOptionEntry entries[] = {
-    { "replace", 0, 0, G_OPTION_ARG_NONE, &replace, NULL, NULL },
-    { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, NULL },
-    { NULL, }
-  };
-  GError *error = NULL;
-
-  ctx = g_option_context_new (NULL);
-  g_option_context_set_help_enabled (ctx, FALSE);
-  g_option_context_add_main_entries (ctx, entries, NULL);
-
-  if (!g_option_context_parse (ctx, argc, (char ***)argv, &error))
-    {
-      g_printerr ("%s\n", error->message);
-      g_error_free (error);
-      exit (1);
-    }
-
-  g_option_context_free (ctx);
-
-  if (filenames == NULL)
-    {
-      g_printerr ("No .ui file specified\n");
-      exit (1);
-    }
-
-  if (g_strv_length (filenames) > 1)
-    {
-      g_printerr ("Can only simplify a single .ui file\n");
-      exit (1);
-    }
-
-  data.input_filename = filenames[0];
-  data.output_filename = NULL;
-
-  if (replace)
-    {
-      int fd;
-      fd = g_file_open_tmp ("gtk-builder-tool-XXXXXX", &data.output_filename, NULL);
-      data.output = fdopen (fd, "w");
-    }
-  else
-    {
-      data.output = stdout;
-    }
-
-  if (!g_file_get_contents (filenames[0], &buffer, NULL, &error))
-    {
-      g_printerr (_("Can’t load file: %s\n"), error->message);
-      exit (1);
-    }
-
-  data.builder = gtk_builder_new ();
-  data.classes = NULL;
-  data.attribute_names = NULL;
-  data.attribute_values = NULL;
-  data.value = NULL;
-  data.packing = FALSE;
-  data.packing_started = FALSE;
-  data.cell_packing = FALSE;
-  data.cell_packing_started = FALSE;
-  data.in_child = 0;
-  data.child_started = 0;
-  data.unclosed_starttag = FALSE;
-  data.indent = 0;
-
-  context = g_markup_parse_context_new (&parser, G_MARKUP_TREAT_CDATA_AS_TEXT, &data, NULL);
-  if (!g_markup_parse_context_parse (context, buffer, -1, &error))
-    {
-      g_printerr (_("Can’t parse file: %s\n"), error->message);
-      exit (1);
-    }
-
-  fclose (data.output);
-
-  if (data.output_filename)
-    {
-      char *content;
-      gsize length;
-
-      if (!g_file_get_contents (data.output_filename, &content, &length, &error))
-        {
-          g_printerr ("Failed to read %s: %s\n", data.output_filename, error->message);
-          exit (1);
-        }
-
-      if (!g_file_set_contents (data.input_filename, content, length, &error))
-        {
-          g_printerr ("Failed to write %s: %s\n", data.input_filename, error->message);
-          exit (1);
-        }
-    }
-}
-
-static GType
-make_fake_type (const gchar *type_name,
-                const gchar *parent_name)
-{
-  GType parent_type;
-  GTypeQuery query;
-
-  parent_type = g_type_from_name (parent_name);
-  if (parent_type == G_TYPE_INVALID)
-    {
-      g_printerr ("Failed to lookup template parent type %s\n", parent_name);
-      exit (1);
-    }
-
-  g_type_query (parent_type, &query);
-  return g_type_register_static_simple (parent_type,
-                                        type_name,
-                                        query.class_size,
-                                        NULL,
-                                        query.instance_size,
-                                        NULL,
-                                        0);
-}
-
-static void
-do_validate_template (const gchar *filename,
-                      const gchar *type_name,
-                      const gchar *parent_name)
-{
-  GType template_type;
-  GtkWidget *widget;
-  GtkBuilder *builder;
-  GError *error = NULL;
-  gint ret;
-
-  /* Only make a fake type if it doesn't exist yet.
-   * This lets us e.g. validate the GtkFileChooserWidget template.
-   */
-  template_type = g_type_from_name (type_name);
-  if (template_type == G_TYPE_INVALID)
-    template_type = make_fake_type (type_name, parent_name);
-
-  widget = g_object_new (template_type, NULL);
-  if (!widget)
-    {
-      g_printerr ("Failed to create an instance of the template type %s\n", type_name);
-      exit (1);
-    }
-
-  builder = gtk_builder_new ();
-  ret = gtk_builder_extend_with_template (builder, widget, template_type, " ", 1, &error);
-  if (ret)
-    ret = gtk_builder_add_from_file (builder, filename, &error);
-  g_object_unref (builder);
-
-  if (ret == 0)
-    {
-      g_printerr ("%s\n", error->message);
-      exit (1);
-    }
-}
-
-static gboolean
-parse_template_error (const gchar  *message,
-                      gchar       **class_name,
-                      gchar       **parent_name)
-{
-  gchar *p;
-
-  if (!strstr (message, "Not expecting to handle a template"))
-    return FALSE;
-
-  p = strstr (message, "(class '");
-  if (p)
-    {
-      *class_name = g_strdup (p + strlen ("(class '"));
-      p = strstr (*class_name, "'");
-      if (p)
-        *p = '\0';
-    }
-  p = strstr (message, ", parent '");
-  if (p)
-    {
-      *parent_name = g_strdup (p + strlen (", parent '"));
-      p = strstr (*parent_name, "'");
-      if (p)
-        *p = '\0';
-    }
-
-  return TRUE;
-}
-
-static void
-do_validate (const gchar *filename)
-{
-  GtkBuilder *builder;
-  GError *error = NULL;
-  gint ret;
-  gchar *class_name = NULL;
-  gchar *parent_name = NULL;
-
-  builder = gtk_builder_new ();
-  ret = gtk_builder_add_from_file (builder, filename, &error);
-  g_object_unref (builder);
-
-  if (ret == 0)
-    {
-      if (g_error_matches (error, GTK_BUILDER_ERROR, GTK_BUILDER_ERROR_UNHANDLED_TAG)  &&
-          parse_template_error (error->message, &class_name, &parent_name))
-        {
-          do_validate_template (filename, class_name, parent_name);
-        }
-      else
-        {
-          g_printerr ("%s\n", error->message);
-          exit (1);
-        }
-    }
-}
-
-static const gchar *
-object_get_name (GObject *object)
-{
-  if (GTK_IS_BUILDABLE (object))
-    return gtk_buildable_get_name (GTK_BUILDABLE (object));
-  else
-    return g_object_get_data (object, "gtk-builder-name");
-}
-
-static void
-do_enumerate (const gchar *filename)
-{
-  GtkBuilder *builder;
-  GError *error = NULL;
-  gint ret;
-  GSList *list, *l;
-  GObject *object;
-  const gchar *name;
-
-  builder = gtk_builder_new ();
-  ret = gtk_builder_add_from_file (builder, filename, &error);
-
-  if (ret == 0)
-    {
-      g_printerr ("%s\n", error->message);
-      exit (1);
-    }
-
-  list = gtk_builder_get_objects (builder);
-  for (l = list; l; l = l->next)
-    {
-      object = l->data;
-      name = object_get_name (object);
-      if (g_str_has_prefix (name, "___") && g_str_has_suffix (name, "___"))
-        continue;
-
-      g_printf ("%s (%s)\n", name, g_type_name_from_instance ((GTypeInstance*)object));
-    }
-  g_slist_free (list);
-
-  g_object_unref (builder);
-}
-
-static void
-set_window_title (GtkWindow  *window,
-                  const char *filename,
-                  const char *id)
-{
-  gchar *name;
-  gchar *title;
-
-  name = g_path_get_basename (filename);
-
-  if (id)
-    title = g_strdup_printf ("%s in %s", id, name);
-  else
-    title = g_strdup (name);
-
-  gtk_window_set_title (window, title);
-
-  g_free (title);
-  g_free (name);
-}
-
-static void
-preview_file (const char *filename,
-              const char *id,
-              const char *cssfile)
-{
-  GtkBuilder *builder;
-  GError *error = NULL;
-  GObject *object;
-  GtkWidget *window;
-
-  if (cssfile)
-    {
-      GtkCssProvider *provider;
-
-      provider = gtk_css_provider_new ();
-      gtk_css_provider_load_from_path (provider, cssfile);
-
-      gtk_style_context_add_provider_for_display (gdk_display_get_default (),
-                                                  GTK_STYLE_PROVIDER (provider),
-                                                  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
-    }
-
-  builder = gtk_builder_new ();
-  if (!gtk_builder_add_from_file (builder, filename, &error))
-    {
-      g_printerr ("%s\n", error->message);
-      exit (1);
-    }
-
-  object = NULL;
-
-  if (id)
-    {
-      object = gtk_builder_get_object (builder, id);
-    }
-  else
-    {
-      GSList *objects, *l;
-
-      objects = gtk_builder_get_objects (builder);
-      for (l = objects; l; l = l->next)
-        {
-          GObject *obj = l->data;
-
-          if (GTK_IS_WINDOW (obj))
-            {
-              object = obj;
-              break;
-            }
-          else if (GTK_IS_WIDGET (obj))
-            {
-              if (object == NULL)
-                object = obj;
-            }
-        }
-      g_slist_free (objects);
-    }
-
-  if (object == NULL)
-    {
-      if (id)
-        g_printerr ("No object with ID '%s' found\n", id);
-      else
-        g_printerr ("No previewable object found\n");
-      exit (1);
-    }
-
-  if (!GTK_IS_WIDGET (object))
-    {
-      g_printerr ("Objects of type %s can't be previewed\n", G_OBJECT_TYPE_NAME (object));
-      exit (1);
-    }
-
-  if (GTK_IS_WINDOW (object))
-    window = GTK_WIDGET (object);
-  else
-    {
-      GtkWidget *widget = GTK_WIDGET (object);
-
-      window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
-
-      if (GTK_IS_BUILDABLE (object))
-        id = gtk_buildable_get_name (GTK_BUILDABLE (object));
-
-      set_window_title (GTK_WINDOW (window), filename, id);
-
-      g_object_ref (widget);
-      if (gtk_widget_get_parent (widget) != NULL)
-        gtk_container_remove (GTK_CONTAINER (gtk_widget_get_parent (widget)), widget);
-      gtk_container_add (GTK_CONTAINER (window), widget);
-      g_object_unref (widget);
-    }
-
-  gtk_window_present (GTK_WINDOW (window));
-
-  gtk_main ();
-
-  g_object_unref (builder);
-}
-
-static void
-do_preview (int          *argc,
-            const char ***argv)
-{
-  GOptionContext *context;
-  char *id = NULL;
-  char *css = NULL;
-  char **filenames = NULL;
-  const GOptionEntry entries[] = {
-    { "id", 0, 0, G_OPTION_ARG_STRING, &id, NULL, NULL },
-    { "css", 0, 0, G_OPTION_ARG_FILENAME, &css, NULL, NULL },
-    { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, NULL },
-    { NULL, }
-  };
-  GError *error = NULL;
-
-  context = g_option_context_new (NULL);
-  g_option_context_set_help_enabled (context, FALSE);
-  g_option_context_add_main_entries (context, entries, NULL);
-
-  if (!g_option_context_parse (context, argc, (char ***)argv, &error))
-    {
-      g_printerr ("%s\n", error->message);
-      g_error_free (error);
-      exit (1);
-    }
-
-  g_option_context_free (context);
-
-  if (filenames == NULL)
-    {
-      g_printerr ("No .ui file specified\n");
-      exit (1);
-    }
-
-  if (g_strv_length (filenames) > 1)
-    {
-      g_printerr ("Can only preview a single .ui file\n");
-      exit (1);
-    }
-
-  preview_file (filenames[0], id, css);
-
-  g_strfreev (filenames);
-  g_free (id);
-  g_free (css);
-}
+extern void do_simplify  (int *argc, const char ***argv);
+extern void do_validate  (int *argc, const char ***argv);
+extern void do_enumerate (int *argc, const char ***argv);
+extern void do_preview   (int *argc, const char ***argv);
 
 static void
 usage (void)
@@ -1090,11 +74,11 @@ main (int argc, const char *argv[])
   argc--;
 
   if (strcmp (argv[0], "validate") == 0)
-    do_validate (argv[1]);
+    do_validate (&argc, &argv);
   else if (strcmp (argv[0], "simplify") == 0)
     do_simplify (&argc, &argv);
   else if (strcmp (argv[0], "enumerate") == 0)
-    do_enumerate (argv[1]);
+    do_enumerate (&argc, &argv);
   else if (strcmp (argv[0], "preview") == 0)
     do_preview (&argc, &argv);
   else
diff --git a/gtk/tools/meson.build b/gtk/tools/meson.build
index 39dcde097f..e082bc833f 100644
--- a/gtk/tools/meson.build
+++ b/gtk/tools/meson.build
@@ -1,7 +1,11 @@
 # Installed tools
 gtk_tools = [
   ['gtk4-query-settings', ['gtk-query-settings.c']],
-  ['gtk4-builder-tool', ['gtk-builder-tool.c']],
+  ['gtk4-builder-tool', ['gtk-builder-tool.c',
+                         'gtk-builder-tool-simplify.c',
+                         'gtk-builder-tool-validate.c',
+                         'gtk-builder-tool-enumerate.c',
+                         'gtk-builder-tool-preview.c']],
   ['gtk4-update-icon-cache', ['updateiconcache.c', 'gtkiconcachevalidator.c']],
   ['gtk4-encode-symbolic-svg', ['encodesymbolic.c', 'gdkpixbufutils.c']],
 ]


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]