From ef683064ca277f74bfbfeecf20dfa7c503c50cd0 Mon Sep 17 00:00:00 2001 From: Skycoder42 Date: Thu, 15 Feb 2018 01:30:37 +0100 Subject: [PATCH] implement basic presenter c++ logic --- .qmake.conf | 2 +- examples/mvvmquick/SampleQuick/SampleView.qml | 11 ++ examples/mvvmquick/SampleQuick/qml.qrc | 3 + src/imports/mvvmquick/QtMvvmApp.qml | 15 +- src/imports/mvvmquick/mvvmquick.pro | 2 +- src/imports/mvvmquick/plugins.qmltypes | 22 +++ src/imports/mvvmquick/qqmlquickpresenter.cpp | 138 +++++++++++++++++- src/imports/mvvmquick/qqmlquickpresenter.h | 44 +++++- src/mvvmquick/quickpresenter.cpp | 19 ++- src/mvvmquick/quickpresenter.h | 1 + src/mvvmquick/quickpresenter_p.h | 11 +- src/mvvmwidgets/widgetspresenter.cpp | 1 + 12 files changed, 257 insertions(+), 12 deletions(-) create mode 100644 examples/mvvmquick/SampleQuick/SampleView.qml diff --git a/.qmake.conf b/.qmake.conf index 4872a0c..b03d030 100644 --- a/.qmake.conf +++ b/.qmake.conf @@ -1,7 +1,7 @@ load(qt_build_config) CONFIG += warning_clean exceptions c++14 -CONFIG += samples_in_build +!win32: CONFIG += samples_in_build # TODO fix on win win32:cross_compile: CONFIG += winrt DEFINES += QT_DEPRECATED_WARNINGS QT_ASCII_CAST_WARNINGS diff --git a/examples/mvvmquick/SampleQuick/SampleView.qml b/examples/mvvmquick/SampleQuick/SampleView.qml new file mode 100644 index 0000000..5f0d7b2 --- /dev/null +++ b/examples/mvvmquick/SampleQuick/SampleView.qml @@ -0,0 +1,11 @@ +import QtQuick 2.10 + +Rectangle { + property QtObject viewModel: null + + anchors.fill: parent + + color: "red" + + Component.onCompleted: console.log(viewModel) +} diff --git a/examples/mvvmquick/SampleQuick/qml.qrc b/examples/mvvmquick/SampleQuick/qml.qrc index 5f6483a..361a83c 100644 --- a/examples/mvvmquick/SampleQuick/qml.qrc +++ b/examples/mvvmquick/SampleQuick/qml.qrc @@ -2,4 +2,7 @@ main.qml + + SampleView.qml + diff --git a/src/imports/mvvmquick/QtMvvmApp.qml b/src/imports/mvvmquick/QtMvvmApp.qml index 04acf2b..cfbbf57 100644 --- a/src/imports/mvvmquick/QtMvvmApp.qml +++ b/src/imports/mvvmquick/QtMvvmApp.qml @@ -1,5 +1,6 @@ import QtQuick 2.10 import QtQuick.Controls 2.3 +import de.skycoder42.qtmvvm.quick 1.0 ApplicationWindow { id: _root @@ -7,5 +8,17 @@ ApplicationWindow { width: 360 height: 520 - PresenterProgress {} + PresenterProgress { + z: -10 //keep it low so its hidden after the first view was shown + } + + function presentItem(item) { + return true; + } + + function presentPopup(item) { + return true; + } + + Component.onCompleted: QuickPresenter.qmlPresenter = _root } diff --git a/src/imports/mvvmquick/mvvmquick.pro b/src/imports/mvvmquick/mvvmquick.pro index 4e21dd8..663e305 100644 --- a/src/imports/mvvmquick/mvvmquick.pro +++ b/src/imports/mvvmquick/mvvmquick.pro @@ -1,4 +1,4 @@ -QT += core qml quick mvvmquick +QT += core qml quick mvvmquick mvvmquick-private CXX_MODULE = mvvmquick TARGETPATH = de/skycoder42/qtmvvm/quick TARGET = declarative_mvvmquick diff --git a/src/imports/mvvmquick/plugins.qmltypes b/src/imports/mvvmquick/plugins.qmltypes index 0221911..b5cd3a6 100644 --- a/src/imports/mvvmquick/plugins.qmltypes +++ b/src/imports/mvvmquick/plugins.qmltypes @@ -15,5 +15,27 @@ Module { isCreatable: false isSingleton: true exportMetaObjectRevisions: [0] + Property { name: "qmlPresenter"; type: "QObject"; isPointer: true } + Property { name: "viewLoading"; type: "bool"; isReadonly: true } + Property { name: "loadingProgress"; type: "double"; isReadonly: true } + Signal { + name: "qmlPresenterChanged" + Parameter { name: "qmlPresenter"; type: "QObject"; isPointer: true } + } + Signal { + name: "viewLoadingChanged" + Parameter { name: "viewLoading"; type: "bool" } + } + Signal { + name: "loadingProgressChanged" + Parameter { name: "loadingProgress"; type: "double" } + } + Method { + name: "present" + Parameter { name: "viewModel"; type: "QtMvvm::ViewModel"; isPointer: true } + Parameter { name: "params"; type: "QVariantHash" } + Parameter { name: "viewUrl"; type: "QUrl" } + Parameter { name: "parent"; type: "QPointer" } + } } } diff --git a/src/imports/mvvmquick/qqmlquickpresenter.cpp b/src/imports/mvvmquick/qqmlquickpresenter.cpp index 3dfd9a0..e177e28 100644 --- a/src/imports/mvvmquick/qqmlquickpresenter.cpp +++ b/src/imports/mvvmquick/qqmlquickpresenter.cpp @@ -1,6 +1,138 @@ #include "qqmlquickpresenter.h" + +#include + +#include + +#include + using namespace QtMvvm; -QQmlQuickPresenter::QQmlQuickPresenter(QObject *parent) : - QObject(parent) -{} +QQmlQuickPresenter::QQmlQuickPresenter(QQmlEngine *engine) : + QObject(engine), + _engine(engine), + _latestComponent(), + _componentCache(), + _loadCache() +{ + QuickPresenterPrivate::setQmlPresenter(this); +} + +bool QQmlQuickPresenter::isViewLoading() const +{ + return _latestComponent; +} + +qreal QQmlQuickPresenter::loadingProgress() const +{ + return _latestComponent ? _latestComponent->progress() : -1.0; +} + +void QQmlQuickPresenter::present(ViewModel *viewModel, const QVariantHash ¶ms, const QUrl &viewUrl, QPointer parent) +{ + auto component = _componentCache.object(viewUrl); + if(component) + addObject(component, viewModel, params, parent); + else { + //create component (and replace latest) + if(_latestComponent) { + disconnect(_latestComponent, &QQmlComponent::progressChanged, + this, &QQmlQuickPresenter::loadingProgressChanged); + } + _latestComponent = new QQmlComponent(_engine, this); + _loadCache.insert(_latestComponent, std::make_tuple(viewModel, params, parent)); + + //setup ui status + emit viewLoadingChanged(true); + emit loadingProgressChanged(0.0); + connect(_latestComponent, &QQmlComponent::progressChanged, + this, &QQmlQuickPresenter::loadingProgressChanged); + connect(_latestComponent, &QQmlComponent::statusChanged, + this, &QQmlQuickPresenter::statusChanged); + _latestComponent->loadUrl(viewUrl, QQmlComponent::Asynchronous); + } +} + +void QQmlQuickPresenter::statusChanged(QQmlComponent::Status status) +{ + auto component = qobject_cast(sender()); + if(!component) + return; + + switch(status) { + case QQmlComponent::Ready: + { + _componentCache.insert(component->url(), component); + auto loadInfo = _loadCache.value(component); + disconnect(component, &QQmlComponent::progressChanged, + this, &QQmlQuickPresenter::loadingProgressChanged); + addObject(component, std::get<0>(loadInfo), std::get<1>(loadInfo), std::get<2>(loadInfo)); + break; + } + case QQmlComponent::Error: + qmlWarning(this, component->errors()) << "Failed to load component"; + component->deleteLater(); + break; + default: + return; + } + + _loadCache.remove(component); + if(_latestComponent == component) { + _latestComponent = nullptr; + emit viewLoadingChanged(false); + } +} + +void QQmlQuickPresenter::addObject(QQmlComponent *component, ViewModel *viewModel, const QVariantHash ¶ms, QPointer parent) +{ + if(!_qmlPresenter) { + qCritical() << "No QML-Presenter registered! Unable to present control of type" + << viewModel->metaObject()->className(); + return; + } + + //create the view item, set initial stuff and them complete creation + auto item = component->beginCreate(_engine->rootContext()); + if(!item) { + qmlWarning(this) << "Unable to create quick view from the loaded component" + << component->url(); + return; + } + item->setProperty("viewModel", QVariant::fromValue(viewModel)); + viewModel->setParent(item); + viewModel->onInit(params); + component->completeCreate(); + + auto presented = false; + if(parent && parent->parent()) + presented = tryPresent(parent->parent(), item); + if(!presented) + presented = tryPresent(_qmlPresenter, item); + + if(presented) + QQmlEngine::setObjectOwnership(item, QQmlEngine::JavaScriptOwnership); + else { + qmlWarning(this) << "Failed to present item for viewModel of type" + << viewModel->metaObject()->className(); + item->deleteLater(); + } +} + +bool QQmlQuickPresenter::tryPresent(QObject *qmlPresenter, QObject *viewObject) +{ + auto meta = qmlPresenter->metaObject(); + auto index = -1; + if(viewObject->inherits("QQuickItem")) + index = meta->indexOfMethod("presentItem(QVariant)"); + if(viewObject->inherits("QQuickPopup")) + index = meta->indexOfMethod("presentPopup(QVariant)"); + + QVariant presented = false; + if(index != -1) { + meta->method(index).invoke(qmlPresenter, Qt::DirectConnection, + Q_RETURN_ARG(QVariant, presented), + Q_ARG(QVariant, QVariant::fromValue(viewObject))); + } + return presented.toBool(); +} diff --git a/src/imports/mvvmquick/qqmlquickpresenter.h b/src/imports/mvvmquick/qqmlquickpresenter.h index 62a26f1..1dee18f 100644 --- a/src/imports/mvvmquick/qqmlquickpresenter.h +++ b/src/imports/mvvmquick/qqmlquickpresenter.h @@ -1,7 +1,17 @@ #ifndef QTMVVM_QQMLQUICKPRESENTER_H #define QTMVVM_QQMLQUICKPRESENTER_H -#include +#include + +#include +#include +#include +#include +#include + +#include + +#include namespace QtMvvm { @@ -9,8 +19,38 @@ class QQmlQuickPresenter : public QObject { Q_OBJECT + Q_PROPERTY(QObject* qmlPresenter MEMBER _qmlPresenter NOTIFY qmlPresenterChanged) + Q_PROPERTY(bool viewLoading READ isViewLoading NOTIFY viewLoadingChanged) + Q_PROPERTY(qreal loadingProgress READ loadingProgress NOTIFY loadingProgressChanged) + public: - explicit QQmlQuickPresenter(QObject *parent = nullptr); + explicit QQmlQuickPresenter(QQmlEngine *engine); + + bool isViewLoading() const; + qreal loadingProgress() const; + +public Q_SLOTS: + void present(QtMvvm::ViewModel *viewModel, const QVariantHash ¶ms, const QUrl &viewUrl, QPointer parent); + +Q_SIGNALS: + void qmlPresenterChanged(QObject* qmlPresenter); + void viewLoadingChanged(bool viewLoading); + void loadingProgressChanged(qreal loadingProgress); + +private Q_SLOTS: + void statusChanged(QQmlComponent::Status status); + +private: + typedef std::tuple> PresentTuple; + QQmlEngine *_engine; + QPointer _qmlPresenter; + + QPointer _latestComponent; + QCache _componentCache; + QHash _loadCache; + + void addObject(QQmlComponent *component, ViewModel *viewModel, const QVariantHash ¶ms, QPointer parent); + bool tryPresent(QObject *qmlPresenter, QObject *viewObject); }; } diff --git a/src/mvvmquick/quickpresenter.cpp b/src/mvvmquick/quickpresenter.cpp index edb9180..cac2a0c 100644 --- a/src/mvvmquick/quickpresenter.cpp +++ b/src/mvvmquick/quickpresenter.cpp @@ -29,7 +29,18 @@ void QuickPresenter::registerViewExplicitly(const QMetaObject *viewModelType, co void QuickPresenter::present(QtMvvm::ViewModel *viewModel, const QVariantHash ¶ms, QPointer parent) { - qDebug(Q_FUNC_INFO); + auto url = findViewUrl(viewModel->metaObject()); + if(!url.isValid()) + throw PresenterException(QByteArrayLiteral("No Url to a QML View found for ") + viewModel->metaObject()->className()); + + if(d->qmlPresenter) { + QMetaObject::invokeMethod(d->qmlPresenter, "present", + Q_ARG(QtMvvm::ViewModel*, viewModel), + Q_ARG(QVariantHash, params), + Q_ARG(QUrl, url), + Q_ARG(QPointer, parent)); + } else + throw PresenterException("QML presenter not ready - cannot present yet"); } void QuickPresenter::showDialog(const QtMvvm::MessageConfig &config, QtMvvm::MessageResult *result) @@ -61,7 +72,6 @@ QUrl QuickPresenter::findViewUrl(const QMetaObject *viewModelType) if(dir.startsWith(QStringLiteral(":"))) { resUrl.setScheme(QStringLiteral("qrc")); resUrl.setPath(iterator.filePath().mid(1)); //skip the beginning colon - logDebug() << "TEST:" << QUrl::fromLocalFile(iterator.filePath()); } else resUrl = QUrl::fromLocalFile(iterator.filePath()); logDebug() << "Found URL for viewmodel" @@ -99,3 +109,8 @@ QuickPresenter *QuickPresenterPrivate::currentPresenter() #endif return presenter; } + +void QuickPresenterPrivate::setQmlPresenter(QObject *presenter) +{ + currentPresenter()->d->qmlPresenter = presenter; +} diff --git a/src/mvvmquick/quickpresenter.h b/src/mvvmquick/quickpresenter.h index 2eb4ecf..8be483f 100644 --- a/src/mvvmquick/quickpresenter.h +++ b/src/mvvmquick/quickpresenter.h @@ -37,6 +37,7 @@ protected: virtual QUrl findViewUrl(const QMetaObject *viewModelType); private: + friend class QtMvvm::QuickPresenterPrivate; QScopedPointer d; }; diff --git a/src/mvvmquick/quickpresenter_p.h b/src/mvvmquick/quickpresenter_p.h index 7cb139c..a1dc996 100644 --- a/src/mvvmquick/quickpresenter_p.h +++ b/src/mvvmquick/quickpresenter_p.h @@ -2,21 +2,28 @@ #define QTMVVM_QUICKPRESENTER_P_H #include +#include #include "qtmvvmquick_global.h" #include "quickpresenter.h" namespace QtMvvm { -class QuickPresenterPrivate +class Q_MVVMQUICK_EXPORT QuickPresenterPrivate { + friend class QtMvvm::QuickPresenter; + public: QuickPresenterPrivate(); + static QuickPresenter *currentPresenter(); + static void setQmlPresenter(QObject *presenter); + +private: QHash explicitMappings; QStringList searchDirs; - static QuickPresenter *currentPresenter(); + QPointer qmlPresenter; }; } diff --git a/src/mvvmwidgets/widgetspresenter.cpp b/src/mvvmwidgets/widgetspresenter.cpp index f695933..d90271a 100644 --- a/src/mvvmwidgets/widgetspresenter.cpp +++ b/src/mvvmwidgets/widgetspresenter.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include