diff --git a/gio/gsettings.c b/gio/gsettings.c index a14ba8094..3eb6c9b10 100644 --- a/gio/gsettings.c +++ b/gio/gsettings.c @@ -2991,6 +2991,148 @@ g_settings_bind_with_mapping (GSettings *settings, binding, g_settings_binding_free); } +typedef struct _BindWithMappingClosuresData +{ + GClosure *get_mapping_closure; + GClosure *set_mapping_closure; +} BindWithMappingClosuresData; + +static BindWithMappingClosuresData * +bind_with_mapping_closures_data_new (GClosure *get_mapping_closure, + GClosure *set_mapping_closure) +{ + BindWithMappingClosuresData *data; + + data = g_new0 (BindWithMappingClosuresData, 1); + + if (get_mapping_closure != NULL) + { + data->get_mapping_closure = g_closure_ref (get_mapping_closure); + g_closure_sink (get_mapping_closure); + if (G_CLOSURE_NEEDS_MARSHAL (get_mapping_closure)) + g_closure_set_marshal (get_mapping_closure, g_cclosure_marshal_generic); + } + + if (set_mapping_closure != NULL) + { + data->set_mapping_closure = g_closure_ref (set_mapping_closure); + g_closure_sink (set_mapping_closure); + if (G_CLOSURE_NEEDS_MARSHAL (set_mapping_closure)) + g_closure_set_marshal (set_mapping_closure, g_cclosure_marshal_generic); + } + + return data; +} + +static void +bind_with_mapping_closures_data_free (BindWithMappingClosuresData *data) +{ + if (data->get_mapping_closure != NULL) + g_closure_unref (data->get_mapping_closure); + + if (data->set_mapping_closure != NULL) + g_closure_unref (data->set_mapping_closure); + + g_free (data); +} + +static gboolean +bind_with_mapping_invoke_get (GValue *value, + GVariant *variant, + void *user_data) +{ + BindWithMappingClosuresData *data = (BindWithMappingClosuresData *) user_data; + GValue params[2] = { G_VALUE_INIT, G_VALUE_INIT }; + GValue out = G_VALUE_INIT; + gboolean retval; + + g_value_init (¶ms[0], G_TYPE_VALUE); + g_value_set_boxed (¶ms[0], value); + g_value_init (¶ms[1], G_TYPE_VARIANT); + g_value_set_variant (¶ms[1], variant); + g_value_init (&out, G_TYPE_BOOLEAN); + + g_closure_invoke (data->get_mapping_closure, &out, 2, params, /* hint = */ NULL); + + retval = g_value_get_boolean (&out); + + g_value_unset (&out); + g_value_unset (¶ms[0]); + g_value_unset (¶ms[1]); + + return retval; +} + +static GVariant * +bind_with_mapping_invoke_set (const GValue *value, + const GVariantType *expected_type, + void *user_data) +{ + BindWithMappingClosuresData *data = (BindWithMappingClosuresData *) user_data; + GValue params[2] = { G_VALUE_INIT, G_VALUE_INIT }; + GValue out = G_VALUE_INIT; + GVariant *retval; + + g_value_init (¶ms[0], G_TYPE_VALUE); + g_value_set_boxed (¶ms[0], value); + g_value_init (¶ms[1], G_TYPE_VARIANT_TYPE); + g_value_set_boxed (¶ms[1], expected_type); + g_value_init (&out, G_TYPE_VARIANT); + + g_closure_invoke (data->set_mapping_closure, &out, 2, params, /* hint = */ NULL); + + retval = g_value_dup_variant (&out); + + g_value_unset (&out); + g_value_unset (¶ms[0]); + g_value_unset (¶ms[1]); + + return retval; +} + +static void +bind_with_mapping_destroy (void *user_data) +{ + BindWithMappingClosuresData *data = (BindWithMappingClosuresData *) user_data; + bind_with_mapping_closures_data_free (data); +} + +/** + * g_settings_bind_with_mapping_closures: (rename-to g_settings_bind_with_mapping): + * @settings: a #GSettings object + * @key: the key to bind + * @object: a #GObject + * @property: the name of the property to bind + * @flags: flags for the binding + * @get_mapping: (nullable): a function that gets called to convert values + * from @settings to @object, or %NULL to use the default GIO mapping + * @set_mapping: (nullable): a function that gets called to convert values + * from @object to @settings, or %NULL to use the default GIO mapping + * + * Version of g_settings_bind_with_mapping() using closures instead of callbacks + * for easier binding in other languages. + * + * Since: 2.82 + */ +void +g_settings_bind_with_mapping_closures (GSettings *settings, + const char *key, + GObject *object, + const char *property, + GSettingsBindFlags flags, + GClosure *get_mapping, + GClosure *set_mapping) +{ + BindWithMappingClosuresData *data; + + data = bind_with_mapping_closures_data_new (get_mapping, set_mapping); + + g_settings_bind_with_mapping (settings, key, object, property, flags, + bind_with_mapping_invoke_get, + bind_with_mapping_invoke_set, data, + bind_with_mapping_destroy); +} + /* Writability binding {{{1 */ typedef struct { diff --git a/gio/gsettings.h b/gio/gsettings.h index 8a9d2b5c7..afe0d8dca 100644 --- a/gio/gsettings.h +++ b/gio/gsettings.h @@ -322,6 +322,14 @@ void g_settings_bind_with_mapping (GSettin GSettingsBindSetMapping set_mapping, gpointer user_data, GDestroyNotify destroy); +GIO_AVAILABLE_IN_2_82 +void g_settings_bind_with_mapping_closures (GSettings *settings, + const char *key, + GObject *object, + const char *property, + GSettingsBindFlags flags, + GClosure *get_mapping, + GClosure *set_mapping); GIO_AVAILABLE_IN_ALL void g_settings_bind_writable (GSettings *settings, const gchar *key, diff --git a/gio/tests/gsettings.c b/gio/tests/gsettings.c index 6995feefd..4c25b3164 100644 --- a/gio/tests/gsettings.c +++ b/gio/tests/gsettings.c @@ -1714,6 +1714,146 @@ test_custom_binding (void) g_object_unref (settings); } +/* Same test as above, but with closures + */ +static void +test_bind_with_mapping_closures (void) +{ + TestObject *obj; + GSettings *settings; + char *s; + gboolean b; + GClosure *get; + GClosure *set; + + settings = g_settings_new ("org.gtk.test.binding"); + obj = test_object_new (); + + g_settings_set_string (settings, "string", "true"); + + get = g_cclosure_new (G_CALLBACK (string_to_bool), NULL, NULL); + set = g_cclosure_new (G_CALLBACK (bool_to_string), NULL, NULL); + + g_settings_bind_with_mapping_closures (settings, "string", + G_OBJECT (obj), "bool", + G_SETTINGS_BIND_DEFAULT, get, set); + + g_settings_set_string (settings, "string", "false"); + g_object_get (obj, "bool", &b, NULL); + g_assert_cmpint (b, ==, FALSE); + + g_settings_set_string (settings, "string", "not true"); + g_object_get (obj, "bool", &b, NULL); + g_assert_cmpint (b, ==, FALSE); + + g_object_set (obj, "bool", TRUE, NULL); + s = g_settings_get_string (settings, "string"); + g_assert_cmpstr (s, ==, "true"); + g_free (s); + + set = g_cclosure_new (G_CALLBACK (bool_to_bool), NULL, NULL); + + g_settings_bind_with_mapping_closures (settings, "string", + G_OBJECT (obj), "bool", + G_SETTINGS_BIND_DEFAULT, get, set); + g_test_expect_message (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, + "*binding mapping function for key 'string' returned" + " GVariant of type 'b' when type 's' was requested*"); + g_object_set (obj, "bool", FALSE, NULL); + g_test_assert_expected_messages (); + + g_object_unref (obj); + g_object_unref (settings); +} + +typedef struct +{ + gboolean get_called; + gboolean set_called; + gboolean get_freed; + gboolean set_freed; +} BindWithMappingData; + +static gboolean +get_callback (GValue *value, + GVariant *variant, + void *user_data) +{ + BindWithMappingData *data = (BindWithMappingData *) user_data; + data->get_called = TRUE; + + g_assert_true (G_VALUE_HOLDS_BOOLEAN (value)); + g_assert_true (g_variant_is_of_type (variant, G_VARIANT_TYPE_STRING)); + + return string_to_bool (value, variant, NULL); +} + +static GVariant * +set_callback (const GValue *value, + const GVariantType *expected_type, + void *user_data) +{ + BindWithMappingData *data = (BindWithMappingData *) user_data; + data->set_called = TRUE; + + g_assert_true (G_VALUE_HOLDS_BOOLEAN (value)); + g_assert_true (g_variant_type_equal (expected_type, G_VARIANT_TYPE_STRING)); + + return bool_to_string (value, expected_type, NULL); +} + +static void +teardown_get (void *user_data, GClosure *closure) +{ + BindWithMappingData *data = (BindWithMappingData *) user_data; + data->get_freed = TRUE; +} + +static void +teardown_set (void *user_data, GClosure *closure) +{ + BindWithMappingData *data = (BindWithMappingData *) user_data; + data->set_freed = TRUE; +} + +/* Tests the types of GValue and GVariant passed to the closures */ +static void +test_bind_with_mapping_closures_parameters (void) +{ + TestObject *obj; + GSettings *settings; + GClosure *get; + GClosure *set; + BindWithMappingData data = { FALSE, FALSE, FALSE, FALSE }; + + settings = g_settings_new ("org.gtk.test.binding"); + obj = test_object_new (); + + g_settings_set_string (settings, "string", "true"); + + get = g_cclosure_new (G_CALLBACK (get_callback), &data, teardown_get); + set = g_cclosure_new (G_CALLBACK (set_callback), &data, teardown_set); + + g_settings_bind_with_mapping_closures (settings, "string", + G_OBJECT (obj), "bool", + G_SETTINGS_BIND_DEFAULT, get, set); + + g_assert_true (data.get_called); + g_assert_false (data.set_called); + + data.get_called = FALSE; + g_object_set (obj, "bool", FALSE, NULL); + g_assert_true (data.set_called); + g_assert_false (data.get_called); + + g_object_unref (obj); + + g_assert_true (data.get_freed); + g_assert_true (data.set_freed); + + g_object_unref (settings); +} + /* Test that with G_SETTINGS_BIND_NO_CHANGES, the * initial settings value is transported to the object * side, but later settings changes do not affect the @@ -3237,6 +3377,8 @@ main (int argc, char *argv[]) g_test_add_func ("/gsettings/simple-binding", test_simple_binding); g_test_add_func ("/gsettings/directional-binding", test_directional_binding); g_test_add_func ("/gsettings/custom-binding", test_custom_binding); + g_test_add_func ("/gsettings/bind-with-mapping-closures", test_bind_with_mapping_closures); + g_test_add_func ("/gsettings/bind-with-mapping-closures-parameters", test_bind_with_mapping_closures_parameters); g_test_add_func ("/gsettings/no-change-binding", test_no_change_binding); g_test_add_func ("/gsettings/unbinding", test_unbind); g_test_add_func ("/gsettings/writable-binding", test_bind_writable);