[gtk/wip/otte/listview: 141/145] builder: Add GtkBuilderScope



commit 5e6884dcf37d0ed0bc2664a9da74649d10b55066
Author: Benjamin Otte <otte redhat com>
Date:   Sat Nov 30 01:17:10 2019 +0100

    builder: Add GtkBuilderScope
    
    GtkBuilderScope is an interface that provides the scope that a builder
    instance operates in.
    It creates closures and resolves types. Language bindings are meant to
    use this interface to customize the behavior of builder files, in
    particular when instantiating templates.
    
    A default implementation for C is provided via GtkBuilderCScope (to keep
    with the awkward naming that glib uses for closures). It is derivable on
    purpose so that languages or extensions that extend C can use it.
    
    The reftest code in fact does derive GtkBuilderCScope for its own scope
    implementation that implements looking up symbols in modules.
    
    gtk-widget-factory was updated to use the new GtkBuilderCScope to add
    its custom callback symbols.
    So it does it different from gtk-demo, which uses the normal way of
    exporting symbols for dlsym() and thereby makes the 2 demos test the 2
    ways GtkBuilder uses for looking up symbols.

 demos/widget-factory/widget-factory.c |  30 ++-
 docs/reference/gtk/gtk4-sections.txt  |  36 ++-
 gtk/gtk.h                             |   1 +
 gtk/gtkbuilder.c                      | 458 +++++++--------------------------
 gtk/gtkbuilder.h                      |  32 +--
 gtk/gtkbuilderscope.c                 | 459 ++++++++++++++++++++++++++++++++++
 gtk/gtkbuilderscope.h                 |  99 ++++++++
 gtk/gtktypes.h                        |   8 +-
 gtk/gtkwidget.c                       |  98 +++-----
 gtk/gtkwidget.h                       |  10 +-
 gtk/meson.build                       |   4 +-
 testsuite/reftests/reftest-snapshot.c | 249 ++++++++++--------
 12 files changed, 885 insertions(+), 599 deletions(-)
---
diff --git a/demos/widget-factory/widget-factory.c b/demos/widget-factory/widget-factory.c
index 565383483c..94ddcfae68 100644
--- a/demos/widget-factory/widget-factory.c
+++ b/demos/widget-factory/widget-factory.c
@@ -1663,6 +1663,7 @@ static void
 activate (GApplication *app)
 {
   GtkBuilder *builder;
+  GtkBuilderScope *scope;
   GtkWindow *window;
   GtkWidget *widget;
   GtkWidget *widget2;
@@ -1716,18 +1717,23 @@ activate (GApplication *app)
   g_object_unref (provider);
 
   builder = gtk_builder_new ();
-  gtk_builder_add_callback_symbol (builder, "on_entry_icon_release", (GCallback)on_entry_icon_release);
-  gtk_builder_add_callback_symbol (builder, "on_scale_button_value_changed", 
(GCallback)on_scale_button_value_changed);
-  gtk_builder_add_callback_symbol (builder, "on_scale_button_query_tooltip", 
(GCallback)on_scale_button_query_tooltip);
-  gtk_builder_add_callback_symbol (builder, "on_record_button_toggled", (GCallback)on_record_button_toggled);
-  gtk_builder_add_callback_symbol (builder, "on_page_combo_changed", (GCallback)on_page_combo_changed);
-  gtk_builder_add_callback_symbol (builder, "on_range_from_changed", (GCallback)on_range_from_changed);
-  gtk_builder_add_callback_symbol (builder, "on_range_to_changed", (GCallback)on_range_to_changed);
-  gtk_builder_add_callback_symbol (builder, "tab_close_cb", (GCallback)tab_close_cb);
-  gtk_builder_add_callback_symbol (builder, "increase_icon_size", (GCallback)increase_icon_size);
-  gtk_builder_add_callback_symbol (builder, "decrease_icon_size", (GCallback)decrease_icon_size);
-  gtk_builder_add_callback_symbol (builder, "reset_icon_size", (GCallback)reset_icon_size);
-  gtk_builder_add_callback_symbol (builder, "osd_frame_pressed", (GCallback)osd_frame_pressed);
+  scope = gtk_builder_cscope_new ();
+  gtk_builder_cscope_add_callback_symbols (GTK_BUILDER_CSCOPE (scope),
+          "on_entry_icon_release", (GCallback)on_entry_icon_release,
+          "on_scale_button_value_changed", (GCallback)on_scale_button_value_changed,
+          "on_scale_button_query_tooltip", (GCallback)on_scale_button_query_tooltip,
+          "on_record_button_toggled", (GCallback)on_record_button_toggled,
+          "on_page_combo_changed", (GCallback)on_page_combo_changed,
+          "on_range_from_changed", (GCallback)on_range_from_changed,
+          "on_range_to_changed", (GCallback)on_range_to_changed,
+          "tab_close_cb", (GCallback)tab_close_cb,
+          "increase_icon_size", (GCallback)increase_icon_size,
+          "decrease_icon_size", (GCallback)decrease_icon_size,
+          "reset_icon_size", (GCallback)reset_icon_size,
+          "osd_frame_pressed", (GCallback)osd_frame_pressed,
+          NULL);
+  gtk_builder_set_scope (builder, scope);
+  g_object_unref (scope);
   gtk_builder_add_from_resource (builder, "/org/gtk/WidgetFactory4/widget-factory.ui", NULL);
 
   window = (GtkWindow *)gtk_builder_get_object (builder, "window");
diff --git a/docs/reference/gtk/gtk4-sections.txt b/docs/reference/gtk/gtk4-sections.txt
index 8c5b7161c9..850aafff11 100644
--- a/docs/reference/gtk/gtk4-sections.txt
+++ b/docs/reference/gtk/gtk4-sections.txt
@@ -642,21 +642,41 @@ GTK_BUILDABLE_CLASS
 GTK_BUILDABLE_GET_IFACE
 </SECTION>
 
+<SECTION>
+<FILE>gtkbuilderscope</FILE>
+<TITLE>GtkBuilderScope</TITLE>
+gtk_builder_cscope_new
+gtk_builder_cscope_add_callback_symbol
+gtk_builder_cscope_add_callback_symbols
+gtk_builder_cscope_lookup_callback_symbol
+<SUBSECTION Standard>
+GTK_BUILDER_SCOPE
+GTK_IS_BUILDER_SCOPE
+GTK_TYPE_BUILDER_SCOPE
+GTK_BUILDER_SCOPE_INTERFACE
+GTK_IS_BUILDER_SCOPE_INTERFACE
+GTK_BUILDER_SCOPE_GET_INTERFACE
+GTK_BUILDER_CSCOPE
+GTK_IS_BUILDER_CSCOPE
+GTK_TYPE_BUILDER_CSCOPE
+GTK_BUILDER_CSCOPE_CLASS
+GTK_IS_BUILDER_CSCOPE_CLASS
+GTK_BUILDER_CSCOPE_GET_CLASS
+<SUBSECTION Private>
+gtk_builder_scope_get_type
+gtk_builder_cscope_get_type
+</SECTION>
+
 <SECTION>
 <FILE>gtkbuilder</FILE>
 <TITLE>GtkBuilder</TITLE>
 GtkBuilder
-GtkBuilderClosureFunc
 GtkBuilderError
 gtk_builder_new
 gtk_builder_new_from_file
 gtk_builder_new_from_resource
 gtk_builder_new_from_string
-gtk_builder_add_callback_symbol
-gtk_builder_add_callback_symbols
-gtk_builder_lookup_callback_symbol
 gtk_builder_create_closure
-gtk_builder_create_cclosure
 gtk_builder_add_from_file
 gtk_builder_add_from_resource
 gtk_builder_add_from_string
@@ -669,10 +689,11 @@ gtk_builder_get_objects
 gtk_builder_expose_object
 gtk_builder_set_current_object
 gtk_builder_get_current_object
+gtk_builder_set_scope
+gtk_builder_get_scope
 gtk_builder_set_translation_domain
 gtk_builder_get_translation_domain
 gtk_builder_get_type_from_name
-gtk_builder_set_closure_func
 gtk_builder_value_from_string
 gtk_builder_value_from_string_type
 GTK_BUILDER_WARN_INVALID_CHILD_TYPE
@@ -688,7 +709,6 @@ GTK_BUILDER_GET_CLASS
 <SUBSECTION Private>
 gtk_builder_get_type
 gtk_builder_error_quark
-GtkBuilderPrivate
 </SECTION>
 
 <SECTION>
@@ -4880,7 +4900,7 @@ gtk_widget_class_bind_template_child_internal_private
 gtk_widget_class_bind_template_child_full
 gtk_widget_class_bind_template_callback
 gtk_widget_class_bind_template_callback_full
-gtk_widget_class_set_closure_func
+gtk_widget_class_set_template_scope
 
 <SUBSECTION>
 gtk_widget_observe_children
diff --git a/gtk/gtk.h b/gtk/gtk.h
index 647e3b025e..d2e9b8b079 100644
--- a/gtk/gtk.h
+++ b/gtk/gtk.h
@@ -56,6 +56,7 @@
 #include <gtk/gtkbuildable.h>
 #include <gtk/gtkbuilder.h>
 #include <gtk/gtkbuilderlistitemfactory.h>
+#include <gtk/gtkbuilderscope.h>
 #include <gtk/gtkbutton.h>
 #include <gtk/gtkcalendar.h>
 #include <gtk/gtkcellarea.h>
diff --git a/gtk/gtkbuilder.c b/gtk/gtkbuilder.c
index 760b7a4d68..7e72877f22 100644
--- a/gtk/gtkbuilder.c
+++ b/gtk/gtkbuilder.c
@@ -162,23 +162,6 @@
  * “last_modification_time” attribute is also allowed, but it does not
  * have a meaning to the builder.
  *
- * By default, GTK+ tries  to find functions (like the handlers for
- * signals) by using g_module_symbol(), but this can be changed by
- * passing a custom #GtkBuilderClosureFunc to gtk_builder_set_closure_func().
- * Bindings in particular will want to make use of this functionality to
- * allow language-specific name mangling and namespacing.
- *
- * The default closure function uses symbols explicitly added to @builder
- * with prior calls to gtk_builder_add_callback_symbol(). In the case that
- * symbols are not explicitly added; it uses #GModule’s introspective
- * features (by opening the module %NULL) to look at the application’s symbol
- * table. From here it tries to match the signal function names given in the
- * interface description with symbols in the application.
- *
- * Note that unless gtk_builder_add_callback_symbol() is called for
- * all signal callbacks which are referenced by the loaded XML, this
- * functionality will require that #GModule be supported on the platform.
- *
  * If you rely on #GModule support to lookup callbacks in the symbol table,
  * the following details should be noted:
  *
@@ -230,9 +213,10 @@
 #include <stdlib.h>
 #include <string.h> /* strlen */
 
-#include "gtkbuilder.h"
-#include "gtkbuildable.h"
 #include "gtkbuilderprivate.h"
+
+#include "gtkbuildable.h"
+#include "gtkbuilderscopeprivate.h"
 #include "gtkdebug.h"
 #include "gtkexpression.h"
 #include "gtkmain.h"
@@ -240,7 +224,6 @@
 #include "gtkprivate.h"
 #include "gtktypebuiltins.h"
 #include "gtkicontheme.h"
-#include "gtktestutils.h"
 
 static void gtk_builder_finalize       (GObject         *object);
 static void gtk_builder_set_property   (GObject         *object,
@@ -255,6 +238,7 @@ static void gtk_builder_get_property   (GObject         *object,
 enum {
   PROP_0,
   PROP_CURRENT_OBJECT,
+  PROP_SCOPE,
   PROP_TRANSLATION_DOMAIN,
   LAST_PROP
 };
@@ -275,19 +259,14 @@ typedef struct
 {
   gchar *domain;
   GHashTable *objects;
-  GHashTable *callbacks;
   GSList *delayed_properties;
   GSList *signals;
   GSList *bindings;
-  GModule *module;
   gchar *filename;
   gchar *resource_prefix;
   GType template_type;
   GObject *current_object;
-
-  GtkBuilderClosureFunc closure_func;
-  gpointer closure_data;
-  GDestroyNotify closure_destroy;
+  GtkBuilderScope *scope;
 } GtkBuilderPrivate;
 
 G_DEFINE_TYPE_WITH_PRIVATE (GtkBuilder, gtk_builder, G_TYPE_OBJECT)
@@ -298,6 +277,7 @@ gtk_builder_dispose (GObject *object)
   GtkBuilderPrivate *priv = gtk_builder_get_instance_private (GTK_BUILDER (object));
 
   g_clear_object (&priv->current_object);
+  g_clear_object (&priv->scope);
 
   G_OBJECT_CLASS (gtk_builder_parent_class)->dispose (object);
 }
@@ -339,6 +319,18 @@ gtk_builder_class_init (GtkBuilderClass *klass)
                            G_TYPE_OBJECT,
                            GTK_PARAM_READWRITE);
 
+ /**
+  * GtkBuilder:scope:
+  *
+  * The scope the builder is operating in
+  */
+  builder_props[PROP_SCOPE] =
+      g_param_spec_object ("scope",
+                           P_("Scope"),
+                           P_("The scope the builder is operating in"),
+                           GTK_TYPE_BUILDER_SCOPE,
+                           GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+
   g_object_class_install_properties (gobject_class, LAST_PROP, builder_props);
 }
 
@@ -362,18 +354,11 @@ gtk_builder_finalize (GObject *object)
 {
   GtkBuilderPrivate *priv = gtk_builder_get_instance_private (GTK_BUILDER (object));
 
-  if (priv->closure_destroy)
-    priv->closure_destroy (priv->closure_data);
-
-  g_clear_pointer (&priv->module, g_module_close);
-
   g_free (priv->domain);
   g_free (priv->filename);
   g_free (priv->resource_prefix);
 
   g_hash_table_destroy (priv->objects);
-  if (priv->callbacks)
-    g_hash_table_destroy (priv->callbacks);
 
   g_slist_free_full (priv->signals, (GDestroyNotify)_free_signal_info);
 
@@ -394,6 +379,10 @@ gtk_builder_set_property (GObject      *object,
       gtk_builder_set_current_object (builder, g_value_get_object (value));
       break;
 
+    case PROP_SCOPE:
+      gtk_builder_set_scope (builder, g_value_get_object (value));
+      break;
+
     case PROP_TRANSLATION_DOMAIN:
       gtk_builder_set_translation_domain (builder, g_value_get_string (value));
       break;
@@ -419,6 +408,10 @@ gtk_builder_get_property (GObject    *object,
       g_value_set_object (value, priv->current_object);
       break;
 
+    case PROP_SCOPE:
+      g_value_set_object (value, priv->scope);
+      break;
+
     case PROP_TRANSLATION_DOMAIN:
       g_value_set_string (value, priv->domain);
       break;
@@ -429,79 +422,6 @@ gtk_builder_get_property (GObject    *object,
     }
 }
 
-
-/*
- * Try to map a type name to a _get_type function
- * and call it, eg:
- *
- * GtkWindow -> gtk_window_get_type
- * GtkHBox -> gtk_hbox_get_type
- * GtkUIManager -> gtk_ui_manager_get_type
- * GWeatherLocation -> gweather_location_get_type
- *
- * Keep in sync with testsuite/gtk/typename.c !
- */
-static gchar *
-type_name_mangle (const gchar *name)
-{
-  GString *symbol_name = g_string_new ("");
-  gint i;
-
-  for (i = 0; name[i] != '\0'; i++)
-    {
-      /* skip if uppercase, first or previous is uppercase */
-      if ((name[i] == g_ascii_toupper (name[i]) &&
-           i > 0 && name[i-1] != g_ascii_toupper (name[i-1])) ||
-           (i > 2 && name[i]   == g_ascii_toupper (name[i]) &&
-           name[i-1] == g_ascii_toupper (name[i-1]) &&
-           name[i-2] == g_ascii_toupper (name[i-2])))
-        g_string_append_c (symbol_name, '_');
-      g_string_append_c (symbol_name, g_ascii_tolower (name[i]));
-    }
-  g_string_append (symbol_name, "_get_type");
-
-  return g_string_free (symbol_name, FALSE);
-}
-
-GModule *
-gtk_builder_get_module (GtkBuilder *builder)
-{
-  GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
-
-  if (priv->module == NULL)
-    {
-      if (!g_module_supported ())
-        return NULL;
-
-      priv->module = g_module_open (NULL, G_MODULE_BIND_LAZY);
-    }
-
-  return priv->module;
-}
-
-static GType
-gtk_builder_resolve_type_lazily (GtkBuilder  *builder,
-                                 const gchar *name)
-{
-  GModule *module;
-  GTypeGetFunc func;
-  gchar *symbol;
-  GType gtype = G_TYPE_INVALID;
-
-  module = gtk_builder_get_module (builder);
-  if (!module)
-    return G_TYPE_INVALID;
-
-  symbol = type_name_mangle (name);
-
-  if (g_module_symbol (module, symbol, (gpointer)&func))
-    gtype = func ();
-
-  g_free (symbol);
-
-  return gtype;
-}
-
 /*
  * GtkBuilder virtual methods
  */
@@ -1815,30 +1735,58 @@ gtk_builder_set_current_object (GtkBuilder *self,
 }
 
 /**
- * GtkBuilderClosureFunc:
- * @builder: a #GtkBuilder
- * @function_name: name of the function to create a closure for
- * @swapped: if the closure should swap user data and instance
- * @object: (nullable): object to use as user data for the closure
- * @user_data: user data passed when setting the function
- * @error: location for error when creating the closure fails
- *
- * Prototype of function used to create closures by @builder. It is meant
- * for influencing how @function_name is resolved.
- *
- * This function is most useful for bindings and can be used with
- * gtk_builder_set_closure_func() or gtk_widget_class_set_closure_func()
- * to allow creating closures for functions defined in the binding's
- * language.
- *
- * If the given @function_name does not match a function name or when the
- * arguments cannot be supported by the bindings, bindings should return
- * %NULL and set @error. Usually %GTK_BUILDER_ERROR_INVALID_FUNCTION will
- * be the right error code to use.
- *
- * Returns: (nullable): a new #GClosure or %NULL when no closure could
- *   be created and @error was set.
- */
+ * gtk_builder_get_scope:
+ * @self: a #GtkBuilder
+ *
+ * Gets the scope in use that was set via gtk_builder_set_scope().
+ *
+ * See the #GtkBuilderScope documentation for details.
+ *
+ * Returns: (transfer none): the current scope 
+ **/
+GtkBuilderScope *
+gtk_builder_get_scope (GtkBuilder *self)
+{
+  GtkBuilderPrivate *priv = gtk_builder_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_BUILDER (self), NULL);
+
+  return priv->scope;
+}
+
+/**
+ * gtk_builder_set_current_object:
+ * @self: a #GtkBuilder
+ * @scope: (nullable) (transfer none): the scope to use or
+ *     %NULL for the default
+ *
+ * Sets the scope the builder should operate in.
+ *
+ * If @scope is %NULL a new #GtkBuilderCScope will be created.
+ *
+ * See the #GtkBuilderScope documentation for details.
+ **/
+void
+gtk_builder_set_scope (GtkBuilder      *self,
+                       GtkBuilderScope *scope)
+{
+  GtkBuilderPrivate *priv = gtk_builder_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_BUILDER (self));
+  g_return_if_fail (scope == NULL || GTK_IS_BUILDER_SCOPE (scope));
+
+  if (scope && priv->scope == scope)
+    return;
+
+  g_clear_object (&priv->scope);
+
+  if (scope)
+    priv->scope = g_object_ref (scope);
+  else
+    priv->scope = gtk_builder_cscope_new ();
+
+  g_object_notify_by_pspec (G_OBJECT (self), builder_props[PROP_SCOPE]);
+}
 
 static gboolean
 gtk_builder_connect_signals (GtkBuilder  *builder,
@@ -2615,21 +2563,15 @@ GType
 gtk_builder_get_type_from_name (GtkBuilder  *builder,
                                 const gchar *type_name)
 {
+  GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
   GType type;
 
   g_return_val_if_fail (GTK_IS_BUILDER (builder), G_TYPE_INVALID);
   g_return_val_if_fail (type_name != NULL, G_TYPE_INVALID);
 
-  type = g_type_from_name (type_name);
+  type = gtk_builder_scope_get_type_from_name (priv->scope, builder, type_name);
   if (type == G_TYPE_INVALID)
-    {
-      type = gtk_builder_resolve_type_lazily (builder, type_name);
-      if (type == G_TYPE_INVALID)
-        {
-          gtk_test_register_all_types ();
-          type = g_type_from_name (type_name);
-        }
-    }
+    return G_TYPE_INVALID;
 
   if (G_TYPE_IS_CLASSED (type))
     g_type_class_unref (g_type_class_ref (type));
@@ -2697,176 +2639,11 @@ _gtk_builder_get_template_type (GtkBuilder *builder)
   return priv->template_type;
 }
 
-/**
- * gtk_builder_add_callback_symbol:
- * @builder: a #GtkBuilder
- * @callback_name: The name of the callback, as expected in the XML
- * @callback_symbol: (scope async): The callback pointer
- *
- * Adds the @callback_symbol to the scope of @builder under the given @callback_name.
- *
- * Using this function overrides the behavior of gtk_builder_create_closure()
- * for any callback symbols that are added. Using this method allows for better
- * encapsulation as it does not require that callback symbols be declared in
- * the global namespace.
- */
-void
-gtk_builder_add_callback_symbol (GtkBuilder  *builder,
-                                 const gchar *callback_name,
-                                 GCallback    callback_symbol)
-{
-  GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
-
-  g_return_if_fail (GTK_IS_BUILDER (builder));
-  g_return_if_fail (callback_name && callback_name[0]);
-  g_return_if_fail (callback_symbol != NULL);
-
-  if (!priv->callbacks)
-    priv->callbacks = g_hash_table_new_full (g_str_hash, g_str_equal,
-                                             g_free, NULL);
-
-  g_hash_table_insert (priv->callbacks, g_strdup (callback_name), callback_symbol);
-}
-
-/**
- * gtk_builder_add_callback_symbols:
- * @builder: a #GtkBuilder
- * @first_callback_name: The name of the callback, as expected in the XML
- * @first_callback_symbol: (scope async): The callback pointer
- * @...: A list of callback name and callback symbol pairs terminated with %NULL
- *
- * A convenience function to add many callbacks instead of calling
- * gtk_builder_add_callback_symbol() for each symbol.
- */
-void
-gtk_builder_add_callback_symbols (GtkBuilder  *builder,
-                                  const gchar *first_callback_name,
-                                  GCallback    first_callback_symbol,
-                                  ...)
-{
-  va_list var_args;
-  const gchar *callback_name;
-  GCallback callback_symbol;
-
-  g_return_if_fail (GTK_IS_BUILDER (builder));
-  g_return_if_fail (first_callback_name && first_callback_name[0]);
-  g_return_if_fail (first_callback_symbol != NULL);
-
-  callback_name = first_callback_name;
-  callback_symbol = first_callback_symbol;
-
-  va_start (var_args, first_callback_symbol);
-
-  do {
-
-    gtk_builder_add_callback_symbol (builder, callback_name, callback_symbol);
-
-    callback_name = va_arg (var_args, const gchar*);
-
-    if (callback_name)
-      callback_symbol = va_arg (var_args, GCallback);
-
-  } while (callback_name != NULL);
-
-  va_end (var_args);
-}
-
-/**
- * gtk_builder_lookup_callback_symbol: (skip)
- * @builder: a #GtkBuilder
- * @callback_name: The name of the callback
- *
- * Fetches a symbol previously added to @builder
- * with gtk_builder_add_callback_symbols()
- *
- * This function is intended for possible use in language bindings
- * or for any case that one might be customizing signal connections
- * using gtk_builder_set_closure_func().
- *
- * Returns: (nullable): The callback symbol in @builder for @callback_name, or %NULL
- */
-GCallback
-gtk_builder_lookup_callback_symbol (GtkBuilder  *builder,
-                                    const gchar *callback_name)
-{
-  GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
-
-  g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
-  g_return_val_if_fail (callback_name && callback_name[0], NULL);
-
-  if (!priv->callbacks)
-    return NULL;
-
-  return g_hash_table_lookup (priv->callbacks, callback_name);
-}
-
-/**
- * gtk_builder_set_closure_func: (skip)
- * @builder: a #GtkBuilder
- * @closure_func: (allow-none) function to call when creating
- *     closures or %NULL to use the default
- * @user_data: (nullable): user data to pass to @closure_func
- * @user_destroy: destroy function for user data
- *
- * Sets the function to call for creating closures.
- * gtk_builder_create_closure() will use this function instead
- * of gtk_builder_create_cclosure().
- *
- * This is useful for bindings.
- **/
-void
-gtk_builder_set_closure_func (GtkBuilder            *builder,
-                              GtkBuilderClosureFunc  closure_func,
-                              gpointer               user_data,
-                              GDestroyNotify         user_destroy)
-{
-  GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
-
-  g_return_if_fail (GTK_IS_BUILDER (builder));
-
-  if (priv->closure_destroy)
-    priv->closure_destroy (priv->closure_data);
-
-  priv->closure_func = closure_func;
-  priv->closure_data = user_data;
-  priv->closure_destroy = user_destroy;
-}
-
-static GClosure *
-gtk_builder_create_closure_for_funcptr (GtkBuilder *builder,
-                                        GCallback   callback,
-                                        gboolean    swapped,
-                                        GObject    *object)
-{
-  GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
-  GClosure *closure;
-
-  if (object == NULL)
-    object = priv->current_object;
-
-  if (object)
-    {
-      if (swapped)
-        closure = g_cclosure_new_object_swap (callback, object);
-      else
-        closure = g_cclosure_new_object (callback, object);
-    }
-  else
-    {
-      if (swapped)
-        closure = g_cclosure_new_swap (callback, NULL, NULL);
-      else
-        closure = g_cclosure_new (callback, NULL, NULL);
-    }
-
-  return closure;
-}
-
 /**
  * gtk_builder_create_closure:
  * @builder: a #GtkBuilder
  * @function_name: name of the function to look up
- * @swapped: %TRUE to create a swapped closure
+ * @flags: closure creation flags
  * @object: (nullable): Object to create the closure with
  * @error: (allow-none): return location for an error, or %NULL
  *
@@ -2882,11 +2659,11 @@ gtk_builder_create_closure_for_funcptr (GtkBuilder *builder,
  * Returns: (nullable): A new closure for invoking @function_name
  **/
 GClosure *
-gtk_builder_create_closure (GtkBuilder *builder,
-                            const char *function_name,
-                            gboolean    swapped,
-                            GObject    *object,
-                            GError    **error)
+gtk_builder_create_closure (GtkBuilder             *builder,
+                            const char             *function_name,
+                            GtkBuilderClosureFlags  flags,
+                            GObject                *object,
+                            GError                **error)
 {
   GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
 
@@ -2895,66 +2672,7 @@ gtk_builder_create_closure (GtkBuilder *builder,
   g_return_val_if_fail (object == NULL || G_IS_OBJECT (object), NULL);
   g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-  if (priv->closure_func)
-    return priv->closure_func (builder, function_name, swapped, object, priv->closure_data, error);
-  else
-    return gtk_builder_create_cclosure (builder, function_name, swapped, object, error);
-}
-
-/**
- * gtk_builder_create_cclosure: (skip)
- * @builder: a #GtkBuilder
- * @function_name: name of the function to look up
- * @swapped: %TRUE to create a swapped closure
- * @object: (nullable): Object to create the closure with
- * @error: (allow-none): return location for an error, or %NULL
- *
- * This is the default function used by gtk_builder_set_closure_func(). Some bindings
- * with C support may want to call this function as a fallback from their closure
- * function.
- *
- * This function has no purpose otherwise.
- *
- * This function will prefer callbacks added via gtk_builder_add_callback_symbol()
- * to looking up public symbols.
- *
- * Returns: (nullable): A new closure for invoking @function_name
- **/
-GClosure *
-gtk_builder_create_cclosure (GtkBuilder *builder,
-                             const char *function_name,
-                             gboolean    swapped,
-                             GObject    *object,
-                             GError    **error)
-{
-  GModule *module = gtk_builder_get_module (builder);
-  GCallback func;
-
-  func = gtk_builder_lookup_callback_symbol (builder, function_name);
-  if (func)
-    return gtk_builder_create_closure_for_funcptr (builder, func, swapped, object);
-
-  if (module == NULL)
-    {
-      g_set_error (error,
-                   GTK_BUILDER_ERROR,
-                   GTK_BUILDER_ERROR_INVALID_FUNCTION,
-                   "Could not look up function `%s`: GModule is not supported.",
-                   function_name);
-      return NULL;
-    }
-
-  if (!g_module_symbol (module, function_name, (gpointer)&func))
-    {
-      g_set_error (error,
-                   GTK_BUILDER_ERROR,
-                   GTK_BUILDER_ERROR_INVALID_FUNCTION,
-                   "No function named `%s`.",
-                   function_name);
-      return NULL;
-    }
-
-  return gtk_builder_create_closure_for_funcptr (builder, func, swapped, object);
+  return gtk_builder_scope_create_closure (priv->scope, builder, function_name, flags, object, error);
 }
 
 /**
diff --git a/gtk/gtkbuilder.h b/gtk/gtkbuilder.h
index 2c90da40f4..41d1160d82 100644
--- a/gtk/gtkbuilder.h
+++ b/gtk/gtkbuilder.h
@@ -23,7 +23,7 @@
 #error "Only <gtk/gtk.h> can be included directly."
 #endif
 
-#include <gtk/gtkapplication.h>
+#include <gtk/gtkbuilderscope.h>
 #include <gtk/gtkwidget.h>
 
 G_BEGIN_DECLS
@@ -98,11 +98,6 @@ GType        gtk_builder_get_type                (void) G_GNUC_CONST;
 GDK_AVAILABLE_IN_ALL
 GtkBuilder*  gtk_builder_new                     (void);
 
-GDK_AVAILABLE_IN_ALL
-void         gtk_builder_set_closure_func        (GtkBuilder    *builder,
-                                                  GtkBuilderClosureFunc closure_func,
-                                                  gpointer       user_data,
-                                                  GDestroyNotify user_destroy);
 GDK_AVAILABLE_IN_ALL
 gboolean     gtk_builder_add_from_file           (GtkBuilder    *builder,
                                                   const gchar   *filename,
@@ -152,6 +147,11 @@ void         gtk_builder_set_translation_domain  (GtkBuilder       *builder,
 GDK_AVAILABLE_IN_ALL
 const gchar* gtk_builder_get_translation_domain  (GtkBuilder           *builder);
 GDK_AVAILABLE_IN_ALL
+GtkBuilderScope *gtk_builder_get_scope           (GtkBuilder    *builder);
+GDK_AVAILABLE_IN_ALL
+void         gtk_builder_set_scope               (GtkBuilder    *builder,
+                                                  GtkBuilderScope *scope);
+GDK_AVAILABLE_IN_ALL
 GType        gtk_builder_get_type_from_name      (GtkBuilder           *builder,
                                                   const char           *type_name);
 
@@ -176,27 +176,9 @@ GtkBuilder * gtk_builder_new_from_string         (const gchar   *string,
                                                   gssize         length);
 
 GDK_AVAILABLE_IN_ALL
-void         gtk_builder_add_callback_symbol     (GtkBuilder    *builder,
-                                                 const gchar   *callback_name,
-                                                 GCallback      callback_symbol);
-GDK_AVAILABLE_IN_ALL
-void         gtk_builder_add_callback_symbols    (GtkBuilder    *builder,
-                                                 const gchar   *first_callback_name,
-                                                 GCallback      first_callback_symbol,
-                                                 ...) G_GNUC_NULL_TERMINATED;
-GDK_AVAILABLE_IN_ALL
-GCallback    gtk_builder_lookup_callback_symbol  (GtkBuilder    *builder,
-                                                 const gchar   *callback_name);
-GDK_AVAILABLE_IN_ALL
 GClosure *   gtk_builder_create_closure          (GtkBuilder    *builder,
                                                   const char    *function_name,
-                                                  gboolean       swapped,
-                                                  GObject       *object,
-                                                  GError       **error);
-GDK_AVAILABLE_IN_ALL
-GClosure *   gtk_builder_create_cclosure         (GtkBuilder    *builder,
-                                                  const char    *function_name,
-                                                  gboolean       swapped,
+                                                  GtkBuilderClosureFlags flags,
                                                   GObject       *object,
                                                   GError       **error);
 
diff --git a/gtk/gtkbuilderscope.c b/gtk/gtkbuilderscope.c
new file mode 100644
index 0000000000..8ae5a6602c
--- /dev/null
+++ b/gtk/gtkbuilderscope.c
@@ -0,0 +1,459 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library 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.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#include "config.h"
+
+#include "gtkbuilderscopeprivate.h"
+
+#include "gtkbuilder.h"
+#include "gtktestutils.h"
+
+/**
+ * SECTION:gtkbuilderscope
+ * @Title: GtkBuilderScope
+ * @Short_description: Bindings for GtkBuilder
+ * @See_also: #GtkBuilder, #GClosure
+ *
+ * #GtkBuilderScope is an interface to provide support to #GtkBuilder, primarily
+ * for looking up programming-language-specific values for strings that are
+ * given in a #GtkBuilder UI file.
+ *
+ * The primary intended audience is bindings that want to provide deeper integration
+ * of #GtkBuilder into the language.
+ *
+ * A #GtkBuilderScope instance may be used with multiple #GtkBuilder objects, even
+ * at once.
+ *
+ * By default, GTK will use its own implementation of #GtkBuilderScope for the C
+ * language which can be created via gtk_builder_cscope_new().
+ *
+ * #GtkBuilderCScope instances use symbols explicitly added to @builder
+ * with prior calls to gtk_builder_scope_add_callback_symbol(). If developers want
+ * to do that, they are encouraged to create their own scopes for that purpose.
+ *
+ * In the case that symbols are not explicitly added; GTK will uses #GModule’s
+ * introspective features (by opening the module %NULL) to look at the application’s
+ * symbol table. From here it tries to match the signal function names given in the
+ * interface description with symbols in the application.
+ *
+ * Note that unless gtk_builder_scope_add_callback_symbol() is called for
+ * all signal callbacks which are referenced by the loaded XML, this
+ * functionality will require that #GModule be supported on the platform.
+ */
+
+G_DEFINE_INTERFACE (GtkBuilderScope, gtk_builder_scope, G_TYPE_OBJECT)
+
+static GType
+gtk_builder_scope_default_get_type_from_name (GtkBuilderScope *self,
+                                              GtkBuilder      *builder,
+                                              const char      *type_name)
+{
+  GType type;
+
+  type = g_type_from_name (type_name);
+  if (type != G_TYPE_INVALID)
+    return type;
+
+  gtk_test_register_all_types ();
+  return g_type_from_name (type_name);
+}
+
+static GClosure *
+gtk_builder_scope_default_create_closure (GtkBuilderScope        *self,
+                                          GtkBuilder             *builder,
+                                          const char             *function_name,
+                                          GtkBuilderClosureFlags  flags,
+                                          GObject                *object,
+                                          GError                **error)
+{
+  g_set_error (error,
+               GTK_BUILDER_ERROR,
+               GTK_BUILDER_ERROR_INVALID_FUNCTION,
+               "Creating closures is not supported by %s",
+               G_OBJECT_TYPE_NAME (self));
+  return NULL;
+}
+
+static void
+gtk_builder_scope_default_init (GtkBuilderScopeInterface *iface)
+{
+  iface->get_type_from_name = gtk_builder_scope_default_get_type_from_name;
+  iface->create_closure = gtk_builder_scope_default_create_closure;
+}
+
+GType
+gtk_builder_scope_get_type_from_name (GtkBuilderScope *self,
+                                      GtkBuilder      *builder,
+                                      const char      *type_name)
+{
+  g_return_val_if_fail (GTK_IS_BUILDER_SCOPE (self), G_TYPE_INVALID);
+  g_return_val_if_fail (GTK_IS_BUILDER (builder), G_TYPE_INVALID);
+  g_return_val_if_fail (type_name != NULL, G_TYPE_INVALID);
+
+  return GTK_BUILDER_SCOPE_GET_IFACE (self)->get_type_from_name (self, builder, type_name);
+}
+
+GClosure *
+gtk_builder_scope_create_closure (GtkBuilderScope        *self,
+                                  GtkBuilder             *builder,
+                                  const char             *function_name,
+                                  GtkBuilderClosureFlags  flags,
+                                  GObject                *object,
+                                  GError                **error)
+{
+  g_return_val_if_fail (GTK_IS_BUILDER_SCOPE (self), NULL);
+  g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
+  g_return_val_if_fail (function_name != NULL, NULL);
+  g_return_val_if_fail (object == NULL || G_IS_OBJECT (object), NULL);
+  g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+  return GTK_BUILDER_SCOPE_GET_IFACE (self)->create_closure (self, builder, function_name, flags, object, 
error);
+}
+
+/*** GTK_BUILDER_CSCOPE ***/
+
+typedef struct _GtkBuilderCScopePrivate GtkBuilderCScopePrivate;
+
+struct _GtkBuilderCScopePrivate
+{
+  GModule *module;
+  GHashTable *callbacks;
+};
+
+static void gtk_builder_cscope_scope_init (GtkBuilderScopeInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (GtkBuilderCScope, gtk_builder_cscope, G_TYPE_OBJECT,
+                         G_ADD_PRIVATE(GtkBuilderCScope)
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDER_SCOPE,
+                                                gtk_builder_cscope_scope_init))
+
+static GModule *
+gtk_builder_cscope_get_module (GtkBuilderCScope *self)
+{
+  GtkBuilderCScopePrivate *priv = gtk_builder_cscope_get_instance_private (self);
+
+  if (priv->module == NULL)
+    {
+      if (!g_module_supported ())
+        return NULL;
+
+      priv->module = g_module_open (NULL, G_MODULE_BIND_LAZY);
+    }
+
+  return priv->module;
+}
+
+/*
+ * Try to map a type name to a _get_type function
+ * and call it, eg:
+ *
+ * GtkWindow -> gtk_window_get_type
+ * GtkHBox -> gtk_hbox_get_type
+ * GtkUIManager -> gtk_ui_manager_get_type
+ * GWeatherLocation -> gweather_location_get_type
+ *
+ * Keep in sync with testsuite/gtk/typename.c !
+ */
+static gchar *
+type_name_mangle (const gchar *name)
+{
+  GString *symbol_name = g_string_new ("");
+  gint i;
+
+  for (i = 0; name[i] != '\0'; i++)
+    {
+      /* skip if uppercase, first or previous is uppercase */
+      if ((name[i] == g_ascii_toupper (name[i]) &&
+           i > 0 && name[i-1] != g_ascii_toupper (name[i-1])) ||
+           (i > 2 && name[i]   == g_ascii_toupper (name[i]) &&
+           name[i-1] == g_ascii_toupper (name[i-1]) &&
+           name[i-2] == g_ascii_toupper (name[i-2])))
+        g_string_append_c (symbol_name, '_');
+      g_string_append_c (symbol_name, g_ascii_tolower (name[i]));
+    }
+  g_string_append (symbol_name, "_get_type");
+
+  return g_string_free (symbol_name, FALSE);
+}
+
+static GType
+gtk_builder_cscope_resolve_type_lazily (GtkBuilderCScope *self,
+                                        const gchar      *name)
+{
+  GModule *module;
+  GType (*func) (void);
+  gchar *symbol;
+  GType gtype = G_TYPE_INVALID;
+
+  module = gtk_builder_cscope_get_module (self);
+  if (!module)
+    return G_TYPE_INVALID;
+
+  symbol = type_name_mangle (name);
+
+  if (g_module_symbol (module, symbol, (gpointer)&func))
+    gtype = func ();
+
+  g_free (symbol);
+
+  return gtype;
+}
+
+static GType
+gtk_builder_cscope_get_type_from_name (GtkBuilderScope *scope,
+                                       GtkBuilder      *builder,
+                                       const char      *type_name)
+{
+  GtkBuilderCScope *self = GTK_BUILDER_CSCOPE (scope);
+  GType type;
+
+  type = g_type_from_name (type_name);
+  if (type != G_TYPE_INVALID)
+    return type;
+
+  type = gtk_builder_cscope_resolve_type_lazily (self, type_name);
+  if (type != G_TYPE_INVALID)
+    return type;
+
+  gtk_test_register_all_types ();
+  type = g_type_from_name (type_name);
+
+  return type;
+}
+
+static GClosure *
+gtk_builder_cscope_create_closure_for_funcptr (GtkBuilderCScope *self,
+                                               GtkBuilder       *builder,
+                                               GCallback         callback,
+                                               gboolean          swapped,
+                                               GObject          *object)
+{
+  GClosure *closure;
+
+  if (object == NULL)
+    object = gtk_builder_get_current_object (builder);
+
+  if (object)
+    {
+      if (swapped)
+        closure = g_cclosure_new_object_swap (callback, object);
+      else
+        closure = g_cclosure_new_object (callback, object);
+    }
+  else
+    {
+      if (swapped)
+        closure = g_cclosure_new_swap (callback, NULL, NULL);
+      else
+        closure = g_cclosure_new (callback, NULL, NULL);
+    }
+
+  return closure;
+}
+
+static GClosure *
+gtk_builder_cscope_create_closure (GtkBuilderScope        *scope,
+                                   GtkBuilder             *builder,
+                                   const char             *function_name,
+                                   GtkBuilderClosureFlags  flags,
+                                   GObject                *object,
+                                   GError                **error)
+{
+  GtkBuilderCScope *self = GTK_BUILDER_CSCOPE (scope);
+  GModule *module = gtk_builder_cscope_get_module (self);
+  GCallback func;
+  gboolean swapped;
+
+  swapped = flags & GTK_BUILDER_CLOSURE_SWAPPED;
+
+  func = gtk_builder_cscope_lookup_callback_symbol (self, function_name);
+  if (func)
+    return gtk_builder_cscope_create_closure_for_funcptr (self, builder, func, swapped, object);
+
+  if (module == NULL)
+    {
+      g_set_error (error,
+                   GTK_BUILDER_ERROR,
+                   GTK_BUILDER_ERROR_INVALID_FUNCTION,
+                   "Could not look up function `%s`: GModule is not supported.",
+                   function_name);
+      return NULL;
+    }
+
+  if (!g_module_symbol (module, function_name, (gpointer)&func))
+    {
+      g_set_error (error,
+                   GTK_BUILDER_ERROR,
+                   GTK_BUILDER_ERROR_INVALID_FUNCTION,
+                   "No function named `%s`.",
+                   function_name);
+      return NULL;
+    }
+
+  return gtk_builder_cscope_create_closure_for_funcptr (self, builder, func, swapped, object);
+}
+
+static void
+gtk_builder_cscope_scope_init (GtkBuilderScopeInterface *iface)
+{
+  iface->get_type_from_name = gtk_builder_cscope_get_type_from_name;
+  iface->create_closure = gtk_builder_cscope_create_closure;
+}
+
+static void
+gtk_builder_cscope_finalize (GObject *object)
+{
+  GtkBuilderCScope *self = GTK_BUILDER_CSCOPE (object);
+  GtkBuilderCScopePrivate *priv = gtk_builder_cscope_get_instance_private (self);
+
+  g_clear_pointer (&priv->callbacks, g_hash_table_destroy);
+  g_clear_pointer (&priv->module, g_module_close);
+
+  G_OBJECT_CLASS (gtk_builder_cscope_parent_class)->dispose (object);
+}
+  
+static void
+gtk_builder_cscope_class_init (GtkBuilderCScopeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gtk_builder_cscope_finalize;
+}
+
+static void
+gtk_builder_cscope_init (GtkBuilderCScope *self)
+{
+}
+
+/**
+ * gtk_builder_cscope_new:
+ *
+ * Creates a new #GtkbuilderCScope object to use with future #GtkBuilder
+ * instances.
+ *
+ * Calling this function is only necessary if you want to add custom
+ * callbacks via gtk_builder_cscope_add_callback_symbol().
+ *
+ * Returns: a new #GtkBuilderCScope
+ **/
+GtkBuilderScope *
+gtk_builder_cscope_new (void)
+{
+  return g_object_new (GTK_TYPE_BUILDER_CSCOPE, NULL);
+}
+
+/**
+ * gtk_builder_cscope_add_callback_symbol:
+ * @self: a #GtkBuilderCScope
+ * @callback_name: The name of the callback, as expected in the XML
+ * @callback_symbol: (scope async): The callback pointer
+ *
+ * Adds the @callback_symbol to the scope of @builder under the given @callback_name.
+ *
+ * Using this function overrides the behavior of gtk_builder_create_closure()
+ * for any callback symbols that are added. Using this method allows for better
+ * encapsulation as it does not require that callback symbols be declared in
+ * the global namespace.
+ */
+void
+gtk_builder_cscope_add_callback_symbol (GtkBuilderCScope *self,
+                                        const gchar      *callback_name,
+                                        GCallback         callback_symbol)
+{
+  GtkBuilderCScopePrivate *priv = gtk_builder_cscope_get_instance_private (self);
+
+  g_return_if_fail (GTK_IS_BUILDER_CSCOPE (self));
+  g_return_if_fail (callback_name && callback_name[0]);
+  g_return_if_fail (callback_symbol != NULL);
+
+  if (!priv->callbacks)
+    priv->callbacks = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                             g_free, NULL);
+
+  g_hash_table_insert (priv->callbacks, g_strdup (callback_name), callback_symbol);
+}
+
+/**
+ * gtk_builder_cscope_add_callback_symbols: (skip)
+ * @self: a #GtkBuilderCScope
+ * @first_callback_name: The name of the callback, as expected in the XML
+ * @first_callback_symbol: (scope async): The callback pointer
+ * @...: A list of callback name and callback symbol pairs terminated with %NULL
+ *
+ * A convenience function to add many callbacks instead of calling
+ * gtk_builder_add_callback_symbol() for each symbol.
+ */
+void
+gtk_builder_cscope_add_callback_symbols (GtkBuilderCScope *self,
+                                         const gchar      *first_callback_name,
+                                         GCallback         first_callback_symbol,
+                                         ...)
+{
+  va_list var_args;
+  const gchar *callback_name;
+  GCallback callback_symbol;
+
+  g_return_if_fail (GTK_IS_BUILDER_CSCOPE (self));
+  g_return_if_fail (first_callback_name && first_callback_name[0]);
+  g_return_if_fail (first_callback_symbol != NULL);
+
+  callback_name = first_callback_name;
+  callback_symbol = first_callback_symbol;
+
+  va_start (var_args, first_callback_symbol);
+
+  do {
+
+    gtk_builder_cscope_add_callback_symbol (self, callback_name, callback_symbol);
+
+    callback_name = va_arg (var_args, const gchar*);
+
+    if (callback_name)
+      callback_symbol = va_arg (var_args, GCallback);
+
+  } while (callback_name != NULL);
+
+  va_end (var_args);
+}
+
+/**
+ * gtk_builder_lookup_callback_symbol: (skip)
+ * @self: a #GtkBuilderCScope
+ * @callback_name: The name of the callback
+ *
+ * Fetches a symbol previously added to @self
+ * with gtk_builder_add_callback_symbols().
+ *
+ * Returns: (nullable): The callback symbol in @builder for @callback_name, or %NULL
+ */
+GCallback
+gtk_builder_cscope_lookup_callback_symbol (GtkBuilderCScope *self,
+                                           const gchar      *callback_name)
+{
+  GtkBuilderCScopePrivate *priv = gtk_builder_cscope_get_instance_private (self);
+
+  g_return_val_if_fail (GTK_IS_BUILDER_CSCOPE (self), NULL);
+  g_return_val_if_fail (callback_name && callback_name[0], NULL);
+
+  if (priv->callbacks == NULL)
+    return NULL;
+
+  return g_hash_table_lookup (priv->callbacks, callback_name);
+}
+
diff --git a/gtk/gtkbuilderscope.h b/gtk/gtkbuilderscope.h
new file mode 100644
index 0000000000..72999b1e0f
--- /dev/null
+++ b/gtk/gtkbuilderscope.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright © 2019 Benjamin Otte
+ *
+ * This library 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.1 of the License, or (at your option) any later version.
+ *
+ * This library 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 this library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: Benjamin Otte <otte gnome org>
+ */
+
+#ifndef __GTK_BUILDER_SCOPE_H__
+#define __GTK_BUILDER_SCOPE_H__
+
+#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
+#error "Only <gtk/gtk.h> can be included directly."
+#endif
+
+#include <gtk/gtktypes.h>
+
+G_BEGIN_DECLS
+
+#define GTK_TYPE_BUILDER_SCOPE               (gtk_builder_scope_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_INTERFACE (GtkBuilderScope, gtk_builder_scope, GTK, BUILDER_SCOPE, GObject)
+
+/**
+ * GtkBuilderClosureFlags:
+ * @GTK_BUILDER_CLOSURE_SWAPPED: The closure should be created swapped. See
+ *   g_cclosure_new_swapped() for details.
+ *
+ * The list of flags that can be passed to gtk_builder_scope_create_closure().
+ * New values may be added in the future for new features, so external
+ * implementations of GtkBuilderScopeInterface should test the flags for unknown
+ * values and raise a %@GTK_BUILDER_ERROR_INVALID_ATTRIBUTE error when they
+ * encounter one.
+ */
+typedef enum {
+  GTK_BUILDER_CLOSURE_SWAPPED = (1 << 0)
+} GtkBuilderClosureFlags;
+
+struct _GtkBuilderScopeInterface
+{
+  /*< private >*/
+  GTypeInterface g_iface;
+
+  /*< public >*/
+  GType                 (* get_type_from_name)                  (GtkBuilderScope        *self,
+                                                                 GtkBuilder             *builder,
+                                                                 const char             *type_name);
+
+  GClosure *            (* create_closure)                      (GtkBuilderScope        *self,
+                                                                 GtkBuilder             *builder,
+                                                                 const char             *function_name,
+                                                                 GtkBuilderClosureFlags  flags,
+                                                                 GObject                *object,
+                                                                 GError                **error);
+};
+
+
+
+struct _GtkBuilderCScopeClass
+{
+  GObjectClass parent_class;
+};
+
+#define GTK_TYPE_BUILDER_CSCOPE               (gtk_builder_cscope_get_type ())
+
+GDK_AVAILABLE_IN_ALL
+G_DECLARE_DERIVABLE_TYPE (GtkBuilderCScope, gtk_builder_cscope, GTK, BUILDER_CSCOPE, GObject)
+
+GDK_AVAILABLE_IN_ALL
+GtkBuilderScope *       gtk_builder_cscope_new                  (void);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_builder_cscope_add_callback_symbol  (GtkBuilderCScope       *self,
+                                                                 const gchar            *callback_name,
+                                                                 GCallback               callback_symbol);
+GDK_AVAILABLE_IN_ALL
+void                    gtk_builder_cscope_add_callback_symbols (GtkBuilderCScope       *self,
+                                                                 const gchar            *first_callback_name,
+                                                                 GCallback               
first_callback_symbol,
+                                                                ...) G_GNUC_NULL_TERMINATED;
+GDK_AVAILABLE_IN_ALL
+GCallback               gtk_builder_cscope_lookup_callback_symbol(GtkBuilderCScope      *self,
+                                                                 const gchar           *callback_name);
+
+
+G_END_DECLS
+
+#endif /* __GTK_BUILDER_SCOPE_H__ */
diff --git a/gtk/gtktypes.h b/gtk/gtktypes.h
index e324ea5b90..0f336f9496 100644
--- a/gtk/gtktypes.h
+++ b/gtk/gtktypes.h
@@ -35,6 +35,7 @@ G_BEGIN_DECLS
 
 typedef struct _GtkAdjustment          GtkAdjustment;
 typedef struct _GtkBuilder             GtkBuilder;
+typedef struct _GtkBuilderScope        GtkBuilderScope;
 typedef struct _GtkClipboard          GtkClipboard;
 typedef struct _GtkEventController     GtkEventController;
 typedef struct _GtkGesture             GtkGesture;
@@ -53,13 +54,6 @@ typedef struct _GtkWidget              GtkWidget;
 typedef struct _GtkWidgetPath          GtkWidgetPath;
 typedef struct _GtkWindow              GtkWindow;
 
-typedef GClosure*       (* GtkBuilderClosureFunc)               (GtkBuilder             *builder,
-                                                                 const char             *function_name,
-                                                                 gboolean                swapped,
-                                                                 GObject                *object,
-                                                                 gpointer                user_data,
-                                                                 GError                **error);
-
 G_END_DECLS
 
 #endif /* __GTK_TYPES_H__ */
diff --git a/gtk/gtkwidget.c b/gtk/gtkwidget.c
index 85239e4c74..2b4e68f423 100644
--- a/gtk/gtkwidget.c
+++ b/gtk/gtkwidget.c
@@ -489,10 +489,7 @@ typedef struct {
 typedef struct {
   GBytes               *data;
   GSList               *children;
-  GSList               *callbacks;
-  GtkBuilderClosureFunc closure_func;
-  gpointer              closure_data;
-  GDestroyNotify        closure_destroy;
+  GtkBuilderScope      *scope;
 } GtkWidgetTemplate;
 
 struct _GtkWidgetClassPrivate
@@ -11999,28 +11996,6 @@ template_child_class_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)
 {
@@ -12028,11 +12003,8 @@ template_data_free (GtkWidgetTemplate *template_data)
     {
       g_bytes_unref (template_data->data);
       g_slist_free_full (template_data->children, (GDestroyNotify)template_child_class_free);
-      g_slist_free_full (template_data->callbacks, (GDestroyNotify)callback_symbol_free);
 
-      if (template_data->closure_data &&
-         template_data->closure_destroy)
-       template_data->closure_destroy (template_data->closure_data);
+      g_object_unref (template_data->scope);
 
       g_slice_free (GtkWidgetTemplate, template_data);
     }
@@ -12157,24 +12129,10 @@ gtk_widget_init_template (GtkWidget *widget)
 
   builder = gtk_builder_new ();
 
-  gtk_builder_set_current_object (builder, G_OBJECT (widget));
+  if (template->scope)
+    gtk_builder_set_scope (builder, template->scope);
 
-  /* Setup closure handling. All signal data from a template receive the 
-   * template instance as user data automatically.
-   *
-   * A GtkBuilderClosureFunc can be provided to gtk_widget_class_set_signal_closure_func()
-   * in order for templates to be usable by bindings.
-   */
-  if (template->closure_func)
-    gtk_builder_set_closure_func (builder, template->closure_func, template->closure_data, NULL);
-
-  /* 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);
-    }
+  gtk_builder_set_current_object (builder, G_OBJECT (widget));
 
   /* 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
@@ -12313,7 +12271,9 @@ gtk_widget_class_set_template_from_resource (GtkWidgetClass    *widget_class,
  * @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().
+ * defined for @widget_type. This function is not supported after
+ * gtk_widget_class_set_template_scope() has been used on @widget_class.
+ * See gtk_builder_cscope_add_callback_symbol().
  *
  * Note that this must be called from a composite widget classes class
  * initializer after calling gtk_widget_class_set_template().
@@ -12323,48 +12283,50 @@ gtk_widget_class_bind_template_callback_full (GtkWidgetClass *widget_class,
                                               const gchar    *callback_name,
                                               GCallback       callback_symbol)
 {
-  CallbackSymbol *cb;
+  GtkWidgetTemplate *template;
 
   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);
+  template = widget_class->priv->template;
+  if (template->scope == NULL)
+    template->scope = gtk_builder_cscope_new ();
+
+  if (GTK_IS_BUILDER_CSCOPE (template->scope))
+    {
+      gtk_builder_cscope_add_callback_symbol (GTK_BUILDER_CSCOPE (template->scope),
+                                              callback_name,
+                                              callback_symbol);
+    }
+  else
+    {
+      g_critical ("Adding a callback to %s, but scope is not a GtkBuilderCScope.", G_OBJECT_CLASS_NAME 
(widget_class));
+    }
 }
 
 /**
- * gtk_widget_class_set_closure_func:
+ * gtk_widget_class_set_template_scope:
  * @widget_class: A #GtkWidgetClass
- * @closure_func: The #GtkBuilderClosureFunc to use when creating closure in the class template
- * @closure_data: The data to pass to @closure_func
- * @closure_data_destroy: The #GDestroyNotify to free @closure_data, this will only be used at
- *                        class finalization time, when no classes of type @widget_type are in use anymore.
+ * @scope: (transfer none): The #GtkBuilderScope to use when loading the class template
  *
- * For use in language bindings, this will override the default #GtkBuilderClosureFunc to be
+ * For use in language bindings, this will override the default #GtkBuilderScope to be
  * used when parsing GtkBuilder XML from this class’s template data.
  *
  * Note that this must be called from a composite widget classes class
  * initializer after calling gtk_widget_class_set_template().
  */
 void
-gtk_widget_class_set_closure_func (GtkWidgetClass        *widget_class,
-                                  GtkBuilderClosureFunc  closure_func,
-                                  gpointer               closure_data,
-                                  GDestroyNotify         closure_data_destroy)
+gtk_widget_class_set_template_scope (GtkWidgetClass  *widget_class,
+                                    GtkBuilderScope *scope)
 {
   g_return_if_fail (GTK_IS_WIDGET_CLASS (widget_class));
   g_return_if_fail (widget_class->priv->template != NULL);
+  g_return_if_fail (GTK_IS_BUILDER_SCOPE (scope));
 
   /* Defensive, destroy any previously set data */
-  if (widget_class->priv->template->closure_data &&
-      widget_class->priv->template->closure_destroy)
-    widget_class->priv->template->closure_destroy (widget_class->priv->template->closure_data);
-
-  widget_class->priv->template->closure_func    = closure_func;
-  widget_class->priv->template->closure_data    = closure_data;
-  widget_class->priv->template->closure_destroy = closure_data_destroy;
+  g_set_object (&widget_class->priv->template->scope, scope);
 }
 
 /**
diff --git a/gtk/gtkwidget.h b/gtk/gtkwidget.h
index f9c45a8ec3..71a484b9d7 100644
--- a/gtk/gtkwidget.h
+++ b/gtk/gtkwidget.h
@@ -850,7 +850,9 @@ void gtk_widget_remove_tick_callback (GtkWidget       *widget,
  * Binds a callback function defined in a template to the @widget_class.
  *
  * This macro is a convenience wrapper around the
- * gtk_widget_class_bind_template_callback_full() function.
+ * gtk_widget_class_bind_template_callback_full() function. It is not
+ * supported after gtk_widget_class_set_template_scope() has been used
+ * on @widget_class.
  */
 #define gtk_widget_class_bind_template_callback(widget_class, callback) \
   gtk_widget_class_bind_template_callback_full (GTK_WIDGET_CLASS (widget_class), \
@@ -959,10 +961,8 @@ void    gtk_widget_class_bind_template_callback_full    (GtkWidgetClass        *
                                                          const gchar           *callback_name,
                                                          GCallback              callback_symbol);
 GDK_AVAILABLE_IN_ALL
-void    gtk_widget_class_set_closure_func               (GtkWidgetClass        *widget_class,
-                                                        GtkBuilderClosureFunc  closure_func,
-                                                         gpointer               closure_data,
-                                                         GDestroyNotify         closure_destroy);
+void    gtk_widget_class_set_template_scope             (GtkWidgetClass        *widget_class,
+                                                         GtkBuilderScope       *scope);
 GDK_AVAILABLE_IN_ALL
 void    gtk_widget_class_bind_template_child_full       (GtkWidgetClass        *widget_class,
                                                          const gchar           *name,
diff --git a/gtk/meson.build b/gtk/meson.build
index d1ff46fccf..e887d3e464 100644
--- a/gtk/meson.build
+++ b/gtk/meson.build
@@ -181,8 +181,9 @@ gtk_public_sources = files([
   'gtkbox.c',
   'gtkbuildable.c',
   'gtkbuilder.c',
-  'gtkbuilderparser.c',
   'gtkbuilderlistitemfactory.c',
+  'gtkbuilderparser.c',
+  'gtkbuilderscope.c',
   'gtkbutton.c',
   'gtkcalendar.c',
   'gtkcellarea.c',
@@ -468,6 +469,7 @@ gtk_public_headers = files([
   'gtkbuildable.h',
   'gtkbuilder.h',
   'gtkbuilderlistitemfactory.h',
+  'gtkbuilderscope.h',
   'gtkbutton.h',
   'gtkcalendar.h',
   'gtkcenterbox.h',
diff --git a/testsuite/reftests/reftest-snapshot.c b/testsuite/reftests/reftest-snapshot.c
index e7f7dcff49..29d5455b32 100644
--- a/testsuite/reftests/reftest-snapshot.c
+++ b/testsuite/reftests/reftest-snapshot.c
@@ -31,6 +31,142 @@
 
 #include <string.h>
 
+#define REFTEST_TYPE_SCOPE               (reftest_scope_get_type ())
+
+G_DECLARE_FINAL_TYPE (ReftestScope, reftest_scope, REFTEST, SCOPE, GtkBuilderCScope)
+
+static GtkBuilderScopeInterface *parent_scope_iface;
+
+struct _ReftestScope
+{
+  GtkBuilderCScope parent_instance;
+
+  char *directory;
+};
+
+static GClosure *
+reftest_scope_create_closure (GtkBuilderScope        *scope,
+                              GtkBuilder             *builder,
+                              const char             *function_name,
+                              GtkBuilderClosureFlags  flags,
+                              GObject                *object,
+                              GError                **error)
+{
+  ReftestScope *self = REFTEST_SCOPE (scope);
+  ReftestModule *module;
+  GCallback func;
+  GClosure *closure;
+  char **split;
+
+  split = g_strsplit (function_name, ":", -1);
+
+  switch (g_strv_length (split))
+    {
+    case 1:
+      closure = parent_scope_iface->create_closure (scope, builder, split[0], flags, object, error);
+      break;
+
+    case 2:
+      module = reftest_module_new (self->directory, split[0]);
+      if (module == NULL)
+        {
+          g_set_error (error,
+                       GTK_BUILDER_ERROR,
+                       GTK_BUILDER_ERROR_INVALID_FUNCTION,
+                       "Could not load module '%s' from '%s' when looking up '%s': %s", split[0], 
self->directory, function_name, g_module_error ());
+          return NULL;
+        }
+      func = reftest_module_lookup (module, split[1]);
+      if (!func)
+        {
+          g_set_error (error,
+                       GTK_BUILDER_ERROR,
+                       GTK_BUILDER_ERROR_INVALID_FUNCTION,
+                       "failed to lookup function for name '%s' in module '%s'", split[1], split[0]);
+          return NULL;
+        }
+
+      if (object)
+        {
+          if (flags & GTK_BUILDER_CLOSURE_SWAPPED)
+            closure = g_cclosure_new_object_swap (func, object);
+          else
+            closure = g_cclosure_new_object (func, object);
+        }
+      else
+        {
+          if (flags & GTK_BUILDER_CLOSURE_SWAPPED)
+            closure = g_cclosure_new_swap (func, NULL, NULL);
+          else
+            closure = g_cclosure_new (func, NULL, NULL);
+        }
+      
+      if (module)
+        g_closure_add_finalize_notifier (closure, module, (GClosureNotify) reftest_module_unref);
+      break;
+
+    default:
+      g_set_error (error,
+                   GTK_BUILDER_ERROR,
+                   GTK_BUILDER_ERROR_INVALID_FUNCTION,
+                   "Could not find function named '%s'", function_name);
+      return NULL;
+    }
+
+  g_strfreev (split);
+
+  return closure;
+}
+
+static void
+reftest_scope_scope_init (GtkBuilderScopeInterface *iface)
+{
+  iface->create_closure = reftest_scope_create_closure;
+
+  parent_scope_iface = g_type_interface_peek_parent (iface);
+}
+
+G_DEFINE_TYPE_WITH_CODE (ReftestScope, reftest_scope, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDER_SCOPE,
+                                                reftest_scope_scope_init))
+
+static void
+reftest_scope_finalize (GObject *object)
+{
+  ReftestScope *self = REFTEST_SCOPE (object);
+
+  g_free (self->directory);
+
+  G_OBJECT_CLASS (reftest_scope_parent_class)->finalize (object);
+}
+
+static void
+reftest_scope_class_init (ReftestScopeClass *scope_class)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (scope_class);
+
+  object_class->finalize = reftest_scope_finalize;
+}
+
+static void
+reftest_scope_init (ReftestScope *self)
+{
+}
+
+static GtkBuilderScope *
+reftest_scope_new (const char *directory)
+{
+  ReftestScope *result;
+
+  g_return_val_if_fail (directory != NULL, NULL);
+
+  result = g_object_new (REFTEST_TYPE_SCOPE, NULL);
+
+  result->directory = g_strdup (directory);
+
+  return GTK_BUILDER_SCOPE (result);
+}
+
 static GtkWidget *
 builder_get_toplevel (GtkBuilder *builder)
 {
@@ -145,119 +281,26 @@ snapshot_widget (GtkWidget *widget)
   return surface;
 }
 
-static GClosure *
-create_closure (GtkBuilder    *builder,
-                const char    *function_name,
-                gboolean       swapped,
-                GObject       *object,
-                gpointer       user_data,
-                GError        **error)
-{
-  ReftestModule *module;
-  const char *directory;
-  GCallback func;
-  GClosure *closure;
-  char **split;
-
-  directory = user_data;
-  split = g_strsplit (function_name, ":", -1);
-
-  switch (g_strv_length (split))
-    {
-    case 1:
-      func = gtk_builder_lookup_callback_symbol (builder, split[0]);
-
-      if (func)
-        {
-          module = NULL;
-        }
-      else
-        {
-          module = reftest_module_new_self ();
-          if (module == NULL)
-            {
-              g_set_error (error,
-                           GTK_BUILDER_ERROR,
-                           GTK_BUILDER_ERROR_INVALID_FUNCTION,
-                           "glib compiled without module support.");
-              return NULL;
-            }
-          func = reftest_module_lookup (module, split[0]);
-          if (!func)
-            {
-              g_set_error (error,
-                           GTK_BUILDER_ERROR,
-                           GTK_BUILDER_ERROR_INVALID_FUNCTION,
-                           "failed to lookup function for name '%s'", split[0]);
-              return NULL;
-            }
-        }
-      break;
-    case 2:
-      if (g_getenv ("REFTEST_MODULE_DIR"))
-        directory = g_getenv ("REFTEST_MODULE_DIR");
-      module = reftest_module_new (directory, split[0]);
-      if (module == NULL)
-        {
-          g_set_error (error,
-                       GTK_BUILDER_ERROR,
-                       GTK_BUILDER_ERROR_INVALID_FUNCTION,
-                       "Could not load module '%s' from '%s' when looking up '%s': %s", split[0], directory, 
function_name, g_module_error ());
-          return NULL;
-        }
-      func = reftest_module_lookup (module, split[1]);
-      if (!func)
-        {
-          g_set_error (error,
-                       GTK_BUILDER_ERROR,
-                       GTK_BUILDER_ERROR_INVALID_FUNCTION,
-                       "failed to lookup function for name '%s' in module '%s'", split[1], split[0]);
-          return NULL;
-        }
-      break;
-    default:
-      g_set_error (error,
-                   GTK_BUILDER_ERROR,
-                   GTK_BUILDER_ERROR_INVALID_FUNCTION,
-                   "Could not find function named '%s'", function_name);
-      return NULL;
-    }
-
-  g_strfreev (split);
-
-  if (object)
-    {
-      if (swapped)
-        closure = g_cclosure_new_object_swap (func, object);
-      else
-        closure = g_cclosure_new_object (func, object);
-    }
-  else
-    {
-      if (swapped)
-        closure = g_cclosure_new_swap (func, NULL, NULL);
-      else
-        closure = g_cclosure_new (func, NULL, NULL);
-    }
-  
-  if (module)
-    g_closure_add_finalize_notifier (closure, module, (GClosureNotify) reftest_module_unref);
-
-  return closure;
-}
-
 cairo_surface_t *
 reftest_snapshot_ui_file (const char *ui_file)
 {
   GtkWidget *window;
   GtkBuilder *builder;
+  GtkBuilderScope *scope;
   GError *error = NULL;
   char *directory;
 
-  directory = g_path_get_dirname (ui_file);
+  if (g_getenv ("REFTEST_MODULE_DIR"))
+    directory = g_strdup (g_getenv ("REFTEST_MODULE_DIR"));
+  else
+    directory = g_path_get_dirname (ui_file);
+  scope = reftest_scope_new (directory);
+  g_free (directory);
 
   builder = gtk_builder_new ();
-  gtk_builder_set_closure_func (builder, create_closure, directory, g_free);
+  gtk_builder_set_scope (builder, scope);
+  g_object_unref (scope);
+
   gtk_builder_add_from_file (builder, ui_file, &error);
   g_assert_no_error (error);
   window = builder_get_toplevel (builder);


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