diff --git a/CMakeLists.txt b/CMakeLists.txt index 921673419..5233ec216 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ add_library(${CORE_WIDGETS_ADDON} SHARED "${PROJECT_SOURCE_DIR}/src/cpp/lib/QtGui/QCursor/qcursor_wrap.cpp" "${PROJECT_SOURCE_DIR}/src/cpp/lib/QtGui/QKeySequence/qkeysequence_wrap.cpp" "${PROJECT_SOURCE_DIR}/src/cpp/lib/QtCore/QObject/qobject_wrap.cpp" + "${PROJECT_SOURCE_DIR}/src/cpp/lib/QtCore/QVariant/qvariant_wrap.cpp" "${PROJECT_SOURCE_DIR}/src/cpp/lib/QtWidgets/QWidget/qwidget_wrap.cpp" "${PROJECT_SOURCE_DIR}/src/cpp/lib/QtWidgets/QGridLayout/qgridlayout_wrap.cpp" "${PROJECT_SOURCE_DIR}/src/cpp/lib/QtWidgets/QDial/qdial_wrap.cpp" diff --git a/config/tests/setup.js b/config/tests/setup.js index e49c1100e..4c9dd001a 100644 --- a/config/tests/setup.js +++ b/config/tests/setup.js @@ -1,5 +1,5 @@ -const { QApplication } = require("../../dist"); +const { QApplication } = require('../../dist'); module.exports = async () => { - global.qApp = QApplication.instance(); - qApp.setQuitOnLastWindowClosed(false); + global.qApp = QApplication.instance(); + qApp.setQuitOnLastWindowClosed(false); }; diff --git a/config/tests/teardown.js b/config/tests/teardown.js index 9c80c7301..340d7b798 100644 --- a/config/tests/teardown.js +++ b/config/tests/teardown.js @@ -1,3 +1,3 @@ module.exports = async () => { - global.qApp.quit(); + global.qApp.quit(); }; diff --git a/jest.config.js b/jest.config.js index 640124f4c..f3d447550 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,16 +1,16 @@ // For a detailed explanation regarding each configuration property, visit: // https://jestjs.io/docs/en/configuration.html module.exports = { - clearMocks: true, - coverageDirectory: "coverage", - collectCoverageFrom: ["**/*.{js,jsx,ts,tsx}", "!**/node_modules/**"], - forceCoverageMatch: ["**/*.{ts,tsx,js,jsx}", "!**/*.test.{ts,tsx,js,jsx}"], - moduleFileExtensions: ["js", "json", "jsx", "ts", "tsx", "node"], - roots: ["/src/lib"], - testEnvironment: "node", - transform: { - "^.+\\.tsx?$": "ts-jest" - }, - globalSetup: "./config/tests/setup.js", - globalTeardown: "./config/tests/teardown.js" + clearMocks: true, + coverageDirectory: 'coverage', + collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', '!**/node_modules/**'], + forceCoverageMatch: ['**/*.{ts,tsx,js,jsx}', '!**/*.test.{ts,tsx,js,jsx}'], + moduleFileExtensions: ['js', 'json', 'jsx', 'ts', 'tsx', 'node'], + roots: ['/src/lib'], + testEnvironment: 'node', + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + globalSetup: './config/tests/setup.js', + globalTeardown: './config/tests/teardown.js', }; diff --git a/package.json b/package.json index f233879d8..ffba2b554 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "dev": "npm run build && qode --inspect dist/demo.js", "postinstall": "npm run build:addon", "build": "tsc && npm run build:addon", - "build:addon": "cross-env CMAKE_BUILD_PARALLEL_LEVEL=8 cmake-js build", - "test": "qode ./node_modules/.bin/jest", + "build:addon": "cross-env CMAKE_BUILD_PARALLEL_LEVEL=8 cmake-js compile", + "test": "qode ./node_modules/.bin/jest -i", "lint:cpp": "clang-format -i --glob=src/cpp/**/*.[h,c]*", "lint:ts": "tsc --noEmit && eslint './src/**/*.{ts,tsx,js,jsx}' --fix" }, @@ -50,7 +50,7 @@ }, "husky": { "hooks": { - "pre-push": "npm run lint:ts && npm run lint:cpp && npm run test" + "pre-push": "npm run build && npm run lint:ts && npm run lint:cpp && npm run test" } } } diff --git a/src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h b/src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h index 656879aee..743330e50 100644 --- a/src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h +++ b/src/cpp/include/nodegui/QtCore/QObject/qobject_macro.h @@ -1,6 +1,7 @@ #pragma once #include "Extras/Utils/nutils.h" +#include "QtCore/QVariant/qvariant_wrap.h" #include "core/Events/eventwidget_macro.h" /* @@ -31,6 +32,17 @@ this->instance->setProperty(name.Utf8Value().c_str(), *variant); \ return env.Null(); \ } \ + Napi::Value property(const Napi::CallbackInfo& info) { \ + Napi::Env env = info.Env(); \ + Napi::HandleScope scope(env); \ + Napi::String name = info[0].As(); \ + Napi::Value value = info[1]; \ + QVariant* variant = \ + new QVariant(this->instance->property(name.Utf8Value().c_str())); \ + auto variantWrap = QVariantWrap::constructor.New( \ + {Napi::External::New(env, variant)}); \ + return variantWrap; \ + } \ Napi::Value setObjectName(const Napi::CallbackInfo& info) { \ Napi::Env env = info.Env(); \ Napi::HandleScope scope(env); \ @@ -55,6 +67,7 @@ \ InstanceMethod("inherits", &ComponentWrapName::inherits), \ InstanceMethod("setProperty", &ComponentWrapName::setProperty), \ + InstanceMethod("property", &ComponentWrapName::property), \ InstanceMethod("setObjectName", &ComponentWrapName::setObjectName), \ InstanceMethod("objectName", &ComponentWrapName::objectName), diff --git a/src/cpp/include/nodegui/QtCore/QVariant/qvariant_wrap.h b/src/cpp/include/nodegui/QtCore/QVariant/qvariant_wrap.h new file mode 100644 index 000000000..e6150cc12 --- /dev/null +++ b/src/cpp/include/nodegui/QtCore/QVariant/qvariant_wrap.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include + +#include "core/Component/component_macro.h" + +class QVariantWrap : public Napi::ObjectWrap { + private: + QSharedPointer instance; + + public: + static Napi::Object init(Napi::Env env, Napi::Object exports); + QVariantWrap(const Napi::CallbackInfo& info); + QVariant* getInternalInstance(); + // class constructor + static Napi::FunctionReference constructor; + Napi::Value toString(const Napi::CallbackInfo& info); + Napi::Value toInt(const Napi::CallbackInfo& info); + Napi::Value toDouble(const Napi::CallbackInfo& info); + Napi::Value toBool(const Napi::CallbackInfo& info); + // wrapped methods + COMPONENT_WRAPPED_METHODS_DECLARATION +}; diff --git a/src/cpp/include/nodegui/QtGui/QPixmap/qpixmap_wrap.h b/src/cpp/include/nodegui/QtGui/QPixmap/qpixmap_wrap.h index c2a45c548..eaeefcb08 100644 --- a/src/cpp/include/nodegui/QtGui/QPixmap/qpixmap_wrap.h +++ b/src/cpp/include/nodegui/QtGui/QPixmap/qpixmap_wrap.h @@ -21,6 +21,12 @@ class QPixmapWrap : public Napi::ObjectWrap { Napi::Value load(const Napi::CallbackInfo& info); Napi::Value save(const Napi::CallbackInfo& info); Napi::Value scaled(const Napi::CallbackInfo& info); + Napi::Value height(const Napi::CallbackInfo& info); + Napi::Value width(const Napi::CallbackInfo& info); COMPONENT_WRAPPED_METHODS_DECLARATION }; + +namespace StaticQPixmapWrapMethods { +Napi::Value fromQVariant(const Napi::CallbackInfo& info); +} // namespace StaticQPixmapWrapMethods \ No newline at end of file diff --git a/src/cpp/lib/Extras/Utils/nutils.cpp b/src/cpp/lib/Extras/Utils/nutils.cpp index a17ee641c..056d24b4d 100644 --- a/src/cpp/lib/Extras/Utils/nutils.cpp +++ b/src/cpp/lib/Extras/Utils/nutils.cpp @@ -1,6 +1,5 @@ #include "Extras/Utils/nutils.h" -#include #include #include #include @@ -91,8 +90,8 @@ QVariant* extrautils::convertToQVariant(Napi::Env& env, Napi::Value& value) { // TODO: fix this return new QVariant(); } else if (value.IsExternal()) { - // TODO: fix this - return new QVariant(); + QVariant* variant = value.As>().Data(); + return variant; } else { return new QVariant(); } diff --git a/src/cpp/lib/QtCore/QVariant/qvariant_wrap.cpp b/src/cpp/lib/QtCore/QVariant/qvariant_wrap.cpp new file mode 100644 index 000000000..98ac85cb6 --- /dev/null +++ b/src/cpp/lib/QtCore/QVariant/qvariant_wrap.cpp @@ -0,0 +1,61 @@ +#include "QtCore/QVariant/qvariant_wrap.h" + +#include "Extras/Utils/nutils.h" + +Napi::FunctionReference QVariantWrap::constructor; + +Napi::Object QVariantWrap::init(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); + char CLASSNAME[] = "QVariant"; + Napi::Function func = + DefineClass(env, CLASSNAME, + {InstanceMethod("toString", &QVariantWrap::toString), + InstanceMethod("toInt", &QVariantWrap::toInt), + InstanceMethod("toDouble", &QVariantWrap::toDouble), + InstanceMethod("toBool", &QVariantWrap::toBool), + COMPONENT_WRAPPED_METHODS_EXPORT_DEFINE}); + constructor = Napi::Persistent(func); + exports.Set(CLASSNAME, func); + return exports; +} + +QVariant* QVariantWrap::getInternalInstance() { return this->instance.data(); } + +QVariantWrap::QVariantWrap(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + if (info.Length() == 1) { + Napi::Value value = info[0].As(); + this->instance = + QSharedPointer(extrautils::convertToQVariant(env, value)); + } else { + this->instance = QSharedPointer(new QVariant()); + } + this->rawData = this->getInternalInstance(); +} + +Napi::Value QVariantWrap::toString(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + QString value = this->instance->value(); + return Napi::Value::From(env, value.toStdString()); +} +Napi::Value QVariantWrap::toInt(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + int value = this->instance->value(); + return Napi::Value::From(env, value); +} +Napi::Value QVariantWrap::toDouble(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + double value = this->instance->value(); + return Napi::Value::From(env, value); +} +Napi::Value QVariantWrap::toBool(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + bool value = this->instance->value(); + return Napi::Value::From(env, value); +} diff --git a/src/cpp/lib/QtGui/QPixmap/qpixmap_wrap.cpp b/src/cpp/lib/QtGui/QPixmap/qpixmap_wrap.cpp index c54ccad88..19452c098 100644 --- a/src/cpp/lib/QtGui/QPixmap/qpixmap_wrap.cpp +++ b/src/cpp/lib/QtGui/QPixmap/qpixmap_wrap.cpp @@ -1,19 +1,22 @@ #include "QtGui/QPixmap/qpixmap_wrap.h" #include "Extras/Utils/nutils.h" -#include "deps/spdlog/spdlog.h" +#include "QtCore/QVariant/qvariant_wrap.h" Napi::FunctionReference QPixmapWrap::constructor; Napi::Object QPixmapWrap::init(Napi::Env env, Napi::Object exports) { Napi::HandleScope scope(env); char CLASSNAME[] = "QPixmap"; - Napi::Function func = - DefineClass(env, CLASSNAME, - {InstanceMethod("load", &QPixmapWrap::load), - InstanceMethod("save", &QPixmapWrap::save), - InstanceMethod("scaled", &QPixmapWrap::scaled), - COMPONENT_WRAPPED_METHODS_EXPORT_DEFINE}); + Napi::Function func = DefineClass( + env, CLASSNAME, + {InstanceMethod("load", &QPixmapWrap::load), + InstanceMethod("save", &QPixmapWrap::save), + InstanceMethod("scaled", &QPixmapWrap::scaled), + InstanceMethod("height", &QPixmapWrap::height), + InstanceMethod("width", &QPixmapWrap::width), + StaticMethod("fromQVariant", &StaticQPixmapWrapMethods::fromQVariant), + COMPONENT_WRAPPED_METHODS_EXPORT_DEFINE}); constructor = Napi::Persistent(func); exports.Set(CLASSNAME, func); return exports; @@ -98,3 +101,28 @@ Napi::Value QPixmapWrap::scaled(const Napi::CallbackInfo& info) { {Napi::External::New(env, updatedPixMap)}); return instance; } + +Napi::Value QPixmapWrap::height(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + return Napi::Value::From(env, this->instance->height()); +} +Napi::Value QPixmapWrap::width(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + return Napi::Value::From(env, this->instance->width()); +} + +Napi::Value StaticQPixmapWrapMethods::fromQVariant( + const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + Napi::Object variantObject = info[0].As(); + QVariantWrap* variantWrap = + Napi::ObjectWrap::Unwrap(variantObject); + QVariant* variant = variantWrap->getInternalInstance(); + QPixmap pixmap = variant->value(); + auto instance = QPixmapWrap::constructor.New( + {Napi::External::New(env, new QPixmap(pixmap))}); + return instance; +} diff --git a/src/cpp/main.cpp b/src/cpp/main.cpp index 3bc5d4f3b..398046b91 100644 --- a/src/cpp/main.cpp +++ b/src/cpp/main.cpp @@ -1,6 +1,7 @@ #include #include "QtCore/QObject/qobject_wrap.h" +#include "QtCore/QVariant/qvariant_wrap.h" #include "QtGui/QApplication/qapplication_wrap.h" #include "QtGui/QClipboard/qclipboard_wrap.h" #include "QtGui/QCursor/qcursor_wrap.h" @@ -38,6 +39,7 @@ Napi::Object Main(Napi::Env env, Napi::Object exports) { InitPrivateHelpers(env); QApplicationWrap::init(env, exports); QObjectWrap::init(env, exports); + QVariantWrap::init(env, exports); QClipboardWrap::init(env, exports); QWidgetWrap::init(env, exports); QPixmapWrap::init(env, exports); diff --git a/src/index.ts b/src/index.ts index fa36530cc..2de27289d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,6 +39,7 @@ export { QAction, QActionEvents } from './lib/QtWidgets/QAction'; export { QShortcut, QShortcutEvents } from './lib/QtWidgets/QShortcut'; // Core export { QObject, NodeObject } from './lib/QtCore/QObject'; +export { QVariant } from './lib/QtCore/QVariant'; // Layouts: export { QGridLayout } from './lib/QtWidgets/QGridLayout'; export { FlexLayout } from './lib/core/FlexLayout'; diff --git a/src/lib/QtCore/QObject.ts b/src/lib/QtCore/QObject.ts index 16bebb0aa..33bd9c599 100644 --- a/src/lib/QtCore/QObject.ts +++ b/src/lib/QtCore/QObject.ts @@ -2,6 +2,7 @@ import { EventWidget } from '../core/EventWidget'; import { NativeElement } from '../core/Component'; import { checkIfNativeElement } from '../utils/helpers'; import addon from '../utils/addon'; +import { QVariant } from './QVariant'; export abstract class NodeObject extends EventWidget { inherits(className: string): boolean { @@ -11,6 +12,10 @@ export abstract class NodeObject extends EventWidget { const finalValue = value.native || value; return this.native.setProperty(name, finalValue); } + property(name: string): QVariant { + const nativeVariant = this.native.property(name); + return new QVariant(nativeVariant); + } setObjectName(objectName: string): void { this.native.setObjectName(objectName); } diff --git a/src/lib/QtCore/QVariant.ts b/src/lib/QtCore/QVariant.ts new file mode 100644 index 000000000..458d21ac2 --- /dev/null +++ b/src/lib/QtCore/QVariant.ts @@ -0,0 +1,32 @@ +import { NativeElement, Component } from '../core/Component'; +import addon from '../utils/addon'; +import { checkIfNativeElement } from '../utils/helpers'; + +type QVariantType = NativeElement | Component | string | number | boolean; + +export class QVariant extends Component { + native: NativeElement; + constructor(arg?: QVariantType) { + super(); + if (checkIfNativeElement(arg)) { + this.native = arg as NativeElement; + } else if (arg) { + const component = (arg as Component).native || arg; + this.native = new addon.QVariant(component); + } else { + this.native = new addon.QVariant(); + } + } + toString(): string { + return this.native.toString(); + } + toInt(): string { + return this.native.toInt(); + } + toDouble(): string { + return this.native.toDouble(); + } + toBool(): string { + return this.native.toBool(); + } +} diff --git a/src/lib/QtCore/__tests__/QObject.test.ts b/src/lib/QtCore/__tests__/QObject.test.ts index 696deeaf3..280541c8c 100644 --- a/src/lib/QtCore/__tests__/QObject.test.ts +++ b/src/lib/QtCore/__tests__/QObject.test.ts @@ -9,4 +9,9 @@ describe('QObject', () => { component.setObjectName('hello'); expect(component.objectName()).toEqual('hello'); }); + it('setProperty', () => { + component.setProperty('objectName', 'testObjName'); + const variant = component.property('objectName'); + expect(variant.toString()).toEqual('testObjName'); + }); }); diff --git a/src/lib/QtCore/__tests__/QVariant.test.ts b/src/lib/QtCore/__tests__/QVariant.test.ts new file mode 100644 index 000000000..6addaf2f3 --- /dev/null +++ b/src/lib/QtCore/__tests__/QVariant.test.ts @@ -0,0 +1,40 @@ +import { QVariant } from '../../../index'; +import { QPixmap } from '../../QtGui/QPixmap'; +import path from 'path'; + +describe('QVariant', () => { + it('inherits from QVariant', () => { + const variant = new QVariant(); + expect(variant.constructor.name).toEqual('QVariant'); + }); + it('initialize empty', () => { + const variant = new QVariant(); + expect(variant.constructor.name).toEqual('QVariant'); + }); + it('initialize with string', () => { + const variant = new QVariant('hello'); + expect(variant.constructor.name).toBe('QVariant'); + expect(variant.toString()).toEqual('hello'); + }); + it('initialize with int', () => { + const variant = new QVariant(123); + expect(variant.constructor.name).toBe('QVariant'); + expect(variant.toInt()).toEqual(123); + }); + it('initialize with double', () => { + const variant = new QVariant(12.33); + expect(variant.constructor.name).toBe('QVariant'); + expect(variant.toDouble()).toEqual(12.33); + }); + it('initialize with boolean', () => { + const variant = new QVariant(true); + expect(variant.constructor.name).toBe('QVariant'); + expect(variant.toBool()).toEqual(true); + }); + it('initialize with complex objects', () => { + const pixmap = new QPixmap(path.resolve(__dirname, 'nodegui.png')); + const variant = new QVariant(pixmap); + expect(variant.constructor.name).toBe('QVariant'); + expect(QPixmap.fromQVariant(variant).height()).toBe(pixmap.height()); + }); +}); diff --git a/src/lib/QtCore/__tests__/nodegui.png b/src/lib/QtCore/__tests__/nodegui.png new file mode 100644 index 000000000..16c3c0282 Binary files /dev/null and b/src/lib/QtCore/__tests__/nodegui.png differ diff --git a/src/lib/QtGui/QPixmap.ts b/src/lib/QtGui/QPixmap.ts index a5c3c77cb..54a3a9a65 100644 --- a/src/lib/QtGui/QPixmap.ts +++ b/src/lib/QtGui/QPixmap.ts @@ -2,6 +2,7 @@ import addon from '../utils/addon'; import { Component, NativeElement } from '../core/Component'; import { AspectRatioMode } from '../QtEnums'; import { checkIfNativeElement } from '../utils/helpers'; +import { QVariant } from '../QtCore/QVariant'; export type ImageFormats = 'BMP' | 'GIF' | 'JPG' | 'JPEG' | 'PNG' | 'PBM' | 'PGM' | 'PPM' | 'XBM' | 'XPM'; export type ReadWriteImageFormats = 'BMP' | 'JPG' | 'JPEG' | 'PNG' | 'PBM' | 'XBM' | 'XPM'; @@ -36,4 +37,13 @@ export class QPixmap extends Component { } return new QPixmap(nativePixmap); }; + height(): number { + return this.native.height(); + } + width(): number { + return this.native.width(); + } + static fromQVariant(variant: QVariant): QPixmap { + return addon.QPixmap.fromQVariant(variant.native); + } } diff --git a/src/lib/QtWidgets/QLineEdit.ts b/src/lib/QtWidgets/QLineEdit.ts index 34ec36a4d..e78415277 100644 --- a/src/lib/QtWidgets/QLineEdit.ts +++ b/src/lib/QtWidgets/QLineEdit.ts @@ -7,8 +7,8 @@ export enum EchoMode { Normal, NoEcho, Password, - PasswordEchoOnEdit, -}; + PasswordEchoOnEdit, +} export const QLineEditEvents = Object.freeze({ ...BaseWidgetEvents, cursorPositionChanged: 'cursorPositionChanged',