[gjs/ewlsh/register-type: 5/5] API Additions




commit b02bd24a7bc0155f49680fbe80c79409dcda8a63
Author: Evan Welsh <contact evanwelsh com>
Date:   Thu Dec 30 21:58:29 2021 -0800

    API Additions

 installed-tests/js/testGObjectClass.js |  58 +++++++++++++
 modules/core/_common.js                |  28 ++++--
 modules/core/overrides/GObject.js      |  32 ++++++-
 modules/core/overrides/Gtk.js          | 154 ++++++++++++++++++++++++++++-----
 modules/script/_legacy.js              |   9 +-
 5 files changed, 246 insertions(+), 35 deletions(-)
---
diff --git a/installed-tests/js/testGObjectClass.js b/installed-tests/js/testGObjectClass.js
index 41e7a8e9..0551c633 100644
--- a/installed-tests/js/testGObjectClass.js
+++ b/installed-tests/js/testGObjectClass.js
@@ -1371,3 +1371,61 @@ describe('GObject class with int64 properties', function () {
         expect(instance.int64).toBe(GLib.MAXINT32 + 1);
     });
 });
+
+class MyStaticRegisteredObject extends GObject.Object {
+}
+
+MyStaticRegisteredObject.register();
+
+class MyStaticRegisteredInterface extends GObject.Interface {
+    anInterfaceMethod() {}
+}
+
+MyStaticRegisteredInterface.register();
+
+describe('GObject class with decorator', function () {
+    it('throws an error when not used with a GObject-derived class', function () {
+        class Foo { }
+        class Bar extends Foo { }
+        expect(() => Bar.register()).toThrow();
+    });
+});
+
+describe('GObject creation using base classes without registered GType', function () {
+    it('fails when trying to instantiate a class that inherits from a GObject type', function () {
+        const BadInheritance = class extends GObject.Object { };
+        const BadDerivedInheritance = class extends Derived { };
+
+        expect(() => new BadInheritance()).toThrowError(
+            /Tried to construct an object without a GType/
+        );
+        expect(() => new BadDerivedInheritance()).toThrowError(
+            /Tried to construct an object without a GType/
+        );
+    });
+
+    it('fails when trying to register a GObject class that inherits from a non-GObject type', function () {
+        class BadInheritance extends GObject.Object { }
+        class BadInheritanceDerived extends BadInheritance { }
+        expect(() => BadInheritanceDerived.register()).toThrowError(
+            /Object 0x[a-f0-9]+ is not a subclass of GObject_Object, it's a Object/
+        );
+    });
+});
+
+describe('GObject class registered with registerType', function () {
+    class SubObject extends MyStaticRegisteredObject {
+    }
+
+    SubObject.register();
+
+    it('extends class registered with registerClass', function () {
+        expect(() => new SubObject()).not.toThrow();
+
+        const instance = new SubObject();
+
+        expect(instance instanceof SubObject).toBeTrue();
+        expect(instance instanceof GObject.Object).toBeTrue();
+        expect(instance instanceof MyStaticRegisteredObject).toBeTrue();
+    });
+});
diff --git a/modules/core/_common.js b/modules/core/_common.js
index 4e361e16..780483b9 100644
--- a/modules/core/_common.js
+++ b/modules/core/_common.js
@@ -59,7 +59,7 @@ function _generateAccessors(pspec, propdesc, GObject) {
     return propdesc;
 }
 
-function _checkAccessors(proto, pspec, GObject) {
+function _checkAccessors(proto, pspec, GObject, {generateAccessors = true, accessorMappingSymbol = null} = 
{}) {
     const {name, flags} = pspec;
     if (flags & GObject.ParamFlags.CONSTRUCT_ONLY)
         return;
@@ -83,13 +83,25 @@ function _checkAccessors(proto, pspec, GObject) {
     if (!propdesc || (readable && !propdesc.get) || (writable && !propdesc.set))
         propdesc = _generateAccessors(pspec, propdesc, GObject);
 
-    if (!dashPropdesc)
-        Object.defineProperty(proto, name, propdesc);
-    if (nameIsCompound) {
-        if (!underscorePropdesc)
-            Object.defineProperty(proto, underscoreName, propdesc);
-        if (!camelPropdesc)
-            Object.defineProperty(proto, camelName, propdesc);
+    if (generateAccessors) {
+        if (!dashPropdesc)
+            Object.defineProperty(proto, name, propdesc);
+        if (nameIsCompound) {
+            if (!underscorePropdesc)
+                Object.defineProperty(proto, underscoreName, propdesc);
+            if (!camelPropdesc)
+                Object.defineProperty(proto, camelName, propdesc);
+        }
+    }
+
+    if (accessorMappingSymbol) {
+        proto[accessorMappingSymbol] = proto[accessorMappingSymbol] ?? {};
+        if (nameIsCompound) {
+            proto[accessorMappingSymbol][underscoreName] = propdesc;
+            proto[accessorMappingSymbol][camelName] = propdesc;
+        } else {
+            proto[accessorMappingSymbol][name] = propdesc;
+        }
     }
 }
 
diff --git a/modules/core/overrides/GObject.js b/modules/core/overrides/GObject.js
index f7817e80..1b5fccdc 100644
--- a/modules/core/overrides/GObject.js
+++ b/modules/core/overrides/GObject.js
@@ -23,6 +23,8 @@ var _gtkCssName = Symbol('GTK widget CSS name');
 var _gtkInternalChildren = Symbol('GTK widget template internal children');
 var _gtkTemplate = Symbol('GTK widget template');
 
+const _accessorMapping = Symbol('GObject accessor mapping for class fields');
+
 function _mapWidgetDefinitionToClass(klass, metaInfo) {
     if ('CssName' in metaInfo)
         klass[_gtkCssName] = metaInfo.CssName;
@@ -351,12 +353,12 @@ function _checkInterface(iface, proto) {
     }
 }
 
-function _checkProperties(klass) {
+function _checkProperties(klass, {generateAccessors}) {
     if (!klass.hasOwnProperty(properties))
         return;
 
     for (let pspec of Object.values(klass[properties]))
-        _checkAccessors(klass.prototype, pspec, GObject);
+        _checkAccessors(klass.prototype, pspec, GObject, {generateAccessors, accessorMappingSymbol: 
_accessorMapping});
 }
 
 function _init() {
@@ -612,6 +614,17 @@ function _init() {
     });
 
     definePublicProperties(GObject.Object, {
+        register(classDefinition = {}) {
+            // Ensure the class derives from GObject.Object or
+            // GObject.Interface
+            _assertDerivesFromGObject(this, [GObject.Object], 'Object.register');
+
+            _mapTypeDefinition(this, classDefinition);
+            _mapClassDefinition(this, classDefinition);
+            _checkProperties(this, {generateAccessors: false});
+
+            this[_registerType]();
+        },
         implements(iface) {
             if (iface.$gtype)
                 return GObject.type_is_a(this, iface.$gtype);
@@ -662,7 +675,7 @@ function _init() {
     });
 
     GObject.Object._classInit = function (klass) {
-        _checkProperties(klass);
+        _checkProperties(klass, {generateAccessors: true});
 
         if (_registerType in klass)
             klass[_registerType]();
@@ -680,6 +693,19 @@ function _init() {
         return false;
     }
 
+    definePublicProperties(GObject.Interface, {
+        register(classDefinition = {}) {
+            // Ensure the class derives from GObject.Interface
+            _assertDerivesFromGObject(this, [GObject.Interface], 'Interface.register');
+
+            _mapTypeDefinition(this, classDefinition);
+            _mapInterfaceDefinition(this, classDefinition);
+            _checkProperties(this, {generateAccessors: false});
+
+            this[_registerType]();
+        },
+    });
+
     definePrivateProperties(GObject.Interface, {
         [_registerType]() {
             let klass = this;
diff --git a/modules/core/overrides/Gtk.js b/modules/core/overrides/Gtk.js
index 1f123539..f70f082e 100644
--- a/modules/core/overrides/Gtk.js
+++ b/modules/core/overrides/Gtk.js
@@ -4,11 +4,48 @@
 
 const Legacy = imports._legacy;
 const {Gio, GjsPrivate, GObject} = imports.gi;
-const {_registerType, definePrivateProperties} = imports._common;
+const {_registerType, definePublicProperties, definePrivateProperties} = imports._common;
 
 let Gtk;
 let BuilderScope;
 
+const _hasTemplateSymbol = Symbol('GTK Widget has template');
+const _defineChildrenAtInitSymbol = Symbol('GTK Widget assign children to properties at init');
+
+function _hasTemplate(constructor) {
+    return constructor.hasOwnProperty(_hasTemplateSymbol) &&
+        constructor[_hasTemplateSymbol];
+}
+
+function _setHasTemplate(klass) {
+    definePrivateProperties(klass, {
+        [_hasTemplateSymbol]: true,
+    });
+}
+
+function _shouldDefineChildrenDuringInit(constructor) {
+    return constructor.hasOwnProperty(_defineChildrenAtInitSymbol) &&
+        constructor[_defineChildrenAtInitSymbol];
+}
+
+function _mapWidgetDefinitionToClass(klass, classDefinition) {
+    if ('CssName' in classDefinition)
+        klass[Gtk.cssName] = classDefinition.CssName;
+    if ('Template' in classDefinition)
+        klass[Gtk.template] = classDefinition.Template;
+    if ('Children' in classDefinition)
+        klass[Gtk.children] = classDefinition.Children;
+    if ('InternalChildren' in classDefinition)
+        klass[Gtk.internalChildren] = classDefinition.InternalChildren;
+}
+
+function _assertDerivesFromWidget(klass, functionName) {
+    if (!Gtk.Widget.prototype.isPrototypeOf(klass.prototype)) {
+        throw new TypeError(`Gtk.${functionName}() used with invalid base ` +
+            `class (is ${Object.getPrototypeOf(klass).name ?? klass})`);
+    }
+}
+
 function defineChildren(instance, constructor, target = instance) {
     let children = constructor[Gtk.children] || [];
     for (let child of children) {
@@ -26,12 +63,92 @@ function defineChildren(instance, constructor, target = instance) {
 function _init() {
     Gtk = this;
 
+    Gtk.defineChildren = defineChildren;
     Gtk.children = GObject.__gtkChildren__;
     Gtk.cssName = GObject.__gtkCssName__;
     Gtk.internalChildren = GObject.__gtkInternalChildren__;
     Gtk.template = GObject.__gtkTemplate__;
 
-    let {GtkWidgetClass} = Legacy.defineGtkLegacyObjects(GObject, Gtk);
+    let {GtkWidgetClass} = Legacy.defineGtkLegacyObjects(GObject, Gtk, _defineChildrenAtInitSymbol);
+
+    // Gtk.Widget instance overrides
+    const {get_template_child} = Gtk.Widget.prototype;
+
+    Object.assign(Gtk.Widget.prototype, {
+        get_template_child(constructorOrName, name) {
+            if (typeof constructorOrName === 'string')
+                return get_template_child.call(this, this.constructor, constructorOrName);
+
+
+            return get_template_child.call(this, constructorOrName, name);
+        },
+    });
+
+    // Gtk.Widget instance additions
+    definePublicProperties(Gtk.Widget.prototype, {
+        _instance_init() {
+            if (_hasTemplate(this.constructor))
+                this.init_template();
+        },
+        get_template_children() {
+            let children = [this.constructor[Gtk.children] || []];
+            let map = {};
+            for (let child of children)
+                map[child.replace(/-/g, '_')] = map.get_template_child(constructor, child);
+        },
+    });
+
+    // Gtk.Widget static overrides
+    const {set_template, set_template_from_resource, bind_template_child_full} = Gtk.Widget;
+
+    Object.assign(Gtk.Widget, {
+        set_template(contents) {
+            set_template.call(this, contents);
+
+            _setHasTemplate(this);
+        },
+        set_template_from_resource(resource) {
+            set_template_from_resource.call(this, resource);
+
+            _setHasTemplate(this);
+        },
+        bind_template_child_full(name, isInternal = false) {
+            bind_template_child_full.call(this, name, isInternal, 0);
+        },
+    });
+
+    // Gtk.Widget static additions
+    definePublicProperties(Gtk.Widget, {
+        set_template_from_uri(template) {
+            if (template.startsWith('resource:///')) {
+                this.set_template_from_resource(template.slice(11));
+            } else if (template.startsWith('file:///')) {
+                let file = Gio.File.new_for_uri(template);
+                let [, contents] = file.load_contents(null);
+                this.set_template(contents);
+            } else {
+                throw new Error(`Invalid template Uri: ${template}`);
+            }
+        },
+        register(classDefinition) {
+            _assertDerivesFromWidget(this, 'Widget.register()');
+
+            _mapWidgetDefinitionToClass(this, classDefinition);
+
+            definePrivateProperties(this, {
+                [_defineChildrenAtInitSymbol]: false,
+            });
+
+            GObject.Object.register.call(this, classDefinition);
+        },
+        bind_template_child(name) {
+            bind_template_child_full.call(this, name, false, 0);
+        },
+        bind_template_child_internal(name) {
+            bind_template_child_full.call(this, name, true, 0);
+        },
+    });
+
     Gtk.Widget.prototype.__metaclass__ = GtkWidgetClass;
 
     if (Gtk.Container && Gtk.Container.prototype.child_set_property) {
@@ -43,7 +160,7 @@ function _init() {
     Gtk.Widget.prototype._init = function (params) {
         let wrapper = this;
 
-        if (wrapper.constructor[Gtk.template]) {
+        if (_hasTemplate(wrapper.constructor)) {
             if (!BuilderScope) {
                 Gtk.Widget.set_connect_func.call(wrapper.constructor,
                     (builder, obj, signalName, handlerName, connectObj, flags) => {
@@ -61,13 +178,17 @@ function _init() {
 
         wrapper = GObject.Object.prototype._init.call(wrapper, params) ?? wrapper;
 
-        if (wrapper.constructor[Gtk.template])
+        if (_hasTemplate(wrapper.constructor) && _shouldDefineChildrenDuringInit(wrapper.constructor))
             defineChildren(this, wrapper.constructor);
 
         return wrapper;
     };
 
     Gtk.Widget._classInit = function (klass) {
+        definePrivateProperties(klass, {
+            [_defineChildrenAtInitSymbol]: true,
+        });
+
         return GObject.Object._classInit(klass);
     };
 
@@ -81,28 +202,15 @@ function _init() {
             let internalChildren = klass[Gtk.internalChildren];
 
             GObject.Object[_registerType].call(klass);
-
-            if (template) {
-                klass.prototype._instance_init = function () {
-                    this.init_template();
-                };
-            }
             if (cssName)
                 Gtk.Widget.set_css_name.call(klass, cssName);
 
             if (template) {
-                if (typeof template === 'string') {
-                    if (template.startsWith('resource:///')) {
-                        Gtk.Widget.set_template_from_resource.call(klass,
-                            template.slice(11));
-                    } else if (template.startsWith('file:///')) {
-                        let file = Gio.File.new_for_uri(template);
-                        let [, contents] = file.load_contents(null);
-                        Gtk.Widget.set_template.call(klass, contents);
-                    }
-                } else {
+                if (typeof template === 'string' &&
+                    (template.startsWith('resource:///') || template.startsWith('file:///')))
+                    Gtk.Widget.set_template_from_uri.call(klass, template);
+                else
                     Gtk.Widget.set_template.call(klass, template);
-                }
 
                 if (BuilderScope)
                     Gtk.Widget.set_template_scope.call(klass, new BuilderScope());
@@ -110,12 +218,12 @@ function _init() {
 
             if (children) {
                 children.forEach(child =>
-                    Gtk.Widget.bind_template_child_full.call(klass, child, false, 0));
+                    Gtk.Widget.bind_template_child_internal.call(klass, child));
             }
 
             if (internalChildren) {
                 internalChildren.forEach(child =>
-                    Gtk.Widget.bind_template_child_full.call(klass, child, true, 0));
+                    Gtk.Widget.bind_template_child.call(klass, child));
             }
         },
     });
diff --git a/modules/script/_legacy.js b/modules/script/_legacy.js
index 3353ef27..4c8c3796 100644
--- a/modules/script/_legacy.js
+++ b/modules/script/_legacy.js
@@ -644,7 +644,7 @@ function defineGObjectLegacyObjects(GObject) {
     return {GObjectMeta, GObjectInterface};
 }
 
-function defineGtkLegacyObjects(GObject, Gtk) {
+function defineGtkLegacyObjects(GObject, Gtk, defineChildrenAtInitSymbol) {
     const GtkWidgetClass = new Class({
         Name: 'GtkWidgetClass',
         Extends: GObject.Class,
@@ -685,6 +685,13 @@ function defineGtkLegacyObjects(GObject, Gtk) {
             this[Gtk.children] = children;
             this[Gtk.internalChildren] = internalChildren;
 
+            Object.defineProperty(this, defineChildrenAtInitSymbol, {
+                value: true,
+                writable: false,
+                enumerable: false,
+                configurable: false,
+            });
+
             if (children) {
                 for (let i = 0; i < children.length; i++)
                     Gtk.Widget.bind_template_child_full.call(this, children[i], false, 0);


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