From ad9c2a624fd53e0ffc9ca61828bdf362fbb68727 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 6 Feb 2019 12:54:18 -0500 Subject: [PATCH 01/11] stack: Convert child properties to a child meta object Create a GtkStackPage public object which holds the former child properties of GtkStack. Adjust all callers. --- demos/widget-factory/widget-factory.c | 2 +- docs/reference/gtk/gtk4-sections.txt | 3 + docs/reference/gtk/gtk4.types.in | 1 + gtk/gtkcontainer.c | 1 - gtk/gtkmenusectionbox.c | 2 +- gtk/gtkstack.c | 645 ++++++++++++++++---------- gtk/gtkstack.h | 20 + gtk/gtkstacksidebar.c | 35 +- gtk/gtkstackswitcher.c | 96 ++-- gtk/inspector/gtkstackcombo.c | 8 +- 10 files changed, 501 insertions(+), 312 deletions(-) diff --git a/demos/widget-factory/widget-factory.c b/demos/widget-factory/widget-factory.c index e9cd3c3a7d..6bfb68d623 100644 --- a/demos/widget-factory/widget-factory.c +++ b/demos/widget-factory/widget-factory.c @@ -556,7 +556,7 @@ set_needs_attention (GtkWidget *page, gboolean needs_attention) GtkWidget *stack; stack = gtk_widget_get_parent (page); - gtk_container_child_set (GTK_CONTAINER (stack), page, + g_object_set (gtk_stack_get_page (GTK_STACK (stack), page), "needs-attention", needs_attention, NULL); } diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt index 0e72ba604c..cfc4be3e1c 100644 --- a/docs/reference/gtk/gtk4-sections.txt +++ b/docs/reference/gtk/gtk4-sections.txt @@ -6242,10 +6242,13 @@ gtk_header_bar_get_type gtkstack GtkStack GtkStack +GtkStackPage gtk_stack_new gtk_stack_add_named gtk_stack_add_titled gtk_stack_get_child_by_name +gtk_stack_get_page +gtk_stack_page_get_child gtk_stack_set_visible_child gtk_stack_get_visible_child gtk_stack_set_visible_child_name diff --git a/docs/reference/gtk/gtk4.types.in b/docs/reference/gtk/gtk4.types.in index 26c78079cf..6d7d94077c 100644 --- a/docs/reference/gtk/gtk4.types.in +++ b/docs/reference/gtk/gtk4.types.in @@ -155,6 +155,7 @@ gtk_snapshot_get_type gtk_spin_button_get_type gtk_spinner_get_type gtk_stack_get_type +gtk_stack_page_get_type gtk_stack_sidebar_get_type gtk_stack_switcher_get_type gtk_statusbar_get_type diff --git a/gtk/gtkcontainer.c b/gtk/gtkcontainer.c index 8dd90a51ec..58085d3135 100644 --- a/gtk/gtkcontainer.c +++ b/gtk/gtkcontainer.c @@ -1877,4 +1877,3 @@ gtk_container_get_path_for_child (GtkContainer *container, return path; } - diff --git a/gtk/gtkmenusectionbox.c b/gtk/gtkmenusectionbox.c index 7b21eebc13..7439345373 100644 --- a/gtk/gtkmenusectionbox.c +++ b/gtk/gtkmenusectionbox.c @@ -303,7 +303,7 @@ gtk_menu_section_box_insert_func (GtkMenuTrackerItem *item, g_object_bind_property (item, "sensitive", widget, "sensitive", G_BINDING_SYNC_CREATE); get_ancestors (GTK_WIDGET (box->toplevel), GTK_TYPE_STACK, &stack, &parent); - gtk_container_child_get (GTK_CONTAINER (stack), parent, "name", &name, NULL); + g_object_get (gtk_stack_get_page (GTK_STACK (stack), parent), "name", &name, NULL); gtk_menu_section_box_new_submenu (item, box->toplevel, widget, name); g_free (name); } diff --git a/gtk/gtkstack.c b/gtk/gtkstack.c index 860af7f59a..7ac18bfc79 100644 --- a/gtk/gtkstack.c +++ b/gtk/gtkstack.c @@ -52,8 +52,29 @@ * These animations respect the #GtkSettings:gtk-enable-animations * setting. * - * The GtkStack widget was added in GTK+ 3.10. + * GtkStack maintains a #GtkStackPage object for each added + * child, which holds additional per-child properties. You + * obtain the #GtkStackPage for a child with gtk_stack_get_page(). * + * # GtkStack as GtkBuildable + * + * To set child-specific properties in a .ui file, create GtkStackPage + * objects explictly, and set the child widget as a property on it: + * |[ + * + * + * + * page1 + * In the beginning… + * + * + * It was dark + * + * + * + * + * ]| + * * # CSS nodes * * GtkStack has a single CSS node named stack. @@ -92,6 +113,39 @@ * filter events out events to the last_child widget during transitions */ +typedef struct { + GList *children; + + GtkStackPage *visible_child; + + gboolean hhomogeneous; + gboolean vhomogeneous; + + GtkStackTransitionType transition_type; + guint transition_duration; + + GtkStackPage *last_visible_child; + GskRenderNode *last_visible_node; + GtkAllocation last_visible_surface_allocation; + guint tick_id; + GtkProgressTracker tracker; + gboolean first_frame_skipped; + + gint last_visible_widget_width; + gint last_visible_widget_height; + + gboolean interpolate_size; + + GtkStackTransitionType active_transition_type; + +} GtkStackPrivate; + +static void gtk_stack_buildable_interface_init (GtkBuildableIface *iface); + +G_DEFINE_TYPE_WITH_CODE (GtkStack, gtk_stack, GTK_TYPE_CONTAINER, + G_ADD_PRIVATE (GtkStack) + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, + gtk_stack_buildable_interface_init)) enum { PROP_0, PROP_HOMOGENEOUS, @@ -109,6 +163,7 @@ enum { enum { CHILD_PROP_0, + CHILD_PROP_CHILD, CHILD_PROP_NAME, CHILD_PROP_TITLE, CHILD_PROP_ICON_NAME, @@ -117,9 +172,8 @@ enum LAST_CHILD_PROP }; -typedef struct _GtkStackChildInfo GtkStackChildInfo; - -struct _GtkStackChildInfo { +struct _GtkStackPage { + GObject instance; GtkWidget *widget; gchar *name; gchar *title; @@ -128,36 +182,223 @@ struct _GtkStackChildInfo { GtkWidget *last_focus; }; -typedef struct { - GList *children; - - GtkStackChildInfo *visible_child; - - gboolean hhomogeneous; - gboolean vhomogeneous; - - GtkStackTransitionType transition_type; - guint transition_duration; - - GtkStackChildInfo *last_visible_child; - GskRenderNode *last_visible_node; - GtkAllocation last_visible_surface_allocation; - guint tick_id; - GtkProgressTracker tracker; - gboolean first_frame_skipped; - - gint last_visible_widget_width; - gint last_visible_widget_height; - - gboolean interpolate_size; - - GtkStackTransitionType active_transition_type; - -} GtkStackPrivate; +struct _GtkStackPageClass { + GObjectClass parent_class; +}; static GParamSpec *stack_props[LAST_PROP] = { NULL, }; static GParamSpec *stack_child_props[LAST_CHILD_PROP] = { NULL, }; +G_DEFINE_TYPE (GtkStackPage, gtk_stack_page, G_TYPE_OBJECT) + +static void +gtk_stack_page_init (GtkStackPage *page) +{ +} + +static void +gtk_stack_page_finalize (GObject *object) +{ + GtkStackPage *page = GTK_STACK_PAGE (object); + + g_clear_object (&page->widget); + g_free (page->name); + g_free (page->title); + g_free (page->icon_name); + + if (page->last_focus) + g_object_remove_weak_pointer (G_OBJECT (page->last_focus), + (gpointer *)&page->last_focus); + + G_OBJECT_CLASS (gtk_stack_page_parent_class)->finalize (object); +} + +static void +gtk_stack_page_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GtkStackPage *info = GTK_STACK_PAGE (object); + GtkWidget *stack = gtk_widget_get_parent (GTK_WIDGET (info->widget)); + GtkStackPrivate *priv = gtk_stack_get_instance_private (GTK_STACK (stack)); + + switch (property_id) + { + case CHILD_PROP_CHILD: + g_value_set_object (value, info->widget); + break; + + case CHILD_PROP_NAME: + g_value_set_string (value, info->name); + break; + + case CHILD_PROP_TITLE: + g_value_set_string (value, info->title); + break; + + case CHILD_PROP_ICON_NAME: + g_value_set_string (value, info->icon_name); + break; + + case CHILD_PROP_POSITION: + g_value_set_int (value, g_list_index (priv->children, info)); + break; + + case CHILD_PROP_NEEDS_ATTENTION: + g_value_set_boolean (value, info->needs_attention); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void reorder_child (GtkStack *stack, + GtkWidget *child, + gint position); + +static void +gtk_stack_page_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GtkStackPage *info = GTK_STACK_PAGE (object); + GtkWidget *stack = NULL; + GtkStackPrivate *priv = NULL; + gchar *name; + GList *l; + + if (info->widget) + { + stack = gtk_widget_get_parent (info->widget); + if (stack) + priv = gtk_stack_get_instance_private (GTK_STACK (stack)); + } + + switch (property_id) + { + case CHILD_PROP_CHILD: + g_set_object (&info->widget, g_value_get_object (value)); + break; + + case CHILD_PROP_NAME: + name = g_value_dup_string (value); + for (l = priv ? priv->children : NULL; l != NULL; l = l->next) + { + GtkStackPage *info2 = l->data; + if (info == info2) + continue; + if (g_strcmp0 (info2->name, name) == 0) + { + g_warning ("Duplicate child name in GtkStack: %s", name); + break; + } + } + + g_free (info->name); + info->name = name; + + g_object_notify_by_pspec (object, pspec); + + if (priv && priv->visible_child == info) + g_object_notify_by_pspec (G_OBJECT (stack), + stack_props[PROP_VISIBLE_CHILD_NAME]); + + break; + + case CHILD_PROP_TITLE: + g_free (info->title); + info->title = g_value_dup_string (value); + g_object_notify_by_pspec (object, pspec); + break; + + case CHILD_PROP_ICON_NAME: + g_free (info->icon_name); + info->icon_name = g_value_dup_string (value); + g_object_notify_by_pspec (object, pspec); + break; + + case CHILD_PROP_POSITION: + if (stack) + reorder_child (GTK_STACK (stack), info->widget, g_value_get_int (value)); + break; + + case CHILD_PROP_NEEDS_ATTENTION: + info->needs_attention = g_value_get_boolean (value); + g_object_notify_by_pspec (object, pspec); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} +static void +gtk_stack_page_class_init (GtkStackPageClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + + object_class->finalize = gtk_stack_page_finalize; + object_class->get_property = gtk_stack_page_get_property; + object_class->set_property = gtk_stack_page_set_property; + + stack_child_props[CHILD_PROP_CHILD] = + g_param_spec_object ("child", + P_("Child"), + P_("The child of the page"), + GTK_TYPE_WIDGET, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + stack_child_props[CHILD_PROP_NAME] = + g_param_spec_string ("name", + P_("Name"), + P_("The name of the child page"), + NULL, + GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); + + stack_child_props[CHILD_PROP_TITLE] = + g_param_spec_string ("title", + P_("Title"), + P_("The title of the child page"), + NULL, + GTK_PARAM_READWRITE); + + stack_child_props[CHILD_PROP_ICON_NAME] = + g_param_spec_string ("icon-name", + P_("Icon name"), + P_("The icon name of the child page"), + NULL, + GTK_PARAM_READWRITE); + + stack_child_props[CHILD_PROP_POSITION] = + g_param_spec_int ("position", + P_("Position"), + P_("The index of the child in the parent"), + -1, G_MAXINT, + 0, + GTK_PARAM_READWRITE); + + /** + * GtkStack:needs-attention: + * + * Sets a flag specifying whether the child requires the user attention. + * This is used by the #GtkStackSwitcher to change the appearance of the + * corresponding button when a page needs attention and it is not the + * current one. + */ + stack_child_props[CHILD_PROP_NEEDS_ATTENTION] = + g_param_spec_boolean ("needs-attention", + P_("Needs Attention"), + P_("Whether this page needs attention"), + FALSE, + GTK_PARAM_READWRITE); + + g_object_class_install_properties (object_class, LAST_CHILD_PROP, stack_child_props); +} + static void gtk_stack_add (GtkContainer *widget, GtkWidget *child); static void gtk_stack_remove (GtkContainer *widget, @@ -190,19 +431,31 @@ static void gtk_stack_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); -static void gtk_stack_get_child_property (GtkContainer *container, - GtkWidget *child, - guint property_id, - GValue *value, - GParamSpec *pspec); -static void gtk_stack_set_child_property (GtkContainer *container, - GtkWidget *child, - guint property_id, - const GValue *value, - GParamSpec *pspec); static void gtk_stack_unschedule_ticks (GtkStack *stack); -G_DEFINE_TYPE_WITH_PRIVATE (GtkStack, gtk_stack, GTK_TYPE_CONTAINER) + +static void gtk_stack_add_page (GtkStack *stack, + GtkStackPage *page); + +static void +gtk_stack_buildable_add_child (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const char *type) +{ + if (GTK_IS_STACK_PAGE (child)) + gtk_stack_add_page (GTK_STACK (buildable), GTK_STACK_PAGE (child)); + else if (GTK_IS_WIDGET (child)) + gtk_container_add (GTK_CONTAINER (buildable), GTK_WIDGET (child)); + else + g_warning ("Can't add a child of type '%s' to '%s'", G_OBJECT_TYPE_NAME (child), G_OBJECT_TYPE_NAME (buildable)); +} + +static void +gtk_stack_buildable_interface_init (GtkBuildableIface *iface) +{ + iface->add_child = gtk_stack_buildable_add_child; +} static void gtk_stack_finalize (GObject *obj) @@ -319,8 +572,6 @@ gtk_stack_class_init (GtkStackClass *klass) container_class->add = gtk_stack_add; container_class->remove = gtk_stack_remove; container_class->forall = gtk_stack_forall; - container_class->set_child_property = gtk_stack_set_child_property; - container_class->get_child_property = gtk_stack_get_child_property; stack_props[PROP_HOMOGENEOUS] = g_param_spec_boolean ("homogeneous", P_("Homogeneous"), P_("Homogeneous sizing"), @@ -374,51 +625,6 @@ gtk_stack_class_init (GtkStackClass *klass) g_object_class_install_properties (object_class, LAST_PROP, stack_props); - stack_child_props[CHILD_PROP_NAME] = - g_param_spec_string ("name", - P_("Name"), - P_("The name of the child page"), - NULL, - GTK_PARAM_READWRITE); - - stack_child_props[CHILD_PROP_TITLE] = - g_param_spec_string ("title", - P_("Title"), - P_("The title of the child page"), - NULL, - GTK_PARAM_READWRITE); - - stack_child_props[CHILD_PROP_ICON_NAME] = - g_param_spec_string ("icon-name", - P_("Icon name"), - P_("The icon name of the child page"), - NULL, - GTK_PARAM_READWRITE); - - stack_child_props[CHILD_PROP_POSITION] = - g_param_spec_int ("position", - P_("Position"), - P_("The index of the child in the parent"), - -1, G_MAXINT, - 0, - GTK_PARAM_READWRITE); - - /** - * GtkStack:needs-attention: - * - * Sets a flag specifying whether the child requires the user attention. - * This is used by the #GtkStackSwitcher to change the appearance of the - * corresponding button when a page needs attention and it is not the - * current one. - */ - stack_child_props[CHILD_PROP_NEEDS_ATTENTION] = - g_param_spec_boolean ("needs-attention", - P_("Needs Attention"), - P_("Whether this page needs attention"), - FALSE, - GTK_PARAM_READWRITE); - - gtk_container_class_install_child_properties (container_class, LAST_CHILD_PROP, stack_child_props); gtk_widget_class_set_accessible_type (widget_class, GTK_TYPE_STACK_ACCESSIBLE); gtk_widget_class_set_css_name (widget_class, I_("stack")); @@ -437,12 +643,12 @@ gtk_stack_new (void) return g_object_new (GTK_TYPE_STACK, NULL); } -static GtkStackChildInfo * +static GtkStackPage * find_child_info_for_widget (GtkStack *stack, GtkWidget *child) { GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); - GtkStackChildInfo *info; + GtkStackPage *info; GList *l; for (l = priv->children; l != NULL; l = l->next) @@ -464,7 +670,7 @@ reorder_child (GtkStack *stack, GList *l; GList *old_link = NULL; GList *new_link = NULL; - GtkStackChildInfo *child_info = NULL; + GtkStackPage *child_info = NULL; gint num = 0; l = priv->children; @@ -481,7 +687,7 @@ reorder_child (GtkStack *stack, if (old_link == NULL) { - GtkStackChildInfo *info; + GtkStackPage *info; info = l->data; /* Keep trying to find the current position and link location of the child */ @@ -507,126 +713,6 @@ reorder_child (GtkStack *stack, gtk_container_child_notify_by_pspec (GTK_CONTAINER (stack), child, stack_child_props[CHILD_PROP_POSITION]); } -static void -gtk_stack_get_child_property (GtkContainer *container, - GtkWidget *child, - guint property_id, - GValue *value, - GParamSpec *pspec) -{ - GtkStack *stack = GTK_STACK (container); - GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); - GtkStackChildInfo *info; - - info = find_child_info_for_widget (stack, child); - if (info == NULL) - { - GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); - return; - } - - switch (property_id) - { - case CHILD_PROP_NAME: - g_value_set_string (value, info->name); - break; - - case CHILD_PROP_TITLE: - g_value_set_string (value, info->title); - break; - - case CHILD_PROP_ICON_NAME: - g_value_set_string (value, info->icon_name); - break; - - case CHILD_PROP_POSITION: - g_value_set_int (value, g_list_index (priv->children, info)); - break; - - case CHILD_PROP_NEEDS_ATTENTION: - g_value_set_boolean (value, info->needs_attention); - break; - - default: - GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); - break; - } -} - -static void -gtk_stack_set_child_property (GtkContainer *container, - GtkWidget *child, - guint property_id, - const GValue *value, - GParamSpec *pspec) -{ - GtkStack *stack = GTK_STACK (container); - GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); - GtkStackChildInfo *info; - GtkStackChildInfo *info2; - gchar *name; - GList *l; - - info = find_child_info_for_widget (stack, child); - if (info == NULL) - { - GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); - return; - } - - switch (property_id) - { - case CHILD_PROP_NAME: - name = g_value_dup_string (value); - for (l = priv->children; l != NULL; l = l->next) - { - info2 = l->data; - if (info == info2) - continue; - if (g_strcmp0 (info2->name, name) == 0) - { - g_warning ("Duplicate child name in GtkStack: %s", name); - break; - } - } - - g_free (info->name); - info->name = name; - - gtk_container_child_notify_by_pspec (container, child, pspec); - - if (priv->visible_child == info) - g_object_notify_by_pspec (G_OBJECT (stack), - stack_props[PROP_VISIBLE_CHILD_NAME]); - - break; - - case CHILD_PROP_TITLE: - g_free (info->title); - info->title = g_value_dup_string (value); - gtk_container_child_notify_by_pspec (container, child, pspec); - break; - - case CHILD_PROP_ICON_NAME: - g_free (info->icon_name); - info->icon_name = g_value_dup_string (value); - gtk_container_child_notify_by_pspec (container, child, pspec); - break; - - case CHILD_PROP_POSITION: - reorder_child (stack, child, g_value_get_int (value)); - break; - - case CHILD_PROP_NEEDS_ATTENTION: - info->needs_attention = g_value_get_boolean (value); - gtk_container_child_notify_by_pspec (container, child, pspec); - break; - - default: - GTK_CONTAINER_WARN_INVALID_CHILD_PROPERTY_ID (container, property_id, pspec); - break; - } -} static inline gboolean is_left_transition (GtkStackTransitionType transition_type) @@ -922,12 +1008,12 @@ gtk_stack_start_transition (GtkStack *stack, static void set_visible_child (GtkStack *stack, - GtkStackChildInfo *child_info, + GtkStackPage *child_info, GtkStackTransitionType transition_type, guint transition_duration) { GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); - GtkStackChildInfo *info; + GtkStackPage *info; GtkWidget *widget = GTK_WIDGET (stack); GList *l; GtkWidget *toplevel; @@ -1061,7 +1147,7 @@ stack_child_visibility_notify_cb (GObject *obj, GtkStack *stack = GTK_STACK (user_data); GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); GtkWidget *child = GTK_WIDGET (obj); - GtkStackChildInfo *child_info; + GtkStackPage *child_info; child_info = find_child_info_for_widget (stack, child); @@ -1079,6 +1165,12 @@ stack_child_visibility_notify_cb (GObject *obj, } } +static void +gtk_stack_add_internal (GtkStack *stack, + GtkWidget *child, + const char *name, + const char *title); + /** * gtk_stack_add_titled: * @stack: a #GtkStack @@ -1100,11 +1192,7 @@ gtk_stack_add_titled (GtkStack *stack, g_return_if_fail (GTK_IS_STACK (stack)); g_return_if_fail (GTK_IS_WIDGET (child)); - gtk_container_add_with_properties (GTK_CONTAINER (stack), - child, - "name", name, - "title", title, - NULL); + gtk_stack_add_internal (stack, child, name, title); } /** @@ -1124,10 +1212,7 @@ gtk_stack_add_named (GtkStack *stack, g_return_if_fail (GTK_IS_STACK (stack)); g_return_if_fail (GTK_IS_WIDGET (child)); - gtk_container_add_with_properties (GTK_CONTAINER (stack), - child, - "name", name, - NULL); + gtk_stack_add_internal (stack, child, name, NULL); } static void @@ -1135,31 +1220,65 @@ gtk_stack_add (GtkContainer *container, GtkWidget *child) { GtkStack *stack = GTK_STACK (container); - GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); - GtkStackChildInfo *child_info; + + gtk_stack_add_internal (stack, child, NULL, NULL); +} + +static void +gtk_stack_add_internal (GtkStack *stack, + GtkWidget *child, + const char *name, + const char *title) +{ + GtkStackPage *child_info; g_return_if_fail (child != NULL); - child_info = g_slice_new (GtkStackChildInfo); - child_info->widget = child; - child_info->name = NULL; - child_info->title = NULL; + child_info = g_object_new (GTK_TYPE_STACK_PAGE, NULL); + child_info->widget = g_object_ref (child); + child_info->name = g_strdup (name); + child_info->title = g_strdup (title); child_info->icon_name = NULL; child_info->needs_attention = FALSE; child_info->last_focus = NULL; - priv->children = g_list_append (priv->children, child_info); + gtk_stack_add_page (stack, child_info); - gtk_widget_set_child_visible (child, FALSE); - gtk_widget_set_parent (child, GTK_WIDGET (stack)); + g_object_unref (child_info); +} - g_signal_connect (child, "notify::visible", +static void +gtk_stack_add_page (GtkStack *stack, + GtkStackPage *child_info) +{ + GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); + GList *l; + + g_return_if_fail (child_info->widget != NULL); + + for (l = priv->children; l != NULL; l = l->next) + { + GtkStackPage *info = l->data; + if (info->name && + g_strcmp0 (info->name, child_info->name) == 0) + { + g_warning ("While adding page: duplicate child name in GtkStack: %s", child_info->name); + break; + } + } + + priv->children = g_list_append (priv->children, g_object_ref (child_info)); + + gtk_widget_set_child_visible (child_info->widget, FALSE); + gtk_widget_set_parent (child_info->widget, GTK_WIDGET (stack)); + + g_signal_connect (child_info->widget, "notify::visible", G_CALLBACK (stack_child_visibility_notify_cb), stack); - gtk_container_child_notify_by_pspec (container, child, stack_child_props[CHILD_PROP_POSITION]); + g_object_notify_by_pspec (G_OBJECT (child_info), stack_child_props[CHILD_PROP_POSITION]); if (priv->visible_child == NULL && - gtk_widget_get_visible (child)) + gtk_widget_get_visible (child_info->widget)) set_visible_child (stack, child_info, priv->transition_type, priv->transition_duration); if (priv->hhomogeneous || priv->vhomogeneous || priv->visible_child == child_info) @@ -1172,7 +1291,7 @@ gtk_stack_remove (GtkContainer *container, { GtkStack *stack = GTK_STACK (container); GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); - GtkStackChildInfo *child_info; + GtkStackPage *child_info; gboolean was_visible; child_info = find_child_info_for_widget (stack, child); @@ -1197,20 +1316,28 @@ gtk_stack_remove (GtkContainer *container, gtk_widget_unparent (child); - g_free (child_info->name); - g_free (child_info->title); - g_free (child_info->icon_name); - - if (child_info->last_focus) - g_object_remove_weak_pointer (G_OBJECT (child_info->last_focus), - (gpointer *)&child_info->last_focus); - - g_slice_free (GtkStackChildInfo, child_info); + g_object_unref (child_info); if ((priv->hhomogeneous || priv->vhomogeneous) && was_visible) gtk_widget_queue_resize (GTK_WIDGET (stack)); } +/** + * gtk_stack_get_page: + * @stack: a #GtkStack + * @child: a child of @stack + * + * Returns the #GtkStackPage object for @child. + * + * Returns: (transfer none): the #GtkStackPage for @child + */ +GtkStackPage * +gtk_stack_get_page (GtkStack *stack, + GtkWidget *child) +{ + return find_child_info_for_widget (stack, child); +} + /** * gtk_stack_get_child_by_name: * @stack: a #GtkStack @@ -1227,7 +1354,7 @@ gtk_stack_get_child_by_name (GtkStack *stack, const gchar *name) { GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); - GtkStackChildInfo *info; + GtkStackPage *info; GList *l; g_return_val_if_fail (GTK_IS_STACK (stack), NULL); @@ -1243,6 +1370,20 @@ gtk_stack_get_child_by_name (GtkStack *stack, return NULL; } +/** + * gtk_stack_page_get_child: + * @page: a #GtkStackPage + * + * Returns the stack child to which @page belongs. + * + * Returns: (transfer none): the child to which @page belongs + */ +GtkWidget * +gtk_stack_page_get_child (GtkStackPage *page) +{ + return page->widget; +} + /** * gtk_stack_set_homogeneous: * @stack: a #GtkStack @@ -1630,7 +1771,7 @@ gtk_stack_set_visible_child (GtkStack *stack, GtkWidget *child) { GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); - GtkStackChildInfo *child_info; + GtkStackPage *child_info; g_return_if_fail (GTK_IS_STACK (stack)); g_return_if_fail (GTK_IS_WIDGET (child)); @@ -1694,7 +1835,7 @@ gtk_stack_set_visible_child_full (GtkStack *stack, GtkStackTransitionType transition) { GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); - GtkStackChildInfo *child_info, *info; + GtkStackPage *child_info, *info; GList *l; g_return_if_fail (GTK_IS_STACK (stack)); @@ -1731,7 +1872,7 @@ gtk_stack_forall (GtkContainer *container, { GtkStack *stack = GTK_STACK (container); GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); - GtkStackChildInfo *child_info; + GtkStackPage *child_info; GList *l; l = priv->children; @@ -1753,7 +1894,7 @@ gtk_stack_compute_expand (GtkWidget *widget, GtkStack *stack = GTK_STACK (widget); GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); gboolean hexpand, vexpand; - GtkStackChildInfo *child_info; + GtkStackPage *child_info; GtkWidget *child; GList *l; @@ -2088,7 +2229,7 @@ gtk_stack_measure (GtkWidget *widget, { GtkStack *stack = GTK_STACK (widget); GtkStackPrivate *priv = gtk_stack_get_instance_private (stack); - GtkStackChildInfo *child_info; + GtkStackPage *child_info; GtkWidget *child; gint child_min, child_nat; GList *l; diff --git a/gtk/gtkstack.h b/gtk/gtkstack.h index b9b4701cf4..6379328041 100644 --- a/gtk/gtkstack.h +++ b/gtk/gtkstack.h @@ -41,6 +41,16 @@ G_BEGIN_DECLS typedef struct _GtkStack GtkStack; typedef struct _GtkStackClass GtkStackClass; +#define GTK_TYPE_STACK_PAGE (gtk_stack_page_get_type ()) +#define GTK_STACK_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_STACK_PAGE, GtkStackPage)) +#define GTK_STACK_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_STACK_PAGE, GtkStackPageClass)) +#define GTK_IS_STACK_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_STACK_PAGE)) +#define GTK_IS_STACK_PAGE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_STACK_PAGE)) +#define GTK_STACK_PAGE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_STACK_PAGE, GtkStackPageClass)) + +typedef struct _GtkStackPage GtkStackPage; +typedef struct _GtkStackPageClass GtkStackPageClass; + typedef enum { GTK_STACK_TRANSITION_TYPE_NONE, GTK_STACK_TRANSITION_TYPE_CROSSFADE, @@ -72,6 +82,9 @@ struct _GtkStackClass { GtkContainerClass parent_class; }; +GDK_AVAILABLE_IN_ALL +GType gtk_stack_page_get_type (void) G_GNUC_CONST; + GDK_AVAILABLE_IN_ALL GType gtk_stack_get_type (void) G_GNUC_CONST; @@ -86,6 +99,13 @@ void gtk_stack_add_titled (GtkStack GtkWidget *child, const gchar *name, const gchar *title); + +GDK_AVAILABLE_IN_ALL +GtkStackPage * gtk_stack_get_page (GtkStack *stack, + GtkWidget *child); +GDK_AVAILABLE_IN_ALL +GtkWidget * gtk_stack_page_get_child (GtkStackPage *page); + GDK_AVAILABLE_IN_ALL GtkWidget * gtk_stack_get_child_by_name (GtkStack *stack, const gchar *name); diff --git a/gtk/gtkstacksidebar.c b/gtk/gtkstacksidebar.c index 552dcab9f8..4e42e1ea2d 100644 --- a/gtk/gtkstacksidebar.c +++ b/gtk/gtkstacksidebar.c @@ -140,7 +140,7 @@ sort_list (GtkListBoxRow *row1, { item = gtk_bin_get_child (GTK_BIN (row1)); widget = g_object_get_data (G_OBJECT (item), "stack-child"); - gtk_container_child_get (GTK_CONTAINER (priv->stack), widget, + g_object_get (gtk_stack_get_page (GTK_STACK (priv->stack), widget), "position", &left, NULL); } @@ -149,7 +149,7 @@ sort_list (GtkListBoxRow *row1, { item = gtk_bin_get_child (GTK_BIN (row2)); widget = g_object_get_data (G_OBJECT (item), "stack-child"); - gtk_container_child_get (GTK_CONTAINER (priv->stack), widget, + g_object_get (gtk_stack_get_page (GTK_STACK (priv->stack), widget), "position", &right, NULL); } @@ -227,10 +227,10 @@ update_row (GtkStackSidebar *sidebar, gboolean needs_attention; GtkStyleContext *context; - gtk_container_child_get (GTK_CONTAINER (priv->stack), widget, - "title", &title, - "needs-attention", &needs_attention, - NULL); + g_object_get (gtk_stack_get_page (GTK_STACK (priv->stack), widget), + "title", &title, + "needs-attention", &needs_attention, + NULL); item = gtk_bin_get_child (GTK_BIN (row)); gtk_label_set_text (GTK_LABEL (item), title); @@ -275,6 +275,7 @@ add_child (GtkWidget *widget, GtkStackSidebarPrivate *priv = gtk_stack_sidebar_get_instance_private (sidebar); GtkWidget *item; GtkWidget *row; + GObject *page; /* Check we don't actually already know about this widget */ if (g_hash_table_lookup (priv->rows, widget)) @@ -291,13 +292,14 @@ add_child (GtkWidget *widget, update_row (sidebar, widget, row); /* Hook up for events */ - g_signal_connect (widget, "child-notify::title", - G_CALLBACK (on_child_updated), sidebar); - g_signal_connect (widget, "child-notify::needs-attention", - G_CALLBACK (on_child_updated), sidebar); + page = gtk_stack_get_page (GTK_STACK (priv->stack), widget); g_signal_connect (widget, "notify::visible", G_CALLBACK (on_child_updated), sidebar); - g_signal_connect (widget, "child-notify::position", + g_signal_connect (page, "notify::title", + G_CALLBACK (on_child_updated), sidebar); + g_signal_connect (page, "notify::needs-attention", + G_CALLBACK (on_child_updated), sidebar); + g_signal_connect (page, "notify::position", G_CALLBACK (on_position_updated), sidebar); g_object_set_data (G_OBJECT (item), I_("stack-child"), widget); @@ -316,8 +318,15 @@ remove_child (GtkWidget *widget, if (!row) return; - g_signal_handlers_disconnect_by_func (widget, on_child_updated, sidebar); - g_signal_handlers_disconnect_by_func (widget, on_position_updated, sidebar); + if (priv->stack) + { + GObject *page = gtk_stack_get_page (GTK_STACK (priv->stack), widget); + if (page) + { + g_signal_handlers_disconnect_by_func (page, on_child_updated, sidebar); + g_signal_handlers_disconnect_by_func (page, on_position_updated, sidebar); + } + } gtk_container_remove (GTK_CONTAINER (priv->list), row); g_hash_table_remove (priv->rows, widget); diff --git a/gtk/gtkstackswitcher.c b/gtk/gtkstackswitcher.c index 3fb4dde191..ab9d91b27d 100644 --- a/gtk/gtkstackswitcher.c +++ b/gtk/gtkstackswitcher.c @@ -159,25 +159,6 @@ rebuild_child (GtkWidget *self, } } -static void -update_needs_attention (GtkWidget *widget, GtkWidget *button, gpointer data) -{ - GtkContainer *container; - gboolean needs_attention; - GtkStyleContext *context; - - container = GTK_CONTAINER (data); - gtk_container_child_get (container, widget, - "needs-attention", &needs_attention, - NULL); - - context = gtk_widget_get_style_context (button); - if (needs_attention) - gtk_style_context_add_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION); - else - gtk_style_context_remove_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION); -} - static void update_button (GtkStackSwitcher *self, GtkWidget *widget, @@ -185,29 +166,35 @@ update_button (GtkStackSwitcher *self, { gchar *title; gchar *icon_name; + gboolean needs_attention; GtkStackSwitcherPrivate *priv; - + GtkStyleContext *context; priv = gtk_stack_switcher_get_instance_private (self); - gtk_container_child_get (GTK_CONTAINER (priv->stack), widget, - "title", &title, - "icon-name", &icon_name, - NULL); + g_object_get (gtk_stack_get_page (priv->stack, widget), + "title", &title, + "icon-name", &icon_name, + "needs-attention", &needs_attention, + NULL); rebuild_child (button, icon_name, title); gtk_widget_set_visible (button, gtk_widget_get_visible (widget) && (title != NULL || icon_name != NULL)); + context = gtk_widget_get_style_context (button); + if (needs_attention) + gtk_style_context_add_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION); + else + gtk_style_context_remove_class (context, GTK_STYLE_CLASS_NEEDS_ATTENTION); + g_free (title); g_free (icon_name); - - update_needs_attention (widget, button, priv->stack); } static void -on_title_icon_visible_updated (GtkWidget *widget, - GParamSpec *pspec, - GtkStackSwitcher *self) +on_visible_updated (GtkWidget *widget, + GParamSpec *pspec, + GtkStackSwitcher *self) { GtkWidget *button; GtkStackSwitcherPrivate *priv; @@ -219,16 +206,34 @@ on_title_icon_visible_updated (GtkWidget *widget, } static void -on_position_updated (GtkWidget *widget, +on_title_icon_updated (GtkStackPage *page, + GParamSpec *pspec, + GtkStackSwitcher *self) +{ + GtkWidget *widget; + GtkWidget *button; + GtkStackSwitcherPrivate *priv; + + priv = gtk_stack_switcher_get_instance_private (self); + + widget = gtk_stack_page_get_child (page); + button = g_hash_table_lookup (priv->buttons, widget); + update_button (self, widget, button); +} + +static void +on_position_updated (GtkStackPage *page, GParamSpec *pspec, GtkStackSwitcher *self) { + GtkWidget *widget; GtkWidget *button; gint position; GtkStackSwitcherPrivate *priv; priv = gtk_stack_switcher_get_instance_private (self); + widget = gtk_stack_page_get_child (page); button = g_hash_table_lookup (priv->buttons, widget); gtk_container_child_get (GTK_CONTAINER (priv->stack), widget, @@ -248,15 +253,17 @@ on_position_updated (GtkWidget *widget, } static void -on_needs_attention_updated (GtkWidget *widget, +on_needs_attention_updated (GtkStackPage *page, GParamSpec *pspec, GtkStackSwitcher *self) { + GtkWidget *widget; GtkWidget *button; GtkStackSwitcherPrivate *priv; priv = gtk_stack_switcher_get_instance_private (self); + widget = gtk_stack_page_get_child (page); button = g_hash_table_lookup (priv->buttons, widget); update_button (self, widget, button); } @@ -354,6 +361,7 @@ add_child (GtkWidget *widget, GtkWidget *button; GList *group; GtkStackSwitcherPrivate *priv; + GtkStackPage *page; priv = gtk_stack_switcher_get_instance_private (self); @@ -362,6 +370,7 @@ add_child (GtkWidget *widget, gtk_widget_set_focus_on_click (button, FALSE); gtk_check_button_set_draw_indicator (GTK_CHECK_BUTTON (button), FALSE); + page = gtk_stack_get_page (GTK_STACK (priv->stack), widget); update_button (self, widget, button); group = gtk_container_get_children (GTK_CONTAINER (self)); @@ -375,11 +384,11 @@ add_child (GtkWidget *widget, g_object_set_data (G_OBJECT (button), "stack-child", widget); g_signal_connect (button, "clicked", G_CALLBACK (on_button_clicked), self); - g_signal_connect (widget, "notify::visible", G_CALLBACK (on_title_icon_visible_updated), self); - g_signal_connect (widget, "child-notify::title", G_CALLBACK (on_title_icon_visible_updated), self); - g_signal_connect (widget, "child-notify::icon-name", G_CALLBACK (on_title_icon_visible_updated), self); - g_signal_connect (widget, "child-notify::position", G_CALLBACK (on_position_updated), self); - g_signal_connect (widget, "child-notify::needs-attention", G_CALLBACK (on_needs_attention_updated), self); + g_signal_connect (widget, "notify::visible", G_CALLBACK (on_visible_updated), self); + g_signal_connect (page, "notify::title", G_CALLBACK (on_title_icon_updated), self); + g_signal_connect (page, "notify::icon-name", G_CALLBACK (on_title_icon_updated), self); + g_signal_connect (page, "notify::position", G_CALLBACK (on_position_updated), self); + g_signal_connect (page, "notify::needs-attention", G_CALLBACK (on_needs_attention_updated), self); g_hash_table_insert (priv->buttons, widget, button); } @@ -393,10 +402,17 @@ remove_child (GtkWidget *widget, priv = gtk_stack_switcher_get_instance_private (self); - g_signal_handlers_disconnect_by_func (widget, on_title_icon_visible_updated, self); - g_signal_handlers_disconnect_by_func (widget, on_position_updated, self); - g_signal_handlers_disconnect_by_func (widget, on_needs_attention_updated, self); - + if (priv->stack) + { + GtkStackPage *page = gtk_stack_get_page (priv->stack, widget); + if (page) + { + g_signal_handlers_disconnect_by_func (page, on_title_icon_updated, self); + g_signal_handlers_disconnect_by_func (page, on_position_updated, self); + g_signal_handlers_disconnect_by_func (page, on_needs_attention_updated, self); + } + g_signal_handlers_disconnect_by_func (widget, on_visible_updated, self); + } button = g_hash_table_lookup (priv->buttons, widget); gtk_container_remove (GTK_CONTAINER (self), button); g_hash_table_remove (priv->buttons, widget); diff --git a/gtk/inspector/gtkstackcombo.c b/gtk/inspector/gtkstackcombo.c index 451863ddca..1be01c6732 100644 --- a/gtk/inspector/gtkstackcombo.c +++ b/gtk/inspector/gtkstackcombo.c @@ -81,10 +81,10 @@ add_child (GtkWidget *widget, { char *name, *title; - gtk_container_child_get (GTK_CONTAINER (self->stack), widget, - "name", &name, - "title", &title, - NULL); + g_object_get (gtk_stack_get_page (self->stack, widget), + "name", &name, + "title", &title, + NULL); gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (self->combo), name, title); From ed3b9669b3052c274df6e9a2c84a824c7e6d6161 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Wed, 6 Feb 2019 18:53:06 -0500 Subject: [PATCH 02/11] builder: Allow specifying objects inline In addition to bar referring to an object with ID bar, we now also parse ... to specify a property 'inline'. --- gtk/gtkbuilder.c | 10 +++++++--- gtk/gtkbuilderparser.c | 18 ++++++++---------- gtk/gtkbuilderprivate.h | 11 +++++++++++ 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c index 52244862ac..cbbed790b9 100644 --- a/gtk/gtkbuilder.c +++ b/gtk/gtkbuilder.c @@ -536,7 +536,7 @@ gtk_builder_get_parameters (GtkBuilder *builder, (G_PARAM_SPEC_VALUE_TYPE (prop->pspec) != G_TYPE_FILE)) { GObject *object = g_hash_table_lookup (priv->objects, - prop->text->str); + g_strstrip (prop->text->str)); if (object) { @@ -722,7 +722,9 @@ _gtk_builder_construct (GtkBuilder *builder, * be set once. */ if (info->constructor || - (info->parent && ((ChildInfo*)info->parent)->internal_child != NULL)) + (info->parent && + info->parent->tag_type == TAG_CHILD && + ((ChildInfo*)info->parent)->internal_child != NULL)) param_filter_flags = G_PARAM_CONSTRUCT_ONLY; else param_filter_flags = G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY; @@ -758,7 +760,9 @@ _gtk_builder_construct (GtkBuilder *builder, if (construct_parameters->len) g_warning ("Can't pass in construct-only parameters to %s", info->id); } - else if (info->parent && ((ChildInfo*)info->parent)->internal_child != NULL) + else if (info->parent && + info->parent->tag_type == TAG_CHILD && + ((ChildInfo*)info->parent)->internal_child != NULL) { gchar *childname = ((ChildInfo*)info->parent)->internal_child; obj = gtk_builder_get_internal_child (builder, info, childname, error); diff --git a/gtk/gtkbuilderparser.c b/gtk/gtkbuilderparser.c index 94c5b8b6c1..2c08df0f02 100644 --- a/gtk/gtkbuilderparser.c +++ b/gtk/gtkbuilderparser.c @@ -34,16 +34,6 @@ static void free_property_info (PropertyInfo *info); static void free_object_info (ObjectInfo *info); -enum { - TAG_PROPERTY, - TAG_MENU, - TAG_REQUIRES, - TAG_OBJECT, - TAG_CHILD, - TAG_SIGNAL, - TAG_INTERFACE, - TAG_TEMPLATE, -}; static inline void state_push (ParserData *data, gpointer info) @@ -1066,6 +1056,12 @@ end_element (GMarkupParseContext *context, { ObjectInfo *object_info = state_pop_info (data, ObjectInfo); ChildInfo* child_info = state_peek_info (data, ChildInfo); + PropertyInfo* prop_info = state_peek_info (data, PropertyInfo); + + if (child_info && child_info->tag_type != TAG_CHILD) + child_info = NULL; + if (prop_info && prop_info->tag_type != TAG_PROPERTY) + prop_info = NULL; if (data->requested_objects && data->inside_requested_object && (data->cur_object_level == data->requested_object_level)) @@ -1089,6 +1085,8 @@ end_element (GMarkupParseContext *context, } if (child_info) child_info->object = object_info->object; + if (prop_info) + g_string_assign (prop_info->text, object_info->id); if (GTK_IS_BUILDABLE (object_info->object) && GTK_BUILDABLE_GET_IFACE (object_info->object)->parser_finished) diff --git a/gtk/gtkbuilderprivate.h b/gtk/gtkbuilderprivate.h index 22577df32d..5b71bdf76d 100644 --- a/gtk/gtkbuilderprivate.h +++ b/gtk/gtkbuilderprivate.h @@ -21,6 +21,17 @@ #include "gtkbuilder.h" +enum { + TAG_PROPERTY, + TAG_MENU, + TAG_REQUIRES, + TAG_OBJECT, + TAG_CHILD, + TAG_SIGNAL, + TAG_INTERFACE, + TAG_TEMPLATE, +}; + typedef struct { guint tag_type; } CommonInfo; From addcf2e52626635aafaea591e5bba82b526de92b Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 7 Feb 2019 04:12:09 -0500 Subject: [PATCH 03/11] 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(-) create mode 100644 gtk/tools/gtk-builder-tool-enumerate.c create mode 100644 gtk/tools/gtk-builder-tool-preview.c create mode 100644 gtk/tools/gtk-builder-tool-simplify.c create mode 100644 gtk/tools/gtk-builder-tool-validate.c 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 . + * + * Author: Matthias Clasen + */ + +#include +#include +#include + +#include +#include +#include +#include +#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 . + * + * Author: Matthias Clasen + */ + +#include +#include +#include + +#include +#include +#include +#include +#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 . + * + * Author: Matthias Clasen + */ + +#include +#include +#include + +#include +#include +#include +#include +#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\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\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\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, "%*sindent, ""); + 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, "\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\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\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 . + * + * Author: Matthias Clasen + */ + +#include +#include +#include + +#include +#include +#include +#include +#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 #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\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\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\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, "%*sindent, ""); - 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, "\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\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\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']], ] From 59152b8a8d092c81d0ed020ec97834339cf7f4a7 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 7 Feb 2019 07:12:15 -0500 Subject: [PATCH 04/11] builder tool: Rewrite the simplify command Rewrite the builder-tool simplify command to have a full parse tree around, and perform simplifications on that tree. This lets us rewrite GtkStack and turn child properties into child meta objects. --- gtk/tools/gtk-builder-tool-simplify.c | 901 ++++++++++++++------------ gtk/tools/gtk-builder-tool.c | 1 + 2 files changed, 485 insertions(+), 417 deletions(-) diff --git a/gtk/tools/gtk-builder-tool-simplify.c b/gtk/tools/gtk-builder-tool-simplify.c index ec3cd271ec..55bcc0edf2 100644 --- a/gtk/tools/gtk-builder-tool-simplify.c +++ b/gtk/tools/gtk-builder-tool-simplify.c @@ -27,26 +27,216 @@ #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; +}; + +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 { - 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; + Element *root; + Element *current; GString *value; - gboolean unclosed_starttag; - gint indent; + GtkBuilder *builder; 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); + + 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"; +} + +/* 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 (const gchar *class_name, + const gchar *property_name, + gboolean packing) +{ + 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 }, + }; + gchar *canonical_name; + gboolean found; + gint k; + + 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 && + packing == props[k].packing) + { + 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", + 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", + "cell-packing", + "attributes", + "action-widgets", + "patterns", + "mime-types", + "attributes", + "row", + "items" + }; + + if (g_strv_contains (names, element->element_name)) + return TRUE; + + return FALSE; +} + static void canonicalize_key (gchar *key) { @@ -71,7 +261,9 @@ canonicalize_key (gchar *key) static GParamSpec * get_property_pspec (MyParserData *data, const gchar *class_name, - const gchar *property_name) + const gchar *property_name, + gboolean packing, + gboolean cell_packing) { GType type; GObjectClass *class; @@ -81,9 +273,7 @@ get_property_pspec (MyParserData *data, 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); + type = gtk_builder_get_type_from_name (data->builder, class_name); if (type == G_TYPE_INVALID) return NULL; } @@ -91,9 +281,9 @@ get_property_pspec (MyParserData *data, class = g_type_class_ref (type); canonical_name = g_strdup (property_name); canonicalize_key (canonical_name); - if (data->packing) + if (packing) pspec = gtk_container_class_find_child_property (class, canonical_name); - else if (data->cell_packing) + else if (cell_packing) { GObjectClass *cell_class; @@ -110,25 +300,26 @@ get_property_pspec (MyParserData *data, return pspec; } - static gboolean value_is_default (MyParserData *data, const gchar *class_name, const gchar *property_name, - const gchar *value_string) + const gchar *value_string, + gboolean packing, + gboolean cell_packing) { GValue value = { 0, }; gboolean ret; GError *error = NULL; GParamSpec *pspec; - pspec = get_property_pspec (data, class_name, property_name); + pspec = get_property_pspec (data, class_name, property_name, packing, cell_packing); if (pspec == NULL) { - if (data->packing) + if (packing) g_printerr (_("Packing property %s::%s not found\n"), class_name, property_name); - else if (data->cell_packing) + else if (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); @@ -151,73 +342,163 @@ value_is_default (MyParserData *data, return ret; } +static const char * +get_class_name (Element *element) +{ + Element *parent = element->parent; + int i; + + if (g_str_equal (element->element_name, "object")) + parent = element; + + if (g_str_equal (parent->element_name, "packing")) + parent = parent->parent->parent; /* child - object */ + + if (g_str_equal (parent->element_name, "object")) + { + for (i = 0; parent->attribute_names[i]; i++) + { + if (g_str_equal (parent->attribute_names[i], "class")) + return parent->attribute_values[i]; + } + } + else if (g_str_equal (parent->element_name, "template")) + { + for (i = 0; parent->attribute_names[i]; i++) + { + if (g_str_equal (parent->attribute_names[i], "parent")) + return parent->attribute_values[i]; + } + } + + return NULL; +} + static gboolean -property_is_boolean (MyParserData *data, - const gchar *class_name, - const gchar *property_name) +property_is_boolean (Element *element, + MyParserData *data) { GParamSpec *pspec; + gboolean packing = FALSE; + const char *class_name; + const char *property_name; + int i; - pspec = get_property_pspec (data, class_name, property_name); + if (g_str_equal (element->parent->element_name, "packing")) + packing = TRUE; + + 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, packing, FALSE); 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) +static gboolean +property_can_be_omitted (Element *element, + MyParserData *data) { - GValue value = G_VALUE_INIT; - gboolean b = FALSE; + gint i; + gboolean bound; + gboolean translatable; + const gchar *class_name; + const gchar *property_name; + const gchar *value_string; + gboolean packing = FALSE; + gboolean cell_packing = FALSE; - if (gtk_builder_value_from_string_type (data->builder, G_TYPE_BOOLEAN, string, &value, NULL)) - b = g_value_get_boolean (&value); + if (g_str_equal (element->parent->element_name, "packing")) + packing = TRUE; + if (g_str_equal (element->parent->element_name, "cell-packing")) + cell_packing = TRUE; - return b ? "1" : "0"; + 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 (translatable) + return FALSE; + + if (bound) + return FALSE; + + if (needs_explicit_setting (class_name, property_name, packing)) + return FALSE; + + return value_is_default (data, class_name, property_name, value_string, packing, cell_packing); } -/* 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) +property_has_been_removed (Element *element, + MyParserData *data) { + const gchar *class_name; + const gchar *property_name; + gboolean packing = FALSE; 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 } + { "GtkActionBar", "position", 1 }, + { "GtkButtonBox", "secondary", 1 }, + { "GtkButtonBox", "non-homogeneous", 1 }, + { "GtkBox", "pack-type", 1 }, + { "GtkBox", "position", 1 }, + { "GtkHeaderBar", "position", 1 }, + { "GtkPopoverMenu", "position", 1 }, + { "GtkMenu", "left-attach", 1 }, + { "GtkMenu", "right-attach", 1 }, + { "GtkMenu", "top-attach", 1 }, + { "GtkMenu", "bottom-attach", 1 } }; gchar *canonical_name; gboolean found; - gint k; + gint i, k; + + if (g_str_equal (element->parent->element_name, "packing")) + packing = TRUE; + + 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; props[k].class; k++) + for (k = 0; k < G_N_ELEMENTS (props); k++) { if (strcmp (class_name, props[k].class) == 0 && strcmp (canonical_name, props[k].property) == 0 && - data->packing == props[k].packing) + packing == props[k].packing) { found = TRUE; break; @@ -229,389 +510,177 @@ needs_explicit_setting (MyParserData *data, return found; } -static void -maybe_start_packing (MyParserData *data) +static Element * +rewrite_stack_child (Element *child, MyParserData *data) { - if (data->packing) + 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) { - if (!data->packing_started) - { - g_fprintf (data->output, "%*s\n", data->indent, ""); - data->indent += 2; - data->packing_started = TRUE; - } + 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; + 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); + 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 -maybe_start_cell_packing (MyParserData *data) +rewrite_stack (Element *element, + MyParserData *data) { - if (data->cell_packing) + GList *l, *new_children; + + new_children = NULL; + for (l = element->children; l; l = l->next) { - if (!data->cell_packing_started) - { - g_fprintf (data->output, "%*s\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\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]; + Element *child = l->data; + new_children = g_list_append (new_children, rewrite_stack_child (child, data)); } - 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, "%*sindent, ""); - 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, "\n"); - } -} - -static void -maybe_close_starttag (MyParserData *data) -{ - if (data->unclosed_starttag) - { - g_fprintf (data->output, ">\n"); - data->unclosed_starttag = FALSE; - } + g_list_free (element->children); + element->children = new_children; } static gboolean -stack_is (GMarkupParseContext *context, - ...) +simplify_element (Element *element, + MyParserData *data) { - va_list args; - gchar *s, *p; - const GSList *stack; + GList *l; - stack = g_markup_parse_context_get_element_stack (context); - - va_start (args, context); - s = va_arg (args, gchar *); - while (s) + 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)) { - 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; + const char *b = canonical_boolean_value (data, element->data); + g_free (element->data); + element->data = g_strdup (b); } - va_end (args); - return TRUE; + 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; + + if (data->convert3to4) + { + if (g_str_equal (element->element_name, "object") && + g_str_equal (get_class_name (element), "GtkStack")) + rewrite_stack (element, data); + + if (g_str_equal (element->element_name, "property") && + property_has_been_removed (element, data)) + return TRUE; + } + + return FALSE; } static void -start_element (GMarkupParseContext *context, - const gchar *element_name, - const gchar **attribute_names, - const gchar **attribute_values, - gpointer user_data, - GError **error) +simplify_tree (MyParserData *data) { - gint i; - gchar *escaped; - MyParserData *data = user_data; + simplify_element (data->root, data); +} - maybe_close_starttag (data); - - if (strcmp (element_name, "property") == 0) +static void +dump_element (Element *element, + FILE *output, + int indent) +{ + g_fprintf (output, "%*s<%s", indent, "", element->element_name); + if (element->attribute_names[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++) + int i; + for (i = 0; element->attribute_names[i]; i++) { - if (strcmp (attribute_names[i], "class") == 0) + 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) { - data->classes = g_list_prepend (data->classes, - g_strdup (attribute_values[i])); - break; + Element *child = l->data; + dump_element (child, output, indent + 2); } + g_fprintf (output, "%*s", indent, ""); } - } - - 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\n", escaped, element_name); - g_free (escaped); - - g_string_free (data->value, TRUE); - data->value = NULL; + 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 - { - if (data->unclosed_starttag) - g_fprintf (data->output, "/>\n"); - else - g_fprintf (data->output, "%*s\n", data->indent - 2, "", element_name); - } - - data->indent -= 2; - data->unclosed_starttag = FALSE; + g_fprintf (output, "/>\n"); } static void -text (GMarkupParseContext *context, - const gchar *text, - gsize text_len, - gpointer user_data, - GError **error) +dump_tree (MyParserData *data) { - MyParserData *data = user_data; - - if (data->value) - { - g_string_append_len (data->value, text, text_len); - return; - } + dump_element (data->root, data->output, 0); } -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) @@ -624,6 +693,7 @@ do_simplify (int *argc, GOptionContext *ctx; const GOptionEntry entries[] = { { "replace", 0, 0, G_OPTION_ARG_NONE, &replace, NULL, NULL }, + { "3to4", 0, 0, G_OPTION_ARG_NONE, &data.convert3to4, NULL, NULL }, { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, NULL }, { NULL, } }; @@ -674,19 +744,10 @@ do_simplify (int *argc, 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; + + 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)) @@ -695,6 +756,12 @@ do_simplify (int *argc, exit (1); } + data.builder = gtk_builder_new (); + + simplify_tree (&data); + + dump_tree (&data); + fclose (data.output); if (data.output_filename) diff --git a/gtk/tools/gtk-builder-tool.c b/gtk/tools/gtk-builder-tool.c index 48b78fb08f..3219baa983 100644 --- a/gtk/tools/gtk-builder-tool.c +++ b/gtk/tools/gtk-builder-tool.c @@ -46,6 +46,7 @@ usage (void) "\n" "Simplify Options:\n" " --replace Replace the file\n" + " --3to4 Convert from a GTK3 to GTK4\n" "\n" "Preview Options:\n" " --id=ID Preview only the named object\n" From 1fb3febacc293da9d36321c31aa4c099f59ace15 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 7 Feb 2019 11:57:09 -0500 Subject: [PATCH 05/11] Document --3to4 --- docs/reference/gtk/gtk4-builder-tool.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/reference/gtk/gtk4-builder-tool.xml b/docs/reference/gtk/gtk4-builder-tool.xml index e9f497599e..992f2d9db1 100644 --- a/docs/reference/gtk/gtk4-builder-tool.xml +++ b/docs/reference/gtk/gtk4-builder-tool.xml @@ -79,6 +79,10 @@ Write the content back to the .ui file instead of stdout. + + + Transform a GTK 3 ui file to GTK 4 + From 72b6644223af1f6ea2bc270204563433e28cf854 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 7 Feb 2019 12:24:49 -0500 Subject: [PATCH 06/11] builder tool: Operate on multiple files There is no strong reason to disallow this, when the --replace option is used. --- gtk/tools/gtk-builder-tool-simplify.c | 127 +++++++++++++++----------- gtk/tools/gtk-builder-tool-validate.c | 26 ++++-- 2 files changed, 92 insertions(+), 61 deletions(-) diff --git a/gtk/tools/gtk-builder-tool-simplify.c b/gtk/tools/gtk-builder-tool-simplify.c index 55bcc0edf2..69bb028569 100644 --- a/gtk/tools/gtk-builder-tool-simplify.c +++ b/gtk/tools/gtk-builder-tool-simplify.c @@ -54,7 +54,7 @@ typedef struct { Element *current; GString *value; GtkBuilder *builder; - char *input_filename; + const char *input_filename; char *output_filename; FILE *output; gboolean convert3to4; @@ -228,7 +228,8 @@ is_container_element (Element *element) "mime-types", "attributes", "row", - "items" + "items", + NULL }; if (g_strv_contains (names, element->element_name)) @@ -318,11 +319,11 @@ value_is_default (MyParserData *data, if (pspec == NULL) { if (packing) - g_printerr (_("Packing property %s::%s not found\n"), class_name, property_name); + g_printerr (_("%s: Packing property %s::%s not found\n"), data->input_filename, class_name, property_name); else if (cell_packing) - g_printerr (_("Cell property %s::%s not found\n"), class_name, property_name); + g_printerr (_("%s: Cell property %s::%s not found\n"), data->input_filename, class_name, property_name); else - g_printerr (_("Property %s::%s not found\n"), class_name, property_name); + g_printerr (_("%s: Property %s::%s not found\n"), data->input_filename, class_name, property_name); return FALSE; } else if (g_type_is_a (G_PARAM_SPEC_VALUE_TYPE (pspec), G_TYPE_OBJECT)) @@ -330,7 +331,7 @@ value_is_default (MyParserData *data, 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_printerr (_("%s: Couldn’t parse value for %s::%s: %s\n"), data->input_filename, class_name, property_name, error->message); g_error_free (error); ret = FALSE; } @@ -681,51 +682,19 @@ dump_tree (MyParserData *data) dump_element (data->root, data->output, 0); } -void -do_simplify (int *argc, - const char ***argv) +gboolean +simplify_file (const char *filename, + gboolean replace, + gboolean convert3to4) { 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 }, - { "3to4", 0, 0, G_OPTION_ARG_NONE, &data.convert3to4, 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.input_filename = filename; data.output_filename = NULL; + data.convert3to4 = convert3to4; if (replace) { @@ -738,13 +707,12 @@ do_simplify (int *argc, data.output = stdout; } - if (!g_file_get_contents (filenames[0], &buffer, NULL, &error)) + if (!g_file_get_contents (filename, &buffer, NULL, &error)) { - g_printerr (_("Can’t load file: %s\n"), error->message); - exit (1); + g_printerr (_("Can’t load '%s': %s\n"), filename, error->message); + return FALSE; } - data.root = NULL; data.current = NULL; data.value = g_string_new (""); @@ -752,8 +720,8 @@ do_simplify (int *argc, 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); + g_printerr (_("Can’t parse '%s': %s\n"), filename, error->message); + return FALSE; } data.builder = gtk_builder_new (); @@ -771,14 +739,65 @@ do_simplify (int *argc, 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); + 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); - exit (1); + 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); + } } diff --git a/gtk/tools/gtk-builder-tool-validate.c b/gtk/tools/gtk-builder-tool-validate.c index 33ee6ff22b..a64bb2d3e1 100644 --- a/gtk/tools/gtk-builder-tool-validate.c +++ b/gtk/tools/gtk-builder-tool-validate.c @@ -119,17 +119,14 @@ parse_template_error (const gchar *message, return TRUE; } -void -do_validate (int *argc, char ***argv) +static gboolean +validate_file (const char *filename) { 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); @@ -144,9 +141,24 @@ do_validate (int *argc, char ***argv) } else { - g_printerr ("%s\n", error->message); - exit (1); + g_printerr ("%s: %s\n", filename, error->message); + return FALSE; } } + + return TRUE; } +void +do_validate (int *argc, char ***argv) +{ + int i; + + for (i = 1; i < *argc; i++) + { + if (!validate_file ((*argv)[i])) + exit (1); + } + + return; +} From 4ace873046fe7bab6f2678c9307f6e2a28f9ff90 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Thu, 7 Feb 2019 03:23:02 -0500 Subject: [PATCH 07/11] Update ui files to new stack syntax This conversion was done with the help of gtk4-builder-tool. --- demos/gtk-demo/application.ui | 4 +- demos/gtk-demo/appmenu.ui | 2 - demos/gtk-demo/blendmodes.ui | 516 +-- demos/gtk-demo/cursors.ui | 2 - demos/gtk-demo/demo.ui | 11 +- demos/gtk-demo/filtermodel.ui | 10 +- demos/gtk-demo/fishbowl.ui | 8 +- demos/gtk-demo/font-features.ui | 44 +- demos/gtk-demo/listbox.ui | 5 +- demos/gtk-demo/main.ui | 11 +- demos/gtk-demo/menus.ui | 1 - demos/gtk-demo/modelbutton.ui | 11 +- demos/gtk-demo/popover.ui | 4 - demos/gtk-demo/revealer.ui | 2 - demos/gtk-demo/scale.ui | 22 +- demos/gtk-demo/shortcuts-boxes.ui | 9 - demos/gtk-demo/shortcuts-builder.ui | 2 - demos/gtk-demo/shortcuts-clocks.ui | 2 - demos/gtk-demo/shortcuts-gedit.ui | 2 - demos/gtk-demo/shortcuts.ui | 2 - demos/gtk-demo/spinbutton.ui | 1 - demos/gtk-demo/stack.ui | 52 +- demos/gtk-demo/theming.ui | 1 - demos/icon-browser/menus.ui | 2 - demos/icon-browser/window.ui | 6 +- demos/widget-factory/widget-factory.ui | 5214 ++++++++++++------------ examples/application10/app-menu.ui | 2 - examples/application10/gears-menu.ui | 2 - examples/application10/prefs.ui | 9 +- examples/application10/window.ui | 2 - examples/application2/window.ui | 5 +- examples/application3/window.ui | 5 +- examples/application4/app-menu.ui | 2 - examples/application4/window.ui | 5 +- examples/application5/app-menu.ui | 2 - examples/application5/window.ui | 5 +- examples/application6/app-menu.ui | 2 - examples/application6/prefs.ui | 9 +- examples/application6/window.ui | 5 +- examples/application7/app-menu.ui | 2 - examples/application7/prefs.ui | 9 +- examples/application7/window.ui | 2 - examples/application8/app-menu.ui | 2 - examples/application8/gears-menu.ui | 2 - examples/application8/prefs.ui | 9 +- examples/application8/window.ui | 2 - examples/application9/app-menu.ui | 2 - examples/application9/gears-menu.ui | 2 - examples/application9/prefs.ui | 9 +- examples/application9/window.ui | 2 - gtk/inspector/actions.ui | 1 - gtk/inspector/css-editor.ui | 1 - gtk/inspector/css-node-tree.ui | 1 - gtk/inspector/data-list.ui | 4 +- gtk/inspector/general.ui | 3 - gtk/inspector/logs.ui | 7 +- gtk/inspector/magnifier.ui | 1 - gtk/inspector/menu.ui | 6 - gtk/inspector/misc-info.ui | 2 - gtk/inspector/object-hierarchy.ui | 1 - gtk/inspector/object-tree.ui | 2 - gtk/inspector/prop-list.ui | 1 - gtk/inspector/recorder.ui | 3 +- gtk/inspector/resource-list.ui | 439 +- gtk/inspector/selector.ui | 1 - gtk/inspector/signals-list.ui | 1 - gtk/inspector/statistics.ui | 266 +- gtk/inspector/visual.ui | 7 +- gtk/inspector/window.ui | 887 ++-- gtk/ui/gtkaboutdialog.ui | 298 +- gtk/ui/gtkappchooserdialog.ui | 2 - gtk/ui/gtkappchooserwidget.ui | 14 +- gtk/ui/gtkapplication-quartz.ui | 8 - gtk/ui/gtkassistant.ui | 2 - gtk/ui/gtkcolorchooserdialog.ui | 2 - gtk/ui/gtkcoloreditor.ui | 4 - gtk/ui/gtkcombobox.ui | 2 - gtk/ui/gtkdialog.ui | 9 +- gtk/ui/gtkemojichooser.ui | 650 +-- gtk/ui/gtkemojicompletion.ui | 2 - gtk/ui/gtkfilechooserdialog.ui | 2 - gtk/ui/gtkfilechooserwidget.ui | 478 +-- gtk/ui/gtkfontchooserdialog.ui | 2 - gtk/ui/gtkfontchooserwidget.ui | 530 +-- gtk/ui/gtkinfobar.ui | 2 - gtk/ui/gtklockbutton.ui | 2 - gtk/ui/gtkmediacontrols.ui | 11 +- gtk/ui/gtkmessagedialog.ui | 2 - gtk/ui/gtkpagesetupunixdialog.ui | 7 - gtk/ui/gtkpathbar.ui | 2 - gtk/ui/gtkplacesview.ui | 232 +- gtk/ui/gtkplacesviewrow.ui | 44 +- gtk/ui/gtkprintunixdialog.ui | 26 +- gtk/ui/gtkscalebutton.ui | 3 - gtk/ui/gtksidebarrow.ui | 3 +- gtk/ui/gtkstatusbar.ui | 2 - gtk/ui/gtktooltipwindow.ui | 2 - gtk/ui/gtkvideo.ui | 5 +- gtk/ui/gtkvolumebutton.ui | 2 - 99 files changed, 4965 insertions(+), 5063 deletions(-) diff --git a/demos/gtk-demo/application.ui b/demos/gtk-demo/application.ui index c08388187f..f4e82dbacd 100644 --- a/demos/gtk-demo/application.ui +++ b/demos/gtk-demo/application.ui @@ -1,4 +1,3 @@ -