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.
This commit is contained in:
Benjamin Otte 2019-11-30 01:17:10 +01:00
parent 1f94028ff7
commit f8a7f30a0d
14 changed files with 942 additions and 594 deletions

View File

@ -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");

View File

@ -516,21 +516,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
@ -543,10 +563,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
@ -562,7 +583,6 @@ GTK_BUILDER_GET_CLASS
<SUBSECTION Private>
gtk_builder_get_type
gtk_builder_error_quark
GtkBuilderPrivate
</SECTION>
<SECTION>
@ -4668,7 +4688,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

View File

@ -55,6 +55,7 @@
#include <gtk/gtkbox.h>
#include <gtk/gtkbuildable.h>
#include <gtk/gtkbuilder.h>
#include <gtk/gtkbuilderscope.h>
#include <gtk/gtkbutton.h>
#include <gtk/gtkcalendar.h>
#include <gtk/gtkcellarea.h>

View File

@ -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 #GModules introspective
* features (by opening the module %NULL) to look at the applications 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,16 +213,16 @@
#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 "gtkmain.h"
#include "gtkintl.h"
#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,
@ -254,6 +237,7 @@ static void gtk_builder_get_property (GObject *object,
enum {
PROP_0,
PROP_CURRENT_OBJECT,
PROP_SCOPE,
PROP_TRANSLATION_DOMAIN,
LAST_PROP
};
@ -274,19 +258,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)
@ -297,6 +276,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);
}
@ -338,6 +318,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);
}
@ -361,18 +353,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);
@ -393,6 +378,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;
@ -418,6 +407,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;
@ -428,79 +421,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
*/
@ -1761,30 +1681,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
* gtk_builder_get_scope:
* @self: a #GtkBuilder
*
* Prototype of function used to create closures by @builder. It is meant
* for influencing how @function_name is resolved.
* Gets the scope in use that was set via gtk_builder_set_scope().
*
* 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.
* See the #GtkBuilderScope documentation for details.
*
* 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: (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
*
* Returns: (nullable): a new #GClosure or %NULL when no closure could
* be created and @error was set.
*/
* 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,
@ -2561,21 +2509,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));
@ -2643,176 +2585,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
*
@ -2830,7 +2607,7 @@ gtk_builder_create_closure_for_funcptr (GtkBuilder *builder,
GClosure *
gtk_builder_create_closure (GtkBuilder *builder,
const char *function_name,
gboolean swapped,
GtkBuilderClosureFlags flags,
GObject *object,
GError **error)
{
@ -2841,66 +2618,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);
}
/**

View File

@ -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);

459
gtk/gtkbuilderscope.c Normal file
View File

@ -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 #GModules
* introspective features (by opening the module %NULL) to look at the applications
* 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);
}

116
gtk/gtkbuilderscope.h Normal file
View File

@ -0,0 +1,116 @@
/*
* 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_swap() 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;
/**
* GtkBuilderScopeInterface:
* @get_type_from_name: Try to lookup a #GType via the its name. See
* gtk_builder_get_type_from_name() for more details.
* The C implementation will use g_type_from_name() and if that fails try to guess the
* correct function name for registering the type and then use dlsym() to load it.
* The default implementation just tries g_type_from_name() and otherwise fails.
* @create_closure: Create a closure with the given arguments. See gtk_builder_create_closure()
* for more details on those.
* The C implementation will try to use dlsym() to locate the function name and then
* g_cclosure_new() to create a closure for the symbol.
* The default implementation just fails and returns %NULL.
*
* The virtual function table to implement for #GtkBuilderScope implementations.
* Default implementations for each function do exist, but they usually just fail,
* so it is suggested that implementations implement all of them.
*/
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__ */

View File

@ -0,0 +1,40 @@
/*
* 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_PRIVATE_H__
#define __GTK_BUILDER_SCOPE_PRIVATE_H__
#include <gtk/gtkbuilderscope.h>
G_BEGIN_DECLS
GType gtk_builder_scope_get_type_from_name (GtkBuilderScope *self,
GtkBuilder *builder,
const char *type_name);
GClosure * gtk_builder_scope_create_closure (GtkBuilderScope *self,
GtkBuilder *builder,
const char *function_name,
GtkBuilderClosureFlags flags,
GObject *object,
GError **error);
G_END_DECLS
#endif /* __GTK_BUILDER_SCOPE_PRIVATE_H__ */

View File

@ -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;
@ -51,13 +52,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);
/**
* GTK_INVALID_LIST_POSITION:
*

View File

@ -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
@ -11996,28 +11993,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)
{
@ -12025,11 +12000,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);
}
@ -12154,25 +12126,11 @@ gtk_widget_init_template (GtkWidget *widget)
builder = gtk_builder_new ();
if (template->scope)
gtk_builder_set_scope (builder, template->scope);
gtk_builder_set_current_object (builder, G_OBJECT (widget));
/* 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);
}
/* 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 infinite recursion.
@ -12310,7 +12268,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().
@ -12320,48 +12280,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 classs 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);
}
/**

View File

@ -847,7 +847,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), \
@ -956,10 +958,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,

View File

@ -178,6 +178,7 @@ gtk_public_sources = files([
'gtkbuildable.c',
'gtkbuilder.c',
'gtkbuilderparser.c',
'gtkbuilderscope.c',
'gtkbutton.c',
'gtkcalendar.c',
'gtkcellarea.c',
@ -443,6 +444,7 @@ gtk_public_headers = files([
'gtkboxlayout.h',
'gtkbuildable.h',
'gtkbuilder.h',
'gtkbuilderscope.h',
'gtkbutton.h',
'gtkcalendar.h',
'gtkcenterbox.h',

View File

@ -140,6 +140,11 @@ test_type (gconstpointer data)
if ((pspec->flags & G_PARAM_READABLE) == 0)
continue;
/* This is set via construct property */
if (g_type_is_a (type, GTK_TYPE_BUILDER) &&
strcmp (pspec->name, "scope") == 0)
continue;
if (g_type_is_a (type, GDK_TYPE_CLIPBOARD) &&
strcmp (pspec->name, "display") == 0)
continue;

View File

@ -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, GTK_TYPE_BUILDER_CSCOPE,
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;
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);