/* 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 . * * Author: Matthias Clasen */ #include #include #include #include #include #include #include #include "gtkbuilderprivate.h" typedef struct Element Element; struct Element { Element *parent; char *element_name; char **attribute_names; char **attribute_values; char *data; GList *children; int line_number; int char_number; }; static void free_element (gpointer data) { Element *element = data; g_list_free_full (element->children, free_element); g_free (element->element_name); g_strfreev (element->attribute_names); g_strfreev (element->attribute_values); g_free (element->data); g_free (element); } typedef struct { Element *root; Element *current; GString *value; GtkBuilder *builder; const char *input_filename; char *output_filename; FILE *output; gboolean convert3to4; } MyParserData; static void start_element (GMarkupParseContext *context, const char *element_name, const char **attribute_names, const char **attribute_values, gpointer user_data, GError **error) { MyParserData *data = user_data; Element *elt; elt = g_new0 (Element, 1); elt->parent = data->current; elt->element_name = g_strdup (element_name); elt->attribute_names = g_strdupv ((char **)attribute_names); elt->attribute_values = g_strdupv ((char **)attribute_values); g_markup_parse_context_get_position (context, &elt->line_number, &elt->char_number); if (data->current) data->current->children = g_list_append (data->current->children, elt); data->current = elt; if (data->root == NULL) data->root = elt; g_string_truncate (data->value, 0); } static void end_element (GMarkupParseContext *context, const char *element_name, gpointer user_data, GError **error) { MyParserData *data = user_data; data->current->data = g_strdup (data->value->str); data->current = data->current->parent; } static void text (GMarkupParseContext *context, const char *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 GMarkupParser parser = { start_element, end_element, text, NULL, NULL }; 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"; } typedef enum { PROP_KIND_OBJECT, PROP_KIND_PACKING, PROP_KIND_CELL_PACKING, PROP_KIND_LAYOUT } PropKind; static PropKind get_prop_kind (Element *element) { g_assert (g_str_equal (element->element_name, "property")); if (g_str_equal (element->parent->element_name, "packing")) return PROP_KIND_PACKING; else if (g_str_equal (element->parent->element_name, "layout")) return PROP_KIND_LAYOUT; else if (g_str_equal (element->parent->element_name, "cell-packing")) return PROP_KIND_CELL_PACKING; else return PROP_KIND_OBJECT; } /* 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 (GParamSpec *pspec, PropKind kind) { struct _Prop { const char *class; const char *property; PropKind kind; } props[] = { { "GtkAboutDialog", "program-name", PROP_KIND_OBJECT }, { "GtkCalendar", "year", PROP_KIND_OBJECT }, { "GtkCalendar", "month", PROP_KIND_OBJECT }, { "GtkCalendar", "day", PROP_KIND_OBJECT }, { "GtkPlacesSidebar", "show-desktop", PROP_KIND_OBJECT }, { "GtkRadioButton", "draw-indicator", PROP_KIND_OBJECT }, { "GtkWidget", "hexpand", PROP_KIND_OBJECT }, { "GtkWidget", "vexpand", PROP_KIND_OBJECT }, { "GtkGridLayoutChild", "top-attach", PROP_KIND_LAYOUT }, { "GtkGridLayoutChild", "left-attach", PROP_KIND_LAYOUT }, }; gboolean found; gint k; const char *class_name; class_name = g_type_name (pspec->owner_type); found = FALSE; for (k = 0; k < G_N_ELEMENTS (props); k++) { if (strcmp (class_name, props[k].class) == 0 && strcmp (pspec->name, props[k].property) == 0 && kind == props[k].kind) { found = TRUE; break; } } return found; } static gboolean keep_for_rewrite (const char *class_name, const char *property_name, PropKind kind) { struct _Prop { const char *class; const char *property; PropKind kind; } props[] = { { "GtkPopover", "modal", PROP_KIND_OBJECT }, { "GtkActionBar", "pack-type", PROP_KIND_PACKING }, { "GtkHeaderBar", "pack-type", PROP_KIND_PACKING }, { "GtkPopoverMenu", "submenu", PROP_KIND_PACKING }, { "GtkToolbar", "expand", PROP_KIND_PACKING }, { "GtkToolbar", "homogeneous", PROP_KIND_PACKING }, { "GtkPaned", "resize", PROP_KIND_PACKING }, { "GtkPaned", "shrink", PROP_KIND_PACKING }, { "GtkOverlay", "measure", PROP_KIND_PACKING }, { "GtkOverlay", "clip-overlay", PROP_KIND_PACKING }, { "GtkGrid", "left-attach", PROP_KIND_PACKING }, { "GtkGrid", "top-attach", PROP_KIND_PACKING }, { "GtkGrid", "width", PROP_KIND_PACKING }, { "GtkGrid", "height", PROP_KIND_PACKING }, { "GtkStack", "name", PROP_KIND_PACKING }, { "GtkStack", "title", PROP_KIND_PACKING }, { "GtkStack", "icon-name", PROP_KIND_PACKING }, { "GtkStack", "needs-attention", PROP_KIND_PACKING }, }; gboolean found; gint k; char *canonical_name; canonical_name = g_strdup (property_name); g_strdelimit (canonical_name, "_", '-'); found = FALSE; for (k = 0; k < G_N_ELEMENTS (props); k++) { if (strcmp (class_name, props[k].class) == 0 && strcmp (canonical_name, props[k].property) == 0 && kind == props[k].kind) { found = TRUE; break; } } g_free (canonical_name); return found; } static gboolean is_pcdata_element (Element *element) { /* elements that can contain text */ const char *names[] = { "property", "attribute", "action-widget", "pattern", "mime-type", "col", "item", "mark", NULL, }; if (g_str_equal (element->element_name, "property") && (g_strv_contains ((const char * const *)element->attribute_names, "bind-source") || g_strv_contains ((const char * const *)element->attribute_names, "bind_source"))) return FALSE; if (g_strv_contains (names, element->element_name)) return TRUE; return FALSE; } static gboolean is_container_element (Element *element) { /* elements that just hold a list of things and * can be omitted when they have no children */ const char *names[] = { "packing", "layout", "cell-packing", "attributes", "action-widgets", "patterns", "mime-types", "attributes", "row", "items", NULL }; if (g_strv_contains (names, element->element_name)) return TRUE; return FALSE; } 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 struct { const char *class; const char *layout_manager; } layout_managers[] = { { "GtkBox", "GtkBoxLayout" }, { "GtkGrid", "GtkGridLayout" }, { "GtkFixed", "GtkFixedLayout" }, { "GtkFileChooserButton", "GtkBinLayout" }, { "GtkFileChooserWidget", "GtkBinLayout" }, { "GtkOverlay", "GtkOverlayLayout" } }; static GParamSpec * get_property_pspec (MyParserData *data, const gchar *class_name, const gchar *property_name, PropKind kind) { GType type; GObjectClass *class; GParamSpec *pspec; gchar *canonical_name; type = g_type_from_name (class_name); if (type == G_TYPE_INVALID) { type = gtk_builder_get_type_from_name (data->builder, class_name); if (type == G_TYPE_INVALID) return NULL; } class = g_type_class_ref (type); canonical_name = g_strdup (property_name); canonicalize_key (canonical_name); switch (kind) { case PROP_KIND_OBJECT: pspec = g_object_class_find_property (class, canonical_name); break; case PROP_KIND_PACKING: pspec = NULL; break; case PROP_KIND_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); } break; case PROP_KIND_LAYOUT: { int i; const char *layout_manager = NULL; pspec = NULL; for (i = 0; i < G_N_ELEMENTS (layout_managers); i++) { if (g_str_equal (layout_managers[i].class, class_name)) { layout_manager = layout_managers[i].layout_manager; break; } } if (layout_manager) { GtkLayoutManagerClass *layout_manager_class; layout_manager_class = GTK_LAYOUT_MANAGER_CLASS (g_type_class_ref (g_type_from_name (layout_manager))); if (layout_manager_class->layout_child_type != G_TYPE_INVALID) { GObjectClass *layout_child_class; layout_child_class = g_type_class_ref (layout_manager_class->layout_child_type); pspec = g_object_class_find_property (layout_child_class, canonical_name); g_type_class_unref (layout_child_class); } g_type_class_unref (layout_manager_class); } } break; default: g_assert_not_reached (); } g_free (canonical_name); g_type_class_unref (class); return pspec; } static const char *get_class_name (Element *element); static gboolean value_is_default (Element *element, MyParserData *data, GParamSpec *pspec, const gchar *value_string) { GValue value = { 0, }; gboolean ret; GError *error = NULL; if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (pspec), G_TYPE_OBJECT)) return FALSE; if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (pspec), G_TYPE_BOXED)) return FALSE; if (!gtk_builder_value_from_string (data->builder, pspec, value_string, &value, &error)) { g_printerr (_("%s:%d: Couldn’t parse value for property '%s': %s\n"), data->input_filename, element->line_number, pspec->name, error->message); g_error_free (error); ret = FALSE; } else { /* GtkWidget::visible has a 'smart' default */ if (pspec->owner_type == GTK_TYPE_WIDGET && g_str_equal (pspec->name, "visible")) { const char *class_name = get_class_name (element); GType type = g_type_from_name (class_name); gboolean default_value; if (g_type_is_a (type, GTK_TYPE_ROOT) || g_type_is_a (type, GTK_TYPE_POPOVER)) default_value = FALSE; else default_value = TRUE; ret = g_value_get_boolean (&value) == default_value; } else ret = g_param_value_defaults (pspec, &value); } g_value_reset (&value); return ret; } static gboolean has_attribute (Element *elt, const char *name, const char *value) { int i; for (i = 0; elt->attribute_names[i]; i++) { if (strcmp (elt->attribute_names[i], name) == 0 && (value == NULL || strcmp (elt->attribute_values[i], value) == 0)) return TRUE; } return FALSE; } static const char * get_attribute_value (Element *element, const char *name) { int i; for (i = 0; element->attribute_names[i]; i++) { if (g_str_equal (element->attribute_names[i], name)) return element->attribute_values[i]; } return NULL; } static void set_attribute_value (Element *element, const char *name, const char *value) { int i; for (i = 0; element->attribute_names[i]; i++) { if (g_str_equal (element->attribute_names[i], name)) { g_free (element->attribute_values[i]); element->attribute_values[i] = g_strdup (value); return; } } } static gboolean element_is_object_or_template (Element *element) { return g_str_equal (element->element_name, "object") || g_str_equal (element->element_name, "template"); } static const char * get_class_name (Element *element) { Element *parent = element->parent; if (element_is_object_or_template (element)) parent = element; if (g_str_equal (parent->element_name, "packing")) parent = parent->parent->parent; /* child - object */ else if (g_str_equal (parent->element_name, "layout")) parent = parent->parent->parent->parent; /* object - child - object */ if (g_str_equal (parent->element_name, "object")) { return get_attribute_value (parent, "class"); } else if (g_str_equal (parent->element_name, "template")) { return get_attribute_value (parent, "parent"); } return NULL; } static gboolean property_is_boolean (Element *element, MyParserData *data) { GParamSpec *pspec; const char *class_name; const char *property_name; int i; PropKind kind; kind = get_prop_kind (element); class_name = get_class_name (element); property_name = ""; for (i = 0; element->attribute_names[i]; i++) { if (strcmp (element->attribute_names[i], "name") == 0) property_name = (const gchar *)element->attribute_values[i]; } pspec = get_property_pspec (data, class_name, property_name, kind); if (pspec) return G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN; return FALSE; } static gboolean property_can_be_omitted (Element *element, MyParserData *data) { gint i; gboolean bound; gboolean translatable; const char *class_name; const char *property_name; const char *value_string; GParamSpec *pspec; PropKind kind; kind = get_prop_kind (element); class_name = get_class_name (element); property_name = ""; value_string = element->data; bound = FALSE; translatable = FALSE; for (i = 0; element->attribute_names[i]; i++) { if (strcmp (element->attribute_names[i], "bind-source") == 0 || strcmp (element->attribute_names[i], "bind_source") == 0) bound = TRUE; else if (strcmp (element->attribute_names[i], "translatable") == 0) translatable = TRUE; else if (strcmp (element->attribute_names[i], "name") == 0) property_name = (const gchar *)element->attribute_values[i]; } if (data->convert3to4 && keep_for_rewrite (class_name, property_name, kind)) return FALSE; /* keep, will be rewritten */ if (translatable) return FALSE; if (bound) return FALSE; pspec = get_property_pspec (data, class_name, property_name, kind); if (pspec == NULL) { const char *kind_str[] = { "", "Packing ", "Cell ", "Layout " }; g_printerr (_("%s:%d: %sproperty %s::%s not found\n"), data->input_filename, element->line_number, kind_str[kind], class_name, property_name); return FALSE; } if (needs_explicit_setting (pspec, kind)) return FALSE; return value_is_default (element, data, pspec, value_string); } static gboolean property_has_been_removed (Element *element, MyParserData *data) { const gchar *class_name; const gchar *property_name; struct _Prop { const char *class; const char *property; PropKind kind; } props[] = { { "GtkActionBar", "position", PROP_KIND_PACKING }, { "GtkButtonBox", "secondary", PROP_KIND_PACKING }, { "GtkButtonBox", "non-homogeneous", PROP_KIND_PACKING }, { "GtkBox", "position", PROP_KIND_PACKING }, { "GtkBox", "pack-type", PROP_KIND_PACKING }, { "GtkHeaderBar", "position", PROP_KIND_PACKING }, { "GtkPopoverMenu", "position",PROP_KIND_PACKING }, }; gchar *canonical_name; gboolean found; gint i, k; PropKind kind; kind = get_prop_kind (element); class_name = get_class_name (element); property_name = ""; for (i = 0; element->attribute_names[i]; i++) { if (strcmp (element->attribute_names[i], "name") == 0) property_name = (const gchar *)element->attribute_values[i]; } canonical_name = g_strdup (property_name); g_strdelimit (canonical_name, "_", '-'); found = FALSE; for (k = 0; k < G_N_ELEMENTS (props); k++) { if (strcmp (class_name, props[k].class) == 0 && strcmp (canonical_name, props[k].property) == 0 && kind == props[k].kind) { found = TRUE; break; } } g_free (canonical_name); return found; } static void maybe_rename_property (Element *element, MyParserData *data) { const gchar *class_name; const gchar *property_name; struct _Prop { const char *class; const char *property; PropKind kind; const char *new_name; } props[] = { { "GtkPopover", "modal", PROP_KIND_OBJECT, "autohide" }, }; char *canonical_name; int i, k; PropKind kind; int prop_name_index = 0; kind = get_prop_kind (element); class_name = get_class_name (element); property_name = NULL; for (i = 0; element->attribute_names[i]; i++) { if (strcmp (element->attribute_names[i], "name") == 0) { prop_name_index = i; property_name = (const gchar *)element->attribute_values[i]; } } if (property_name == NULL) return; canonical_name = g_strdup (property_name); g_strdelimit (canonical_name, "_", '-'); for (k = 0; k < G_N_ELEMENTS (props); k++) { if (strcmp (class_name, props[k].class) == 0 && strcmp (canonical_name, props[k].property) == 0 && kind == props[k].kind) { g_free (element->attribute_values[prop_name_index]); element->attribute_values[prop_name_index] = g_strdup (props[k].new_name); break; } } g_free (canonical_name); } static Element * rewrite_stack_child (Element *child, MyParserData *data) { Element *object = NULL; Element *packing = NULL; Element *new_object; Element *prop; GList *l; if (!g_str_equal (child->element_name, "child")) return child; for (l = child->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "object")) object = elt; else if (g_str_equal (elt->element_name, "packing")) packing = elt; } if (!packing) return child; new_object = g_new0 (Element, 1); new_object->element_name = g_strdup ("object"); new_object->attribute_names = g_new0 (char *, 2); new_object->attribute_names[0] = g_strdup ("class"); new_object->attribute_values = g_new0 (char *, 2); new_object->attribute_values[0] = g_strdup ("GtkStackPage"); new_object->children = packing->children; new_object->parent = child; packing->children = NULL; prop = g_new0 (Element, 1); prop->element_name = g_strdup ("property"); prop->attribute_names = g_new0 (char *, 2); prop->attribute_names[0] = g_strdup ("name"); prop->attribute_values = g_new0 (char *, 2); prop->attribute_values[0] = g_strdup ("child"); prop->children = g_list_append (prop->children, object); prop->parent = new_object; new_object->children = g_list_append (new_object->children, prop); g_list_free (child->children); child->children = g_list_append (NULL, new_object); return child; } static void rewrite_stack (Element *element, MyParserData *data) { GList *l, *new_children; new_children = NULL; for (l = element->children; l; l = l->next) { Element *child = l->data; new_children = g_list_append (new_children, rewrite_stack_child (child, data)); } g_list_free (element->children); element->children = new_children; } static Element * rewrite_assistant_child (Element *child, MyParserData *data) { Element *object = NULL; Element *packing = NULL; Element *new_object; Element *prop; GList *l; if (!g_str_equal (child->element_name, "child")) return child; for (l = child->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "object")) object = elt; else if (g_str_equal (elt->element_name, "packing")) packing = elt; } if (!packing) return child; new_object = g_new0 (Element, 1); new_object->element_name = g_strdup ("object"); new_object->attribute_names = g_new0 (char *, 2); new_object->attribute_names[0] = g_strdup ("class"); new_object->attribute_values = g_new0 (char *, 2); new_object->attribute_values[0] = g_strdup ("GtkAssistantPage"); new_object->children = packing->children; new_object->parent = child; packing->children = NULL; prop = g_new0 (Element, 1); prop->element_name = g_strdup ("property"); prop->attribute_names = g_new0 (char *, 2); prop->attribute_names[0] = g_strdup ("name"); prop->attribute_values = g_new0 (char *, 2); prop->attribute_values[0] = g_strdup ("child"); prop->children = g_list_append (prop->children, object); prop->parent = new_object; new_object->children = g_list_append (new_object->children, prop); g_list_free (child->children); child->children = g_list_append (NULL, new_object); return child; } static void rewrite_assistant (Element *element, MyParserData *data) { GList *l, *new_children; new_children = NULL; for (l = element->children; l; l = l->next) { Element *child = l->data; new_children = g_list_append (new_children, rewrite_assistant_child (child, data)); } g_list_free (element->children); element->children = new_children; } static Element * rewrite_notebook_page (Element *child, Element *tab, MyParserData *data) { Element *object = NULL; Element *tab_obj = NULL; Element *packing = NULL; Element *new_object; Element *prop; GList *l; if (!g_str_equal (child->element_name, "child")) return child; if (has_attribute (child, "type", "tab") || has_attribute (child, "type", "action-start") || has_attribute (child, "type", "action-end")) return child; for (l = child->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "object")) object = elt; else if (g_str_equal (elt->element_name, "packing")) packing = elt; } if (!packing && !tab) return child; if (tab) { for (l = tab->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "object")) tab_obj = elt; } } new_object = g_new0 (Element, 1); new_object->element_name = g_strdup ("object"); new_object->attribute_names = g_new0 (char *, 2); new_object->attribute_names[0] = g_strdup ("class"); new_object->attribute_values = g_new0 (char *, 2); new_object->attribute_values[0] = g_strdup ("GtkNotebookPage"); new_object->parent = child; if (packing) { new_object->children = packing->children; packing->children = NULL; } prop = g_new0 (Element, 1); prop->element_name = g_strdup ("property"); prop->attribute_names = g_new0 (char *, 2); prop->attribute_names[0] = g_strdup ("name"); prop->attribute_values = g_new0 (char *, 2); prop->attribute_values[0] = g_strdup ("child"); prop->children = g_list_append (prop->children, object); prop->parent = new_object; new_object->children = g_list_append (new_object->children, prop); if (tab_obj) { prop = g_new0 (Element, 1); prop->element_name = g_strdup ("property"); prop->attribute_names = g_new0 (char *, 2); prop->attribute_names[0] = g_strdup ("name"); prop->attribute_values = g_new0 (char *, 2); prop->attribute_values[0] = g_strdup ("tab"); prop->children = g_list_append (prop->children, tab_obj); prop->parent = new_object; new_object->children = g_list_append (new_object->children, prop); } g_list_free (child->children); child->children = g_list_append (NULL, new_object); return child; } static void rewrite_notebook (Element *element, MyParserData *data) { GList *l, *new_children; new_children = NULL; for (l = element->children; l; l = l->next) { Element *child = l->data; Element *tab = l->next ? l->next->data : NULL; if (tab && has_attribute (tab, "type", "tab")) { new_children = g_list_append (new_children, rewrite_notebook_page (child, tab, data)); l = l->next; /* skip the tab */ } else new_children = g_list_append (new_children, rewrite_notebook_page (child, NULL, data)); } g_list_free (element->children); element->children = new_children; } static void rewrite_pack_type_child (Element *element, MyParserData *data) { Element *pack_type = NULL; GList *l, *ll; if (!g_str_equal (element->element_name, "child")) return; for (l = element->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "packing")) { for (ll = elt->children; ll; ll = ll->next) { Element *elt2 = ll->data; if (g_str_equal (elt2->element_name, "property") && has_attribute (elt2, "name", "pack-type")) { pack_type = elt2; elt->children = g_list_remove (elt->children, pack_type); if (elt->children == NULL) { element->children = g_list_remove (element->children, elt); free_element (elt); } break; } } } if (pack_type) break; } if (pack_type) { char **attnames = g_new0 (char *, g_strv_length (element->attribute_names) + 2); char **attvalues = g_new0 (char *, g_strv_length (element->attribute_names) + 2); int i; for (i = 0; element->attribute_names[i]; i++) { attnames[i] = g_strdup (element->attribute_names[i]); attvalues[i] = g_strdup (element->attribute_values[i]); } attnames[i] = g_strdup ("type"); attvalues[i] = g_strdup (pack_type->data); g_strfreev (element->attribute_names); g_strfreev (element->attribute_values); element->attribute_names = attnames; element->attribute_values = attvalues; free_element (pack_type); } } static void rewrite_pack_type (Element *element, MyParserData *data) { GList *l; for (l = element->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "child")) rewrite_pack_type_child (elt, data); } } static void rewrite_child_prop_to_prop_child (Element *element, MyParserData *data, const char *child_prop, const char *prop) { Element *object = NULL; Element *replaced = NULL; GList *l, *ll; if (!g_str_equal (element->element_name, "child")) return; for (l = element->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "object")) object = elt; if (g_str_equal (elt->element_name, "packing")) { for (ll = elt->children; ll; ll = ll->next) { Element *elt2 = ll->data; if (g_str_equal (elt2->element_name, "property") && has_attribute (elt2, "name", child_prop)) { replaced = elt2; elt->children = g_list_remove (elt->children, replaced); if (elt->children == NULL) { element->children = g_list_remove (element->children, elt); free_element (elt); } break; } } } if (replaced) break; } if (replaced) { Element *elt; elt = g_new0 (Element, 1); elt->parent = element; elt->element_name = g_strdup ("property"); elt->attribute_names = g_new0 (char *, 2); elt->attribute_names[0] = g_strdup ("name"); elt->attribute_values = g_new0 (char *, 2); elt->attribute_values[0] = g_strdup (prop); elt->data = g_strdup (replaced->data); object->children = g_list_prepend (object->children, elt); free_element (replaced); } } static void rewrite_child_prop_to_prop (Element *element, MyParserData *data, const char *child_prop, const char *prop) { GList *l; for (l = element->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "child")) rewrite_child_prop_to_prop_child (elt, data, child_prop, prop); } } static void rewrite_paned_child (Element *element, MyParserData *data, Element *child, const char *suffix) { Element *resize = NULL; Element *shrink = NULL; GList *l, *ll; for (l = child->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "packing")) { for (ll = elt->children; ll; ll = ll->next) { Element *elt2 = ll->data; if (g_str_equal (elt2->element_name, "property") && has_attribute (elt2, "name", "resize")) resize = elt2; if (g_str_equal (elt2->element_name, "property") && has_attribute (elt2, "name", "shrink")) shrink = elt2; } if (resize) elt->children = g_list_remove (elt->children, resize); if (shrink) elt->children = g_list_remove (elt->children, shrink); if (elt->children == NULL) { child->children = g_list_remove (child->children, elt); free_element (elt); } } if (resize || shrink) break; } if (resize) { Element *elt; elt = g_new0 (Element, 1); elt->parent = element; elt->element_name = g_strdup ("property"); elt->attribute_names = g_new0 (char *, 2); elt->attribute_names[0] = g_strdup ("name"); elt->attribute_values = g_new0 (char *, 2); elt->attribute_values[0] = g_strconcat ("resize-", suffix, NULL); elt->data = g_strdup (resize->data); element->children = g_list_prepend (element->children, elt); free_element (resize); } if (shrink) { Element *elt; elt = g_new0 (Element, 1); elt->parent = element; elt->element_name = g_strdup ("property"); elt->attribute_names = g_new0 (char *, 2); elt->attribute_names[0] = g_strdup ("name"); elt->attribute_values = g_new0 (char *, 2); elt->attribute_values[0] = g_strconcat ("shrink-", suffix, NULL); elt->data = g_strdup (shrink->data); element->children = g_list_prepend (element->children, elt); free_element (shrink); } } static void rewrite_paned (Element *element, MyParserData *data) { Element *child1 = NULL; Element *child2 = NULL; GList *l; for (l = element->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "child")) { if (child1 == NULL) child1 = elt; else if (child2 == NULL) child2 = elt; else break; } } if (child1) rewrite_paned_child (element, data, child1, "child1"); if (child2) rewrite_paned_child (element, data, child2, "child2"); } static void rewrite_dialog (Element *element, MyParserData *data) { Element *content_area = NULL; Element *vbox = NULL; Element *action_area = NULL; GList *l; for (l = element->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "child") && g_strcmp0 (get_attribute_value (elt, "internal-child"), "vbox") == 0) { content_area = elt; break; } } if (!content_area || !content_area->children) return; vbox = content_area->children->data; for (l = vbox->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "child") && g_strcmp0 (get_attribute_value (elt, "internal-child"), "action_area") == 0) { action_area = elt; break; } } if (!action_area) return; set_attribute_value (content_area, "internal-child", "content_area"); vbox->children = g_list_remove (vbox->children, action_area); action_area->parent = element; element->children = g_list_append (element->children, action_area); for (l = action_area->children; l; l = l->next) { Element *elt = l->data; if (g_str_equal (elt->element_name, "packing")) { action_area->children = g_list_remove (action_area->children, elt); free_element (elt); break; } } } static void rewrite_layout_props (Element *element, MyParserData *data) { GList *l, *ll; for (l = element->children; l; l = l->next) { Element *child = l->data; if (g_str_equal (child->element_name, "child")) { Element *object = NULL; Element *packing = NULL; for (ll = child->children; ll; ll = ll->next) { Element *elt2 = ll->data; if (g_str_equal (elt2->element_name, "object")) object = elt2; if (g_str_equal (elt2->element_name, "packing")) packing = elt2; } if (object && packing) { child->children = g_list_remove (child->children, packing); g_free (packing->element_name); packing->element_name = g_strdup ("layout"); packing->parent = object; object->children = g_list_append (object->children, packing); } } } } static void rewrite_grid_layout_prop (Element *element, const char *attr_name, const char *old_value, const char *new_value) { char *canonical_name; canonical_name = g_strdup (old_value); g_strdelimit (canonical_name, "_", '-'); if (g_str_equal (element->element_name, "property")) { if (has_attribute (element, attr_name, old_value) || has_attribute (element, attr_name, canonical_name)) { set_attribute_value (element, attr_name, new_value); } } g_free (canonical_name); } static void rewrite_grid_layout (Element *element, MyParserData *data) { struct _Prop { const char *attr_name; const char *old_value; const char *new_value; } props[] = { { "name", "width", "column-span", }, { "name", "height", "row-span", }, }; GList *l, *ll; for (l = element->children; l; l = l->next) { Element *child = l->data; if (g_str_equal (child->element_name, "child")) { Element *object = NULL; Element *packing = NULL; for (ll = child->children; ll; ll = ll->next) { Element *elt2 = ll->data; if (g_str_equal (elt2->element_name, "object")) object = elt2; if (g_str_equal (elt2->element_name, "packing")) packing = elt2; } if (object && packing) { int i; child->children = g_list_remove (child->children, packing); g_free (packing->element_name); packing->element_name = g_strdup ("layout"); packing->parent = object; object->children = g_list_append (object->children, packing); for (ll = packing->children; ll; ll = ll->next) { Element *elt = ll->data; for (i = 0; i < G_N_ELEMENTS (props); i++) rewrite_grid_layout_prop (elt, props[i].attr_name, props[i].old_value, props[i].new_value); } } } } } /* returns TRUE to remove the element from the parent */ static gboolean simplify_element (Element *element, MyParserData *data) { GList *l; if (!is_pcdata_element (element)) g_clear_pointer (&element->data, g_free); else if (g_str_equal (element->element_name, "property") && property_is_boolean (element, data)) { const char *b = canonical_boolean_value (data, element->data); g_free (element->data); element->data = g_strdup (b); } l = element->children; while (l) { GList *next = l->next; Element *child = l->data; if (simplify_element (child, data)) { element->children = g_list_remove (element->children, child); free_element (child); } l = next; } if (is_container_element (element) && element->children == NULL) return TRUE; if (g_str_equal (element->element_name, "property") && property_can_be_omitted (element, data)) return TRUE; return FALSE; } static void simplify_tree (MyParserData *data) { simplify_element (data->root, data); } static gboolean rewrite_element (Element *element, MyParserData *data) { GList *l; l = element->children; while (l) { GList *next = l->next; Element *child = l->data; if (rewrite_element (child, data)) { element->children = g_list_remove (element->children, child); free_element (child); } l = next; } if (element_is_object_or_template (element) && g_str_equal (get_class_name (element), "GtkStack")) rewrite_stack (element, data); if (element_is_object_or_template (element) && g_str_equal (get_class_name (element), "GtkAssistant")) rewrite_assistant (element, data); if (element_is_object_or_template (element) && g_str_equal (get_class_name (element), "GtkNotebook")) rewrite_notebook (element, data); if (element_is_object_or_template (element) && (g_str_equal (get_class_name (element), "GtkActionBar") || g_str_equal (get_class_name (element), "GtkHeaderBar"))) rewrite_pack_type (element, data); if (element_is_object_or_template (element) && g_str_equal (get_class_name (element), "GtkPopoverMenu")) rewrite_child_prop_to_prop (element, data, "submenu", "name"); if (element_is_object_or_template (element) && g_str_equal (get_class_name (element), "GtkToolbar")) rewrite_child_prop_to_prop (element, data, "expand", "expand-item"); if (element_is_object_or_template (element) && g_str_equal (get_class_name (element), "GtkToolbar")) rewrite_child_prop_to_prop (element, data, "homogeneous", "homogeneous"); if (element_is_object_or_template (element) && g_str_equal (get_class_name (element), "GtkPaned")) rewrite_paned (element, data); if (element_is_object_or_template (element) && g_str_equal (get_class_name (element), "GtkDialog")) rewrite_dialog (element, data); if (element_is_object_or_template (element) && g_str_equal (get_class_name (element), "GtkOverlay")) rewrite_layout_props (element, data); if (element_is_object_or_template (element) && g_str_equal (get_class_name (element), "GtkGrid")) rewrite_grid_layout (element, data); if (element_is_object_or_template (element) && g_str_equal (get_class_name (element), "GtkFixed")) rewrite_layout_props (element, data); if (g_str_equal (element->element_name, "property")) maybe_rename_property (element, data); if (g_str_equal (element->element_name, "property") && property_has_been_removed (element, data)) return TRUE; return FALSE; } static void rewrite_tree (MyParserData *data) { rewrite_element (data->root, data); } /* For properties which have changed their default * value between 3 and 4, we make sure that their * old default value is present in the tree before * simplifying it. * * So far, this is just GtkWidget::visible, * changing its default from 0 to 1. */ static void add_old_default_properties (Element *element, MyParserData *data) { const char *class_name; GType type; if (!g_str_equal (element->element_name, "object")) return; class_name = get_class_name (element); type = g_type_from_name (class_name); if (g_type_is_a (type, GTK_TYPE_WIDGET)) { GList *l; gboolean has_visible = FALSE; for (l = element->children; l; l = l->next) { Element *prop = l->data; const char *name = get_attribute_value (prop, "name"); if (g_str_equal (prop->element_name, "property") && g_str_equal (name, "visible")) has_visible = TRUE; } if (!has_visible) { Element *new_prop = g_new0 (Element, 1); new_prop->parent = element; new_prop->element_name = g_strdup ("property"); new_prop->attribute_names = g_new0 (char *, 2); new_prop->attribute_names[0] = g_strdup ("name"); new_prop->attribute_values = g_new0 (char *, 2); new_prop->attribute_values[0] = g_strdup ("visible"); new_prop->data = g_strdup ("0"); element->children = g_list_prepend (element->children, new_prop); } } } static void enhance_element (Element *element, MyParserData *data) { GList *l; add_old_default_properties (element, data); for (l = element->children; l; l = l->next) { Element *child = l->data; enhance_element (child, data); } } static void enhance_tree (MyParserData *data) { enhance_element (data->root, data); } static void dump_element (Element *element, FILE *output, int indent) { g_fprintf (output, "%*s<%s", indent, "", element->element_name); if (element->attribute_names[0]) { int i; for (i = 0; element->attribute_names[i]; i++) { char *escaped = g_markup_escape_text (element->attribute_values[i], -1); g_fprintf (output, " %s=\"%s\"", element->attribute_names[i], escaped); g_free (escaped); } } if (element->children || element->data) { g_fprintf (output, ">"); if (element->children) { GList *l; g_fprintf (output, "\n"); for (l = element->children; l; l = l->next) { Element *child = l->data; dump_element (child, output, indent + 2); } g_fprintf (output, "%*s", indent, ""); } else { char *escaped = g_markup_escape_text (element->data, -1); g_fprintf (output, "%s", escaped); g_free (escaped); } g_fprintf (output, "\n", element->element_name); } else g_fprintf (output, "/>\n"); } static void write_xml_declaration (FILE *output) { g_fprintf (output, "\n"); } static void dump_tree (MyParserData *data) { write_xml_declaration (data->output); dump_element (data->root, data->output, 0); } gboolean simplify_file (const char *filename, gboolean replace, gboolean convert3to4) { GMarkupParseContext *context; gchar *buffer; MyParserData data; GError *error = NULL; data.input_filename = filename; data.output_filename = NULL; data.convert3to4 = convert3to4; 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 (filename, &buffer, NULL, &error)) { g_printerr (_("Can’t load “%s”: %s\n"), filename, error->message); return FALSE; } data.root = NULL; data.current = NULL; data.value = g_string_new (""); 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 “%s”: %s\n"), filename, error->message); return FALSE; } data.builder = gtk_builder_new (); if (data.convert3to4) { enhance_tree (&data); rewrite_tree (&data); } simplify_tree (&data); dump_tree (&data); 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); return FALSE; } if (!g_file_set_contents (data.input_filename, content, length, &error)) { g_printerr (_("Failed to write %s: “%s”\n"), data.input_filename, error->message); return FALSE; } } return TRUE; } void do_simplify (int *argc, const char ***argv) { gboolean replace = FALSE; gboolean convert3to4 = FALSE; char **filenames = NULL; GOptionContext *ctx; const GOptionEntry entries[] = { { "replace", 0, 0, G_OPTION_ARG_NONE, &replace, NULL, NULL }, { "3to4", 0, 0, G_OPTION_ARG_NONE, &convert3to4, NULL, NULL }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, NULL }, { NULL, } }; GError *error = NULL; int i; 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 && !replace) { g_printerr (_("Can only simplify a single .ui file without --replace\n")); exit (1); } for (i = 0; filenames[i]; i++) { if (!simplify_file (filenames[i], replace, convert3to4)) exit (1); } }