diff --git a/src/cpp/include/nodegui/QtCore/QAbstractItemModel/nabstractitemmodel.hpp b/src/cpp/include/nodegui/QtCore/QAbstractItemModel/nabstractitemmodel.hpp index cd7c818af..4a79fe8d5 100644 --- a/src/cpp/include/nodegui/QtCore/QAbstractItemModel/nabstractitemmodel.hpp +++ b/src/cpp/include/nodegui/QtCore/QAbstractItemModel/nabstractitemmodel.hpp @@ -35,6 +35,10 @@ class DLL_EXPORT NAbstractItemModel : public QAbstractItemModel, return *newIndex; } + QObject *parent() const { + return nullptr; + } + QModelIndex parent(const QModelIndex& child) const override { Napi::Env env = this->dispatchOnNode.Env(); Napi::HandleScope scope(env); diff --git a/src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h b/src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h index 83880d5b7..47eca9eed 100644 --- a/src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h +++ b/src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h @@ -5,6 +5,8 @@ #include "Extras/Utils/nutils.h" #include "QtCore/QVariant/qvariant_wrap.h" #include "core/Events/eventwidget_macro.h" +#include "core/WrapperCache/wrappercache.h" + /* This macro adds common QObject exported methods @@ -88,6 +90,15 @@ int id = info[0].As().Int32Value(); \ this->instance->killTimer(id); \ return env.Null(); \ + } \ + Napi::Value parent(const Napi::CallbackInfo& info) { \ + Napi::Env env = info.Env(); \ + QObject *parent = this->instance->parent(); \ + if (parent) { \ + return WrapperCache::instance.getWrapper(env, parent); \ + } else { \ + return env.Null(); \ + } \ } // Ideally this macro below should go in @@ -131,7 +142,8 @@ InstanceMethod("dumpObjectInfo", &ComponentWrapName::dumpObjectInfo), \ InstanceMethod("setParent", &ComponentWrapName::setParent), \ InstanceMethod("startTimer", &ComponentWrapName::startTimer), \ - InstanceMethod("killTimer", &ComponentWrapName::killTimer), + InstanceMethod("killTimer", &ComponentWrapName::killTimer), \ + InstanceMethod("parent", &ComponentWrapName::parent), #endif // QOBJECT_WRAPPED_METHODS_EXPORT_DEFINE diff --git a/src/cpp/include/nodegui/QtCore/QObject/qobject_wrap.h b/src/cpp/include/nodegui/QtCore/QObject/qobject_wrap.h index a0129e050..eaf50c5b2 100644 --- a/src/cpp/include/nodegui/QtCore/QObject/qobject_wrap.h +++ b/src/cpp/include/nodegui/QtCore/QObject/qobject_wrap.h @@ -20,5 +20,6 @@ class DLL_EXPORT QObjectWrap : public Napi::ObjectWrap { NObject* getInternalInstance(); // class constructor static Napi::FunctionReference constructor; + static Napi::Object wrapFunc(Napi::Env env, QObject* qobject); // wrapped methods }; diff --git a/src/cpp/include/nodegui/core/WrapperCache/wrappercache.h b/src/cpp/include/nodegui/core/WrapperCache/wrappercache.h index 2e379d5fb..d7d1fb8fd 100644 --- a/src/cpp/include/nodegui/core/WrapperCache/wrappercache.h +++ b/src/cpp/include/nodegui/core/WrapperCache/wrappercache.h @@ -14,6 +14,8 @@ struct CachedObject { napi_env env; }; +typedef Napi::Object (*WrapFunc)(Napi::Env, QObject *); + /** * C++ side cache for wrapper objects. * @@ -26,6 +28,7 @@ class DLL_EXPORT WrapperCache : public QObject { private: QMap cache; + QMap wrapperRegistry; public: /** @@ -64,6 +67,35 @@ class DLL_EXPORT WrapperCache : public QObject { return wrapper; } + void registerWrapper(QString typeName, WrapFunc wrapFunc) { + this->wrapperRegistry[typeName] = wrapFunc; + } + + Napi::Value getWrapper(Napi::Env env, QObject* qobject) { + if (qobject == nullptr) { + return env.Null(); + } + + uint64_t ptrHash = extrautils::hashPointerTo53bit(qobject); + if (this->cache.contains(ptrHash)) { + napi_value result = nullptr; + napi_get_reference_value(env, this->cache[ptrHash].ref, &result); + + napi_valuetype valuetype; + napi_typeof(env, result, &valuetype); + if (valuetype != napi_null) { + return Napi::Object(env, result); + } + } + + // QString className(object->metaObject()->className()); + // if (this->wrapperRegistry.contains(className)) { + // this->wrapperRegistry[className] + // } + + return env.Null(); + } + /** * Store a mapping from Qt Object to wrapper * @@ -89,8 +121,8 @@ class DLL_EXPORT WrapperCache : public QObject { static Napi::Object init(Napi::Env env, Napi::Object exports) { exports.Set("WrapperCache_injectCallback", Napi::Function::New(env)); - // exports.Set("WrapperCache_storeJS", - // Napi::Function::New(env)); + exports.Set("WrapperCache_store", + Napi::Function::New(env)); return exports; } @@ -101,6 +133,17 @@ class DLL_EXPORT WrapperCache : public QObject { return env.Null(); } + static Napi::Value storeJS(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + + Napi::Object objectWrapper = info[0].As(); + QObject* qobject = info[1].As>().Data(); + + uint64_t ptrHash = extrautils::hashPointerTo53bit(qobject); + instance.store(env, ptrHash, qobject, objectWrapper, false); + return env.Null(); + } + static Napi::FunctionReference destroyedCallback; public Q_SLOTS: diff --git a/src/cpp/lib/QtCore/QObject/qobject_wrap.cpp b/src/cpp/lib/QtCore/QObject/qobject_wrap.cpp index bd0ac6401..4c21135e0 100644 --- a/src/cpp/lib/QtCore/QObject/qobject_wrap.cpp +++ b/src/cpp/lib/QtCore/QObject/qobject_wrap.cpp @@ -1,6 +1,7 @@ #include "QtCore/QObject/qobject_wrap.h" #include "Extras/Utils/nutils.h" +#include "core/WrapperCache/wrappercache.h" Napi::FunctionReference QObjectWrap::constructor; @@ -11,6 +12,7 @@ Napi::Object QObjectWrap::init(Napi::Env env, Napi::Object exports) { env, CLASSNAME, {QOBJECT_WRAPPED_METHODS_EXPORT_DEFINE(QObjectWrap)}); constructor = Napi::Persistent(func); exports.Set(CLASSNAME, func); + WrapperCache::instance.registerWrapper(QString("NObject"), QObjectWrap::wrapFunc); return exports; } @@ -37,4 +39,11 @@ QObjectWrap::QObjectWrap(const Napi::CallbackInfo& info) .ThrowAsJavaScriptException(); } this->rawData = extrautils::configureQObject(this->getInternalInstance()); + // WrapperCache::instance.store(env, this->getInternalInstance(), this); +} + +Napi::Object QObjectWrap::wrapFunc(Napi::Env env, QObject *qobject) { + // Qtype *exactQObject = dynamic_cast(qobject) + Napi::Object wrapper = QObjectWrap::constructor.New({Napi::External::New(env, qobject)}); + return wrapper; } diff --git a/src/lib/QtCore/QObject.ts b/src/lib/QtCore/QObject.ts index fcd53452b..65d86f4b9 100644 --- a/src/lib/QtCore/QObject.ts +++ b/src/lib/QtCore/QObject.ts @@ -4,6 +4,7 @@ import { checkIfNativeElement } from '../utils/helpers'; import addon from '../utils/addon'; import { QVariant, QVariantType } from './QVariant'; import { TimerType } from '../QtEnums/TimerType'; +import { wrapperCache } from '../core/WrapperCache'; export class QObject extends EventWidget { constructor(nativeElementOrParent?: NativeElement | QObject) { @@ -18,6 +19,9 @@ export class QObject extends Ev native = new addon.QObject(); } super(native); + + wrapperCache.store(this); + this.setNodeParent(parent); } @@ -51,6 +55,9 @@ export class QObject extends Ev this.native.setParent(null); } } + parent(): QObject { + return wrapperCache.getWrapper(this.native.parent()); + } startTimer(intervalMS: number, timerType = TimerType.CoarseTimer): number { return this.native.startTimer(intervalMS, timerType); } diff --git a/src/lib/core/WrapperCache.ts b/src/lib/core/WrapperCache.ts index 13a218d22..ae07fb28b 100644 --- a/src/lib/core/WrapperCache.ts +++ b/src/lib/core/WrapperCache.ts @@ -13,29 +13,68 @@ import { NativeElement } from './Component'; * wrapper automatically and unexpectedly garbage collected. */ export class WrapperCache { - private _cache = new Map(); + private _strongCache = new Map(); + private _weakCache = new Map>(); constructor() { addon.WrapperCache_injectCallback(this._objectDestroyedCallback.bind(this)); } private _objectDestroyedCallback(objectId: number): void { - if (!this._cache.has(objectId)) { - return; + if (this._strongCache.has(objectId)) { + const wrapper = this._strongCache.get(objectId); + wrapper.native = null; + this._strongCache.delete(objectId); + } + + const wrapperRef = this._weakCache.get(objectId); + if (wrapperRef != null) { + const wrapper = wrapperRef.deref(); + if (wrapper != null) { + wrapper.native = null; + this._weakCache.delete(objectId); + } } - const wrapper = this._cache.get(objectId); - wrapper.native = null; - this._cache.delete(objectId); } - get(wrapperConstructor: { new (native: any): T }, native: NativeElement): T { + get(wrapperConstructor: { new (native: any): T }, native: NativeElement): T { const id = native.__id__(); - if (this._cache.has(id)) { - return this._cache.get(id) as T; + if (this._strongCache.has(id)) { + return this._strongCache.get(id) as T; } const wrapper = new wrapperConstructor(native); - this._cache.set(id, wrapper); + this._strongCache.set(id, wrapper); return wrapper; } + + getWrapper(native: any): QObject | null { + if (native == null) { + return null; + } + const id = native.__id__(); + + if (this._strongCache.has(id)) { + return this._strongCache.get(id); + } + + const ref = this._weakCache.get(id); + if (ref != null) { + const wrapper = ref.deref(); + if (wrapper != null) { + return wrapper; + } + } + + return null; // FIXME: Create new wrapper on demand. + } + + store(wrapper: QObject): void { + if (wrapper.native != null) { + const id = wrapper.native.__id__(); + this._weakCache.set(id, new WeakRef(wrapper)); + + addon.WrapperCache_store(wrapper.native, wrapper.native.__external_qobject__()); + } + } } export const wrapperCache = new WrapperCache(); diff --git a/src/lib/core/__test__/WrapperCache.test.ts b/src/lib/core/__test__/WrapperCache.test.ts index fe5cde994..678655c3f 100644 --- a/src/lib/core/__test__/WrapperCache.test.ts +++ b/src/lib/core/__test__/WrapperCache.test.ts @@ -1,3 +1,4 @@ +import { QObject } from '../../QtCore/QObject'; import { QApplication } from '../../QtGui/QApplication'; import { CacheTestQObject } from './CacheTestQObject'; @@ -43,5 +44,20 @@ describe('WrapperCache using CacheTestQObject', () => { expect(foo).not.toEqual(bar); expect(foo.native.__id__()).not.toEqual(bar.native.__id__()); }); + + it('QObject.parent() can be null', () => { + const a = new QObject(); + expect(a.parent()).toBeNull(); + }); + + it('QObject.parent() === QObject.parent()', () => { + const a = new QObject(); + const b = new QObject(a); + expect(a.native.__id__()).toEqual(b.parent().native.__id__()); + expect(a).toEqual(b.parent()); + (a)['magic'] = true; + expect((b.parent())['magic']).toBe(true); + }); + qApp.quit(); }); diff --git a/tsconfig.json b/tsconfig.json index dd79bd9a2..6abc59d20 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "target": "ES2015", + "target": "ES2021", "module": "commonjs", "declaration": true, "sourceMap": false,