From 3b7fc8cdc9edab23755c1d79a74555485d14dd9c Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Wed, 20 Mar 2013 11:56:39 +0900 Subject: [PATCH] Add Composite Child machinery and APIs to GtkWidget This commit implements the needed machinery for GtkWidget to build it's composite content from GtkBuilder XML and adds the following API: o gtk_widget_init_template() An api to be called in instance initializers of any GtkWidget subclass that uses template XML to build it's components. o gtk_widget_class_set_template() API to associate GtkBuilder XML to a given GtkWidget subclass o gtk_widget_class_automate_child() API to declare an object built by GtkBuilder to be associated with an instance structure offset and automatically set. o gtk_widget_get_automated_child() API for bindings to fetch a child declared to be automated by gtk_widget_class_automate_child(), for the case where bindings do not generate GObjects under the hood and cannot use structure offsets to resolve composite object pointers. o gtk_widget_class_declare_callback[s]() Declare static functions to be used in signal callbacks from a given class's template XML o gtk_widget_class_set_connect_func() API for bindings to override the signal connection machinery for a given GtkWidget derived class. --- docs/reference/gtk/gtk3-sections.txt | 12 + gtk/gtk.symbols | 7 + gtk/gtkbuilder.c | 4 + gtk/gtkbuilder.h | 9 - gtk/gtktypes.h | 9 + gtk/gtkwidget.c | 752 +++++++++++++++++++++++++++ gtk/gtkwidget.h | 73 +++ 7 files changed, 857 insertions(+), 9 deletions(-) diff --git a/docs/reference/gtk/gtk3-sections.txt b/docs/reference/gtk/gtk3-sections.txt index 6ab2f340ff..deb83f9eb5 100644 --- a/docs/reference/gtk/gtk3-sections.txt +++ b/docs/reference/gtk/gtk3-sections.txt @@ -5385,6 +5385,18 @@ gtk_widget_set_vexpand_set gtk_widget_queue_compute_expand gtk_widget_compute_expand + +gtk_widget_init_template +gtk_widget_class_set_template +gtk_widget_class_set_template_from_resource +gtk_widget_get_automated_child +gtk_widget_class_bind_child +gtk_widget_class_bind_child_internal +gtk_widget_class_automate_child +gtk_widget_class_bind_callback +gtk_widget_class_declare_callback +gtk_widget_class_set_connect_func + GTK_WIDGET GTK_IS_WIDGET diff --git a/gtk/gtk.symbols b/gtk/gtk.symbols index 31a2a8bce7..1937f11fd1 100644 --- a/gtk/gtk.symbols +++ b/gtk/gtk.symbols @@ -3691,6 +3691,8 @@ gtk_widget_add_tick_callback gtk_widget_can_activate_accel gtk_widget_child_focus gtk_widget_child_notify +gtk_widget_class_automate_child +gtk_widget_class_declare_callback gtk_widget_class_find_style_property gtk_widget_class_install_style_property gtk_widget_class_install_style_property_parser @@ -3698,6 +3700,9 @@ gtk_widget_class_list_style_properties gtk_widget_class_path gtk_widget_class_set_accessible_role gtk_widget_class_set_accessible_type +gtk_widget_class_set_signal_connect_func +gtk_widget_class_set_template +gtk_widget_class_set_template_from_resource gtk_widget_compute_expand gtk_widget_create_pango_context gtk_widget_create_pango_layout @@ -3715,6 +3720,7 @@ gtk_widget_get_allocated_width gtk_widget_get_allocation gtk_widget_get_ancestor gtk_widget_get_app_paintable +gtk_widget_get_automated_child gtk_widget_get_can_default gtk_widget_get_can_focus gtk_widget_get_child_requisition @@ -3792,6 +3798,7 @@ gtk_widget_help_type_get_type gtk_widget_hide gtk_widget_hide_on_delete gtk_widget_in_destruction +gtk_widget_init_template gtk_widget_input_shape_combine_region gtk_widget_intersect gtk_widget_is_ancestor diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c index f51f79a2d9..326b5a149f 100644 --- a/gtk/gtkbuilder.c +++ b/gtk/gtkbuilder.c @@ -216,6 +216,10 @@ * GtkFileFilter, * GtkTextTagTable. * + * + * Additionally, since 3.10 a special <template> tag has been added to the format + * allowing one to define a widget class's components. + * * * * Embedding other XML diff --git a/gtk/gtkbuilder.h b/gtk/gtkbuilder.h index f002353d36..7f5cb153f2 100644 --- a/gtk/gtkbuilder.h +++ b/gtk/gtkbuilder.h @@ -36,7 +36,6 @@ G_BEGIN_DECLS #define GTK_BUILDER_ERROR (gtk_builder_error_quark ()) -typedef struct _GtkBuilder GtkBuilder; typedef struct _GtkBuilderClass GtkBuilderClass; typedef struct _GtkBuilderPrivate GtkBuilderPrivate; @@ -108,14 +107,6 @@ struct _GtkBuilderClass void (*_gtk_reserved8) (void); }; -typedef void (*GtkBuilderConnectFunc) (GtkBuilder *builder, - GObject *object, - const gchar *signal_name, - const gchar *handler_name, - GObject *connect_object, - GConnectFlags flags, - gpointer user_data); - GType gtk_builder_get_type (void) G_GNUC_CONST; GtkBuilder* gtk_builder_new (void); diff --git a/gtk/gtktypes.h b/gtk/gtktypes.h index ed93bc537f..f1b645a182 100644 --- a/gtk/gtktypes.h +++ b/gtk/gtktypes.h @@ -32,6 +32,7 @@ G_BEGIN_DECLS typedef struct _GtkAdjustment GtkAdjustment; +typedef struct _GtkBuilder GtkBuilder; typedef struct _GtkClipboard GtkClipboard; typedef struct _GtkIconSet GtkIconSet; typedef struct _GtkIconSource GtkIconSource; @@ -51,6 +52,14 @@ typedef gboolean (*GtkRcPropertyParser) (const GParamSpec *pspec, const GString *rc_string, GValue *property_value); +typedef void (*GtkBuilderConnectFunc) (GtkBuilder *builder, + GObject *object, + const gchar *signal_name, + const gchar *handler_name, + GObject *connect_object, + GConnectFlags flags, + gpointer user_data); + G_END_DECLS #endif /* __GTK_TYPES_H__ */ diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c index e64d3dc868..71a8f8bad8 100644 --- a/gtk/gtkwidget.c +++ b/gtk/gtkwidget.c @@ -299,6 +299,69 @@ * * * + * + * Building composite widgets from template XML + * + * GtkWidget exposes some facilities to automate the proceedure + * of creating composite widgets using #GtkBuilder interface description + * language. + * + * + * To create composite widgets with #GtkBuilder XML, one must associate + * the interface description with the widget class at class initialization + * time using gtk_widget_class_set_template(). + * + * + * The interface description semantics expected in composite template descriptions + * is slightly different from regulare #GtkBuilder XML. + * + * + * Unlike regular interface descriptions, gtk_widget_class_set_template() will expect a + * <template> tag as a direct child of the toplevel <interface> + * tag. The <template> tag must specify the "class" attribute which + * must be the type name of the widget. Optionally, the "parent" attribute + * may be specified to specify the direct parent type of the widget type, this + * is ignored by the GtkBuilder but required for Glade to introspect what kind + * of properties and internal children exist for a given type when the actual + * type does not exist. + * + * + * The XML which is contained inside the <template> tag behaves as if + * it were added to the <object> tag defining @widget itself. You may set + * properties on @widget by inserting <property> tags into the <template> + * tag, and also add <child> tags to add children and extend @widget in the + * normal way you would with <object> tags. + * + * + * Additionally, <object> tags can also be added before and + * after the initial <template> tag in the normal way, allowing + * one to define auxilary objects which might be referenced by other + * widgets declared as children of the <template> tag. + * + * + * + * A GtkBuilder Template Definition + * + * + * + * ]]> + * + * + * */ /* Add flags here that should not be propagated to children. By default, @@ -312,6 +375,26 @@ #define GTK_STATE_FLAGS_BITS 9 +typedef struct { + gchar *name; /* Name of the template automatic child */ + gboolean internal_child; /* Whether the automatic widget should be exported as an */ + gssize offset; /* Instance private data offset where to set the automatic child (or -1) */ +} AutomaticChildClass; + +typedef struct { + gchar *callback_name; + GCallback callback_symbol; +} CallbackSymbol; + +typedef struct { + GBytes *data; + GSList *children; + GSList *callbacks; + GtkBuilderConnectFunc connect_func; + gpointer connect_data; + GDestroyNotify destroy_notify; +} GtkWidgetTemplate; + struct _GtkWidgetPrivate { /* The state of the widget. Needs to be able to hold all GtkStateFlags bits @@ -415,6 +498,10 @@ struct _GtkWidgetPrivate /* Animations and other things to update on clock ticks */ GList *tick_callbacks; + /* A hash by GType key, containing hash tables by widget name + */ + GHashTable *auto_children; + #ifdef G_ENABLE_DEBUG /* Number of gtk_widget_push_verify_invariants () */ guint verifying_invariants_count; @@ -425,6 +512,7 @@ struct _GtkWidgetClassPrivate { GType accessible_type; AtkRole accessible_role; + GtkWidgetTemplate *template; }; enum { @@ -704,6 +792,24 @@ static void gtk_widget_real_adjust_size_allocation (GtkWidget gint *allocated_pos, gint *allocated_size); +/* --- functions dealing with template data structures --- */ +static AutomaticChildClass *automatic_child_class_new (const gchar *name, + gboolean internal_child, + gssize offset); +static void automatic_child_class_free (AutomaticChildClass *child_class); +static CallbackSymbol *callback_symbol_new (const gchar *name, + GCallback callback); +static void callback_symbol_free (CallbackSymbol *callback); +static void template_data_free (GtkWidgetTemplate *template_data); +static GHashTable *get_auto_child_hash (GtkWidget *widget, + GType type, + gboolean create); +static gboolean setup_automatic_child (GtkWidgetTemplate *template_data, + GType class_type, + AutomaticChildClass *child_class, + GtkWidget *widget, + GtkBuilder *builder); + static void gtk_widget_set_usize_internal (GtkWidget *widget, gint width, gint height, @@ -811,6 +917,7 @@ gtk_widget_base_class_init (gpointer g_class) GtkWidgetClass *klass = g_class; klass->priv = G_TYPE_CLASS_GET_PRIVATE (g_class, GTK_TYPE_WIDGET, GtkWidgetClassPrivate); + klass->priv->template = NULL; } static void @@ -3420,6 +3527,8 @@ gtk_widget_base_class_finalize (GtkWidgetClass *klass) g_param_spec_unref (pspec); } g_list_free (list); + + template_data_free (klass->priv->template); } static void @@ -10798,6 +10907,81 @@ gtk_widget_dispose (GObject *object) G_OBJECT_CLASS (gtk_widget_parent_class)->dispose (object); } +#ifdef G_ENABLE_DEBUG +typedef struct { + AutomaticChildClass *child_class; + GType widget_type; + GObject *object; + gboolean did_finalize; +} FinalizeAssertion; + +static void +finalize_assertion_weak_ref (gpointer data, + GObject *where_the_object_was) +{ + FinalizeAssertion *assertion = (FinalizeAssertion *)data; + assertion->did_finalize = TRUE; +} + +static FinalizeAssertion * +finalize_assertion_new (GtkWidget *widget, + GType widget_type, + AutomaticChildClass *child_class) +{ + FinalizeAssertion *assertion = NULL; + GObject *object; + + object = gtk_widget_get_automated_child (widget, widget_type, child_class->name); + + /* We control the hash table entry, the object should never be NULL + */ + g_assert (object); + if (!G_IS_OBJECT (object)) + g_critical ("Automated component `%s' of class `%s' seems to have been prematurely finalized", + child_class->name, g_type_name (widget_type)); + else + { + assertion = g_slice_new0 (FinalizeAssertion); + assertion->child_class = child_class; + assertion->widget_type = widget_type; + assertion->object = object; + + g_object_weak_ref (object, finalize_assertion_weak_ref, assertion); + } + + return assertion; +} + +static GSList * +build_finalize_assertion_list (GtkWidget *widget) +{ + GType class_type; + GtkWidgetClass *class; + GSList *l, *list = NULL; + + for (class = GTK_WIDGET_GET_CLASS (widget); + GTK_IS_WIDGET_CLASS (class); + class = g_type_class_peek_parent (class)) + { + if (!class->priv->template) + continue; + + class_type = G_OBJECT_CLASS_TYPE (class); + + for (l = class->priv->template->children; l; l = l->next) + { + AutomaticChildClass *child_class = l->data; + FinalizeAssertion *assertion; + + assertion = finalize_assertion_new (widget, class_type, child_class); + list = g_slist_prepend (list, assertion); + } + } + + return list; +} +#endif /* G_ENABLE_DEBUG */ + static void gtk_widget_real_destroy (GtkWidget *object) { @@ -10806,6 +10990,77 @@ gtk_widget_real_destroy (GtkWidget *object) GtkWidgetPrivate *priv = widget->priv; GList *l; + if (priv->auto_children) + { + GType class_type; + GtkWidgetClass *class; + GSList *l; + +#ifdef G_ENABLE_DEBUG + GSList *assertions = NULL; + + /* Note, GTK_WIDGET_ASSERT_COMPONENTS is very useful + * to catch ref counting bugs, but can only be used in + * test cases which simply create and destroy a composite + * widget. + * + * This is because some API can expose components explicitly, + * and so we cannot assert that a component is expected to finalize + * in a full application ecosystem. + */ + if (g_getenv ("GTK_WIDGET_ASSERT_COMPONENTS") != NULL) + assertions = build_finalize_assertion_list (widget); +#endif /* G_ENABLE_DEBUG */ + + /* Release references to all automated children */ + g_hash_table_destroy (priv->auto_children); + priv->auto_children = NULL; + +#ifdef G_ENABLE_DEBUG + for (l = assertions; l; l = l->next) + { + FinalizeAssertion *assertion = l->data; + + if (!assertion->did_finalize) + g_critical ("Automated component `%s' of class `%s' did not finalize in gtk_widget_destroy(). " + "Current reference count is %d", + assertion->child_class->name, + g_type_name (assertion->widget_type), + assertion->object->ref_count); + + g_slice_free (FinalizeAssertion, assertion); + } + g_slist_free (assertions); +#endif /* G_ENABLE_DEBUG */ + + /* Set any automatic private data pointers to NULL */ + for (class = GTK_WIDGET_GET_CLASS (widget); + GTK_IS_WIDGET_CLASS (class); + class = g_type_class_peek_parent (class)) + { + if (!class->priv->template) + continue; + + class_type = G_OBJECT_CLASS_TYPE (class); + + for (l = class->priv->template->children; l; l = l->next) + { + AutomaticChildClass *child_class = l->data; + + if (child_class->offset >= 0) + { + gpointer class_private; + GObject **destination; + + /* Nullify instance private data for internal children */ + class_private = G_TYPE_INSTANCE_GET_PRIVATE (widget, class_type, gpointer); + destination = G_STRUCT_MEMBER_P (class_private, child_class->offset); + *destination = NULL; + } + } + } + } + if (GTK_WIDGET_GET_CLASS (widget)->priv->accessible_type != GTK_TYPE_ACCESSIBLE) { GtkAccessible *accessible = g_object_steal_qdata (G_OBJECT (widget), quark_accessible_object); @@ -12603,9 +12858,41 @@ gtk_widget_buildable_get_internal_child (GtkBuildable *buildable, GtkBuilder *builder, const gchar *childname) { + GtkWidgetClass *class; + GSList *l; + GType internal_child_type = 0; + if (strcmp (childname, "accessible") == 0) return G_OBJECT (gtk_widget_get_accessible (GTK_WIDGET (buildable))); + /* Find a widget type which has declared an automated child as internal by + * the name 'childname', if any. + */ + for (class = GTK_WIDGET_GET_CLASS (buildable); + GTK_IS_WIDGET_CLASS (class); + class = g_type_class_peek_parent (class)) + { + GtkWidgetTemplate *template = class->priv->template; + + if (!template) + continue; + + for (l = template->children; l && internal_child_type == 0; l = l->next) + { + AutomaticChildClass *child_class = l->data; + + if (child_class->internal_child && strcmp (childname, child_class->name) == 0) + internal_child_type = G_OBJECT_CLASS_TYPE (class); + } + } + + /* Now return the 'internal-child' from the class which declared it, note + * that gtk_widget_get_automated_child() an API used to access objects + * which are in the private scope of a given class. + */ + if (internal_child_type != 0) + return gtk_widget_get_automated_child (GTK_WIDGET (buildable), internal_child_type, childname); + return NULL; } @@ -14894,3 +15181,468 @@ gtk_widget_insert_action_group (GtkWidget *widget, else g_action_muxer_remove (muxer, name); } + +/**************************************************************** + * GtkBuilder automated templates * + ****************************************************************/ +static AutomaticChildClass * +automatic_child_class_new (const gchar *name, + gboolean internal_child, + gssize offset) +{ + AutomaticChildClass *child_class = g_slice_new0 (AutomaticChildClass); + + child_class->name = g_strdup (name); + child_class->internal_child = internal_child; + child_class->offset = offset; + + return child_class; +} + +static void +automatic_child_class_free (AutomaticChildClass *child_class) +{ + if (child_class) + { + g_free (child_class->name); + g_slice_free (AutomaticChildClass, child_class); + } +} + +static CallbackSymbol * +callback_symbol_new (const gchar *name, + GCallback callback) +{ + CallbackSymbol *cb = g_slice_new0 (CallbackSymbol); + + cb->callback_name = g_strdup (name); + cb->callback_symbol = callback; + + return cb; +} + +static void +callback_symbol_free (CallbackSymbol *callback) +{ + if (callback) + { + g_free (callback->callback_name); + g_slice_free (CallbackSymbol, callback); + } +} + +static void +template_data_free (GtkWidgetTemplate *template_data) +{ + if (template_data) + { + g_bytes_unref (template_data->data); + g_slist_free_full (template_data->children, (GDestroyNotify)automatic_child_class_free); + g_slist_free_full (template_data->callbacks, (GDestroyNotify)callback_symbol_free); + + if (template_data->connect_data && + template_data->destroy_notify) + template_data->destroy_notify (template_data->connect_data); + + g_slice_free (GtkWidgetTemplate, template_data); + } +} + +static GHashTable * +get_auto_child_hash (GtkWidget *widget, + GType type, + gboolean create) +{ + GHashTable *auto_child_hash; + + if (widget->priv->auto_children == NULL) + { + if (!create) + return NULL; + + widget->priv->auto_children = + g_hash_table_new_full (g_direct_hash, + g_direct_equal, + NULL, + (GDestroyNotify)g_hash_table_destroy); + } + + auto_child_hash = + g_hash_table_lookup (widget->priv->auto_children, GSIZE_TO_POINTER (type)); + + if (!auto_child_hash && create) + { + auto_child_hash = g_hash_table_new_full (g_str_hash, + g_str_equal, + NULL, + (GDestroyNotify)g_object_unref); + + g_hash_table_insert (widget->priv->auto_children, + GSIZE_TO_POINTER (type), + auto_child_hash); + } + + return auto_child_hash; +} + +static gboolean +setup_automatic_child (GtkWidgetTemplate *template_data, + GType class_type, + AutomaticChildClass *child_class, + GtkWidget *widget, + GtkBuilder *builder) +{ + GHashTable *auto_child_hash; + GObject *object; + + object = gtk_builder_get_object (builder, child_class->name); + if (!object) + { + g_critical ("Unable to retrieve object '%s' from class template for type '%s' while building a '%s'", + child_class->name, g_type_name (class_type), G_OBJECT_TYPE_NAME (widget)); + return FALSE; + } + + /* Insert into the hash so that it can be fetched with + * gtk_widget_get_automated_child() and also in automated + * implementations of GtkBuildable.get_internal_child() + */ + auto_child_hash = get_auto_child_hash (widget, class_type, TRUE); + g_hash_table_insert (auto_child_hash, child_class->name, g_object_ref (object)); + + if (child_class->offset >= 0) + { + gpointer class_private; + GObject **destination; + + /* Assign 'object' to the specified offset in the instance private data */ + class_private = G_TYPE_INSTANCE_GET_PRIVATE (widget, class_type, gpointer); + destination = G_STRUCT_MEMBER_P (class_private, child_class->offset); + *destination = object; + } + + return TRUE; +} + +/** + * gtk_widget_init_template: + * @widget: a #GtkWidget + * + * Creates and initializes child widgets defined in templates. This + * function must be called in the instance initializer for any + * class which assigned itself a template using gtk_widget_class_set_template() + * + * It is important to call this function in the instance initializer + * of a #GtkWidget subclass and not in #GObject.constructed() or + * #GObject.constructor() for two reasons. + * + * One reason is that generally derived widgets will assume that parent + * class composite widgets have been created in their instance + * initializers. + * + * Another reason is that when calling g_object_new() on a widget with + * composite templates, it's important to build the composite widgets + * before the construct properties are set. Properties passed to g_object_new() + * should take precedence over properties set in the private template XML. + * + * Since: 3.10 + */ +void +gtk_widget_init_template (GtkWidget *widget) +{ + GtkWidgetTemplate *template; + GtkBuilder *builder; + GError *error = NULL; + GObject *object; + GSList *l; + GType class_type; + + g_return_if_fail (GTK_IS_WIDGET (widget)); + + object = G_OBJECT (widget); + class_type = G_OBJECT_TYPE (widget); + + template = GTK_WIDGET_GET_CLASS (widget)->priv->template; + g_return_if_fail (template != NULL); + + builder = gtk_builder_new (); + + /* Add any callback symbols declared for this GType to the GtkBuilder namespace */ + for (l = template->callbacks; l; l = l->next) + { + CallbackSymbol *callback = l->data; + + gtk_builder_add_callback_symbol (builder, callback->callback_name, callback->callback_symbol); + } + + /* This will build the template XML as children to the widget instance, also it + * will validate that the template is created for the correct GType and assert that + * there is no infinate recursion. + */ + if (!_gtk_builder_extend_with_template (builder, widget, class_type, + (const gchar *)g_bytes_get_data (template->data, NULL), + g_bytes_get_size (template->data), + &error)) + { + g_critical ("Error building template class '%s' for an instance of type '%s': %s", + g_type_name (class_type), G_OBJECT_TYPE_NAME (object), error->message); + g_error_free (error); + + /* This should never happen, if the template XML cannot be built + * then it is a critical programming error. + */ + g_object_unref (builder); + return; + } + + /* Build the automatic child data + */ + for (l = template->children; l; l = l->next) + { + AutomaticChildClass *child_class = l->data; + + /* This will setup the pointer of an automated child, and cause + * it to be available in any GtkBuildable.get_internal_child() + * invocations which may follow by reference in child classes. + */ + if (!setup_automatic_child (template, + class_type, + child_class, + widget, + builder)) + { + g_object_unref (builder); + return; + } + } + + /* Connect signals. All signal data from a template receive the + * template instance as user data automatically. + * + * A GtkBuilderConnectFunc can be provided to gtk_widget_class_set_signal_connect_func() + * in order for templates to be usable by bindings. + */ + if (template->connect_func) + gtk_builder_connect_signals_full (builder, template->connect_func, template->connect_data); + else + gtk_builder_connect_signals (builder, object); + + g_object_unref (builder); +} + +/** + * gtk_widget_class_set_template: + * @widget_class: A #GtkWidgetClass + * @template_bytes: A #GBytes holding the #GtkBuilder XML + * + * This should be called at class initialization time to specify + * the GtkBuilder XML to be used to extend a widget. + * + * For convenience, gtk_widget_class_set_template_from_resource() is also provided. + * + * Any class that installs templates must call gtk_widget_init_template() + * in the widget's instance initializer + * + * Since: 3.10 + */ +void +gtk_widget_class_set_template (GtkWidgetClass *widget_class, + GBytes *template_bytes) +{ + g_return_if_fail (GTK_IS_WIDGET_CLASS (widget_class)); + g_return_if_fail (widget_class->priv->template == NULL); + g_return_if_fail (template_bytes != NULL); + + widget_class->priv->template = g_slice_new0 (GtkWidgetTemplate); + widget_class->priv->template->data = g_bytes_ref (template_bytes); +} + +/** + * gtk_widget_class_set_template_from_resource: + * @widget_class: A #GtkWidgetClass + * @resource_name: The name of the resource to load the template from + * + * A convenience function to call gtk_widget_class_set_template(). + * + * Any class that installs templates must call gtk_widget_init_template() + * in the widget's instance initializer + * + * Since: 3.10 + */ +void +gtk_widget_class_set_template_from_resource (GtkWidgetClass *widget_class, + const gchar *resource_name) +{ + GError *error = NULL; + GBytes *bytes = NULL; + + g_return_if_fail (GTK_IS_WIDGET_CLASS (widget_class)); + g_return_if_fail (widget_class->priv->template == NULL); + g_return_if_fail (resource_name && resource_name[0]); + + /* This is a hack, because class initializers now access resources + * and GIR/gtk-doc initializes classes without initializing GTK+, + * we ensure that our base resources are registered here and + * avoid warnings which building GIRs/documentation. + */ + _gtk_ensure_resources (); + + bytes = g_resources_lookup_data (resource_name, 0, &error); + if (!bytes) + { + g_critical ("Unable to load resource for composite template for type '%s': %s", + G_OBJECT_CLASS_NAME (widget_class), error->message); + g_error_free (error); + return; + } + + gtk_widget_class_set_template (widget_class, bytes); + g_bytes_unref (bytes); +} + +/** + * gtk_widget_class_declare_callback: + * @widget_class: A #GtkWidgetClass + * @callback_name: The name of the callback as expected in the template XML + * @callback_symbol: (scope async): The callback symbol + * + * Declares a @callback_symbol to handle @callback_name from the template XML + * defined for @widget_type. See gtk_builder_add_callback_symbol(). + * + * This must be called from a composite widget classes class + * initializer after calling gtk_widget_class_set_template() + * + * Since: 3.10 + */ +void +gtk_widget_class_declare_callback (GtkWidgetClass *widget_class, + const gchar *callback_name, + GCallback callback_symbol) +{ + CallbackSymbol *cb; + + g_return_if_fail (GTK_IS_WIDGET_CLASS (widget_class)); + g_return_if_fail (widget_class->priv->template != NULL); + g_return_if_fail (callback_name && callback_name[0]); + g_return_if_fail (callback_symbol != NULL); + + cb = callback_symbol_new (callback_name, callback_symbol); + widget_class->priv->template->callbacks = g_slist_prepend (widget_class->priv->template->callbacks, cb); +} + +/** + * gtk_widget_class_set_connect_func: + * @widget_class: A #GtkWidgetClass + * @connect_func: The #GtkBuilderConnectFunc to use when connecting signals in the class template + * @connect_data: The data to pass to @connect_func + * @connect_data_destroy: The #GDestroyNotify to free @connect_data, this will only be used at + * class finalization time, when no classes of type @widget_type are in use anymore. + * + * For use in lanuage bindings, this will override the default #GtkBuilderConnectFunc to be + * used when parsing GtkBuilder xml from this class's template data. + * + * This must be called from a composite widget classes class + * initializer after calling gtk_widget_class_set_template() + * + * Since: 3.10 + */ +void +gtk_widget_class_set_connect_func (GtkWidgetClass *widget_class, + GtkBuilderConnectFunc connect_func, + gpointer connect_data, + GDestroyNotify connect_data_destroy) +{ + g_return_if_fail (GTK_IS_WIDGET_CLASS (widget_class)); + g_return_if_fail (widget_class->priv->template != NULL); + + /* Defensive, destroy any previously set data */ + if (widget_class->priv->template->connect_data && + widget_class->priv->template->destroy_notify) + widget_class->priv->template->destroy_notify (widget_class->priv->template->connect_data); + + widget_class->priv->template->connect_func = connect_func; + widget_class->priv->template->connect_data = connect_data; + widget_class->priv->template->destroy_notify = connect_data_destroy; +} + +/** + * gtk_widget_class_automate_child: + * @widget_class: A #GtkWidgetClass + * @name: The "id" of the child defined in the template XML + * @internal_child: Whether the child should be accessible as an "internal-child" + * when this class is used in GtkBuilder XML + * @struct_offset: The structure offset into the composite widget's instance private structure + * where the automated child pointer should be set, or -1 to not assign the pointer. + * + * Automatically assign an object declared in the class template XML to be set to a location + * on a freshly built instance's private data, or alternatively accessible via gtk_widget_get_automated_child(). + * + * An explicit strong reference will be held automatically for the duration of your + * instance's life cycle, it will be released automatically when #GObjectClass.dispose() runs + * on your instance and if a @struct_offset that is >= 0 is specified, then the automatic location + * in your instance private data will be set to %NULL. You can however access an automated child + * pointer the first time your classes #GObjectClass.dispose() runs, or alternatively in + * #GtkWidgetClass.destroy(). + * + * If @internal_child is specified, #GtkBuildableIface.get_internal_child() will be automatically + * implemented by the #GtkWidget class so there is no need to implement it manually. + * + * This must be called from a composite widget classes class + * initializer after calling gtk_widget_class_set_template() + * + * Since: 3.10 + */ +void +gtk_widget_class_automate_child (GtkWidgetClass *widget_class, + const gchar *name, + gboolean internal_child, + gssize struct_offset) +{ + AutomaticChildClass *child_class; + + g_return_if_fail (GTK_IS_WIDGET_CLASS (widget_class)); + g_return_if_fail (widget_class->priv->template != NULL); + g_return_if_fail (name && name[0]); + + child_class = automatic_child_class_new (name, + internal_child, + struct_offset); + widget_class->priv->template->children = + g_slist_prepend (widget_class->priv->template->children, child_class); +} + +/** + * gtk_widget_get_automated_child: + * @widget: A #GtkWidget + * @widget_type: The #GType to get an automated child for + * @name: The "id" of the child defined in the template XML + * + * Fetch an object build from the template XML for @widget_type in this @widget instance. + * + * This will only report children which were previously declared with gtk_widget_class_automate_child(). + * + * This function is only meant to be called for code which is private to the @widget_type which + * declared the child and is meant for language bindings which cannot easily make use + * of the GObject structure offsets. + * + * Returns: (transfer none): The object built in the template XML with the id @name + */ +GObject * +gtk_widget_get_automated_child (GtkWidget *widget, + GType widget_type, + const gchar *name) +{ + GHashTable *auto_child_hash; + GObject *ret = NULL; + + g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL); + g_return_val_if_fail (g_type_name (widget_type) != NULL, NULL); + g_return_val_if_fail (name && name[0], NULL); + + auto_child_hash = get_auto_child_hash (widget, widget_type, FALSE); + + if (auto_child_hash) + ret = g_hash_table_lookup (auto_child_hash, name); + + return ret; +} diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h index b026499744..3d57ddeb5a 100644 --- a/gtk/gtkwidget.h +++ b/gtk/gtkwidget.h @@ -943,6 +943,79 @@ GDK_AVAILABLE_IN_3_8 void gtk_widget_remove_tick_callback (GtkWidget *widget, guint id); +/** + * gtk_widget_class_bind_callback: + * @widget_class: a #GtkWidgetClass + * @callback: the callback symbol + * + * Shorthand for gtk_widget_class_declare_callback(), adds a symbol + * by it's own name to the @widget_class. + * + * Since: 3.10 + */ +#define gtk_widget_class_bind_callback(widget_class, callback) \ + gtk_widget_class_declare_callback (GTK_WIDGET_CLASS (widget_class), \ + #callback, G_CALLBACK(callback)) + +/** + * gtk_widget_class_bind_child: + * @widget_class: a #GtkWidgetClass + * @private_data_type: the type of this widget class's instance private data + * @member_name: name of the instance private member on @private_data_type + * + * Shorthand for gtk_widget_class_automate_child(). This macro assumes that + * the @member_name is the name of the component instance to lookup as specified + * in the composite template. + * + * Since: 3.10 + */ +#define gtk_widget_class_bind_child(widget_class, private_data_type, member_name) \ + gtk_widget_class_automate_child (widget_class, #member_name, FALSE, \ + G_STRUCT_OFFSET (private_data_type, member_name)) + +/** + * gtk_widget_class_bind_child_internal: + * @widget_class: a #GtkWidgetClass + * @private_data_type: the type name of this widget class's instance private data + * @member_name: name of the instance private member on @private_data_type + * + * Shorthand for gtk_widget_class_automate_child(). Essentially the same as + * gtk_widget_class_bind_child() except that it will export the child as + * an internal child. + * + * Since: 3.10 + */ +#define gtk_widget_class_bind_child_internal(widget_class, private_data_type, member_name) \ + gtk_widget_class_automate_child (widget_class, #member_name, TRUE, \ + G_STRUCT_OFFSET (private_data_type, member_name)) + +GDK_AVAILABLE_IN_3_10 +void gtk_widget_init_template (GtkWidget *widget); +GDK_AVAILABLE_IN_3_10 +GObject *gtk_widget_get_automated_child (GtkWidget *widget, + GType widget_type, + const gchar *name); +GDK_AVAILABLE_IN_3_10 +void gtk_widget_class_set_template (GtkWidgetClass *widget_class, + GBytes *template_bytes); +GDK_AVAILABLE_IN_3_10 +void gtk_widget_class_set_template_from_resource (GtkWidgetClass *widget_class, + const gchar *resource_name); +GDK_AVAILABLE_IN_3_10 +void gtk_widget_class_declare_callback (GtkWidgetClass *widget_class, + const gchar *callback_name, + GCallback callback_symbol); +GDK_AVAILABLE_IN_3_10 +void gtk_widget_class_set_connect_func (GtkWidgetClass *widget_class, + GtkBuilderConnectFunc connect_func, + gpointer connect_data, + GDestroyNotify connect_data_destroy); +GDK_AVAILABLE_IN_3_10 +void gtk_widget_class_automate_child (GtkWidgetClass *widget_class, + const gchar *name, + gboolean internal_child, + gssize struct_offset); + G_END_DECLS #endif /* __GTK_WIDGET_H__ */