gtk2/gtk/gtkbuilder.c
2022-07-31 21:37:20 -07:00

3145 lines
98 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* GTK - The GIMP Toolkit
* Copyright (C) 1998-2002 James Henstridge <james@daa.com.au>
* Copyright (C) 2006-2007 Async Open Source,
* Johan Dahlin <jdahlin@async.com.br>,
* Henrique Romano <henrique@async.com.br>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* GtkBuilder:
*
* A `GtkBuilder` reads XML descriptions of a user interface and
* instantiates the described objects.
*
* To create a `GtkBuilder` from a user interface description, call
* [ctor@Gtk.Builder.new_from_file], [ctor@Gtk.Builder.new_from_resource]
* or [ctor@Gtk.Builder.new_from_string].
*
* In the (unusual) case that you want to add user interface
* descriptions from multiple sources to the same `GtkBuilder` you can
* call [ctor@Gtk.Builder.new] to get an empty builder and populate it by
* (multiple) calls to [method@Gtk.Builder.add_from_file],
* [method@Gtk.Builder.add_from_resource] or
* [method@Gtk.Builder.add_from_string].
*
* A `GtkBuilder` holds a reference to all objects that it has constructed
* and drops these references when it is finalized. This finalization can
* cause the destruction of non-widget objects or widgets which are not
* contained in a toplevel window. For toplevel windows constructed by a
* builder, it is the responsibility of the user to call
* [method@Gtk.Window.destroy] to get rid of them and all the widgets
* they contain.
*
* The functions [method@Gtk.Builder.get_object] and
* [method@Gtk.Builder.get_objects] can be used to access the widgets in
* the interface by the names assigned to them inside the UI description.
* Toplevel windows returned by these functions will stay around until the
* user explicitly destroys them with [method@Gtk.Window.destroy]. Other
* widgets will either be part of a larger hierarchy constructed by the
* builder (in which case you should not have to worry about their lifecycle),
* or without a parent, in which case they have to be added to some container
* to make use of them. Non-widget objects need to be reffed with
* g_object_ref() to keep them beyond the lifespan of the builder.
*
* # GtkBuilder UI Definitions
*
* `GtkBuilder` parses textual descriptions of user interfaces which are
* specified in XML format. We refer to these descriptions as “GtkBuilder
* UI definitions” or just “UI definitions” if the context is clear.
*
* The toplevel element is `<interface>`. It optionally takes a “domain”
* attribute, which will make the builder look for translated strings
* using `dgettext()` in the domain specified. This can also be done by
* calling [method@Gtk.Builder.set_translation_domain] on the builder.
*
* Objects are described by `<object>` elements, which can contain
* `<property>` elements to set properties, `<signal>` elements which
* connect signals to handlers, and `<child>` elements, which describe
* child objects (most often widgets inside a container, but also e.g.
* actions in an action group, or columns in a tree model). A `<child>`
* element contains an `<object>` element which describes the child object.
*
* The target toolkit version(s) are described by `<requires>` elements,
* the “lib” attribute specifies the widget library in question (currently
* the only supported value is “gtk”) and the “version” attribute specifies
* the target version in the form “`<major>`.`<minor>`”. `GtkBuilder` will
* error out if the version requirements are not met.
*
* Typically, the specific kind of object represented by an `<object>`
* element is specified by the “class” attribute. If the type has not
* been loaded yet, GTK tries to find the `get_type()` function from the
* class name by applying heuristics. This works in most cases, but if
* necessary, it is possible to specify the name of the `get_type()`
* function explicitly with the "type-func" attribute.
*
* Objects may be given a name with the “id” attribute, which allows the
* application to retrieve them from the builder with
* [method@Gtk.Builder.get_object]. An id is also necessary to use the
* object as property value in other parts of the UI definition. GTK
* reserves ids starting and ending with `___` (three consecutive
* underscores) for its own purposes.
*
* Setting properties of objects is pretty straightforward with the
* `<property>` element: the “name” attribute specifies the name of the
* property, and the content of the element specifies the value.
* If the “translatable” attribute is set to a true value, GTK uses
* `gettext()` (or `dgettext()` if the builder has a translation domain set)
* to find a translation for the value. This happens before the value
* is parsed, so it can be used for properties of any type, but it is
* probably most useful for string properties. It is also possible to
* specify a context to disambiguate short strings, and comments which
* may help the translators.
*
* `GtkBuilder` can parse textual representations for the most common
* property types: characters, strings, integers, floating-point numbers,
* booleans (strings like “TRUE”, “t”, “yes”, “y”, “1” are interpreted
* as %TRUE, strings like “FALSE”, “f”, “no”, “n”, “0” are interpreted
* as %FALSE), enumerations (can be specified by their name, nick or
* integer value), flags (can be specified by their name, nick, integer
* value, optionally combined with “|”, e.g.
* “GTK_INPUT_HINT_EMOJI|GTK_INPUT_HINT_LOWERCASE”)
* and colors (in a format understood by [method@Gdk.RGBA.parse]).
*
* `GVariant`s can be specified in the format understood by
* g_variant_parse(), and pixbufs can be specified as a filename of an
* image file to load.
*
* Objects can be referred to by their name and by default refer to
* objects declared in the local XML fragment and objects exposed via
* [method@Gtk.Builder.expose_object]. In general, `GtkBuilder` allows
* forward references to objects — declared in the local XML; an object
* doesnt have to be constructed before it can be referred to. The
* exception to this rule is that an object has to be constructed before
* it can be used as the value of a construct-only property.
*
* It is also possible to bind a property value to another object's
* property value using the attributes "bind-source" to specify the
* source object of the binding, and optionally, "bind-property" and
* "bind-flags" to specify the source property and source binding flags
* respectively. Internally, `GtkBuilder` implements this using `GBinding`
* objects. For more information see g_object_bind_property().
*
* Sometimes it is necessary to refer to widgets which have implicitly
* been constructed by GTK as part of a composite widget, to set
* properties on them or to add further children (e.g. the content area
* of a `GtkDialog`). This can be achieved by setting the “internal-child”
* property of the `<child>` element to a true value. Note that `GtkBuilder`
* still requires an `<object>` element for the internal child, even if it
* has already been constructed.
*
* A number of widgets have different places where a child can be added
* (e.g. tabs vs. page content in notebooks). This can be reflected in
* a UI definition by specifying the “type” attribute on a `<child>`
* The possible values for the “type” attribute are described in the
* sections describing the widget-specific portions of UI definitions.
*
* # Signal handlers and function pointers
*
* Signal handlers are set up with the `<signal>` element. The “name”
* attribute specifies the name of the signal, and the “handler” attribute
* specifies the function to connect to the signal.
* The remaining attributes, “after”, “swapped” and “object”, have the
* same meaning as the corresponding parameters of the
* g_signal_connect_object() or g_signal_connect_data() functions. A
* “last_modification_time” attribute is also allowed, but it does not
* have a meaning to the builder.
*
* If you rely on `GModule` support to lookup callbacks in the symbol table,
* the following details should be noted:
*
* When compiling applications for Windows, you must declare signal callbacks
* with %G_MODULE_EXPORT, or they will not be put in the symbol table.
* On Linux and Unix, this is not necessary; applications should instead
* be compiled with the -Wl,--export-dynamic `CFLAGS`, and linked against
* `gmodule-export-2.0`.
*
* # A GtkBuilder UI Definition
*
* ```xml
* <interface>
* <object class="GtkDialog" id="dialog1">
* <child internal-child="content_area">
* <object class="GtkBox" id="vbox1">
* <child internal-child="action_area">
* <object class="GtkBox" id="hbuttonbox1">
* <child>
* <object class="GtkButton" id="ok_button">
* <property name="label" translatable="yes">_Ok</property>
* <property name="use-underline">True</property>
* <signal name="clicked" handler="ok_button_clicked"/>
* </object>
* </child>
* </object>
* </child>
* </object>
* </child>
* </object>
* </interface>
* ```
*
* Beyond this general structure, several object classes define their
* own XML DTD fragments for filling in the ANY placeholders in the DTD
* above. Note that a custom element in a <child> element gets parsed by
* the custom tag handler of the parent object, while a custom element in
* an <object> element gets parsed by the custom tag handler of the object.
*
* These XML fragments are explained in the documentation of the
* respective objects.
*
* A `<template>` tag can be used to define a widget classs components.
* See the [GtkWidget documentation](class.Widget.html#building-composite-widgets-from-template-xml) for details.
*/
#include "config.h"
#include <errno.h> /* errno */
#include <stdlib.h>
#include <string.h> /* strlen */
#include "gtkbuilderprivate.h"
#include "gtkbuildableprivate.h"
#include "gtkbuilderlistitemfactory.h"
#include "gtkbuilderscopeprivate.h"
#include "gtkdebug.h"
#include "gtkexpression.h"
#include "gtkmain.h"
#include "gtkicontheme.h"
#include "gtkintl.h"
#include "gtkprivate.h"
#include "gtkshortcutactionprivate.h"
#include "gtkshortcuttrigger.h"
#include "gtktestutils.h"
#include "gtktypebuiltins.h"
#include "gtkicontheme.h"
#include "gtkiconthemeprivate.h"
#include "gtkdebug.h"
static void gtk_builder_finalize (GObject *object);
static void gtk_builder_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void gtk_builder_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
enum {
PROP_0,
PROP_CURRENT_OBJECT,
PROP_SCOPE,
PROP_TRANSLATION_DOMAIN,
LAST_PROP
};
static GParamSpec *builder_props[LAST_PROP];
struct _GtkBuilder
{
GObject parent_instance;
};
struct _GtkBuilderClass
{
GObjectClass parent_class;
};
typedef struct
{
char *domain;
GHashTable *objects;
GSList *delayed_properties;
GPtrArray *signals;
GSList *bindings;
char *filename;
char *resource_prefix;
GType template_type;
GObject *current_object;
GtkBuilderScope *scope;
} GtkBuilderPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (GtkBuilder, gtk_builder, G_TYPE_OBJECT)
static void
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);
}
static void
gtk_builder_class_init (GtkBuilderClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->dispose = gtk_builder_dispose;
gobject_class->finalize = gtk_builder_finalize;
gobject_class->set_property = gtk_builder_set_property;
gobject_class->get_property = gtk_builder_get_property;
/**
* GtkBuilder:translation-domain: (attributes org.gtk.Property.get=gtk_builder_get_translation_domain org.gtk.Property.set=gtk_builder_set_translation_domain)
*
* The translation domain used when translating property values that
* have been marked as translatable.
*
* If the translation domain is %NULL, `GtkBuilder` uses gettext(),
* otherwise g_dgettext().
*/
builder_props[PROP_TRANSLATION_DOMAIN] =
g_param_spec_string ("translation-domain", NULL, NULL,
NULL,
GTK_PARAM_READWRITE);
/**
* GtkBuilder:current-object: (attributes org.gtk.Property.get=gtk_builder_get_current_object org.gtk.Property.set=gtk_builder_set_current_object)
*
* The object the builder is evaluating for.
*/
builder_props[PROP_CURRENT_OBJECT] =
g_param_spec_object ("current-object", NULL, NULL,
G_TYPE_OBJECT,
GTK_PARAM_READWRITE);
/**
* GtkBuilder:scope: (attributes org.gtk.Property.get=gtk_builder_get_scope org.gtk.Property.set=gtk_builder_set_scope)
*
* The scope the builder is operating in
*/
builder_props[PROP_SCOPE] =
g_param_spec_object ("scope", NULL, NULL,
GTK_TYPE_BUILDER_SCOPE,
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT);
g_object_class_install_properties (gobject_class, LAST_PROP, builder_props);
}
static void
gtk_builder_init (GtkBuilder *builder)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
priv->domain = NULL;
priv->objects = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, g_object_unref);
}
/*
* GObject virtual methods
*/
static void
gtk_builder_finalize (GObject *object)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (GTK_BUILDER (object));
g_free (priv->domain);
g_free (priv->filename);
g_free (priv->resource_prefix);
#ifdef G_ENABLE_DEBUG
if (GTK_DEBUG_CHECK (BUILDER_OBJECTS))
{
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init (&iter, priv->objects);
while (g_hash_table_iter_next (&iter, &key, &value))
{
if (G_OBJECT (value)->ref_count == 1)
g_message ("builder: %s with id %s unused",
G_OBJECT_TYPE_NAME (value), (const char *)key);
}
}
#endif
g_hash_table_destroy (priv->objects);
if (priv->signals)
g_ptr_array_free (priv->signals, TRUE);
G_OBJECT_CLASS (gtk_builder_parent_class)->finalize (object);
}
static void
gtk_builder_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkBuilder *builder = GTK_BUILDER (object);
switch (prop_id)
{
case PROP_CURRENT_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;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gtk_builder_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkBuilder *builder = GTK_BUILDER (object);
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
switch (prop_id)
{
case PROP_CURRENT_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;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/*
* GtkBuilder virtual methods
*/
typedef struct
{
char *object;
GParamSpec *pspec;
char *value;
int line;
int col;
} DelayedProperty;
typedef struct
{
GPtrArray *names;
GArray *values;
} ObjectProperties;
static void
object_properties_init (ObjectProperties *self)
{
self->names = NULL;
self->values = NULL;
}
static void
object_properties_destroy (ObjectProperties *self)
{
if (self == NULL)
return;
if (self->names)
g_ptr_array_unref (self->names);
if (self->values)
g_array_unref (self->values);
}
static void
object_properties_add (ObjectProperties *self,
const char *name,
const GValue *value)
{
if (!self->names)
{
self->names = g_ptr_array_sized_new (8);
self->values = g_array_sized_new (FALSE, FALSE, sizeof (GValue), 8);
g_array_set_clear_func (self->values, (GDestroyNotify) g_value_unset);
}
g_ptr_array_add (self->names, (char *) name);
g_array_append_vals (self->values, value, 1);
g_assert (self->names->len == self->values->len);
}
static const char *
object_properties_get_name (const ObjectProperties *self,
guint idx)
{
g_assert (self->names);
return g_ptr_array_index (self->names, idx);
}
static GValue *
object_properties_get_value (const ObjectProperties *self,
guint idx)
{
g_assert (self->values);
return &g_array_index (self->values, GValue, idx);
}
static void
gtk_builder_get_parameters (GtkBuilder *builder,
GType object_type,
const char *object_name,
GPtrArray *properties,
GParamFlags filter_flags,
ObjectProperties *parameters,
ObjectProperties *filtered_parameters)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
DelayedProperty *property;
GError *error = NULL;
if (!properties)
return;
for (guint i = 0; i < properties->len; i++)
{
PropertyInfo *prop = g_ptr_array_index (properties, i);
const char *property_name = prop->pspec->name;
GValue property_value = G_VALUE_INIT;
ObjectProperties *params;
if (prop->applied)
continue;
if ((prop->pspec->flags & filter_flags) != 0)
params = filtered_parameters;
else
params = parameters;
if (!params)
continue;
if (prop->value)
{
g_value_init (&property_value, G_PARAM_SPEC_VALUE_TYPE (prop->pspec));
if (G_PARAM_SPEC_VALUE_TYPE (prop->pspec) == GTK_TYPE_EXPRESSION)
gtk_value_set_expression (&property_value, prop->value);
else
g_assert_not_reached ();
}
else if (prop->bound && (!prop->text || prop->text->len == 0))
{
/* Ignore properties with a binding and no value since they are
* only there for to express the binding.
*/
continue;
}
else if (G_IS_PARAM_SPEC_OBJECT (prop->pspec) &&
(G_PARAM_SPEC_VALUE_TYPE (prop->pspec) != GDK_TYPE_PIXBUF) &&
(G_PARAM_SPEC_VALUE_TYPE (prop->pspec) != GDK_TYPE_TEXTURE) &&
(G_PARAM_SPEC_VALUE_TYPE (prop->pspec) != GDK_TYPE_PAINTABLE) &&
(G_PARAM_SPEC_VALUE_TYPE (prop->pspec) != GTK_TYPE_SHORTCUT_TRIGGER) &&
(G_PARAM_SPEC_VALUE_TYPE (prop->pspec) != GTK_TYPE_SHORTCUT_ACTION) &&
(G_PARAM_SPEC_VALUE_TYPE (prop->pspec) != G_TYPE_FILE))
{
GObject *object = g_hash_table_lookup (priv->objects,
g_strstrip (prop->text->str));
if (object)
{
g_value_init (&property_value, G_OBJECT_TYPE (object));
g_value_set_object (&property_value, object);
}
else
{
if (prop->pspec->flags & G_PARAM_CONSTRUCT_ONLY)
{
g_warning ("Failed to get construct only property "
"%s of %s with value '%s'",
prop->pspec->name, object_name, prop->text->str);
continue;
}
/* Delay setting property */
prop->applied = TRUE;
property = g_slice_new (DelayedProperty);
property->pspec = prop->pspec;
property->object = g_strdup (object_name);
property->value = g_strdup (prop->text->str);
property->line = prop->line;
property->col = prop->col;
priv->delayed_properties = g_slist_prepend (priv->delayed_properties,
property);
continue;
}
}
else if (!gtk_builder_value_from_string (builder, prop->pspec,
prop->text->str,
&property_value,
&error))
{
g_warning ("Failed to set property %s.%s to %s: %s",
g_type_name (object_type), prop->pspec->name, prop->text->str,
error->message);
g_error_free (error);
error = NULL;
continue;
}
/* At this point, property_value has been set, and we need to either
* copy it to one of the two arrays, or unset it.
*/
g_assert (G_IS_VALUE (&property_value));
object_properties_add (params, property_name, &property_value);
prop->applied = TRUE;
}
}
static const char *
object_get_id (GObject *object)
{
if (GTK_IS_BUILDABLE (object))
return gtk_buildable_get_buildable_id (GTK_BUILDABLE (object));
else
return g_object_get_data (object, "gtk-builder-id");
}
static GObject *
gtk_builder_get_internal_child (GtkBuilder *builder,
ObjectInfo *info,
const char *childname,
GError **error)
{
GObject *obj = NULL;
while (!obj)
{
if (!info->parent)
break;
info = (ObjectInfo*)((ChildInfo*)info->parent)->parent;
if (!info)
break;
GTK_NOTE (BUILDER,
g_message ("Trying to get internal child %s from %s",
childname, object_get_id (info->object)));
if (GTK_IS_BUILDABLE (info->object))
obj = gtk_buildable_get_internal_child (GTK_BUILDABLE (info->object),
builder,
childname);
};
if (!obj)
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Unknown internal child: %s", childname);
}
return obj;
}
static inline void
object_set_id (GObject *object,
const char *id)
{
if (GTK_IS_BUILDABLE (object))
gtk_buildable_set_buildable_id (GTK_BUILDABLE (object), id);
else
g_object_set_data_full (object, "gtk-builder-id", g_strdup (id), g_free);
}
void
_gtk_builder_add_object (GtkBuilder *builder,
const char *id,
GObject *object)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
object_set_id (object, id);
g_hash_table_insert (priv->objects, g_strdup (id), g_object_ref (object));
}
void
gtk_builder_take_bindings (GtkBuilder *builder,
GObject *target,
GSList *bindings)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GSList *l;
for (l = bindings; l; l = l->next)
{
CommonInfo *common_info = l->data;
if (common_info->tag_type == TAG_BINDING)
{
BindingInfo *info = l->data;
info->target = target;
}
else if (common_info->tag_type == TAG_BINDING_EXPRESSION)
{
BindingExpressionInfo *info = l->data;
info->target = target;
}
else
{
g_assert_not_reached ();
}
}
priv->bindings = g_slist_concat (priv->bindings, bindings);
}
static void
ensure_special_construct_parameters (GtkBuilder *builder,
GType object_type,
ObjectProperties *construct_parameters)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GValue value = G_VALUE_INIT;
if (g_type_is_a (object_type, GTK_TYPE_BUILDER_LIST_ITEM_FACTORY))
{
g_value_init (&value, GTK_TYPE_BUILDER_SCOPE);
g_value_set_object (&value, priv->scope);
object_properties_add (construct_parameters, "scope", &value);
}
}
GObject *
_gtk_builder_construct (GtkBuilder *builder,
ObjectInfo *info,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
ObjectProperties parameters, construct_parameters;
GObject *obj;
int i;
GParamFlags param_filter_flags;
g_assert (info->type != G_TYPE_INVALID);
if (priv->template_type != 0 &&
g_type_is_a (info->type, priv->template_type))
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_OBJECT_TYPE_REFUSED,
"Refused to build object of type '%s' because it "
"conforms to the template type '%s', avoiding infinite recursion.",
g_type_name (info->type), g_type_name (priv->template_type));
return NULL;
}
/* If there is a manual constructor (like UIManager), or if this is a
* reference to an internal child, then we filter out construct-only
* and warn that they cannot be set.
*
* Otherwise if we are calling g_object_new_with_properties(), we want
* to pass both G_PARAM_CONSTRUCT and G_PARAM_CONSTRUCT_ONLY to the
* object's constructor.
*
* Passing all construct properties to g_object_new_with_properties()
* slightly improves performance as the construct properties will only
* be set once.
*/
if (info->constructor ||
(info->parent &&
info->parent->tag_type == TAG_CHILD &&
((ChildInfo*)info->parent)->internal_child != NULL))
param_filter_flags = G_PARAM_CONSTRUCT_ONLY;
else
param_filter_flags = G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY;
object_properties_init (&parameters);
object_properties_init (&construct_parameters);
gtk_builder_get_parameters (builder, info->type,
info->id,
info->properties,
param_filter_flags,
&parameters,
&construct_parameters);
if (info->constructor)
{
GObject *constructor;
constructor = g_hash_table_lookup (priv->objects, info->constructor);
if (constructor == NULL)
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Unknown object constructor for %s: %s",
info->id,
info->constructor);
object_properties_destroy (&parameters);
object_properties_destroy (&construct_parameters);
return NULL;
}
obj = gtk_buildable_construct_child (GTK_BUILDABLE (constructor),
builder,
info->id);
g_assert (obj != NULL);
if (construct_parameters.names->len > 0)
g_warning ("Can't pass in construct-only parameters to %s", info->id);
}
else if (info->parent &&
info->parent->tag_type == TAG_CHILD &&
((ChildInfo*)info->parent)->internal_child != NULL)
{
char *childname = ((ChildInfo*)info->parent)->internal_child;
obj = gtk_builder_get_internal_child (builder, info, childname, error);
if (!obj)
{
object_properties_destroy (&parameters);
object_properties_destroy (&construct_parameters);
return NULL;
}
if (construct_parameters.names)
g_warning ("Can't pass in construct-only parameters to %s", childname);
g_object_ref (obj);
}
else
{
ensure_special_construct_parameters (builder, info->type, &construct_parameters);
if (construct_parameters.names)
obj = g_object_new_with_properties (info->type,
construct_parameters.names->len,
(const char **) construct_parameters.names->pdata,
(GValue *) construct_parameters.values->data);
else
obj = g_object_new (info->type, NULL);
/* No matter what, make sure we have a reference.
*
* If it's an initially unowned object, sink it.
* If it's not initially unowned then we have the reference already.
*
* In the case that this is a window it will be sunk already and
* this is effectively a call to g_object_ref(). That's what
* we want.
*/
if (G_IS_INITIALLY_UNOWNED (obj))
g_object_ref_sink (obj);
GTK_NOTE (BUILDER,
g_message ("created %s of type %s", info->id, g_type_name (info->type)));
}
object_properties_destroy (&construct_parameters);
if (parameters.names)
{
GtkBuildableIface *iface = NULL;
gboolean custom_set_property = FALSE;
GtkBuildable *buildable = NULL;
if (GTK_IS_BUILDABLE (obj))
{
buildable = GTK_BUILDABLE (obj);
iface = GTK_BUILDABLE_GET_IFACE (obj);
if (iface->set_buildable_property)
custom_set_property = TRUE;
}
if (custom_set_property)
{
for (i = 0; i < parameters.names->len; i++)
{
const char *name = object_properties_get_name (&parameters, i);
const GValue *value = object_properties_get_value (&parameters, i);
iface->set_buildable_property (buildable, builder, name, value);
#ifdef G_ENABLE_DEBUG
if (GTK_DEBUG_CHECK (BUILDER))
{
char *str = g_strdup_value_contents (value);
g_message ("set %s: %s = %s", info->id, name, str);
g_free (str);
}
#endif
}
}
else
{
g_object_setv (obj,
parameters.names->len,
(const char **) parameters.names->pdata,
(GValue *) parameters.values->data);
#ifdef G_ENABLE_DEBUG
if (GTK_DEBUG_CHECK (BUILDER))
{
for (i = 0; i < parameters.names->len; i++)
{
const char *name = object_properties_get_name (&parameters, i);
const GValue *value = object_properties_get_value (&parameters, i);
char *str = g_strdup_value_contents (value);
g_message ("set %s: %s = %s", info->id, name, str);
g_free (str);
}
}
#endif
}
}
object_properties_destroy (&parameters);
/* put it in the hash table. */
_gtk_builder_add_object (builder, info->id, obj);
/* we already own a reference to obj. */
g_object_unref (obj);
return obj;
}
void
_gtk_builder_apply_properties (GtkBuilder *builder,
ObjectInfo *info,
GError **error)
{
ObjectProperties parameters;
g_assert (info->object != NULL);
g_assert (info->type != G_TYPE_INVALID);
object_properties_init (&parameters);
/* Fetch all properties that are not construct-only */
gtk_builder_get_parameters (builder, info->type,
info->id,
info->properties,
G_PARAM_CONSTRUCT_ONLY,
&parameters, NULL);
if (parameters.names)
{
GtkBuildableIface *iface = NULL;
GtkBuildable *buildable = NULL;
gboolean custom_set_property = FALSE;
int i;
if (GTK_IS_BUILDABLE (info->object))
{
buildable = GTK_BUILDABLE (info->object);
iface = GTK_BUILDABLE_GET_IFACE (info->object);
if (iface->set_buildable_property)
custom_set_property = TRUE;
}
if (custom_set_property)
{
for (i = 0; i < parameters.names->len; i++)
{
const char *name = object_properties_get_name (&parameters, i);
const GValue *value = object_properties_get_value (&parameters, i);
iface->set_buildable_property (buildable, builder, name, value);
#ifdef G_ENABLE_DEBUG
if (GTK_DEBUG_CHECK (BUILDER))
{
char *str = g_strdup_value_contents (value);
g_message ("set %s: %s = %s", info->id, name, str);
g_free (str);
}
#endif
}
}
else
{
g_object_setv (info->object,
parameters.names->len,
(const char **) parameters.names->pdata,
(GValue *) parameters.values->data);
#ifdef G_ENABLE_DEBUG
if (GTK_DEBUG_CHECK (BUILDER))
{
for (i = 0; i < parameters.names->len; i++)
{
const char *name = object_properties_get_name (&parameters, i);
const GValue *value = object_properties_get_value (&parameters, i);
char *str = g_strdup_value_contents (value);
g_message ("set %s: %s = %s", info->id, name, str);
g_free (str);
}
}
#endif
}
}
object_properties_destroy (&parameters);
}
void
_gtk_builder_add (GtkBuilder *builder,
ChildInfo *child_info)
{
GObject *object;
GObject *parent;
/* Internal children are already added
* Also prevent us from being called twice.
*/
if (!child_info ||
child_info->internal_child ||
child_info->added)
return;
object = child_info->object;
if (!object)
return;
if (!child_info->parent)
{
g_warning ("%s: Not adding, No parent", object_get_id (object));
return;
}
g_assert (object != NULL);
parent = ((ObjectInfo*)child_info->parent)->object;
GTK_NOTE (BUILDER,
g_message ("adding %s to %s", object_get_id (object), object_get_id (parent)));
if (G_IS_LIST_STORE (parent))
{
if (child_info->type != NULL)
{
GTK_BUILDER_WARN_INVALID_CHILD_TYPE (parent, child_info->type);
}
else
{
g_list_store_append (G_LIST_STORE (parent), object);
}
}
else
{
g_assert (GTK_IS_BUILDABLE (parent));
gtk_buildable_add_child (GTK_BUILDABLE (parent), builder, object,
child_info->type);
}
child_info->added = TRUE;
}
void
_gtk_builder_add_signals (GtkBuilder *builder,
GPtrArray *signals)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
if (G_UNLIKELY (!priv->signals))
priv->signals = g_ptr_array_new_with_free_func ((GDestroyNotify)_free_signal_info);
g_ptr_array_extend_and_steal (priv->signals, signals);
}
static gboolean
gtk_builder_apply_delayed_properties (GtkBuilder *builder,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GSList *l, *props;
gboolean result = TRUE;
/* take the list over from the builder->priv.
*
* g_slist_reverse does not copy the list, so the list now
* belongs to us (and we free it at the end of this function).
*/
props = g_slist_reverse (priv->delayed_properties);
priv->delayed_properties = NULL;
for (l = props; l; l = l->next)
{
DelayedProperty *property = l->data;
GObject *object, *obj;
if (result)
{
object = g_hash_table_lookup (priv->objects, property->object);
g_assert (object != NULL);
obj = gtk_builder_lookup_object (builder, property->value, property->line, property->col, error);
if (obj)
g_object_set (object, property->pspec->name, obj, NULL);
else
result = FALSE;
}
g_free (property->value);
g_free (property->object);
g_slice_free (DelayedProperty, property);
}
g_slist_free (props);
return result;
}
static inline gboolean
gtk_builder_create_bindings (GtkBuilder *builder,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GSList *l;
gboolean result = TRUE;
for (l = priv->bindings; l; l = l->next)
{
CommonInfo *common_info = l->data;
if (common_info->tag_type == TAG_BINDING)
{
BindingInfo *info = l->data;
GObject *source;
source = gtk_builder_lookup_object (builder, info->source, info->line, info->col, error);
if (source)
g_object_bind_property (source, info->source_property,
info->target, info->target_pspec->name,
info->flags);
else
error = NULL;
_free_binding_info (info, NULL);
}
else if (common_info->tag_type == TAG_BINDING_EXPRESSION)
{
BindingExpressionInfo *info = l->data;
GtkExpression *expression;
GObject *object;
if (info->object_name)
{
object = gtk_builder_lookup_object (builder, info->object_name, info->line, info->col, error);
if (object == NULL)
{
error = NULL;
result = FALSE;
}
}
else if (priv->current_object)
{
object = priv->current_object;
}
else
{
object = info->target;
}
if (object)
{
expression = expression_info_construct (builder, info->expr, error);
if (expression == NULL)
{
g_prefix_error (error, "%s:%d:%d: ", priv->filename, info->line, info->col);
error = NULL;
result = FALSE;
}
else
{
gtk_expression_bind (expression, info->target, info->target_pspec->name, object);
}
}
free_binding_expression_info (info);
}
else
g_assert_not_reached ();
}
g_slist_free (priv->bindings);
priv->bindings = NULL;
return result;
}
/**
* gtk_builder_new:
*
* Creates a new empty builder object.
*
* This function is only useful if you intend to make multiple calls
* to [method@Gtk.Builder.add_from_file], [method@Gtk.Builder.add_from_resource]
* or [method@Gtk.Builder.add_from_string] in order to merge multiple UI
* descriptions into a single builder.
*
* Returns: a new (empty) `GtkBuilder` object
*/
GtkBuilder *
gtk_builder_new (void)
{
return g_object_new (GTK_TYPE_BUILDER, NULL);
}
/**
* gtk_builder_add_from_file:
* @builder: a `GtkBuilder`
* @filename: (type filename): the name of the file to parse
* @error: (nullable): return location for an error
*
* Parses a file containing a UI definition and merges it with
* the current contents of @builder.
*
* This function is useful if you need to call
* [method@Gtk.Builder.set_current_object]) to add user data to
* callbacks before loading GtkBuilder UI. Otherwise, you probably
* want [ctor@Gtk.Builder.new_from_file] instead.
*
* If an error occurs, 0 will be returned and @error will be assigned a
* `GError` from the `GTK_BUILDER_ERROR`, `G_MARKUP_ERROR` or `G_FILE_ERROR`
* domains.
*
* Its not really reasonable to attempt to handle failures of this
* call. You should not use this function with untrusted files (ie:
* files that are not part of your application). Broken `GtkBuilder`
* files can easily crash your program, and its possible that memory
* was leaked leading up to the reported failure. The only reasonable
* thing to do when an error is detected is to call `g_error()`.
*
* Returns: %TRUE on success, %FALSE if an error occurred
*/
gboolean
gtk_builder_add_from_file (GtkBuilder *builder,
const char *filename,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
char *buffer;
gsize length;
GError *tmp_error;
g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
g_return_val_if_fail (filename != NULL, 0);
g_return_val_if_fail (error == NULL || *error == NULL, 0);
tmp_error = NULL;
if (!g_file_get_contents (filename, &buffer, &length, &tmp_error))
{
g_propagate_error (error, tmp_error);
return FALSE;
}
g_free (priv->filename);
g_free (priv->resource_prefix);
priv->filename = g_strdup (filename);
priv->resource_prefix = NULL;
_gtk_builder_parser_parse_buffer (builder, filename,
buffer, (gssize)length,
NULL,
&tmp_error);
g_free (buffer);
if (tmp_error != NULL)
{
g_propagate_error (error, tmp_error);
return FALSE;
}
return TRUE;
}
/**
* gtk_builder_add_objects_from_file:
* @builder: a `GtkBuilder`
* @filename: (type filename): the name of the file to parse
* @object_ids: (array zero-terminated=1) (element-type utf8): nul-terminated array of objects to build
* @error: (nullable): return location for an error
*
* Parses a file containing a UI definition building only the
* requested objects and merges them with the current contents
* of @builder.
*
* Upon errors, 0 will be returned and @error will be assigned a
* `GError` from the %GTK_BUILDER_ERROR, %G_MARKUP_ERROR or %G_FILE_ERROR
* domain.
*
* If you are adding an object that depends on an object that is not
* its child (for instance a `GtkTreeView` that depends on its
* `GtkTreeModel`), you have to explicitly list all of them in @object_ids.
*
* Returns: %TRUE on success, %FALSE if an error occurred
*/
gboolean
gtk_builder_add_objects_from_file (GtkBuilder *builder,
const char *filename,
const char **object_ids,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
char *buffer;
gsize length;
GError *tmp_error;
g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
g_return_val_if_fail (filename != NULL, 0);
g_return_val_if_fail (object_ids != NULL && object_ids[0] != NULL, 0);
g_return_val_if_fail (error == NULL || *error == NULL, 0);
tmp_error = NULL;
if (!g_file_get_contents (filename, &buffer, &length, &tmp_error))
{
g_propagate_error (error, tmp_error);
return 0;
}
g_free (priv->filename);
g_free (priv->resource_prefix);
priv->filename = g_strdup (filename);
priv->resource_prefix = NULL;
_gtk_builder_parser_parse_buffer (builder, filename,
buffer, (gssize)length,
object_ids,
&tmp_error);
g_free (buffer);
if (tmp_error != NULL)
{
g_propagate_error (error, tmp_error);
return FALSE;
}
return TRUE;
}
/**
* gtk_builder_extend_with_template:
* @builder: a `GtkBuilder`
* @object: the object that is being extended
* @template_type: the type that the template is for
* @buffer: the string to parse
* @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
* @error: (nullable): return location for an error
*
* Main private entry point for building composite components
* from template XML.
*
* Most likely you do not need to call this function in applications as
* templates are handled by `GtkWidget`.
*
* Returns: A positive value on success, 0 if an error occurred
*/
gboolean
gtk_builder_extend_with_template (GtkBuilder *builder,
GObject *object,
GType template_type,
const char *buffer,
gssize length,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
const char *name;
GError *tmp_error;
char *filename;
g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
g_return_val_if_fail (G_IS_OBJECT (object), 0);
g_return_val_if_fail (g_type_name (template_type) != NULL, 0);
g_return_val_if_fail (g_type_is_a (G_OBJECT_TYPE (object), template_type), 0);
g_return_val_if_fail (buffer && buffer[0], 0);
tmp_error = NULL;
g_free (priv->filename);
g_free (priv->resource_prefix);
priv->filename = g_strdup (".");
priv->resource_prefix = NULL;
priv->template_type = template_type;
/* We specifically allow this function to be called multiple times with
* the same @template_type as that is used in applications like Builder
* to implement UI merging.
*/
name = g_type_name (template_type);
if (gtk_builder_get_object (builder, name) != object)
gtk_builder_expose_object (builder, name, object);
filename = g_strconcat ("<", name, " template>", NULL);
_gtk_builder_parser_parse_buffer (builder, filename,
buffer, length,
NULL,
&tmp_error);
g_free (filename);
if (tmp_error != NULL)
{
g_propagate_error (error, tmp_error);
return FALSE;
}
return TRUE;
}
/**
* gtk_builder_add_from_resource:
* @builder: a `GtkBuilder`
* @resource_path: the path of the resource file to parse
* @error: (nullable): return location for an erro
*
* Parses a resource file containing a UI definition
* and merges it with the current contents of @builder.
*
* This function is useful if you need to call
* [method@Gtk.Builder.set_current_object] to add user data to
* callbacks before loading GtkBuilder UI. Otherwise, you probably
* want [ctor@Gtk.Builder.new_from_resource] instead.
*
* If an error occurs, 0 will be returned and @error will be assigned a
* `GError` from the %GTK_BUILDER_ERROR, %G_MARKUP_ERROR or %G_RESOURCE_ERROR
* domain.
*
* Its not really reasonable to attempt to handle failures of this
* call. The only reasonable thing to do when an error is detected is
* to call g_error().
*
* Returns: %TRUE on success, %FALSE if an error occurred
*/
gboolean
gtk_builder_add_from_resource (GtkBuilder *builder,
const char *resource_path,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GError *tmp_error;
GBytes *data;
char *filename_for_errors;
char *slash;
g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
g_return_val_if_fail (resource_path != NULL, 0);
g_return_val_if_fail (error == NULL || *error == NULL, 0);
tmp_error = NULL;
data = g_resources_lookup_data (resource_path, 0, &tmp_error);
if (data == NULL)
{
g_propagate_error (error, tmp_error);
return 0;
}
g_free (priv->filename);
g_free (priv->resource_prefix);
priv->filename = g_strdup (".");
slash = strrchr (resource_path, '/');
if (slash != NULL)
priv->resource_prefix = g_strndup (resource_path, slash - resource_path + 1);
else
priv->resource_prefix = g_strdup ("/");
filename_for_errors = g_strconcat ("<resource>", resource_path, NULL);
_gtk_builder_parser_parse_buffer (builder, filename_for_errors,
g_bytes_get_data (data, NULL), g_bytes_get_size (data),
NULL,
&tmp_error);
g_free (filename_for_errors);
g_bytes_unref (data);
if (tmp_error != NULL)
{
g_propagate_error (error, tmp_error);
return FALSE;
}
return TRUE;
}
/**
* gtk_builder_add_objects_from_resource:
* @builder: a `GtkBuilder`
* @resource_path: the path of the resource file to parse
* @object_ids: (array zero-terminated=1) (element-type utf8): nul-terminated array of objects to build
* @error: (nullable): return location for an error
*
* Parses a resource file containing a UI definition, building
* only the requested objects and merges them with the current
* contents of @builder.
*
* Upon errors, 0 will be returned and @error will be assigned a
* `GError` from the %GTK_BUILDER_ERROR, %G_MARKUP_ERROR or %G_RESOURCE_ERROR
* domain.
*
* If you are adding an object that depends on an object that is not
* its child (for instance a `GtkTreeView` that depends on its
* `GtkTreeModel`), you have to explicitly list all of them in @object_ids.
*
* Returns: %TRUE on success, %FALSE if an error occurred
*/
gboolean
gtk_builder_add_objects_from_resource (GtkBuilder *builder,
const char *resource_path,
const char **object_ids,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GError *tmp_error;
GBytes *data;
char *filename_for_errors;
char *slash;
g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
g_return_val_if_fail (resource_path != NULL, 0);
g_return_val_if_fail (object_ids != NULL && object_ids[0] != NULL, 0);
g_return_val_if_fail (error == NULL || *error == NULL, 0);
tmp_error = NULL;
data = g_resources_lookup_data (resource_path, 0, &tmp_error);
if (data == NULL)
{
g_propagate_error (error, tmp_error);
return FALSE;
}
g_free (priv->filename);
g_free (priv->resource_prefix);
priv->filename = g_strdup (".");
slash = strrchr (resource_path, '/');
if (slash != NULL)
priv->resource_prefix = g_strndup (resource_path, slash - resource_path + 1);
else
priv->resource_prefix = g_strdup ("/");
filename_for_errors = g_strconcat ("<resource>", resource_path, NULL);
_gtk_builder_parser_parse_buffer (builder, filename_for_errors,
g_bytes_get_data (data, NULL), g_bytes_get_size (data),
object_ids,
&tmp_error);
g_free (filename_for_errors);
g_bytes_unref (data);
if (tmp_error != NULL)
{
g_propagate_error (error, tmp_error);
return FALSE;
}
return TRUE;
}
/**
* gtk_builder_add_from_string:
* @builder: a `GtkBuilder`
* @buffer: the string to parse
* @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
* @error: (nullable): return location for an error
*
* Parses a string containing a UI definition and merges it
* with the current contents of @builder.
*
* This function is useful if you need to call
* [method@Gtk.Builder.set_current_object] to add user data to
* callbacks before loading `GtkBuilder` UI. Otherwise, you probably
* want [ctor@Gtk.Builder.new_from_string] instead.
*
* Upon errors %FALSE will be returned and @error will be assigned a
* `GError` from the %GTK_BUILDER_ERROR, %G_MARKUP_ERROR or
* %G_VARIANT_PARSE_ERROR domain.
*
* Its not really reasonable to attempt to handle failures of this
* call. The only reasonable thing to do when an error is detected is
* to call g_error().
*
* Returns: %TRUE on success, %FALSE if an error occurred
*/
gboolean
gtk_builder_add_from_string (GtkBuilder *builder,
const char *buffer,
gssize length,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GError *tmp_error;
g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
g_return_val_if_fail (buffer != NULL, 0);
g_return_val_if_fail (error == NULL || *error == NULL, 0);
tmp_error = NULL;
g_free (priv->filename);
g_free (priv->resource_prefix);
priv->filename = g_strdup (".");
priv->resource_prefix = NULL;
_gtk_builder_parser_parse_buffer (builder, "<input>",
buffer, length,
NULL,
&tmp_error);
if (tmp_error != NULL)
{
g_propagate_error (error, tmp_error);
return FALSE;
}
return TRUE;
}
/**
* gtk_builder_add_objects_from_string:
* @builder: a `GtkBuilder`
* @buffer: the string to parse
* @length: the length of @buffer (may be -1 if @buffer is nul-terminated)
* @object_ids: (array zero-terminated=1) (element-type utf8): nul-terminated array of objects to build
* @error: (nullable): return location for an error
*
* Parses a string containing a UI definition, building only the
* requested objects and merges them with the current contents of
* @builder.
*
* Upon errors %FALSE will be returned and @error will be assigned a
* `GError` from the %GTK_BUILDER_ERROR or %G_MARKUP_ERROR domain.
*
* If you are adding an object that depends on an object that is not
* its child (for instance a `GtkTreeView` that depends on its
* `GtkTreeModel`), you have to explicitly list all of them in @object_ids.
*
* Returns: %TRUE on success, %FALSE if an error occurred
*/
gboolean
gtk_builder_add_objects_from_string (GtkBuilder *builder,
const char *buffer,
gssize length,
const char **object_ids,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GError *tmp_error;
g_return_val_if_fail (GTK_IS_BUILDER (builder), 0);
g_return_val_if_fail (buffer != NULL, 0);
g_return_val_if_fail (object_ids != NULL && object_ids[0] != NULL, 0);
g_return_val_if_fail (error == NULL || *error == NULL, 0);
tmp_error = NULL;
g_free (priv->filename);
g_free (priv->resource_prefix);
priv->filename = g_strdup (".");
priv->resource_prefix = NULL;
_gtk_builder_parser_parse_buffer (builder, "<input>",
buffer, length,
object_ids,
&tmp_error);
if (tmp_error != NULL)
{
g_propagate_error (error, tmp_error);
return FALSE;
}
return TRUE;
}
/**
* gtk_builder_get_object:
* @builder: a `GtkBuilder`
* @name: name of object to get
*
* Gets the object named @name.
*
* Note that this function does not increment the reference count
* of the returned object.
*
* Returns: (nullable) (transfer none): the object named @name
*/
GObject *
gtk_builder_get_object (GtkBuilder *builder,
const char *name)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
g_return_val_if_fail (name != NULL, NULL);
return g_hash_table_lookup (priv->objects, name);
}
/**
* gtk_builder_get_objects:
* @builder: a `GtkBuilder`
*
* Gets all objects that have been constructed by @builder.
*
* Note that this function does not increment the reference
* counts of the returned objects.
*
* Returns: (element-type GObject) (transfer container): a
* newly-allocated `GSList` containing all the objects
* constructed by the `GtkBuilder instance`. It should be
* freed by g_slist_free()
*/
GSList *
gtk_builder_get_objects (GtkBuilder *builder)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GSList *objects = NULL;
GObject *object;
GHashTableIter iter;
g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
g_hash_table_iter_init (&iter, priv->objects);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&object))
objects = g_slist_prepend (objects, object);
return g_slist_reverse (objects);
}
/**
* gtk_builder_set_translation_domain: (attributes org.gtk.Method.set_property=translation-domain)
* @builder: a `GtkBuilder`
* @domain: (nullable): the translation domain
*
* Sets the translation domain of @builder.
*/
void
gtk_builder_set_translation_domain (GtkBuilder *builder,
const char *domain)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
char *new_domain;
g_return_if_fail (GTK_IS_BUILDER (builder));
new_domain = g_strdup (domain);
g_free (priv->domain);
priv->domain = new_domain;
g_object_notify_by_pspec (G_OBJECT (builder), builder_props[PROP_TRANSLATION_DOMAIN]);
}
/**
* gtk_builder_get_translation_domain: (attributes org.gtk.Method.get_property=translation-domain)
* @builder: a `GtkBuilder`
*
* Gets the translation domain of @builder.
*
* Returns: (transfer none) (nullable): the translation domain
*/
const char *
gtk_builder_get_translation_domain (GtkBuilder *builder)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
return priv->domain;
}
/**
* gtk_builder_expose_object:
* @builder: a `GtkBuilder`
* @name: the name of the object exposed to the builder
* @object: the object to expose
*
* Add @object to the @builder object pool so it can be
* referenced just like any other object built by builder.
*
* Only a single object may be added using @name. However,
* it is not an error to expose the same object under multiple
* names. `gtk_builder_get_object()` may be used to determine
* if an object has already been added with @name.
*/
void
gtk_builder_expose_object (GtkBuilder *builder,
const char *name,
GObject *object)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
g_return_if_fail (GTK_IS_BUILDER (builder));
g_return_if_fail (name && name[0]);
g_return_if_fail (!g_hash_table_contains (priv->objects, name));
object_set_id (object, name);
g_hash_table_insert (priv->objects,
g_strdup (name),
g_object_ref (object));
}
/**
* gtk_builder_get_current_object: (attributes org.gtk.Method.get_property=current-object)
* @builder: a `GtkBuilder`
*
* Gets the current object set via gtk_builder_set_current_object().
*
* Returns: (nullable) (transfer none): the current object
*/
GObject *
gtk_builder_get_current_object (GtkBuilder *builder)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
return priv->current_object;
}
/**
* gtk_builder_set_current_object: (attributes org.gtk.Method.set_property=current-object)
* @builder: a `GtkBuilder`
* @current_object: (nullable) (transfer none): the new current object
*
* Sets the current object for the @builder.
*
* The current object can be thought of as the `this` object that the
* builder is working for and will often be used as the default object
* when an object is optional.
*
* [method@Gtk.Widget.init_template] for example will set the current
* object to the widget the template is inited for. For functions like
* [ctor@Gtk.Builder.new_from_resource], the current object will be %NULL.
*/
void
gtk_builder_set_current_object (GtkBuilder *builder,
GObject *current_object)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
g_return_if_fail (GTK_IS_BUILDER (builder));
g_return_if_fail (current_object || G_IS_OBJECT (current_object));
if (!g_set_object (&priv->current_object, current_object))
return;
g_object_notify_by_pspec (G_OBJECT (builder), builder_props[PROP_CURRENT_OBJECT]);
}
/**
* gtk_builder_get_scope: (attributes org.gtk.Method.get_property=scope)
* @builder: a `GtkBuilder`
*
* Gets the scope in use that was set via gtk_builder_set_scope().
*
* Returns: (transfer none): the current scope
*/
GtkBuilderScope *
gtk_builder_get_scope (GtkBuilder *builder)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
return priv->scope;
}
/**
* gtk_builder_set_scope: (attributes org.gtk.Method.set_property=scope)
* @builder: a `GtkBuilder`
* @scope: (nullable) (transfer none): the scope to use
*
* Sets the scope the builder should operate in.
*
* If @scope is %NULL, a new [class@Gtk.BuilderCScope] will be created.
*/
void
gtk_builder_set_scope (GtkBuilder *builder,
GtkBuilderScope *scope)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
g_return_if_fail (GTK_IS_BUILDER (builder));
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 (builder), builder_props[PROP_SCOPE]);
}
static gboolean
gtk_builder_connect_signals (GtkBuilder *builder,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GObject *object;
GObject *connect_object;
gboolean result = TRUE;
if (!priv->signals ||
priv->signals->len == 0)
return TRUE;
for (guint i = 0; i < priv->signals->len; i++)
{
SignalInfo *signal = g_ptr_array_index (priv->signals, i);
GClosure *closure;
g_assert (signal != NULL);
g_assert (signal->id != 0);
object = g_hash_table_lookup (priv->objects, signal->object_name);
g_assert (object != NULL);
connect_object = NULL;
if (signal->connect_object_name)
{
connect_object = g_hash_table_lookup (priv->objects,
signal->connect_object_name);
if (!connect_object)
{
g_set_error (error,
GTK_BUILDER_ERROR, GTK_BUILDER_ERROR_INVALID_ID,
"Could not lookup object %s on signal %s of object %s",
signal->connect_object_name, g_signal_name (signal->id),
signal->object_name);
break;
}
}
closure = gtk_builder_create_closure (builder,
signal->handler,
signal->flags & G_CONNECT_SWAPPED ? TRUE : FALSE,
connect_object,
error);
if (closure == NULL)
{
result = false;
break;
}
g_signal_connect_closure_by_id (object,
signal->id,
signal->detail,
closure,
signal->flags & G_CONNECT_AFTER ? TRUE : FALSE);
}
g_ptr_array_free (priv->signals, TRUE);
priv->signals = NULL;
return result;
}
gboolean
_gtk_builder_finish (GtkBuilder *builder,
GError **error)
{
return gtk_builder_apply_delayed_properties (builder, error) &&
gtk_builder_create_bindings (builder, error) &&
gtk_builder_connect_signals (builder, error);
}
/**
* gtk_builder_value_from_string:
* @builder: a `GtkBuilder`
* @pspec: the `GParamSpec` for the property
* @string: the string representation of the value
* @value: (out): the `GValue` to store the result in
* @error: (nullable): return location for an error
*
* Demarshals a value from a string.
*
* This function calls g_value_init() on the @value argument,
* so it need not be initialised beforehand.
*
* Can handle char, uchar, boolean, int, uint, long,
* ulong, enum, flags, float, double, string, `GdkRGBA` and
* `GtkAdjustment` type values.
*
* Upon errors %FALSE will be returned and @error will be
* assigned a `GError` from the %GTK_BUILDER_ERROR domain.
*
* Returns: %TRUE on success
*/
gboolean
gtk_builder_value_from_string (GtkBuilder *builder,
GParamSpec *pspec,
const char *string,
GValue *value,
GError **error)
{
g_return_val_if_fail (GTK_IS_BUILDER (builder), FALSE);
g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE);
g_return_val_if_fail (string != NULL, FALSE);
g_return_val_if_fail (value != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/*
* GParamSpecUnichar has the internal type G_TYPE_UINT,
* so we cannot handle this in the switch, do it separately
*/
if (G_IS_PARAM_SPEC_UNICHAR (pspec))
{
gunichar c;
g_value_init (value, G_TYPE_UINT);
c = g_utf8_get_char_validated (string, -1);
if (c != 0 && c != (gunichar)-1 && c != (gunichar)-2)
g_value_set_uint (value, c);
return TRUE;
}
/*
* GParamSpecVariant can specify a GVariantType which can help with
* parsing, so we need to take care of that here.
*/
if (G_IS_PARAM_SPEC_VARIANT (pspec))
{
GParamSpecVariant *variant_pspec = G_PARAM_SPEC_VARIANT (pspec);
const GVariantType *type;
GVariant *variant;
g_value_init (value, G_TYPE_VARIANT);
/* The GVariant parser doesn't deal with indefinite types */
if (g_variant_type_is_definite (variant_pspec->type))
type = variant_pspec->type;
else
type = NULL;
variant = g_variant_parse (type, string, NULL, NULL, error);
if (variant == NULL)
return FALSE;
g_value_take_variant (value, variant);
return TRUE;
}
return gtk_builder_value_from_string_type (builder,
G_PARAM_SPEC_VALUE_TYPE (pspec),
string, value, error);
}
gboolean
_gtk_builder_boolean_from_string (const char *string,
gboolean *value,
GError **error)
{
if (string[0] == '\0')
goto error;
else if (string[1] == '\0')
{
char c;
c = string[0];
if (c == '1' ||
c == 'y' || c == 't' ||
c == 'Y' || c == 'T')
*value = TRUE;
else if (c == '0' ||
c == 'n' || c == 'f' ||
c == 'N' || c == 'F')
*value = FALSE;
else
goto error;
}
else
{
if (g_ascii_strcasecmp (string, "true") == 0 ||
g_ascii_strcasecmp (string, "yes") == 0)
*value = TRUE;
else if (g_ascii_strcasecmp (string, "false") == 0 ||
g_ascii_strcasecmp (string, "no") == 0)
*value = FALSE;
else
goto error;
}
return TRUE;
error:
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not parse boolean '%s'",
string);
return FALSE;
}
/**
* gtk_builder_value_from_string_type:
* @builder: a `GtkBuilder`
* @type: the `GType` of the value
* @string: the string representation of the value
* @value: (out): the `GValue` to store the result in
* @error: (nullable): return location for an error
*
* Demarshals a value from a string.
*
* Unlike [method@Gtk.Builder.value_from_string], this function
* takes a `GType` instead of `GParamSpec`.
*
* Calls g_value_init() on the @value argument, so it
* need not be initialised beforehand.
*
* Upon errors %FALSE will be returned and @error will be
* assigned a `GError` from the %GTK_BUILDER_ERROR domain.
*
* Returns: %TRUE on success
*/
gboolean
gtk_builder_value_from_string_type (GtkBuilder *builder,
GType type,
const char *string,
GValue *value,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
gboolean ret = TRUE;
g_return_val_if_fail (string != NULL, FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
g_value_init (value, type);
switch (G_TYPE_FUNDAMENTAL (type))
{
case G_TYPE_CHAR:
g_value_set_schar (value, string[0]);
break;
case G_TYPE_UCHAR:
g_value_set_uchar (value, (guchar)string[0]);
break;
case G_TYPE_BOOLEAN:
{
gboolean b;
if (!_gtk_builder_boolean_from_string (string, &b, error))
{
ret = FALSE;
break;
}
g_value_set_boolean (value, b);
break;
}
case G_TYPE_INT:
case G_TYPE_LONG:
case G_TYPE_INT64:
{
gint64 l;
char *endptr = NULL;
errno = 0;
l = g_ascii_strtoll (string, &endptr, 0);
if (errno || endptr == string)
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not parse integer '%s'",
string);
ret = FALSE;
break;
}
if (G_VALUE_HOLDS_INT (value))
g_value_set_int (value, l);
else if (G_VALUE_HOLDS_LONG (value))
g_value_set_long (value, l);
else
g_value_set_int64 (value, l);
break;
}
case G_TYPE_UINT:
case G_TYPE_ULONG:
case G_TYPE_UINT64:
{
guint64 ul;
char *endptr = NULL;
errno = 0;
ul = g_ascii_strtoull (string, &endptr, 0);
if (errno || endptr == string)
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not parse unsigned integer '%s'",
string);
ret = FALSE;
break;
}
if (G_VALUE_HOLDS_UINT (value))
g_value_set_uint (value, ul);
else if (G_VALUE_HOLDS_ULONG (value))
g_value_set_ulong (value, ul);
else
g_value_set_uint64 (value, ul);
break;
}
case G_TYPE_ENUM:
{
int enum_value;
if (!_gtk_builder_enum_from_string (type, string, &enum_value, error))
{
ret = FALSE;
break;
}
g_value_set_enum (value, enum_value);
break;
}
case G_TYPE_FLAGS:
{
guint flags_value;
if (!_gtk_builder_flags_from_string (type, string, &flags_value, error))
{
ret = FALSE;
break;
}
g_value_set_flags (value, flags_value);
break;
}
case G_TYPE_FLOAT:
case G_TYPE_DOUBLE:
{
double d;
char *endptr = NULL;
errno = 0;
d = g_ascii_strtod (string, &endptr);
if (errno || endptr == string)
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not parse double '%s'",
string);
ret = FALSE;
break;
}
if (G_VALUE_HOLDS_FLOAT (value))
g_value_set_float (value, d);
else
g_value_set_double (value, d);
break;
}
case G_TYPE_STRING:
g_value_set_string (value, string);
break;
case G_TYPE_VARIANT:
{
GVariant *variant;
variant = g_variant_parse (NULL, string, NULL, NULL, error);
if (value != NULL)
g_value_take_variant (value, variant);
else
ret = FALSE;
}
break;
case G_TYPE_BOXED:
if (G_VALUE_HOLDS (value, GDK_TYPE_RGBA))
{
GdkRGBA rgba = { 0 };
if (gdk_rgba_parse (&rgba, string))
g_value_set_boxed (value, &rgba);
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not parse RGBA color '%s'",
string);
ret = FALSE;
}
}
else if (G_VALUE_HOLDS (value, GDK_TYPE_CONTENT_FORMATS))
{
GdkContentFormats *formats;
formats = gdk_content_formats_parse (string);
if (formats)
g_value_take_boxed (value, formats);
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not parse GdkContentFormats '%s'",
string);
ret = FALSE;
}
}
else if (G_VALUE_HOLDS (value, GSK_TYPE_TRANSFORM))
{
GskTransform *transform;
if (gsk_transform_parse (string, &transform))
g_value_take_boxed (value, transform);
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not parse transform '%s'",
string);
ret = FALSE;
}
}
else if (G_VALUE_HOLDS (value, G_TYPE_STRV))
{
char **vector = g_strsplit (string, "\n", 0);
g_value_take_boxed (value, vector);
}
else if (G_VALUE_HOLDS (value, G_TYPE_BYTES))
{
g_value_take_boxed (value, g_bytes_new (string, strlen (string)));
}
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not parse '%s' as a %s",
string, G_VALUE_TYPE_NAME (value));
ret = FALSE;
}
break;
case G_TYPE_OBJECT:
case G_TYPE_INTERFACE:
if (G_VALUE_HOLDS (value, GDK_TYPE_PAINTABLE) ||
G_VALUE_HOLDS (value, GDK_TYPE_TEXTURE))
{
GObject *object = g_hash_table_lookup (priv->objects, string);
char *filename;
GError *tmp_error = NULL;
GdkTexture *texture = NULL;
if (object)
{
if (g_type_is_a (G_OBJECT_TYPE (object), G_VALUE_TYPE (value)))
{
g_value_set_object (value, object);
return TRUE;
}
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not load image '%s': "
" '%s' is already used as object id for a %s",
string, string, G_OBJECT_TYPE_NAME (object));
return FALSE;
}
}
filename = _gtk_builder_get_resource_path (builder, string);
if (filename != NULL)
{
texture = gdk_texture_new_from_resource (filename);
}
else
{
GFile *file;
filename = _gtk_builder_get_absolute_filename (builder, string);
file = g_file_new_for_path (filename);
texture = gdk_texture_new_from_file (file, &tmp_error);
g_object_unref (file);
}
g_free (filename);
if (!texture)
{
g_warning ("Could not load image '%s': %s", string, tmp_error->message);
g_error_free (tmp_error);
texture = gdk_texture_new_from_resource (IMAGE_MISSING_RESOURCE_PATH);
}
g_value_take_object (value, texture);
ret = TRUE;
}
else if (G_VALUE_HOLDS (value, GDK_TYPE_PIXBUF))
{
char *filename;
GError *tmp_error = NULL;
GdkPixbuf *pixbuf = NULL;
GObject *object;
object = g_hash_table_lookup (priv->objects, string);
if (object)
{
if (g_type_is_a (G_OBJECT_TYPE (object), G_VALUE_TYPE (value)))
{
g_value_set_object (value, object);
return TRUE;
}
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not load image '%s': "
" '%s' is already used as object id for a %s",
string, string, G_OBJECT_TYPE_NAME (object));
return FALSE;
}
}
filename = _gtk_builder_get_resource_path (builder, string);
if (filename != NULL)
{
GInputStream *stream = g_resources_open_stream (filename, 0, &tmp_error);
if (stream != NULL)
{
pixbuf = gdk_pixbuf_new_from_stream (stream, NULL, &tmp_error);
g_object_unref (stream);
}
}
else
{
filename = _gtk_builder_get_absolute_filename (builder, string);
pixbuf = gdk_pixbuf_new_from_file (filename, &tmp_error);
}
if (pixbuf == NULL)
{
g_warning ("Could not load image '%s': %s", string, tmp_error->message);
g_error_free (tmp_error);
pixbuf = gdk_pixbuf_new_from_resource (IMAGE_MISSING_RESOURCE_PATH, NULL);
}
g_value_take_object (value, pixbuf);
g_free (filename);
ret = TRUE;
}
else if (G_VALUE_HOLDS (value, G_TYPE_FILE))
{
GFile *file;
if (g_hash_table_contains (priv->objects, string))
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not create file '%s': "
" '%s' is already used as object id",
string, string);
return FALSE;
}
file = g_file_new_for_uri (string);
g_value_set_object (value, file);
g_object_unref (G_OBJECT (file));
ret = TRUE;
}
else if (G_VALUE_HOLDS (value, GTK_TYPE_SHORTCUT_TRIGGER))
{
GtkShortcutTrigger *trigger = gtk_shortcut_trigger_parse_string (string);
if (trigger)
g_value_take_object (value, trigger);
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not parse shortcut trigger '%s'",
string);
ret = FALSE;
}
}
else if (G_VALUE_HOLDS (value, GTK_TYPE_SHORTCUT_ACTION))
{
GtkShortcutAction *action = gtk_shortcut_action_parse_builder (builder, string, error);
/* Works for success and failure (NULL) case */
g_value_take_object (value, action);
}
else
{
GObject *object = g_hash_table_lookup (priv->objects, string);
if (object && g_value_type_compatible (G_OBJECT_TYPE (object), type))
{
g_value_set_object (value, object);
}
else if (object)
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Object named \"%s\" is of type \"%s\" which is not compatible with expected type \%s\"",
string, G_OBJECT_TYPE_NAME (object), g_type_name (type));
ret = FALSE;
}
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"No object named \"%s\"", string);
ret = FALSE;
}
}
break;
case G_TYPE_POINTER:
if (G_VALUE_HOLDS (value, G_TYPE_GTYPE))
{
GType resolved_type;
resolved_type = gtk_builder_get_type_from_name (builder, string);
if (resolved_type == G_TYPE_INVALID)
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Unsupported GType '%s' for value of type 'GType'", string);
return FALSE;
}
g_value_set_gtype (value, resolved_type);
ret = TRUE;
}
else
ret = FALSE;
break;
default:
ret = FALSE;
break;
}
/* Catch unassigned error for object types as well as any unsupported types.
* While parsing GtkBuilder; object types are deserialized
* without calling gtk_builder_value_from_string_type().
*/
if (!ret && error && *error == NULL)
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Unsupported GType '%s'", g_type_name (type));
return ret;
}
gboolean
_gtk_builder_enum_from_string (GType type,
const char *string,
int *enum_value,
GError **error)
{
GEnumClass *eclass;
GEnumValue *ev;
char *endptr;
int value;
gboolean ret;
g_return_val_if_fail (G_TYPE_IS_ENUM (type), FALSE);
g_return_val_if_fail (string != NULL, FALSE);
ret = TRUE;
endptr = NULL;
errno = 0;
value = g_ascii_strtoull (string, &endptr, 0);
if (errno == 0 && endptr != string) /* parsed a number */
*enum_value = value;
else
{
eclass = g_type_class_ref (type);
ev = g_enum_get_value_by_nick (eclass, string);
if (!ev)
ev = g_enum_get_value_by_name (eclass, string);
if (ev)
*enum_value = ev->value;
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not parse enum: '%s'",
string);
ret = FALSE;
}
g_type_class_unref (eclass);
}
return ret;
}
gboolean
_gtk_builder_flags_from_string (GType type,
const char *string,
guint *flags_value,
GError **error)
{
GFlagsClass *fclass;
char *endptr, *prevptr;
guint i, j, value;
char *flagstr;
GFlagsValue *fv;
const char *flag;
gunichar ch;
gboolean eos, ret;
g_return_val_if_fail (G_TYPE_IS_FLAGS (type), FALSE);
g_return_val_if_fail (string != 0, FALSE);
ret = TRUE;
endptr = NULL;
errno = 0;
value = g_ascii_strtoull (string, &endptr, 0);
if (errno == 0 && endptr != string) /* parsed a number */
*flags_value = value;
else
{
fclass = g_type_class_ref (type);
flagstr = g_strdup (string);
for (value = i = j = 0; ; i++)
{
eos = flagstr[i] == '\0';
if (!eos && flagstr[i] != '|')
continue;
flag = &flagstr[j];
endptr = &flagstr[i];
if (!eos)
{
flagstr[i++] = '\0';
j = i;
}
/* trim spaces */
for (;;)
{
ch = g_utf8_get_char (flag);
if (!g_unichar_isspace (ch))
break;
flag = g_utf8_next_char (flag);
}
while (endptr > flag)
{
prevptr = g_utf8_prev_char (endptr);
ch = g_utf8_get_char (prevptr);
if (!g_unichar_isspace (ch))
break;
endptr = prevptr;
}
if (endptr > flag)
{
*endptr = '\0';
fv = NULL;
if (!fv)
fv = g_flags_get_value_by_name (fclass, flag);
if (!fv)
fv = g_flags_get_value_by_nick (fclass, flag);
if (fv)
value |= fv->value;
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Unknown flag: '%s'",
flag);
ret = FALSE;
break;
}
}
if (eos)
{
*flags_value = value;
break;
}
}
g_free (flagstr);
g_type_class_unref (fclass);
}
return ret;
}
/**
* gtk_builder_get_type_from_name:
* @builder: a `GtkBuilder`
* @type_name: type name to lookup
*
* Looks up a type by name.
*
* This is using the virtual function that `GtkBuilder` has
* for that purpose. This is mainly used when implementing
* the `GtkBuildable` interface on a type.
*
* Returns: the `GType` found for @type_name or %G_TYPE_INVALID
* if no type was found
*/
GType
gtk_builder_get_type_from_name (GtkBuilder *builder,
const char *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 = gtk_builder_scope_get_type_from_name (priv->scope, builder, type_name);
if (type == G_TYPE_INVALID)
return G_TYPE_INVALID;
if (G_TYPE_IS_CLASSED (type))
g_type_class_unref (g_type_class_ref (type));
return type;
}
GQuark
gtk_builder_error_quark (void)
{
return g_quark_from_static_string ("gtk-builder-error-quark");
}
char *
_gtk_builder_get_resource_path (GtkBuilder *builder, const char *string)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
if (g_str_has_prefix (string, "resource:///"))
return g_uri_unescape_string (string + 11, "/");
if (g_path_is_absolute (string) ||
priv->resource_prefix == NULL)
return NULL;
return g_build_path ("/", priv->resource_prefix, string, NULL);
}
char *
_gtk_builder_get_absolute_filename (GtkBuilder *builder,
const char *string)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
char *filename;
char *dirname = NULL;
if (g_path_is_absolute (string))
return g_strdup (string);
if (priv->filename &&
strcmp (priv->filename, ".") != 0)
{
dirname = g_path_get_dirname (priv->filename);
if (strcmp (dirname, ".") == 0)
{
g_free (dirname);
dirname = g_get_current_dir ();
}
}
else
dirname = g_get_current_dir ();
filename = g_build_filename (dirname, string, NULL);
g_free (dirname);
return filename;
}
GType
_gtk_builder_get_template_type (GtkBuilder *builder)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
return priv->template_type;
}
/**
* gtk_builder_create_closure:
* @builder: a `GtkBuilder`
* @function_name: name of the function to look up
* @flags: closure creation flags
* @object: (nullable): Object to create the closure with
* @error: (nullable): return location for an error
*
* Creates a closure to invoke the function called @function_name.
*
* This is using the create_closure() implementation of @builder's
* [iface@Gtk.BuilderScope].
*
* If no closure could be created, %NULL will be returned and @error
* will be set.
*
* Returns: (nullable): A new closure for invoking @function_name
*/
GClosure *
gtk_builder_create_closure (GtkBuilder *builder,
const char *function_name,
GtkBuilderClosureFlags flags,
GObject *object,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
g_return_val_if_fail (GTK_IS_BUILDER (builder), NULL);
g_return_val_if_fail (function_name, 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_create_closure (priv->scope, builder, function_name, flags, object, error);
}
/**
* gtk_builder_new_from_file: (constructor)
* @filename: (type filename): filename of user interface description file
*
* Parses the UI definition in the file @filename.
*
* If there is an error opening the file or parsing the description then
* the program will be aborted. You should only ever attempt to parse
* user interface descriptions that are shipped as part of your program.
*
* Returns: (transfer full): a `GtkBuilder` containing the described interface
*/
GtkBuilder *
gtk_builder_new_from_file (const char *filename)
{
GError *error = NULL;
GtkBuilder *builder;
builder = gtk_builder_new ();
if (!gtk_builder_add_from_file (builder, filename, &error))
g_error ("failed to add UI from file %s: %s", filename, error->message);
return builder;
}
/**
* gtk_builder_new_from_resource: (constructor)
* @resource_path: a `GResource` resource path
*
* Parses the UI definition at @resource_path.
*
* If there is an error locating the resource or parsing the
* description, then the program will be aborted.
*
* Returns: (transfer full): a `GtkBuilder` containing the described interface
*/
GtkBuilder *
gtk_builder_new_from_resource (const char *resource_path)
{
GError *error = NULL;
GtkBuilder *builder;
builder = gtk_builder_new ();
if (!gtk_builder_add_from_resource (builder, resource_path, &error))
g_error ("failed to add UI from resource %s: %s", resource_path, error->message);
return builder;
}
/**
* gtk_builder_new_from_string: (constructor)
* @string: a user interface (XML) description
* @length: the length of @string, or -1
*
* Parses the UI definition in @string.
*
* If @string is %NULL-terminated, then @length should be -1.
* If @length is not -1, then it is the length of @string.
*
* If there is an error parsing @string then the program will be
* aborted. You should not attempt to parse user interface description
* from untrusted sources.
*
* Returns: (transfer full): a `GtkBuilder` containing the interface described by @string
*/
GtkBuilder *
gtk_builder_new_from_string (const char *string,
gssize length)
{
GError *error = NULL;
GtkBuilder *builder;
builder = gtk_builder_new ();
if (!gtk_builder_add_from_string (builder, string, length, &error))
g_error ("failed to add UI: %s", error->message);
return builder;
}
/*< private >
* _gtk_builder_prefix_error:
* @builder: a `GtkBuilder`
* @context: the `GtkBuildableParseContext`
* @error: an error
*
* Calls g_prefix_error() to prepend a filename:line:column marker
* to the given error. The filename is taken from @builder, and
* the line and column are obtained by calling
* g_markup_parse_context_get_position().
*
* This is intended to be called on errors returned by
* g_markup_collect_attributes() in a start_element vfunc.
*/
void
_gtk_builder_prefix_error (GtkBuilder *builder,
GtkBuildableParseContext *context,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
int line, col;
gtk_buildable_parse_context_get_position (context, &line, &col);
g_prefix_error (error, "%s:%d:%d ", priv->filename, line, col);
}
/*< private >
* _gtk_builder_error_unhandled_tag:
* @builder: a `GtkBuilder`
* @context: the `GtkBuildableParseContext`
* @object: name of the object that is being handled
* @element_name: name of the element whose start tag is being handled
* @error: return location for the error
*
* Sets @error to a suitable error indicating that an @element_name
* tag is not expected in the custom markup for @object.
*
* This is intended to be called in a start_element vfunc.
*/
void
_gtk_builder_error_unhandled_tag (GtkBuilder *builder,
GtkBuildableParseContext *context,
const char *object,
const char *element_name,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
int line, col;
gtk_buildable_parse_context_get_position (context, &line, &col);
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_UNHANDLED_TAG,
"%s:%d:%d Unsupported tag for %s: <%s>",
priv->filename, line, col,
object, element_name);
}
/*< private >
* @builder: a `GtkBuilder`
* @context: the `GtkBuildableParseContext`
* @parent_name: the name of the expected parent element
* @error: return location for an error
*
* Checks that the parent element of the currently handled
* start tag is @parent_name and set @error if it isn't.
*
* This is intended to be called in start_element vfuncs to
* ensure that element nesting is as intended.
*
* Returns: %TRUE if @parent_name is the parent element
*/
gboolean
_gtk_builder_check_parent (GtkBuilder *builder,
GtkBuildableParseContext *context,
const char *parent_name,
GError **error)
{
return _gtk_builder_check_parents (builder, context, error, parent_name, NULL);
}
gboolean
_gtk_builder_check_parents (GtkBuilder *builder,
GtkBuildableParseContext *context,
GError **error,
...)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GPtrArray *stack;
int line, col;
const char *parent;
const char *element;
va_list args;
gboolean in_template;
stack = gtk_buildable_parse_context_get_element_stack (context);
element = g_ptr_array_index (stack, stack->len - 1);
parent = stack->len > 1 ? g_ptr_array_index (stack, stack->len - 2) : "";
in_template = g_str_equal (parent, "template");
va_start (args, error);
while (1) {
char *parent_name = va_arg (args, char *);
if (parent_name == NULL)
break;
if (g_str_equal (parent_name, parent) || (in_template && g_str_equal (parent_name, "object")))
{
va_end (args);
return TRUE;
}
}
va_end (args);
gtk_buildable_parse_context_get_position (context, &line, &col);
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_TAG,
"%s:%d:%d Can't use <%s> here",
priv->filename, line, col, element);
return FALSE;
}
GObject *
gtk_builder_lookup_object (GtkBuilder *builder,
const char *name,
int line,
int col,
GError **error)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GObject *obj;
obj = g_hash_table_lookup (priv->objects, name);
if (obj == NULL)
{
g_set_error (error,
GTK_BUILDER_ERROR, GTK_BUILDER_ERROR_INVALID_ID,
"%s:%d:%d Object with ID %s not found",
priv->filename, line, col, name);
}
return obj;
}
/*< private >
* @builder: a `GtkBuilder`
* @name: object name to look up
* @line: line number where @name was encountered
* @col: column number where @name was encountered
*
* Looks up an object by name. Similar to gtk_builder_get_object(),
* but sets an error if lookup fails during custom_tag_end,
* custom_finished or parser_finished vfuncs.
*
* The reason for doing things this way is that these vfuncs don't
* take a GError** parameter to return an error.
*
* Returns: the found object
*/
GObject *
_gtk_builder_lookup_object (GtkBuilder *builder,
const char *name,
int line,
int col)
{
GtkBuilderPrivate *priv = gtk_builder_get_instance_private (builder);
GObject *obj;
GError *error = NULL;
obj = g_hash_table_lookup (priv->objects, name);
error = (GError *) g_object_get_data (G_OBJECT (builder), "lookup-error");
if (!obj && !error)
{
g_set_error (&error,
GTK_BUILDER_ERROR, GTK_BUILDER_ERROR_INVALID_ID,
"%s:%d:%d Object with ID %s not found",
priv->filename, line, col, name);
g_object_set_data_full (G_OBJECT (builder), "lookup-error",
error, (GDestroyNotify)g_error_free);
}
return obj;
}
/*< private >
* _gtk_builder_lookup_failed:
* @GtkBuilder: a `GtkBuilder`
* @error: return location for error
*
* Finds whether any object lookups have failed.
*
* Returns: %TRUE if @error has been set
*/
gboolean
_gtk_builder_lookup_failed (GtkBuilder *builder,
GError **error)
{
GError *lookup_error;
lookup_error = (GError*) g_object_steal_data (G_OBJECT (builder), "lookup-error");
if (lookup_error)
{
g_propagate_error (error, lookup_error);
return TRUE;
}
return FALSE;
}