From e15d6b14ac3182bbda9ae5859769f89e222637e9 Mon Sep 17 00:00:00 2001 From: Atul R Date: Sun, 23 Jun 2019 17:34:54 +0200 Subject: [PATCH] Adds custom keyevent handler. And remove dependency on napi-thread-safe-callback --- config/application.gypi | 1 + config/deps.gypi | 2 +- demo.ts | 9 ++-- devdocs/signal_and_event_handling.md | 28 +++++------- package.json | 1 - src/cpp/QtWidgets/QPushButton/npushbutton.h | 24 +++++----- .../QtWidgets/QPushButton/qpushbutton_wrap.h | 1 - src/cpp/core/Events/eventwidget.cpp | 14 +++--- src/cpp/core/Events/eventwidget.h | 4 +- src/cpp/core/Events/eventwidget_macro.h | 2 +- .../Events/types/KeyEvent/keyevent_wrap.cpp | 44 +++++++++++++++++++ .../Events/types/KeyEvent/keyevent_wrap.h | 20 +++++++++ src/cpp/main.cpp | 2 + src/lib/QtGui/QKeyEvent/index.ts | 13 ++++++ src/lib/core/EventWidget/index.ts | 2 +- yarn.lock | 5 --- 16 files changed, 124 insertions(+), 48 deletions(-) create mode 100644 src/cpp/core/Events/types/KeyEvent/keyevent_wrap.cpp create mode 100644 src/cpp/core/Events/types/KeyEvent/keyevent_wrap.h create mode 100644 src/lib/QtGui/QKeyEvent/index.ts diff --git a/config/application.gypi b/config/application.gypi index 8ea70e697..489dbdace 100644 --- a/config/application.gypi +++ b/config/application.gypi @@ -29,6 +29,7 @@ "../src/cpp/QtWidgets/QProgressBar/qprogressbar_wrap.cpp", "../src/cpp/QtWidgets/QRadioButton/qradiobutton_wrap.cpp", "../src/cpp/QtWidgets/QLineEdit/qlineedit_wrap.cpp", + "../src/cpp/core/Events/types/KeyEvent/keyevent_wrap.cpp", ], } ] diff --git a/config/deps.gypi b/config/deps.gypi index f0833b1cd..0370389ec 100644 --- a/config/deps.gypi +++ b/config/deps.gypi @@ -1,7 +1,7 @@ { "includes": [], "target_defaults": { - "include_dirs": ['../deps/', " { @@ -70,8 +71,10 @@ const testFlexLayout = () => { // -> view2 -> button const win = new QMainWindow(); - win.addEventListener("MouseMove", (...args) => { - console.log(...args); + win.addEventListener("KeyPress", nativeEvent => { + const evt = new KeyEvent(nativeEvent); + console.log(evt.text()); + console.log("KeyPress", evt); }); win.setObjectName("win"); win.resize(300, 300); @@ -142,5 +145,5 @@ const testFlexLayout = () => { return win; }; -// (global as any).win1 = testGridLayout(); //to keep gc from collecting +(global as any).win1 = testGridLayout(); //to keep gc from collecting (global as any).win2 = testFlexLayout(); //to keep gc from collecting diff --git a/devdocs/signal_and_event_handling.md b/devdocs/signal_and_event_handling.md index f957134b8..f98a31c33 100644 --- a/devdocs/signal_and_event_handling.md +++ b/devdocs/signal_and_event_handling.md @@ -112,24 +112,24 @@ public: void connectWidgetSignalsToEventEmitter() { // Qt Connects: Implement all signal connects here QObject::connect(this, &QPushButton::clicked, [=](bool checked) { - this->emitOnNode->call([=](Napi::Env env, std::vector& args) { - args = { Napi::String::New(env, "clicked"), Napi::Value::From(env, checked) }; - }); + Napi::Env env = this->emitOnNode.Env(); + Napi::HandleScope scope(env); + this->emitOnNode.Call({ Napi::String::New(env, "clicked"), Napi::Value::From(env, checked) }); }); QObject::connect(this, &QPushButton::released, [=]() { - this->emitOnNode->call([=](Napi::Env env, std::vector& args) { - args = { Napi::String::New(env, "released") }; - }); + Napi::Env env = this->emitOnNode.Env(); + Napi::HandleScope scope(env); + this->emitOnNode.Call({ Napi::String::New(env, "released") }); }); QObject::connect(this, &QPushButton::pressed, [=]() { - this->emitOnNode->call([=](Napi::Env env, std::vector& args) { - args = { Napi::String::New(env, "pressed") }; - }); + Napi::Env env = this->emitOnNode.Env(); + Napi::HandleScope scope(env); + this->emitOnNode.Call({ Napi::String::New(env, "pressed") }); }); QObject::connect(this, &QPushButton::toggled, [=](bool checked) { - this->emitOnNode->call([=](Napi::Env env, std::vector& args) { - args = { Napi::String::New(env, "toggled"), Napi::Value::From(env, checked) }; - }); + Napi::Env env = this->emitOnNode.Env(); + Napi::HandleScope scope(env); + this->emitOnNode.Call({ Napi::String::New(env, "toggled"), Napi::Value::From(env, checked) }); }); } }; @@ -148,8 +148,4 @@ We need to run Qt's MOC (Meta Object Compiler) on the file whenever we use Q_OBJ 1. On JS side for each widget instance we create an instance of NodeJS's Event Emitter. This is done by the class `EventWidget` from which `NodeWidget` inherits 2. We send this event emiiter's `emit` function to the C++ side by calling `initNodeEventEmitter` method and store a pointer to the event emitter's emit function using `emitOnNode`. initNodeEventEmitter function is added by a macro from EventWidget (c++). You can find the initNodeEventEmitter method with the event widget macros. 3. We setup Qt's connect method for all the signals that we want to listen to and call the emitOnNode (which is actually emit from Event emitter) whenever a signal arrives. This is done manually on every widget by overriding the method `connectWidgetSignalsToEventEmitter`. Check `npushbutton.h` for details. This takes care of all the signals of the widgets. Now to export all qt events of the widget, we had overriden the widgets `event(Event*)` method to listen to events received by the widget and send it to the event emitter. This is done inside the EVENTWIDGET_IMPLEMENTATIONS macro - - -> Note that we **can't** just store Napi::Function emit directly and use it. This is because we would need access to `Napi::Env` while making a call and there is no way to do it asynchronously. -> Since NAPI (node-addon-api) doesnt support asynchronous callbacks properly yet. (Although work in underway) we use this third party library (https://github.com/mika-fischer/napi-thread-safe-callback) to do so. This library provides us a way to access the Napi::Env variable whenever we need it. ``` diff --git a/package.json b/package.json index cbe7b9918..62d6bc70a 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ }, "dependencies": { "bindings": "^1.5.0", - "napi-thread-safe-callback": "^0.0.6", "node-addon-api": "^1.6.3" }, "gypfile": true diff --git a/src/cpp/QtWidgets/QPushButton/npushbutton.h b/src/cpp/QtWidgets/QPushButton/npushbutton.h index d6aec88c8..65cd634d5 100644 --- a/src/cpp/QtWidgets/QPushButton/npushbutton.h +++ b/src/cpp/QtWidgets/QPushButton/npushbutton.h @@ -13,24 +13,24 @@ public: void connectWidgetSignalsToEventEmitter() { // Qt Connects: Implement all signal connects here QObject::connect(this, &QPushButton::clicked, [=](bool checked) { - this->emitOnNode->call([=](Napi::Env env, std::vector& args) { - args = { Napi::String::New(env, "clicked"), Napi::Value::From(env, checked) }; - }); + Napi::Env env = this->emitOnNode.Env(); + Napi::HandleScope scope(env); + this->emitOnNode.Call({ Napi::String::New(env, "clicked"), Napi::Value::From(env, checked) }); }); QObject::connect(this, &QPushButton::released, [=]() { - this->emitOnNode->call([=](Napi::Env env, std::vector& args) { - args = { Napi::String::New(env, "released") }; - }); + Napi::Env env = this->emitOnNode.Env(); + Napi::HandleScope scope(env); + this->emitOnNode.Call({ Napi::String::New(env, "released") }); }); QObject::connect(this, &QPushButton::pressed, [=]() { - this->emitOnNode->call([=](Napi::Env env, std::vector& args) { - args = { Napi::String::New(env, "pressed") }; - }); + Napi::Env env = this->emitOnNode.Env(); + Napi::HandleScope scope(env); + this->emitOnNode.Call({ Napi::String::New(env, "pressed") }); }); QObject::connect(this, &QPushButton::toggled, [=](bool checked) { - this->emitOnNode->call([=](Napi::Env env, std::vector& args) { - args = { Napi::String::New(env, "toggled"), Napi::Value::From(env, checked) }; - }); + Napi::Env env = this->emitOnNode.Env(); + Napi::HandleScope scope(env); + this->emitOnNode.Call({ Napi::String::New(env, "toggled"), Napi::Value::From(env, checked) }); }); } }; diff --git a/src/cpp/QtWidgets/QPushButton/qpushbutton_wrap.h b/src/cpp/QtWidgets/QPushButton/qpushbutton_wrap.h index 91eca3bad..4e4647886 100644 --- a/src/cpp/QtWidgets/QPushButton/qpushbutton_wrap.h +++ b/src/cpp/QtWidgets/QPushButton/qpushbutton_wrap.h @@ -9,7 +9,6 @@ class QPushButtonWrap : public Napi::ObjectWrap { private: NPushButton* instance; - // std::unique_ptr emitOnNode; public: static Napi::Object init(Napi::Env env, Napi::Object exports); QPushButtonWrap(const Napi::CallbackInfo& info); diff --git a/src/cpp/core/Events/eventwidget.cpp b/src/cpp/core/Events/eventwidget.cpp index 9aed3f722..654647e99 100644 --- a/src/cpp/core/Events/eventwidget.cpp +++ b/src/cpp/core/Events/eventwidget.cpp @@ -17,10 +17,14 @@ void EventWidget::event(QEvent* event){ try { QEvent::Type evtType = event->type(); std::string eventTypeString = subscribedEvents.at(evtType); - this->emitOnNode->call([=](Napi::Env env, std::vector& args) { - Napi::Value nativeEvent = Napi::External::New(env, event); - args = { Napi::String::New(env, eventTypeString), nativeEvent }; - }); + + Napi::Env env = this->emitOnNode.Env(); + Napi::HandleScope scope(env); + + Napi::Value nativeEvent = Napi::External::New(env, event); + std::vector args = { Napi::String::New(env, eventTypeString), nativeEvent }; + + this->emitOnNode.Call(args); } catch (...) { // Do nothing } @@ -34,6 +38,6 @@ void EventWidget::connectWidgetSignalsToEventEmitter(){ EventWidget::~EventWidget(){ if(this->emitOnNode){ - this->emitOnNode.release(); + this->emitOnNode.Reset(); } } \ No newline at end of file diff --git a/src/cpp/core/Events/eventwidget.h b/src/cpp/core/Events/eventwidget.h index 8aca81ce8..6c63b0ab9 100644 --- a/src/cpp/core/Events/eventwidget.h +++ b/src/cpp/core/Events/eventwidget.h @@ -1,12 +1,12 @@ #pragma once #include -#include #include "src/cpp/core/Events/eventsmap.h" +#include class EventWidget { public: - std::unique_ptr emitOnNode = nullptr; + Napi::FunctionReference emitOnNode; std::unordered_map subscribedEvents; void subscribeToQtEvent(std::string evtString); diff --git a/src/cpp/core/Events/eventwidget_macro.h b/src/cpp/core/Events/eventwidget_macro.h index 80124634b..b8eceb97b 100644 --- a/src/cpp/core/Events/eventwidget_macro.h +++ b/src/cpp/core/Events/eventwidget_macro.h @@ -14,7 +14,7 @@ \ Napi::Value initNodeEventEmitter(const Napi::CallbackInfo& info) { \ Napi::Env env = info.Env(); \ - this->instance->emitOnNode = std::make_unique(info[0].As()); \ + this->instance->emitOnNode = Napi::Persistent(info[0].As()); \ this->instance->connectWidgetSignalsToEventEmitter(); \ return env.Null(); \ } \ diff --git a/src/cpp/core/Events/types/KeyEvent/keyevent_wrap.cpp b/src/cpp/core/Events/types/KeyEvent/keyevent_wrap.cpp new file mode 100644 index 000000000..2d47e672c --- /dev/null +++ b/src/cpp/core/Events/types/KeyEvent/keyevent_wrap.cpp @@ -0,0 +1,44 @@ +#include "keyevent_wrap.h" +#include "src/cpp/Extras/Utils/nutils.h" +#include +#include "deps/spdlog/spdlog.h" + + +Napi::FunctionReference QKeyEventWrap::constructor; + +Napi::Object QKeyEventWrap::init(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); + char CLASSNAME[] = "QKeyEvent"; + Napi::Function func = DefineClass(env, CLASSNAME, { + InstanceMethod("text", &QKeyEventWrap::text), + }); + constructor = Napi::Persistent(func); + exports.Set(CLASSNAME, func); + return exports; +} + +QKeyEvent* QKeyEventWrap::getInternalInstance() { + return this->instance; +} + +QKeyEventWrap::QKeyEventWrap(const Napi::CallbackInfo& info): Napi::ObjectWrap(info) { + Napi::Env env = info.Env(); + Napi::HandleScope scope(env); + if(info.Length() == 1) { + Napi::External eventObject = info[0].As>(); + this->instance = eventObject.Data(); + } else { + extrautils::throwTypeError(env, "Wrong number of arguments"); + } +} + +QKeyEventWrap::~QKeyEventWrap() { + // Do not destroy instance here. It will be done by Qt Event loop. +} + +Napi::Value QKeyEventWrap::text(const Napi::CallbackInfo& info) { + Napi::Env env = info.Env(); + int keyText = this->instance->key(); + Napi::String keyValue = Napi::String::New(env, std::to_string(keyText)); + return keyValue; +} \ No newline at end of file diff --git a/src/cpp/core/Events/types/KeyEvent/keyevent_wrap.h b/src/cpp/core/Events/types/KeyEvent/keyevent_wrap.h new file mode 100644 index 000000000..a680dbcc4 --- /dev/null +++ b/src/cpp/core/Events/types/KeyEvent/keyevent_wrap.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +class QKeyEventWrap : public Napi::ObjectWrap{ + private: + QKeyEvent* instance; + + public: + static Napi::Object init(Napi::Env env, Napi::Object exports); + QKeyEventWrap(const Napi::CallbackInfo& info); + ~QKeyEventWrap(); + QKeyEvent* getInternalInstance(); + //class constructor + static Napi::FunctionReference constructor; + //wrapped methods + Napi::Value text(const Napi::CallbackInfo& info); + // Napi::Value setFlexNode(const Napi::CallbackInfo& info); +}; \ No newline at end of file diff --git a/src/cpp/main.cpp b/src/cpp/main.cpp index a62f51dd4..3c853c895 100644 --- a/src/cpp/main.cpp +++ b/src/cpp/main.cpp @@ -10,6 +10,7 @@ #include "src/cpp/QtWidgets/QRadioButton/qradiobutton_wrap.h" #include "src/cpp/QtWidgets/QLineEdit/qlineedit_wrap.h" #include "src/cpp/core/FlexLayout/flexlayout_wrap.h" +#include "src/cpp/core/Events/types/KeyEvent/keyevent_wrap.h" #include // These cant be instantiated in JS Side @@ -29,6 +30,7 @@ Napi::Object Main(Napi::Env env, Napi::Object exports) { QProgressBarWrap::init(env, exports); QRadioButtonWrap::init(env, exports); QLineEditWrap::init(env, exports); + QKeyEventWrap::init(env, exports); return QLabelWrap::init(env, exports); } diff --git a/src/lib/QtGui/QKeyEvent/index.ts b/src/lib/QtGui/QKeyEvent/index.ts new file mode 100644 index 000000000..f7771f439 --- /dev/null +++ b/src/lib/QtGui/QKeyEvent/index.ts @@ -0,0 +1,13 @@ +import addon from "../../core/addon"; +import { NativeElement } from "../../core/Component"; +import { NativeEvent } from "../../core/EventWidget"; + +export class KeyEvent { + native: NativeElement; + constructor(event: NativeEvent) { + this.native = new addon.QKeyEvent(event); + } + text = (): string => { + return this.native.text(); + }; +} diff --git a/src/lib/core/EventWidget/index.ts b/src/lib/core/EventWidget/index.ts index 6f383a17e..55899dfc6 100644 --- a/src/lib/core/EventWidget/index.ts +++ b/src/lib/core/EventWidget/index.ts @@ -2,7 +2,7 @@ import { EventEmitter } from "events"; import { YogaWidget } from "../YogaWidget"; import { NativeElement } from "../Component"; -type NativeEvent = {}; +export type NativeEvent = {}; export abstract class EventWidget extends YogaWidget { private emitter: EventEmitter; constructor(native: NativeElement) { diff --git a/yarn.lock b/yarn.lock index 2a08399f5..308ed5d2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -413,11 +413,6 @@ mkdirp@^0.5.0: dependencies: minimist "0.0.8" -napi-thread-safe-callback@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/napi-thread-safe-callback/-/napi-thread-safe-callback-0.0.6.tgz#ef86a149b5312e480f74e89a614e6d9e3b17b456" - integrity sha512-X7uHCOCdY4u0yamDxDrv3jF2NtYc8A1nvPzBQgvpoSX+WB3jAe2cVNsY448V1ucq7Whf9Wdy02HEUoLW5rJKWg== - node-addon-api@^1.6.3: version "1.6.3" resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.3.tgz#3998d4593e2dca2ea82114670a4eb003386a9fe1"