diff --git a/examples/mvvmquick/SampleQuick/SampleQuick.pro b/examples/mvvmquick/SampleQuick/SampleQuick.pro index 0bc86a0..ad1be61 100644 --- a/examples/mvvmquick/SampleQuick/SampleQuick.pro +++ b/examples/mvvmquick/SampleQuick/SampleQuick.pro @@ -4,7 +4,8 @@ QT += core gui qml quick mvvmquick TARGET = SampleQuick -SOURCES += main.cpp +SOURCES += main.cpp \ + quickeventservice.cpp RESOURCES += qml.qrc @@ -44,11 +45,12 @@ samples_in_build { QMLDEPPATH = $$PWD/../../../src/imports/mvvmquick system($$QMAKE_MKDIR $$shell_quote($$shell_path($$FAKEPATH))) - # next, symlink all "compiled" files + # next, symlink all "compiled" files (whole dir for core, as it has no qml files build_symlink_target.target = create_qml_build_symlinks - build_symlink_target.commands += $$QMAKE_SYMBOLIC_LINK $$shell_path($$ORIGPATH/libdeclarative_mvvmquick.so) $$shell_quote($$shell_path($$FAKEPATH/libdeclarative_mvvmquick.so)) \ - $$escape_expand(\n\t)$$QMAKE_SYMBOLIC_LINK $$shell_path($$ORIGPATH/plugins.qmltypes) $$shell_quote($$shell_path($$FAKEPATH/plugins.qmltypes)) \ - $$escape_expand(\n\t)$$QMAKE_SYMBOLIC_LINK $$shell_path($$ORIGPATH/qmldir) $$shell_quote($$shell_path($$FAKEPATH/qmldir)) + build_symlink_target.commands += $$QMAKE_SYMBOLIC_LINK $$shell_path(../../../../../../../qml/de/skycoder42/qtmvvm/core) $$shell_path(qml/de/skycoder42/qtmvvm/core) \ + $$escape_expand(\n\t)$$QMAKE_SYMBOLIC_LINK $$shell_path($$ORIGPATH/libdeclarative_mvvmquick.so) $$shell_path($$FAKEPATH/libdeclarative_mvvmquick.so) \ + $$escape_expand(\n\t)$$QMAKE_SYMBOLIC_LINK $$shell_path($$ORIGPATH/plugins.qmltypes) $$shell_path($$FAKEPATH/plugins.qmltypes) \ + $$escape_expand(\n\t)$$QMAKE_SYMBOLIC_LINK $$shell_path($$ORIGPATH/qmldir) $$shell_path($$FAKEPATH/qmldir) QMAKE_EXTRA_TARGETS += build_symlink_target # next, prepare compiler to symlink all the qml files @@ -68,3 +70,6 @@ samples_in_build { QML_IMPORT_PATH = $$OUT_PWD/qml/ DEFINES += QML_PATH=\\\"$$QML_IMPORT_PATH\\\" } + +HEADERS += \ + quickeventservice.h diff --git a/examples/mvvmquick/SampleQuick/main.cpp b/examples/mvvmquick/SampleQuick/main.cpp index 789e944..fa3f809 100644 --- a/examples/mvvmquick/SampleQuick/main.cpp +++ b/examples/mvvmquick/SampleQuick/main.cpp @@ -1,18 +1,29 @@ #include #include +#include +#include #include +#include + +#include "quickeventservice.h" QTMVVM_REGISTER_CORE_APP(SampleCoreApp) int main(int argc, char *argv[]) { +#ifdef QML_PATH qputenv("QML2_IMPORT_PATH", QML_PATH); +#endif QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - QGuiApplication app(argc, argv); + QtMvvm::QuickPresenter::registerAsPresenter(); + + QtMvvm::ServiceRegistry::instance()->registerObject(); + QtMvvm::ServiceRegistry::instance()->registerInterface(); + QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) diff --git a/examples/mvvmquick/SampleQuick/main.qml b/examples/mvvmquick/SampleQuick/main.qml index 65960ee..84a4a20 100644 --- a/examples/mvvmquick/SampleQuick/main.qml +++ b/examples/mvvmquick/SampleQuick/main.qml @@ -1,6 +1,11 @@ import QtQuick 2.10 +import de.skycoder42.qtmvvm.core 1.0 import de.skycoder42.qtmvvm.quick 1.0 App { title: qsTr("Hello World") + + QtMvvmBinding { + id: binding + } } diff --git a/examples/mvvmquick/SampleQuick/quickeventservice.cpp b/examples/mvvmquick/SampleQuick/quickeventservice.cpp new file mode 100644 index 0000000..5f0b4df --- /dev/null +++ b/examples/mvvmquick/SampleQuick/quickeventservice.cpp @@ -0,0 +1,48 @@ +#include "quickeventservice.h" + +QuickEventService::QuickEventService(QObject *parent) : + QObject(parent), + IEventService(), + _cnt(0), + _events(), + _echoService(nullptr) +{} + +QuickEventService::QuickEventService(EchoService *svc, QObject *parent) : + QObject(parent), + IEventService(), + _cnt(0), + _events(), + _echoService(svc) +{ + qtmvvm_init(); +} + +int QuickEventService::addEvent(const QString &name) +{ + QSharedPointer timer { + new QTimer(this), + &QTimer::deleteLater + }; + + _events.insert(_cnt, timer); + connect(timer.data(), &QTimer::timeout, this, [this, name]() { + _echoService->ping(name); + }); + timer->start(1000); + + return _cnt++; +} + +void QuickEventService::removeEvent(int eventId) +{ + _events.remove(eventId); +} + +void QuickEventService::qtmvvm_init() +{ + qDebug(Q_FUNC_INFO); + Q_ASSERT(_echoService); + connect(_echoService, &EchoService::pong, + this, &QuickEventService::eventTriggered); +} diff --git a/examples/mvvmquick/SampleQuick/quickeventservice.h b/examples/mvvmquick/SampleQuick/quickeventservice.h new file mode 100644 index 0000000..2e885fb --- /dev/null +++ b/examples/mvvmquick/SampleQuick/quickeventservice.h @@ -0,0 +1,38 @@ +#ifndef QUICKEVENTSERVICE_H +#define QUICKEVENTSERVICE_H + +#include +#include +#include +#include + +#include +#include +#include + +class QuickEventService : public QObject, public IEventService +{ + Q_OBJECT + Q_INTERFACES(IEventService) + + QTMVVM_INJECT_PROP(EchoService*, echoService, _echoService) + +public: + Q_INVOKABLE explicit QuickEventService(QObject *parent = nullptr); + explicit QuickEventService(EchoService* svc, QObject *parent = nullptr); + + int addEvent(const QString &name) override; + void removeEvent(int eventId) override; + +Q_SIGNALS: + void eventTriggered(const QString &event) final; + +private: + int _cnt; + QHash> _events; + EchoService* _echoService; + + Q_INVOKABLE void qtmvvm_init(); +}; + +#endif // QUICKEVENTSERVICE_H diff --git a/examples/mvvmwidgets/SampleWidgets/main.cpp b/examples/mvvmwidgets/SampleWidgets/main.cpp index fd01d6e..71fb760 100644 --- a/examples/mvvmwidgets/SampleWidgets/main.cpp +++ b/examples/mvvmwidgets/SampleWidgets/main.cpp @@ -23,7 +23,7 @@ int main(int argc, char *argv[]) QtMvvm::WidgetsPresenter::registerView(); if(TEST_CURRENT == TEST_DIRECT) - QtMvvm::ServiceRegistry::instance()->registerObject(); + QtMvvm::ServiceRegistry::instance()->registerObject(); if(TEST_CURRENT == TEST_FN) QtMvvm::ServiceRegistry::instance()->registerObject([]() { return new EchoService(nullptr); diff --git a/src/imports/imports.pro b/src/imports/imports.pro index 413847b..ff9e4b7 100644 --- a/src/imports/imports.pro +++ b/src/imports/imports.pro @@ -1,4 +1,5 @@ TEMPLATE = subdirs SUBDIRS += \ - mvvmquick + mvvmquick \ + mvvmcore diff --git a/src/imports/mvvmcore/mvvmcore.pro b/src/imports/mvvmcore/mvvmcore.pro new file mode 100644 index 0000000..10176d1 --- /dev/null +++ b/src/imports/mvvmcore/mvvmcore.pro @@ -0,0 +1,33 @@ +QT += core qml quick mvvmcore +CXX_MODULE = mvvmcore +TARGETPATH = de/skycoder42/qtmvvm/core +TARGET = declarative_mvvmcore +IMPORT_VERSION = 1.0 + +HEADERS += \ + qtmvvmcore_plugin.h \ + qqmlmvvmbinding.h + +SOURCES += \ + qtmvvmcore_plugin.cpp \ + qqmlmvvmbinding.cpp + +OTHER_FILES += qmldir + +generate_qmltypes { + typeextra1.target = qmltypes + typeextra1.depends += export LD_LIBRARY_PATH := "$$shadowed($$dirname(_QMAKE_CONF_))/lib/:$(LD_LIBRARY_PATH)" + typeextra2.target = qmltypes + typeextra2.depends += export QML2_IMPORT_PATH := "$$shadowed($$dirname(_QMAKE_CONF_))/qml/" + QMAKE_EXTRA_TARGETS += typeextra1 typeextra2 +} + +load(qml_plugin) + +generate_qmltypes { + qmltypes.depends = ../../../qml/$$TARGETPATH/$(TARGET) #overwrite the target deps + + mfirst.target = all + mfirst.depends += qmltypes + QMAKE_EXTRA_TARGETS += mfirst +} diff --git a/src/imports/mvvmcore/plugins.qmltypes b/src/imports/mvvmcore/plugins.qmltypes new file mode 100644 index 0000000..2a6176d --- /dev/null +++ b/src/imports/mvvmcore/plugins.qmltypes @@ -0,0 +1,57 @@ +import QtQuick.tooling 1.2 + +// This file describes the plugin-supplied types contained in the library. +// It is used for QML tooling purposes only. +// +// This file was auto-generated by: +// 'qmlplugindump -nonrelocatable de.skycoder42.qtmvvm.core 1.0' + +Module { + dependencies: ["QtQuick 2.8"] + Component { + name: "QtMvvm::QQmlMvvmBinding" + prototype: "QObject" + exports: ["de.skycoder42.qtmvvm.core/QtMvvmBinding 1.0"] + exportMetaObjectRevisions: [0] + Enum { + name: "BindingDirection" + values: { + "SingleInit": 1, + "OneWayToView": 3, + "OneWayToViewModel": 4, + "TwoWay": 7 + } + } + Property { name: "viewModel"; type: "QObject"; isPointer: true } + Property { name: "viewModelProperty"; type: "string" } + Property { name: "view"; type: "QObject"; isPointer: true } + Property { name: "viewProperty"; type: "string" } + Property { name: "type"; type: "BindingDirection" } + Signal { + name: "viewModelChanged" + Parameter { name: "viewModel"; type: "QObject"; isPointer: true } + } + Signal { + name: "viewModelPropertyChanged" + Parameter { name: "viewModelProperty"; type: "string" } + } + Signal { + name: "viewChanged" + Parameter { name: "view"; type: "QObject"; isPointer: true } + } + Signal { + name: "viewPropertyChanged" + Parameter { name: "viewProperty"; type: "string" } + } + Signal { + name: "typeChanged" + Parameter { name: "type"; type: "BindingDirection" } + } + Method { + name: "setType" + Parameter { name: "type"; type: "BindingDirection" } + } + Method { name: "unbind" } + Method { name: "isValid"; type: "bool" } + } +} diff --git a/src/imports/mvvmcore/qmldir b/src/imports/mvvmcore/qmldir new file mode 100644 index 0000000..2681fa5 --- /dev/null +++ b/src/imports/mvvmcore/qmldir @@ -0,0 +1,4 @@ +module de.skycoder42.qtmvvm.core +plugin declarative_mvvmcore +classname QtMvvmCoreDeclarativeModule +typeinfo plugins.qmltypes diff --git a/src/imports/mvvmcore/qqmlmvvmbinding.cpp b/src/imports/mvvmcore/qqmlmvvmbinding.cpp new file mode 100644 index 0000000..3ce296b --- /dev/null +++ b/src/imports/mvvmcore/qqmlmvvmbinding.cpp @@ -0,0 +1,67 @@ +#include "qqmlmvvmbinding.h" +using namespace QtMvvm; + +QQmlMvvmBinding::QQmlMvvmBinding(QObject *parent) : + QObject(parent), + QQmlParserStatus(), + _binding(), + _completed(false), + _viewModel(nullptr), + _viewModelProperty(), + _view(nullptr), + _viewProperty(), + _type(TwoWay) +{ + connect(this, &QQmlMvvmBinding::viewModelChanged, + this, &QQmlMvvmBinding::resetBinding); + connect(this, &QQmlMvvmBinding::viewModelPropertyChanged, + this, &QQmlMvvmBinding::resetBinding); + connect(this, &QQmlMvvmBinding::viewChanged, + this, &QQmlMvvmBinding::resetBinding); + connect(this, &QQmlMvvmBinding::viewPropertyChanged, + this, &QQmlMvvmBinding::resetBinding); + connect(this, &QQmlMvvmBinding::typeChanged, + this, &QQmlMvvmBinding::resetBinding); +} + +void QQmlMvvmBinding::classBegin() {} + +void QQmlMvvmBinding::componentComplete() +{ + _completed = true; + resetBinding(); +} + +QQmlMvvmBinding::BindingDirection QQmlMvvmBinding::type() const +{ + return _type; +} + +bool QQmlMvvmBinding::isValid() const +{ + return _binding.isValid(); +} + +void QQmlMvvmBinding::setType(BindingDirection type) +{ + if (_type == type) + return; + + _type = type; + emit typeChanged(_type); +} + +void QQmlMvvmBinding::unbind() +{ + _binding.unbind(); +} + +void QQmlMvvmBinding::resetBinding() +{ + if(!_completed || !_viewModel || !_view) + return; + _binding.unbind(); + _binding = QtMvvm::bind(_viewModel, qUtf8Printable(_viewModelProperty), + _view, qUtf8Printable(_viewProperty), + static_cast(static_cast(_type))); +} diff --git a/src/imports/mvvmcore/qqmlmvvmbinding.h b/src/imports/mvvmcore/qqmlmvvmbinding.h new file mode 100644 index 0000000..043ebc2 --- /dev/null +++ b/src/imports/mvvmcore/qqmlmvvmbinding.h @@ -0,0 +1,67 @@ +#ifndef QTMVVM_QQMLMVVMBINDING_H +#define QTMVVM_QQMLMVVMBINDING_H + +#include +#include + +#include + +namespace QtMvvm { + +class QQmlMvvmBinding : public QObject, public QQmlParserStatus +{ + Q_OBJECT + Q_INTERFACES(QQmlParserStatus) + + Q_PROPERTY(QObject* viewModel MEMBER _viewModel NOTIFY viewModelChanged) + Q_PROPERTY(QString viewModelProperty MEMBER _viewModelProperty NOTIFY viewModelPropertyChanged) + Q_PROPERTY(QObject* view MEMBER _view NOTIFY viewChanged) + Q_PROPERTY(QString viewProperty MEMBER _viewProperty NOTIFY viewPropertyChanged) + Q_PROPERTY(BindingDirection type READ type WRITE setType NOTIFY typeChanged) //MEMBER is broken for flags + +public: + enum BindingDirectionFlag { //copy flags from binding + SingleInit = Binding::SingleInit, + OneWayToView = Binding::OneWayToView, + OneWayToViewModel = Binding::OneWayToViewModel, + TwoWay = Binding::TwoWay + }; + Q_DECLARE_FLAGS(BindingDirection, BindingDirectionFlag) + Q_FLAG(BindingDirection) + + explicit QQmlMvvmBinding(QObject *parent = nullptr); + + void classBegin() override; + void componentComplete() override; + + BindingDirection type() const; + Q_INVOKABLE bool isValid() const; + +public Q_SLOTS: + void setType(BindingDirection type); + void unbind(); + +Q_SIGNALS: + void viewModelChanged(QObject* viewModel); + void viewModelPropertyChanged(QString viewModelProperty); + void viewChanged(QObject* view); + void viewPropertyChanged(QString viewProperty); + void typeChanged(BindingDirection type); + +private Q_SLOTS: + void resetBinding(); + +private: + Binding _binding; + bool _completed; + + QObject* _viewModel; + QString _viewModelProperty; + QObject* _view; + QString _viewProperty; + BindingDirection _type; +}; + +} + +#endif // QTMVVM_QQMLMVVMBINDING_H diff --git a/src/imports/mvvmcore/qtmvvmcore_plugin.cpp b/src/imports/mvvmcore/qtmvvmcore_plugin.cpp new file mode 100644 index 0000000..1563389 --- /dev/null +++ b/src/imports/mvvmcore/qtmvvmcore_plugin.cpp @@ -0,0 +1,16 @@ +#include "qtmvvmcore_plugin.h" + +#include + +#include "qqmlmvvmbinding.h" + +QtMvvmCoreDeclarativeModule::QtMvvmCoreDeclarativeModule(QObject *parent) : + QQmlExtensionPlugin(parent) +{} + +void QtMvvmCoreDeclarativeModule::registerTypes(const char *uri) +{ + Q_ASSERT(qstrcmp(uri, "de.skycoder42.qtmvvm.core") == 0); + + qmlRegisterType(uri, 1, 0, "QtMvvmBinding"); +} diff --git a/src/imports/mvvmcore/qtmvvmcore_plugin.h b/src/imports/mvvmcore/qtmvvmcore_plugin.h new file mode 100644 index 0000000..69c7379 --- /dev/null +++ b/src/imports/mvvmcore/qtmvvmcore_plugin.h @@ -0,0 +1,16 @@ +#ifndef QTMVVMCORE_PLUGIN_H +#define QTMVVMCORE_PLUGIN_H + +#include + +class QtMvvmCoreDeclarativeModule : public QQmlExtensionPlugin +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlExtensionInterface_iid) + +public: + QtMvvmCoreDeclarativeModule(QObject *parent = nullptr); + void registerTypes(const char *uri) override; +}; + +#endif // QTMVVMCORE_PLUGIN_H diff --git a/src/imports/mvvmquick/mvvmquick.pro b/src/imports/mvvmquick/mvvmquick.pro index e11524d..69e5652 100644 --- a/src/imports/mvvmquick/mvvmquick.pro +++ b/src/imports/mvvmquick/mvvmquick.pro @@ -22,17 +22,16 @@ OTHER_FILES += qmldir generate_qmltypes { typeextra1.target = qmltypes typeextra1.depends += export LD_LIBRARY_PATH := "$$shadowed($$dirname(_QMAKE_CONF_))/lib/:$(LD_LIBRARY_PATH)" - qmltypes.depends += typeextra1 - typeextra2.target = qmltypes typeextra2.depends += export QML2_IMPORT_PATH := "$$shadowed($$dirname(_QMAKE_CONF_))/qml/" - qmltypes.depends += typeextra2 QMAKE_EXTRA_TARGETS += typeextra1 typeextra2 } load(qml_plugin) generate_qmltypes { + qmltypes.depends = ../../../qml/$$TARGETPATH/$(TARGET) #overwrite the target deps + mfirst.target = all mfirst.depends += qmltypes QMAKE_EXTRA_TARGETS += mfirst diff --git a/src/mvvmcore/binding.cpp b/src/mvvmcore/binding.cpp index f7e3b3b..7504557 100644 --- a/src/mvvmcore/binding.cpp +++ b/src/mvvmcore/binding.cpp @@ -100,6 +100,14 @@ bool Binding::isValid() const return d; } +void Binding::unbind() +{ + if(d) { + d->deleteLater(); + d.clear(); + } +} + // ------------- Private Implementation ------------- Binding BindingPrivate::bind(QObject *viewModel, const QMetaProperty &viewModelProperty, QObject *view, const QMetaProperty &viewProperty, Binding::BindingDirection type, const QMetaMethod &viewModelChangeSignal, const QMetaMethod &viewChangeSignal) diff --git a/src/mvvmcore/binding.h b/src/mvvmcore/binding.h index c60b870..9f79174 100644 --- a/src/mvvmcore/binding.h +++ b/src/mvvmcore/binding.h @@ -31,6 +31,7 @@ public: ~Binding(); bool isValid() const; + void unbind(); private: QPointer d; diff --git a/src/mvvmquick/quickpresenter.cpp b/src/mvvmquick/quickpresenter.cpp index 8514d14..6594279 100644 --- a/src/mvvmquick/quickpresenter.cpp +++ b/src/mvvmquick/quickpresenter.cpp @@ -6,6 +6,11 @@ QuickPresenter::QuickPresenter(QObject *parent) : IPresenter() {} +void QuickPresenter::registerViewExplicitly(const QMetaObject *viewModelType, const QUrl &viewUrl) +{ + +} + void QuickPresenter::present(QtMvvm::ViewModel *viewModel, const QVariantHash ¶ms, QPointer parent) { @@ -15,3 +20,8 @@ void QuickPresenter::showDialog(const QtMvvm::MessageConfig &config, QtMvvm::Mes { } + +QUrl QuickPresenter::findViewUrl(const QMetaObject *viewModelType) +{ + +} diff --git a/src/mvvmquick/quickpresenter.h b/src/mvvmquick/quickpresenter.h index 7fbba06..ed0803e 100644 --- a/src/mvvmquick/quickpresenter.h +++ b/src/mvvmquick/quickpresenter.h @@ -4,6 +4,7 @@ #include #include +#include #include "QtMvvmQuick/qtmvvmquick_global.h" @@ -17,10 +18,35 @@ class Q_MVVMQUICK_EXPORT QuickPresenter : public QObject, public IPresenter public: explicit QuickPresenter(QObject *parent = nullptr); + template + static void registerAsPresenter(); + + template + static void registerViewExplicitly(const QUrl &viewUrl); + static void registerViewExplicitly(const QMetaObject *viewModelType, const QUrl &viewUrl); + void present(ViewModel *viewModel, const QVariantHash ¶ms, QPointer parent) override; void showDialog(const MessageConfig &config, MessageResult *result) override; + +protected: + virtual QUrl findViewUrl(const QMetaObject *viewModelType); }; +template +void QuickPresenter::registerAsPresenter() +{ + static_assert(std::is_base_of::value, "TPresenter must inherit QtMvvm::QuickPresenter!"); + CoreApp::setMainPresenter(new TPresenter()); +} + +template +void QuickPresenter::registerViewExplicitly(const QUrl &viewUrl) +{ + static_assert(std::is_base_of::value, "TViewModel must inherit ViewModel!"); + registerViewExplicitly(&TViewModel::staticMetaObject, viewUrl); +} + + } #endif // QTMVVM_QUICKPRESENTER_H