[gjs: 1/2] Add override for g_object_bind_property_full()




commit 8979d72188fbc18a2c714d63dd1620de551c38ac
Author: Florian Müllner <fmuellner gnome org>
Date:   Tue Aug 10 18:19:31 2021 +0200

    Add override for g_object_bind_property_full()
    
    g_object_bind_property_full() isn't introspectable, because two callbacks
    share the same closure/destroy. That's why g_object_bind_with_closures()
    was added, but unfortunately that doesn't work for us either due to a
    combination of the following:
    
     1. we special-case GValue arguments and pass the unwrapped value
        to javascript instead of the actual GValue
    
     2. there are no annotations for GClosures ("the 3rd parameter
        is (out)")
    
    Changing the former would be a massive API break, not to mention that
    unwrapping GValues is almost always what we want.
    
    The latter would require lots of work on the gobject-introspection
    side for a very limited number of use cases.
    
    A custom override on the other hand only needs a very thin wrapper,
    so add that to finally make bind_property_full() available to JS.

 installed-tests/js/testGObjectClass.js | 33 ++++++++++++++++++++++++++++++++
 libgjs-private/gjs-util.c              | 26 +++++++++++++++++++++++++
 libgjs-private/gjs-util.h              | 35 ++++++++++++++++++++++++++++++++++
 modules/core/overrides/GObject.js      |  4 ++++
 4 files changed, 98 insertions(+)
---
diff --git a/installed-tests/js/testGObjectClass.js b/installed-tests/js/testGObjectClass.js
index 15878a9b..4b611aed 100644
--- a/installed-tests/js/testGObjectClass.js
+++ b/installed-tests/js/testGObjectClass.js
@@ -757,6 +757,39 @@ describe('Signal handler matching', function () {
     });
 });
 
+describe('Property bindings', function () {
+    const ObjectWithProperties = GObject.registerClass({
+        Properties: {
+            'string': GObject.ParamSpec.string('string', 'String', 'String property',
+                GObject.ParamFlags.READWRITE, ''),
+            'bool': GObject.ParamSpec.boolean('bool', 'Bool', 'Bool property',
+                GObject.ParamFlags.READWRITE, true),
+        },
+    }, class ObjectWithProperties extends GObject.Object {});
+
+    let a, b;
+    beforeEach(function () {
+        a = new ObjectWithProperties();
+        b = new ObjectWithProperties();
+    });
+
+    it('can bind properties of the same type', function () {
+        a.bind_property('string', b, 'string', GObject.BindingFlags.NONE);
+        a.string = 'foo';
+        expect(a.string).toEqual('foo');
+        expect(b.string).toEqual('foo');
+    });
+
+    it('can use custom mappings to bind properties of different types', function () {
+        a.bind_property_full('bool', b, 'string', GObject.BindingFlags.NONE,
+            (bind, source) => [true, `${source}`],
+            null);
+        a.bool = true;
+        expect(a.bool).toEqual(true);
+        expect(b.string).toEqual('true');
+    });
+});
+
 describe('Auto accessor generation', function () {
     const AutoAccessors = GObject.registerClass({
         Properties: {
diff --git a/libgjs-private/gjs-util.c b/libgjs-private/gjs-util.c
index 15060950..15ccc153 100644
--- a/libgjs-private/gjs-util.c
+++ b/libgjs-private/gjs-util.c
@@ -98,6 +98,32 @@ gjs_param_spec_get_owner_type(GParamSpec *pspec)
     return pspec->owner_type;
 }
 
+#define G_CLOSURE_NOTIFY(func) ((GClosureNotify)(void (*)(void))func)
+
+GBinding* gjs_g_object_bind_property_full(
+    GObject* source, const char* source_property, GObject* target,
+    const char* target_property, GBindingFlags flags,
+    GjsBindingTransformFunc to_callback, void* to_data,
+    GDestroyNotify to_notify, GjsBindingTransformFunc from_callback,
+    void* from_data, GDestroyNotify from_notify) {
+    GClosure* to_closure = NULL;
+    GClosure* from_closure = NULL;
+
+    if (to_callback)
+        to_closure = g_cclosure_new(G_CALLBACK(to_callback), to_data,
+                                    G_CLOSURE_NOTIFY(to_notify));
+
+    if (from_callback)
+        from_closure = g_cclosure_new(G_CALLBACK(from_callback), from_data,
+                                      G_CLOSURE_NOTIFY(from_notify));
+
+    return g_object_bind_property_with_closures(source, source_property, target,
+                                                target_property, flags,
+                                                to_closure, from_closure);
+}
+
+#undef G_CLOSURE_NOTIFY
+
 static GParamSpec* gjs_gtk_container_class_find_child_property(
     GIObjectInfo* container_info, GObject* container, const char* property) {
     GIBaseInfo* class_info = NULL;
diff --git a/libgjs-private/gjs-util.h b/libgjs-private/gjs-util.h
index 320337c5..7dfce639 100644
--- a/libgjs-private/gjs-util.h
+++ b/libgjs-private/gjs-util.h
@@ -79,6 +79,41 @@ GType       gjs_param_spec_get_value_type (GParamSpec *pspec);
 GJS_EXPORT
 GType       gjs_param_spec_get_owner_type (GParamSpec *pspec);
 
+/**
+ * GjsBindingTransformFunc:
+ * @binding:
+ * @from_value:
+ * @to_value: (out):
+ * @user_data:
+ */
+typedef gboolean (*GjsBindingTransformFunc)(GBinding* binding,
+                                            const GValue* from_value,
+                                            GValue* to_value, void* user_data);
+
+/**
+ * gjs_g_object_bind_property_full:
+ * @source:
+ * @source_property:
+ * @target:
+ * @target_property:
+ * @flags:
+ * @to_callback: (scope notified) (nullable):
+ * @to_data: (closure to_callback):
+ * @to_notify: (destroy to_data):
+ * @from_callback: (scope notified) (nullable):
+ * @from_data: (closure from_callback):
+ * @from_notify: (destroy from_data):
+ *
+ * Returns: (transfer none):
+ */
+GJS_EXPORT
+GBinding* gjs_g_object_bind_property_full(
+    GObject* source, const char* source_property, GObject* target,
+    const char* target_property, GBindingFlags flags,
+    GjsBindingTransformFunc to_callback, void* to_data,
+    GDestroyNotify to_notify, GjsBindingTransformFunc from_callback,
+    void* from_data, GDestroyNotify from_notify);
+
 /* For imports.overrides.Gtk */
 GJS_EXPORT
 void gjs_gtk_container_child_set_property(GObject* container, GObject* child,
diff --git a/modules/core/overrides/GObject.js b/modules/core/overrides/GObject.js
index 8058a2ec..6bfaf144 100644
--- a/modules/core/overrides/GObject.js
+++ b/modules/core/overrides/GObject.js
@@ -556,6 +556,10 @@ function _init() {
         Object.assign(this, params);
     };
 
+    GObject.Object.prototype.bind_property_full = function (...args) {
+        return GjsPrivate.g_object_bind_property_full(this, ...args);
+    };
+
     // fake enum for signal accumulators, keep in sync with gi/object.c
     GObject.AccumulatorType = {
         NONE: 0,


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