diff --git a/doc/qtmvvm.dox b/doc/qtmvvm.dox index 4ea3ca9..d4eb170 100644 --- a/doc/qtmvvm.dox +++ b/doc/qtmvvm.dox @@ -103,6 +103,8 @@ The following list shows which classes belong to which Qt module, in alphabetica - @ref QtMvvm::ExchangeDevicesModel "ExchangeDevicesModel" - @ref QtMvvm::DataSyncViewModel "DataSyncViewModel" (uncreatable) - @ref QtMvvm::NetworkExchangeViewModel "NetworkExchangeViewModel" (uncreatable) +- @ref QtMvvm::DataSyncSettingsViewModel "DataSyncSettingsViewModel" (uncreatable) +- @ref QtMvvm::DataSyncSettingsEntry "DataSyncSettingsEntry" (uncreatable) - PChangeRemoteViewModel (uncreatable, internal) - PExportSetupViewModel (uncreatable, internal) - PIdentityEditViewModel (uncreatable, internal) diff --git a/examples/mvvmquick/SampleQuick/ContainerView.qml b/examples/mvvmquick/SampleQuick/ContainerView.qml index ce24aee..ca057d5 100644 --- a/examples/mvvmquick/SampleQuick/ContainerView.qml +++ b/examples/mvvmquick/SampleQuick/ContainerView.qml @@ -35,30 +35,13 @@ Page { ViewPlaceholder { id: viewPlaceholder - viewModelType: containerView.viewModel.vmType - parentViewModel: containerView.viewModel - autoPresent: false - Layout.fillWidth: true Layout.fillHeight: true BusyIndicator { anchors.centerIn: parent - anchors.verticalCenterOffset: -(hookButton.height/2) running: !viewPlaceholder.loadedView } - - Button { - id: hookButton - Layout.fillWidth: true - text: qsTr("Load from QML") - onClicked: viewPlaceholder.presentView(); - enabled: !viewPlaceholder.loadedView - - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - } } Button { diff --git a/src/imports/mvvmquick/plugins.qmltypes b/src/imports/mvvmquick/plugins.qmltypes index 1cb6710..e67abba 100644 --- a/src/imports/mvvmquick/plugins.qmltypes +++ b/src/imports/mvvmquick/plugins.qmltypes @@ -130,30 +130,10 @@ Module { prototype: "QQuickItem" exports: ["de.skycoder42.QtMvvm.Quick/ViewPlaceholder 1.1"] exportMetaObjectRevisions: [0] - Property { name: "viewModelType"; type: "string" } - Property { name: "showParams"; type: "QVariantHash" } - Property { name: "parentViewModel"; type: "QtMvvm::ViewModel"; isPointer: true } - Property { name: "autoPresent"; type: "bool" } Property { name: "autoResizeView"; type: "bool" } Property { name: "replaceViews"; type: "bool" } Property { name: "closeViewOnAction"; type: "bool" } Property { name: "loadedView"; type: "QQuickItem"; isReadonly: true; isPointer: true } - Signal { - name: "viewModelTypeChanged" - Parameter { name: "viewModelType"; type: "string" } - } - Signal { - name: "showParamsChanged" - Parameter { name: "showParams"; type: "QVariantHash" } - } - Signal { - name: "parentViewModelChanged" - Parameter { name: "parentViewModel"; type: "QtMvvm::ViewModel"; isPointer: true } - } - Signal { - name: "autoPresentChanged" - Parameter { name: "autoPresent"; type: "bool" } - } Signal { name: "autoResizeViewChanged" Parameter { name: "autoResizeView"; type: "bool" } @@ -170,12 +150,11 @@ Module { name: "loadedViewChanged" Parameter { name: "loadedView"; type: "QQuickItem"; isPointer: true } } - Method { name: "presentView" } Method { name: "discardView" } Method { name: "presentItem" type: "bool" - Parameter { name: "item"; type: "QVariant" } + Parameter { name: "item"; type: "QQuickItem"; isPointer: true } } Method { name: "closeAction"; type: "bool" } } diff --git a/src/imports/mvvmquick/qqmlviewplaceholder.cpp b/src/imports/mvvmquick/qqmlviewplaceholder.cpp index 9257721..1c060d2 100644 --- a/src/imports/mvvmquick/qqmlviewplaceholder.cpp +++ b/src/imports/mvvmquick/qqmlviewplaceholder.cpp @@ -8,11 +8,7 @@ using namespace QtMvvm; QQmlViewPlaceholder::QQmlViewPlaceholder(QQuickItem *parent) : QQuickItem{parent} { - // auto presenting - connect(this, &QQmlViewPlaceholder::viewModelTypeChanged, - this, &QQmlViewPlaceholder::presentIfReady); - connect(this, &QQmlViewPlaceholder::autoPresentChanged, - this, &QQmlViewPlaceholder::presentIfReady); + setImplicitSize(0, 0); // init to 0 // size changes connect(this, &QQmlViewPlaceholder::widthChanged, @@ -23,18 +19,12 @@ QQmlViewPlaceholder::QQmlViewPlaceholder(QQuickItem *parent) : this, &QQmlViewPlaceholder::resizeView); } -bool QQmlViewPlaceholder::presentItem(const QVariant &item) +bool QQmlViewPlaceholder::presentItem(QQuickItem *item) { - // check if the parameter is valid - auto quickItem = item.value(); - if(!quickItem) { - qmlWarning(this) << "presentItem called with invalid item of type: " << item.typeName(); - return false; - } - // handle already existing view case if(_loadedView) { if(_replaceViews) { // quick discard without reenableing all children + disconnectSizeChanges(false); _loadedView->setVisible(false); _loadedView->deleteLater(); _loadedView = nullptr; @@ -45,11 +35,12 @@ bool QQmlViewPlaceholder::presentItem(const QVariant &item) } // add - _loadedView = quickItem; - quickItem->setParent(this); - quickItem->setParentItem(this); + _loadedView = item; + _loadedView->setParent(this); + _loadedView->setParentItem(this); + connectSizeChanges(); resizeView(); - quickItem->setVisible(true); + _loadedView->setVisible(true); // hide all children for(auto child : childItems()) { @@ -87,52 +78,11 @@ bool QQmlViewPlaceholder::closeAction() return false; } -void QQmlViewPlaceholder::setParentViewModel(ViewModel *parentViewModel) -{ - // first: clear the auto-connected viewmodel, if required - if(_clearParentVmCon && _parentVmCon) - disconnect(_parentVmCon); - - // then: set property as usual - if(_parentViewModel == parentViewModel) - return; - - _parentViewModel = parentViewModel; - emit parentViewModelChanged(_parentViewModel); - - // check the vm parent for a presenter method - auto view = qobject_cast(_parentViewModel->parent()); - if(view) { - if(view->metaObject()->indexOfMethod("presentItem(QVariant)") == -1) - qmlWarning(this) << R"(Parent item of "parentViewModel" does not have a "presentItem" method. Check the ViewPlaceholder documentation!)"; - } else - qmlWarning(this) << R"(Parent item of "parentViewModel" is not an Item)"; - - presentIfReady(); -} - QQuickItem *QQmlViewPlaceholder::loadedView() const { return _loadedView; } -void QQmlViewPlaceholder::componentComplete() -{ - // auto-set the vm if not already set - if(!_parentViewModel) - getParentViewModel(); - - // last step: call base implementation, then present - QQuickItem::componentComplete(); - _isReady = true; - presentIfReady(); -} - -void QQmlViewPlaceholder::presentView() -{ - CoreApp::show(qUtf8Printable(_viewModelType), _showParams, _parentViewModel); -} - void QQmlViewPlaceholder::discardView() { // hide view @@ -145,57 +95,46 @@ void QQmlViewPlaceholder::discardView() } // now delete it + disconnectSizeChanges(true); _loadedView->deleteLater(); _loadedView = nullptr; emit loadedViewChanged(nullptr); } -void QQmlViewPlaceholder::parentItemVmChanged(ViewModel *viewModel) +void QQmlViewPlaceholder::resizeView() { - // set vm without clearing the connection - _clearParentVmCon = false; - setParentViewModel(viewModel); - _clearParentVmCon = true; + if(_loadedView && _autoResizeView) { + _loadedView->setWidth(width()); + _loadedView->setHeight(height()); + } } -void QQmlViewPlaceholder::presentIfReady() +void QQmlViewPlaceholder::updateImpHeight() { - if(_isReady && - _autoPresent && - !_loadedView && - _parentViewModel && - !_viewModelType.isEmpty()) - presentView(); + setImplicitHeight(_loadedView->implicitHeight()); } -void QQmlViewPlaceholder::resizeView() +void QQmlViewPlaceholder::updateImpWidth() { - if(_loadedView && _autoResizeView) { - _loadedView->setWidth(width()); - _loadedView->setHeight(height()); - } + setImplicitWidth(_loadedView->implicitWidth()); +} + +void QQmlViewPlaceholder::connectSizeChanges() +{ + connect(_loadedView, &QQuickItem::implicitWidthChanged, + this, &QQmlViewPlaceholder::updateImpWidth); + connect(_loadedView, &QQuickItem::implicitHeightChanged, + this, &QQmlViewPlaceholder::updateImpHeight); + setImplicitSize(_loadedView->implicitWidth(), + _loadedView->implicitHeight()); } -void QQmlViewPlaceholder::getParentViewModel() +void QQmlViewPlaceholder::disconnectSizeChanges(bool resetSize) { - auto pItem = parentItem(); - if(!pItem) - return; - - QQmlProperty vmProp{pItem, QStringLiteral("viewModel")}; - if(!vmProp.isValid()) - return; - - // set the vm from the property - auto vm = vmProp.read().value(); - if(vm) - setParentViewModel(vm); // no warning here - might be ok for lazy presented vms - - // connect to further changes, via helper slot - auto cSlotIndex = metaObject()->indexOfSlot("parentItemVmChanged(QtMvvm::ViewModel*)"); - Q_ASSERT(cSlotIndex != -1); - if(_parentVmCon) - disconnect(_parentVmCon); - _parentVmCon = connect(pItem, vmProp.property().notifySignal(), - this, metaObject()->method(cSlotIndex)); + disconnect(_loadedView, &QQuickItem::implicitWidthChanged, + this, &QQmlViewPlaceholder::updateImpWidth); + disconnect(_loadedView, &QQuickItem::implicitHeightChanged, + this, &QQmlViewPlaceholder::updateImpHeight); + if(resetSize) + setImplicitSize(0, 0); } diff --git a/src/imports/mvvmquick/qqmlviewplaceholder.h b/src/imports/mvvmquick/qqmlviewplaceholder.h index a0ee2bc..6e233e0 100644 --- a/src/imports/mvvmquick/qqmlviewplaceholder.h +++ b/src/imports/mvvmquick/qqmlviewplaceholder.h @@ -5,58 +5,184 @@ #include +#ifdef DOXYGEN_RUN +namespace de::skycoder42::QtMvvm::Quick { + +/*! + * @brief A placeholder item to show a view within another view + * + * @extends QtQuick.Item + * @since 1.1 + * + * The following snippet shows how use the placeholder + * @code{.qml} +Page { + id: view + property MyViewModel viewModel + + function presentItem(item) { + return viewPlaceholder.presentItem(item); + } + + function closeAction() { + return viewPlaceholder.closeAction(); + } + + PresenterProgress {} + + ColumnLayout { + anchors.fill: parent + + ViewPlaceholder { + id: viewPlaceholder + + Layout.fillWidth: true + Layout.fillHeight: true + + BusyIndicator { + anchors.centerIn: parent + running: !viewPlaceholder.loadedView + } + } + + Button { + Layout.fillWidth: true + text: viewPlaceholder.loadedView ? qsTr("Discard child") : qsTr("Show Child") + onClicked: viewPlaceholder.loadedView ? viewPlaceholder.discardView() : viewModel.loadChild(); + } + } +} + * @endcode + */ +class ViewPlaceholder +#else namespace QtMvvm { class QQmlViewPlaceholder : public QQuickItem +#endif { Q_OBJECT - Q_PROPERTY(QString viewModelType MEMBER _viewModelType NOTIFY viewModelTypeChanged) - Q_PROPERTY(QVariantHash showParams MEMBER _showParams NOTIFY showParamsChanged) - Q_PROPERTY(QtMvvm::ViewModel* parentViewModel MEMBER _parentViewModel WRITE setParentViewModel NOTIFY parentViewModelChanged) - Q_PROPERTY(bool autoPresent MEMBER _autoPresent NOTIFY autoPresentChanged) + /*! + * @brief Specify whether to automatically resize the placeholders content view + * + * @default{`true`} + * + * When enabled, the view that is loaded into the placeholder is always automatically + * resized to exactly fit into the placeholder. If not, the view must resize itself. + * + * @accessors{ + * @memberAc{autoResizeView} + * @notifyAc{autoResizeViewChanged()} + * } + * + * @sa width, height, implicitWidth, implicitHeight + */ Q_PROPERTY(bool autoResizeView MEMBER _autoResizeView NOTIFY autoResizeViewChanged) + /*! + * @brief Specify what happens when showing a new viewmodel while one already exists + * + * @default{`false`} + * + * When set to false, trying to present a new viewmodel on this placeholder will fail. If + * true, the old one is destroyed and replaced by the new one instead. + * + * @accessors{ + * @memberAc{replaceViews} + * @notifyAc{replaceViewsChanged()} + * } + */ Q_PROPERTY(bool replaceViews MEMBER _replaceViews NOTIFY replaceViewsChanged) + /*! + * @brief Specify whether discarding the contained view is a close action + * + * @default{`false`} + * + * When the placeholder receives a close action and the contained view does not handle it, + * either the placeholder only closes the contained view and thus accepts the action (that + * happens if true), or does not accept it keep the view so that the next higher instance + * can handle the request (that happens if false). + * + * @accessors{ + * @memberAc{closeViewOnAction} + * @notifyAc{closeViewOnActionChanged()} + * } + * + * @sa ViewPlaceholder::closeAction + */ Q_PROPERTY(bool closeViewOnAction MEMBER _closeViewOnAction NOTIFY closeViewOnActionChanged) + /*! + * @brief Returns the currently loaded view + * + * @default{`nullptr`} + * + * If a view is contained in the placeholder, that one is returned. Otherwise nullptr is + * returned by the property + * + * @accessors{ + * @memberAc{closeViewOnAction} + * @notifyAc{closeViewOnActionChanged()} + * @readonlyAc + * } + */ Q_PROPERTY(QQuickItem* loadedView READ loadedView NOTIFY loadedViewChanged) public: + //! @private explicit QQmlViewPlaceholder(QQuickItem *parent = nullptr); - Q_INVOKABLE bool presentItem(const QVariant &item); + /*! + * @brief The presenter method to be called from the parent view + * + * @param item The item to be presented in this placeholder + * @returns true if the item was presented as the placeholders content, false if not + * + * Implement a method with the same signature in the view that holds the placeholder and + * call this method from your implementation. This way you can automatically pass views + * to be presented to the placeholder. + * + * @sa ViewPlaceholder + */ + Q_INVOKABLE bool presentItem(QQuickItem *item); + /*! + * @brief A close action that propagates to the contained item + * + * @returns true if the contained item accepted the action or was discarded, false + * otherwise + * + * Implement a method with the same signature in the view that holds the placeholder and + * call this method from your implementation. This way close actions are properly handeled + * automatically. + * + * @sa ViewPlaceholder, ViewPlaceholder::closeViewOnAction + */ Q_INVOKABLE bool closeAction(); - void setParentViewModel(ViewModel *parentViewModel); + //! @private QQuickItem* loadedView() const; - void componentComplete() override; - public Q_SLOTS: - void presentView(); + //! Closes the currenty contained view by destoying it void discardView(); Q_SIGNALS: - void viewModelTypeChanged(const QString &viewModelType); - void showParamsChanged(const QVariantHash &showParams); - void parentViewModelChanged(QtMvvm::ViewModel* parentViewModel); - void autoPresentChanged(bool autoPresent); + //! @notifyAcFn{ViewPlaceholder::autoResizeView} void autoResizeViewChanged(bool autoResizeView); + //! @notifyAcFn{ViewPlaceholder::replaceViews} void replaceViewsChanged(bool replaceViews); + //! @notifyAcFn{ViewPlaceholder::closeViewOnAction} void closeViewOnActionChanged(bool closeViewOnAction); + //! @notifyAcFn{ViewPlaceholder::loadedView} void loadedViewChanged(QQuickItem* loadedView); private Q_SLOTS: - void parentItemVmChanged(QtMvvm::ViewModel *viewModel); - void presentIfReady(); - void resizeView(); + void updateImpHeight(); + void updateImpWidth(); + private: - QString _viewModelType; - QVariantHash _showParams; - ViewModel *_parentViewModel = nullptr; - bool _autoPresent = true; bool _autoResizeView = true; bool _replaceViews = false; bool _closeViewOnAction = false; @@ -64,12 +190,10 @@ private: QPointer _loadedView = nullptr; bool _isReady = false; - bool _clearParentVmCon = true; QMetaObject::Connection _parentVmCon; - void getParentViewModel(); void connectSizeChanges(); - void disconnectSizeChanges(); + void disconnectSizeChanges(bool resetSize); }; }