Compare commits

...

3 Commits

Author SHA1 Message Date
Emmanuele Bassi
24b69a9a10 examples: Update to bind settings inside UI definitions 2021-09-09 19:22:48 +01:00
Emmanuele Bassi
043eea6ae3 docs: Document the settings binding in GtkBuilder UI files
And clean up the structure while we're at it.
2021-09-09 19:22:06 +01:00
Emmanuele Bassi
181fce8342 Support binding properties to GSettings keys in GtkBuilder
We already support binding properties; by adding new attributes to the
`property` element we can automatically call g_settings_bind() on the
object ourselves.
2021-09-09 19:12:40 +01:00
5 changed files with 136 additions and 54 deletions

View File

@@ -7,10 +7,6 @@
struct _ExampleAppPrefs
{
GtkDialog parent;
GSettings *settings;
GtkWidget *font;
GtkWidget *transition;
};
G_DEFINE_TYPE (ExampleAppPrefs, example_app_prefs, GTK_TYPE_DIALOG)
@@ -19,41 +15,20 @@ static void
example_app_prefs_init (ExampleAppPrefs *prefs)
{
gtk_widget_init_template (GTK_WIDGET (prefs));
prefs->settings = g_settings_new ("org.gtk.exampleapp");
g_settings_bind (prefs->settings, "font",
prefs->font, "font",
G_SETTINGS_BIND_DEFAULT);
g_settings_bind (prefs->settings, "transition",
prefs->transition, "active-id",
G_SETTINGS_BIND_DEFAULT);
}
static void
example_app_prefs_dispose (GObject *object)
{
ExampleAppPrefs *prefs;
prefs = EXAMPLE_APP_PREFS (object);
g_clear_object (&prefs->settings);
G_OBJECT_CLASS (example_app_prefs_parent_class)->dispose (object);
}
static void
example_app_prefs_class_init (ExampleAppPrefsClass *class)
{
G_OBJECT_CLASS (class)->dispose = example_app_prefs_dispose;
gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (class),
"/org/gtk/exampleapp/prefs.ui");
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppPrefs, font);
gtk_widget_class_bind_template_child (GTK_WIDGET_CLASS (class), ExampleAppPrefs, transition);
}
ExampleAppPrefs *
example_app_prefs_new (ExampleAppWindow *win)
{
return g_object_new (EXAMPLE_APP_PREFS_TYPE, "transient-for", win, "use-header-bar", TRUE, NULL);
return g_object_new (EXAMPLE_APP_PREFS_TYPE,
"transient-for", win,
"use-header-bar", TRUE,
NULL);
}

View File

@@ -28,6 +28,7 @@
</child>
<child>
<object class="GtkFontButton" id="font">
<property name="font" bind-settings-schema="org.gtk.exampleapp" bind-settings-key="font"/>
<layout>
<property name="column">1</property>
<property name="row">0</property>
@@ -48,6 +49,7 @@
</child>
<child>
<object class="GtkComboBoxText" id="transition">
<property name="active-id" bind-settings-schema="org.gtk.exampleapp" bind-settings-key="transition"/>
<items>
<item translatable="yes" id="none">None</item>
<item translatable="yes" id="crossfade">Fade</item>

View File

@@ -65,6 +65,14 @@
* using `dgettext()` in the domain specified. This can also be done by
* calling [method@Gtk.Builder.set_translation_domain] on the builder.
*
* 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.
*
* ### Objects
*
* 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
@@ -72,16 +80,10 @@
* 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
* 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.
*
@@ -92,6 +94,8 @@
* reserves ids starting and ending with `___` (three consecutive
* underscores) for its own purposes.
*
* ### Properties and values
*
* 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.
@@ -125,12 +129,27 @@
* 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 bindings
*
* It is 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().
* objects. For more information see the documentation for
* [method@GObject.Object.bind_property].
*
* ### Settings bindings
*
* It is possible to bind a property value to a [class@Gio.Settings] key
* inside a schema, by using the attributes "bind-settings-schema" to
* define the schema, and "bind-settings-key" to define the key. If either
* attribute is set, the other is mandatory. Additionally, you can use
* the "bind-flags" attribute to specify the [enum@Gio.SettingsBindFlags]
* to be used by the binding. For more information see the documentation
* for [method@Gio.Settings.bind].
*
* ### Internal children
*
* Sometimes it is necessary to refer to widgets which have implicitly
* been constructed by GTK as part of a composite widget, to set
@@ -146,7 +165,7 @@
* 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 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
@@ -161,9 +180,9 @@
* 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.
* 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
* be compiled with the `-Wl,--export-dynamic` `CFLAGS`, and linked against
* `gmodule-export-2.0`.
*
* # A GtkBuilder UI Definition
@@ -191,10 +210,10 @@
* ```
*
* 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
* 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.
* 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.
@@ -719,6 +738,11 @@ gtk_builder_take_bindings (GtkBuilder *builder,
BindingExpressionInfo *info = l->data;
info->target = target;
}
else if (common_info->tag_type == TAG_BINDING_SETTING)
{
BindingSettingsInfo *info = l->data;
info->target = target;
}
else
{
g_assert_not_reached ();
@@ -1146,6 +1170,17 @@ gtk_builder_create_bindings (GtkBuilder *builder,
free_binding_expression_info (info);
}
else if (common_info->tag_type == TAG_BINDING_SETTING)
{
BindingSettingsInfo *info = l->data;
GSettings *settings;
settings = g_settings_new (info->schema);
g_settings_bind (settings, info->key, info->target, info->target_pspec->name, info->flags);
g_object_unref (settings);
free_binding_settings_info (info);
}
else
g_assert_not_reached ();
}

View File

@@ -874,7 +874,8 @@ parse_property (ParserData *data,
const char *bind_source = NULL;
const char *bind_property = NULL;
const char *bind_flags_str = NULL;
GBindingFlags bind_flags = G_BINDING_DEFAULT;
const char *bind_schema = NULL;
const char *bind_key = NULL;
gboolean translatable = FALSE;
ObjectInfo *object_info;
GParamSpec *pspec = NULL;
@@ -897,6 +898,8 @@ parse_property (ParserData *data,
G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-source", &bind_source,
G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-property", &bind_property,
G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-flags", &bind_flags_str,
G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-settings-schema", &bind_schema,
G_MARKUP_COLLECT_STRING|G_MARKUP_COLLECT_OPTIONAL, "bind-settings-key", &bind_key,
G_MARKUP_COLLECT_INVALID))
{
_gtk_builder_prefix_error (data->builder, &data->ctx, error);
@@ -916,21 +919,59 @@ parse_property (ParserData *data,
return;
}
if (bind_flags_str)
gtk_buildable_parse_context_get_position (&data->ctx, &line, &col);
if (bind_schema != NULL)
{
if (!_gtk_builder_flags_from_string (G_TYPE_BINDING_FLAGS, NULL, bind_flags_str, &bind_flags, error))
GSettingsBindFlags bind_flags = G_SETTINGS_BIND_DEFAULT;
if (bind_flags_str)
{
_gtk_builder_prefix_error (data->builder, &data->ctx, error);
if (!_gtk_builder_flags_from_string (G_TYPE_SETTINGS_BIND_FLAGS, NULL, bind_flags_str, &bind_flags, error))
{
_gtk_builder_prefix_error (data->builder, &data->ctx, error);
return;
}
}
if (bind_key != NULL)
{
BindingSettingsInfo *binfo;
binfo = g_slice_new0 (BindingSettingsInfo);
binfo->tag_type = TAG_BINDING_SETTING;
binfo->target = NULL;
binfo->target_pspec = pspec;
binfo->schema = g_strdup (bind_schema);
binfo->key = g_strdup (bind_key);
binfo->flags = bind_flags;
binfo->line = line;
binfo->col = col;
object_info->bindings = g_slist_prepend (object_info->bindings, binfo);
}
else
{
error_missing_attribute (data, element_name,
"bind-settings-key",
error);
return;
}
}
gtk_buildable_parse_context_get_position (&data->ctx, &line, &col);
if (bind_source)
else if (bind_source != NULL)
{
GBindingFlags bind_flags = G_BINDING_DEFAULT;
BindingInfo *binfo;
if (bind_flags_str)
{
if (!_gtk_builder_flags_from_string (G_TYPE_BINDING_FLAGS, NULL, bind_flags_str, &bind_flags, error))
{
_gtk_builder_prefix_error (data->builder, &data->ctx, error);
return;
}
}
binfo = g_slice_new0 (BindingInfo);
binfo->tag_type = TAG_BINDING;
binfo->target = NULL;
@@ -943,7 +984,14 @@ parse_property (ParserData *data,
object_info->bindings = g_slist_prepend (object_info->bindings, binfo);
}
else if (bind_property)
else if (bind_key != NULL)
{
error_missing_attribute (data, element_name,
"bind-settings-schema",
error);
return;
}
else if (bind_property != NULL)
{
error_missing_attribute (data, element_name,
"bind-source",
@@ -956,7 +1004,7 @@ parse_property (ParserData *data,
info->pspec = pspec;
info->text = g_string_new ("");
info->translatable = translatable;
info->bound = bind_source != NULL;
info->bound = bind_source != NULL || bind_schema != NULL;
info->context = g_strdup (context);
info->line = line;
info->col = col;
@@ -1563,6 +1611,14 @@ free_binding_expression_info (BindingExpressionInfo *info)
g_slice_free (BindingExpressionInfo, info);
}
void
free_binding_settings_info (BindingSettingsInfo *info)
{
g_free (info->schema);
g_free (info->key);
g_slice_free (BindingSettingsInfo, info);
}
static void
free_requires_info (RequiresInfo *info,
gpointer user_data)

View File

@@ -27,6 +27,7 @@ enum {
TAG_PROPERTY,
TAG_BINDING,
TAG_BINDING_EXPRESSION,
TAG_BINDING_SETTING,
TAG_REQUIRES,
TAG_OBJECT,
TAG_CHILD,
@@ -141,6 +142,18 @@ typedef struct
int col;
} BindingExpressionInfo;
typedef struct
{
guint tag_type;
GObject *target;
GParamSpec *target_pspec;
char *schema;
char *key;
GSettingsBindFlags flags;
int line;
int col;
} BindingSettingsInfo;
typedef struct {
guint tag_type;
char *library;
@@ -228,6 +241,7 @@ void _free_signal_info (SignalInfo *info,
void _free_binding_info (BindingInfo *info,
gpointer user_data);
void free_binding_expression_info (BindingExpressionInfo *info);
void free_binding_settings_info (BindingSettingsInfo *info);
GtkExpression * expression_info_construct (GtkBuilder *builder,
ExpressionInfo *info,
GError **error);